/** * Provider system for Beignet * * Providers are modular extensions that can add new ports or replace existing ones * during application initialization. They support configuration via Standard Schema * and optional lifecycle hooks. */ import type { StandardSchemaV1 } from "@standard-schema/spec"; type ProviderPorts = Record; declare const noProvidedPorts: unique symbol; type NoProvidedPorts = { [noProvidedPorts]?: never; }; /** * Extract the output type from a Standard Schema. * This is the validated/parsed type that results from schema validation. */ export type InferOutput = StandardSchemaV1.InferOutput; /** * Configuration definition for a service provider. * Specifies the schema for validating config and optional environment variable prefix. */ export interface ProviderConfigDef { /** * Standard Schema for validating provider configuration. * Can be Zod, Valibot, ArkType, or any Standard Schema compatible library. */ schema: CfgSchema; /** * Optional prefix to read env vars, e.g. "REDIS_". * When provided, the implementation will read process.env keys starting with this prefix * and pass them to the schema for validation. */ envPrefix?: string; /** * Field-level config overrides, keyed by schema field name. Defined values * are merged over the env-derived (or server-supplied) input before * validation, so factory options win over environment variables and still * satisfy required fields when the env var is absent. `undefined` values * are ignored. */ overrides?: Record; } /** * Value or promise of that value. */ export type MaybePromise = T | Promise; /** * Late-bound service context factory exposed to providers. * * Calling it during provider setup or after provider shutdown throws. Start * and stop hooks may invoke it after every provider has contributed ports and * the server has verified that no deferred port remains unbound. Runtime * entrypoints such as job dispatch, listeners, or scheduled work can close * over it for later use. * * App-local providers can type the factory by declaring `Context` and * `ServiceInput` through the curried `createProvider()` form. Untyped providers see `(input: void) => * Promise`. */ export type ProviderServiceContextFactory = (input: ServiceInput) => Promise; /** * Context passed to provider lifecycle hooks. */ export type ProviderLifecycleContext = { /** * Final app ports after provider setup. */ ports: Readonly; /** * Build an app service context through the server context blueprint. */ createServiceContext: ProviderServiceContextFactory; }; /** * Result returned from provider setup. */ export type ProviderSetupResult = { /** * Ports contributed by this provider. * Keys overwrite earlier ports with the same name at runtime. Prefer unique * keys unless the replacement implements the same port contract. */ ports?: ProvidedPorts; /** * Optional hook called after all providers have contributed their ports. * * Declared as a method so typed providers stay assignable to loosely typed * provider lists. Hooks that take `ctx` with an unannotated parameter keep * TypeScript from inferring `ProvidedPorts` from the returned `ports`. * Prefer closing over setup locals, or annotate `ctx` with * `ProviderLifecycleContext<...>`. */ start?(ctx: ProviderLifecycleContext): MaybePromise; /** * Optional hook called when the server is stopped. */ stop?(ctx: ProviderLifecycleContext): MaybePromise; }; /** * A service provider that can extend or replace ports during app initialization. * * Providers support: * - Configuration via Standard Schema (any compatible library: Zod, Valibot, etc.) * - Returning ports with new capabilities (e.g., cache, mailer) * - Replacing existing ports by returning the same key * - Optional start/stop hooks * * @example * ```ts * const cacheProvider = createProvider({ * name: "cache-redis", * config: { * schema: z.object({ URL: z.string().url() }), * envPrefix: "REDIS_", * }, * async setup({ config }) { * const client = new Redis(config.URL); * return { * ports: { * cache: { * get: (key) => client.get(key), * set: (key, value) => client.set(key, value), * }, * }, * stop: () => client.quit(), * }; * }, * }); * ``` */ export interface ServiceProvider, ProvidedPorts extends ProviderPorts = NoProvidedPorts, Context = unknown, ServiceInput = void> { /** * Unique name for this provider (used for logging/debugging) */ name: string; /** * Optional configuration definition. * If provided, the config will be loaded and validated before calling setup. */ config?: ProviderConfigDef; /** * Setup phase: create the ports this provider contributes. * Called during server initialization before provider `start` hooks and * before the server handles requests. * * @param ctx.ports - Ports contributed by previous providers * @param ctx.config - Validated config (if config was defined), or undefined * @param ctx.createServiceContext - Late-bound service context factory. * Throws during setup. Start and stop hooks may invoke it after all provider * ports have been contributed and validated; runtime entrypoints such as job * dispatchers and event listeners may call it lazily afterward. */ setup(ctx: { ports: Readonly; config: InferOutput | undefined; createServiceContext: ProviderServiceContextFactory; }): MaybePromise>; /** * Type-only marker for ports this provider contributes. * Runtime provider objects do not need to set this property. */ readonly __providedPorts?: ProvidedPorts; } /** * A provider configuration schema whose concrete validation library and input * shape are intentionally erased while its validated output may stay typed. * * Reusable provider packages use this in their named provider return types so * internal Zod schemas do not become part of the package's public API. */ export type AnyProviderConfigSchema = StandardSchemaV1; /** * Loosely typed service provider. * * Required-port, config, app-context, and service-input generics are erased * here so any provider created with `createProvider(...)` — including the * typed curried form — stays assignable. Use this for code that works across * arbitrary providers, such as provider lists and test helpers. */ export type AnyServiceProvider = ServiceProvider; /** * Extract the ports a provider contributes. */ export type ProvidedPortsOf = TProvider extends ServiceProvider ? ProvidedPorts : NoProvidedPorts; type UnionToIntersection = (T extends unknown ? (value: T) => void : never) extends (value: infer I) => void ? I : never; /** * Extract and merge the ports contributed by a provider list. * * Use this with `typeof providers` to type provider-contributed ports in app * code without hand-written casts: * * @example * ```ts * import type { InferProviderPorts } from "@beignet/core/providers"; * import type { providers } from "@/server/providers"; * import type { AppPorts } from "@/ports"; * * export type AppRuntimePorts = AppPorts & InferProviderPorts; * ``` */ export type InferProviderPorts = TProviders extends readonly unknown[] ? [TProviders[number]] extends [never] ? NoProvidedPorts : UnionToIntersection> : NoProvidedPorts; /** * Helper function to create a provider with proper type inference. * * This is a simple identity function that helps TypeScript infer the correct types * for the provider definition. * * App-local providers can use the curried zero-argument form to declare the * ports they require from earlier providers plus their app context and * service-context input. The required ports, `ctx.ports`, and * `ctx.createServiceContext` are then fully typed with no casts. * * @example * ```ts * export const myProvider = createProvider({ * name: "my-provider", * config: { * schema: z.object({ apiKey: z.string() }), * envPrefix: "MY_SERVICE_", * }, * async setup({ config }) { * return { ports: { myService: createMyService(config) } }; * }, * }); * * // Typed app-local provider: * export const appDatabaseProvider = createProvider< * { db: DbPort; devtools?: DevtoolsPort }, * AppContext, * AppServiceContextInput * >()({ * name: "app-database", * async setup({ ports, createServiceContext }) { * const repositories = createRepositories(ports.db.drizzle); * return { ports: repositories }; * }, * }); * ``` */ export declare function createProvider(): , Provided extends ProviderPorts = NoProvidedPorts>(def: ServiceProvider) => ServiceProvider; export declare function createProvider, ProvidedPorts extends ProviderPorts = NoProvidedPorts>(def: ServiceProvider): ServiceProvider; export {}; //# sourceMappingURL=provider.d.ts.map