@forinda/kickjs 5.12.0 → 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-BRvrS9xR.d.mts → adapter-D1NzavIt.d.mts} +4 -4
- package/dist/{adapter-BRvrS9xR.d.mts.map → adapter-D1NzavIt.d.mts.map} +1 -1
- package/dist/{app-module-BE7hAASB.d.mts → app-module-iAA20Cc6.d.mts} +3 -3
- package/dist/{app-module-BE7hAASB.d.mts.map → app-module-iAA20Cc6.d.mts.map} +1 -1
- package/dist/application-CbRBh5Dd.mjs +12 -0
- package/dist/application-CbRBh5Dd.mjs.map +1 -0
- package/dist/{application-BNxhBgji.d.mts → application-ChL6ufgq.d.mts} +6 -6
- package/dist/application-ChL6ufgq.d.mts.map +1 -0
- package/dist/{bootstrap-qvftPBfr.d.mts → bootstrap-Cgt_j-0Q.d.mts} +2 -2
- package/dist/{bootstrap-qvftPBfr.d.mts.map → bootstrap-Cgt_j-0Q.d.mts.map} +1 -1
- package/dist/{bootstrap-DpnpCnL0.mjs → bootstrap-DK8o5euq.mjs} +3 -3
- package/dist/{bootstrap-DpnpCnL0.mjs.map → bootstrap-DK8o5euq.mjs.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-DF9KJwp8.mjs → config-CfIpCoJs.mjs} +3 -3
- package/dist/{config-DF9KJwp8.mjs.map → config-CfIpCoJs.mjs.map} +1 -1
- package/dist/{container-BZKeHc5X.d.mts → container-B51bkskE.d.mts} +2 -2
- package/dist/container-B51bkskE.d.mts.map +1 -0
- package/dist/container-CBeLvm_9.mjs +108 -0
- package/dist/container-CBeLvm_9.mjs.map +1 -0
- package/dist/{context-C2yPZ22U.d.mts → context-D7sGbb8E.d.mts} +98 -1
- package/dist/context-D7sGbb8E.d.mts.map +1 -0
- package/dist/context-DpIvcO8X.mjs +12 -0
- package/dist/context-DpIvcO8X.mjs.map +1 -0
- package/dist/{context-decorator-BYf8HdMl.d.mts → context-decorator-BzcOhlej.d.mts} +3 -3
- package/dist/{context-decorator-BYf8HdMl.d.mts.map → context-decorator-BzcOhlej.d.mts.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.d.mts +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +1 -1
- package/dist/core/container.d.mts +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/decorators.d.mts +1 -1
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/errors.d.mts +2 -2
- package/dist/core/errors.mjs +2 -2
- package/dist/core/errors.mjs.map +1 -1
- package/dist/core/interfaces.d.mts +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.mjs +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-pTiSM6v2.mjs → core-Cs7Djz9w.mjs} +3 -3
- package/dist/{core-pTiSM6v2.mjs.map → core-Cs7Djz9w.mjs.map} +1 -1
- package/dist/cors-BfgqfLPd.d.mts.map +1 -1
- package/dist/csrf-DiK9zept.d.mts.map +1 -1
- package/dist/{decorators-BYi8lB37.d.mts → decorators-KbaPUBXd.d.mts} +8 -8
- package/dist/decorators-KbaPUBXd.d.mts.map +1 -0
- package/dist/{env-iO4nTPjc.mjs → env-B9_HjyB6.mjs} +3 -3
- package/dist/{env-iO4nTPjc.mjs.map → env-B9_HjyB6.mjs.map} +1 -1
- package/dist/{error-handler-Xud_3Lah.d.mts → error-handler-C4SG5o_i.d.mts} +6 -1
- package/dist/error-handler-C4SG5o_i.d.mts.map +1 -0
- package/dist/errors-DytV77Hb.d.mts +241 -0
- package/dist/errors-DytV77Hb.d.mts.map +1 -0
- package/dist/helmet-CaMbjzBG.d.mts.map +1 -1
- package/dist/http/application.d.mts +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.d.mts +1 -1
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.mjs +1 -1
- package/dist/http/middleware/error-handler.d.mts +1 -1
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/error-handler.mjs.map +1 -1
- package/dist/http/middleware/helmet.mjs +1 -1
- package/dist/http/middleware/rate-limit.mjs +1 -1
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/session.mjs +1 -1
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +1 -1
- package/dist/http/middleware/validate.mjs +2 -2
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/{index-DM1PteZT.d.mts → index-BM32LMqZ.d.mts} +4 -4
- package/dist/{index-DM1PteZT.d.mts.map → index-BM32LMqZ.d.mts.map} +1 -1
- package/dist/{index-qZqR_Mxr.d.mts → index-BYLVLFA8.d.mts} +63 -7
- package/dist/index-BYLVLFA8.d.mts.map +1 -0
- package/dist/index.d.mts +19 -19
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{interfaces-B50n7dwl.d.mts → interfaces-ClcFeDhd.d.mts} +20 -20
- package/dist/{interfaces-B50n7dwl.d.mts.map → interfaces-ClcFeDhd.d.mts.map} +1 -1
- package/dist/logger-SKhhzmU2.d.mts.map +1 -1
- package/dist/{metadata-PnPdY-2N.mjs → metadata-epuUfh0i.mjs} +2 -2
- package/dist/{metadata-PnPdY-2N.mjs.map → metadata-epuUfh0i.mjs.map} +1 -1
- package/dist/{plugin-CyoGnRbq.d.mts → plugin-DG_jFA6h.d.mts} +5 -5
- package/dist/{plugin-CyoGnRbq.d.mts.map → plugin-DG_jFA6h.d.mts.map} +1 -1
- package/dist/{query-bbA6sG2R.mjs → query-BXkCd9cg.mjs} +2 -2
- package/dist/{query-bbA6sG2R.mjs.map → query-BXkCd9cg.mjs.map} +1 -1
- package/dist/rate-limit-DUwoICtI.d.mts.map +1 -1
- package/dist/request-id-BgglvZHN.d.mts.map +1 -1
- package/dist/request-logger-CWHbAzTE.d.mts.map +1 -1
- package/dist/{router-builder-R6dyZ-A3.d.mts → router-builder-CSAOc9MR.d.mts} +2 -2
- package/dist/{router-builder-R6dyZ-A3.d.mts.map → router-builder-CSAOc9MR.d.mts.map} +1 -1
- package/dist/session-N0hActOO.d.mts.map +1 -1
- package/dist/{spa-DFxiK7jQ.d.mts → spa-DNOAvuFy.d.mts} +2 -2
- package/dist/{spa-DFxiK7jQ.d.mts.map → spa-DNOAvuFy.d.mts.map} +1 -1
- package/dist/{upload-5goxbO9A.d.mts → upload-7RRWSKkI.d.mts} +2 -2
- package/dist/{upload-5goxbO9A.d.mts.map → upload-7RRWSKkI.d.mts.map} +1 -1
- package/dist/validate-HwAyWTwH.d.mts.map +1 -1
- package/dist/{views-BvKu3uGv.d.mts → views-srW0E4Ll.d.mts} +4 -4
- package/dist/{views-BvKu3uGv.d.mts.map → views-srW0E4Ll.d.mts.map} +1 -1
- package/package.json +9 -22
- package/dist/application-BAVtRjLt.mjs +0 -12
- package/dist/application-BAVtRjLt.mjs.map +0 -1
- package/dist/application-BNxhBgji.d.mts.map +0 -1
- package/dist/container-BZKeHc5X.d.mts.map +0 -1
- package/dist/container-Dwl5vSSQ.mjs +0 -12
- package/dist/container-Dwl5vSSQ.mjs.map +0 -1
- package/dist/context-C2yPZ22U.d.mts.map +0 -1
- package/dist/context-Cm6r9rSR.mjs +0 -12
- package/dist/context-Cm6r9rSR.mjs.map +0 -1
- package/dist/decorators-BYi8lB37.d.mts.map +0 -1
- package/dist/error-handler-Xud_3Lah.d.mts.map +0 -1
- package/dist/errors-uaHCeXRz.d.mts +0 -81
- package/dist/errors-uaHCeXRz.d.mts.map +0 -1
- package/dist/index-qZqR_Mxr.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core-pTiSM6v2.mjs","names":["mergeConfig","composeName","reconstructCycle","mergeConfig","composeName"],"sources":["../src/core/define-module.ts","../src/core/define-modules.ts","../src/core/module-registry.ts","../src/core/circuit-breaker.ts","../src/core/context-decorator.ts","../src/core/context-errors.ts","../src/core/contributor-pipeline.ts","../src/core/contributor-runner.ts","../src/core/mount-sort.ts","../src/core/define-plugin.ts","../src/core/define-adapter.ts","../src/core/augmentation.ts"],"sourcesContent":["import type { AppModule } from './app-module'\nimport type { BuildContext } from './define-plugin'\n\n/**\n * Options passed to {@link defineModule}. Mirrors {@link DefineAdapterOptions}\n * and {@link DefinePluginOptions} so adopters learn one mental model\n * across all three primitives.\n *\n * The optional `TExtra` generic lets a module ship public methods\n * beyond the standard {@link AppModule} contract — they're preserved\n * on the returned module instance so external callers (tests, sibling\n * modules) can invoke them directly.\n */\nexport interface DefineModuleOptions<TConfig, TExtra = unknown> {\n /**\n * Stable identity surfaced in diagnostics, route logs, and\n * `.scoped()` namespacing. Required because boot-time error\n * messages reference modules by name — anonymous modules make\n * cycles + missing-dep errors hard to localize.\n */\n name: string\n /** Optional version string surfaced in diagnostics. */\n version?: string\n /**\n * Optional kickjs version constraint. Enforced by the framework\n * when implemented; today it lives on the definition for\n * documentation and forward compatibility.\n */\n requires?: { kickjs?: string }\n /**\n * Default config merged with overrides at instantiation time\n * (`MyModule({ field: x })`). Call-site overrides win.\n */\n defaults?: Partial<TConfig>\n /**\n * Build the AppModule shape from the resolved config and context.\n * `ctx.name` is the resolved instance name (the bare definition\n * name for the default call, or `${name}:${scope}` for\n * `.scoped()`); `ctx.scoped` discriminates so the build function\n * can branch on namespacing if needed.\n *\n * Return any extension methods alongside the standard\n * {@link AppModule} surface — they're typed via `TExtra` and stay\n * accessible on the resulting instance.\n */\n build(config: TConfig, ctx: BuildContext): AppModule & TExtra\n}\n\n/**\n * Factory returned by {@link defineModule}. Mirrors {@link AdapterFactory}\n * — call (`MyModule({...})`), scope (`MyModule.scoped('public', {...})`),\n * inspect (`MyModule.definition`).\n *\n * `.async()` is intentionally **not** part of the module factory\n * surface. Module config has no async-resolution window: `register()`\n * and `routes()` both run synchronously during bootstrap, before any\n * adapter `beforeStart` hook fires. Adopters that need async-resolved\n * config push it into an adapter (which has `beforeStart`), then\n * inject the resolved value into the module via DI tokens.\n */\nexport interface ModuleFactory<TConfig, TExtra = unknown> {\n (config?: Partial<TConfig>): AppModule & TExtra\n scoped(scopeName: string, config?: Partial<TConfig>): AppModule & TExtra\n readonly definition: Readonly<DefineModuleOptions<TConfig, TExtra>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build an {@link AppModule} factory from a typed definition. Pairs\n * with {@link defineAdapter}, {@link definePlugin}, and\n * {@link defineContextDecorator} as the fourth `define*` primitive\n * — adopters write modules the same way they write adapters and\n * plugins.\n *\n * The returned factory produces real {@link AppModule} instances\n * that drop into `bootstrap({ modules })` directly. The legacy\n * class form (`class TasksModule implements AppModule { ... }`) keeps\n * working — `bootstrap` accepts either shape.\n *\n * @example\n * ```ts\n * interface TasksConfig {\n * scope: 'public' | 'admin'\n * }\n *\n * const TasksModule = defineModule<TasksConfig>({\n * name: 'TasksModule',\n * defaults: { scope: 'public' },\n * build: (config, { name }) => ({\n * register(container) {\n * container.registerInstance(`tasks:scope:${name}`, config.scope)\n * },\n * routes() {\n * return { path: `/${config.scope}/tasks`, router: buildTasksRouter() }\n * },\n * contributors() {\n * return [LoadTenant.registration]\n * },\n * }),\n * })\n *\n * bootstrap({\n * modules: [\n * TasksModule(), // public scope (defaults)\n * TasksModule.scoped('admin', { scope: 'admin' }),\n * ],\n * })\n * ```\n */\nexport function defineModule<TConfig = Record<string, unknown>, TExtra = unknown>(\n options: DefineModuleOptions<TConfig, TExtra>,\n): ModuleFactory<TConfig, TExtra> {\n if (options === null || typeof options !== 'object') {\n throw new TypeError(\n 'defineModule: options must be an object literal. ' +\n 'See architecture.md §21 for the spec shape.',\n )\n }\n if (typeof options.name !== 'string' || options.name.length === 0) {\n const got = typeof options.name === 'string' ? '\"\"' : typeof options.name\n throw new TypeError(\n `defineModule: options.name must be a non-empty string (got ${got}). ` +\n 'Module names appear in diagnostics and `.scoped()` namespacing — anonymous ' +\n 'modules make cycle / missing-dep errors hard to localize.',\n )\n }\n if (typeof options.build !== 'function') {\n throw new TypeError(\n `defineModule(${options.name}): options.build is required and must be a function ` +\n `(got ${typeof options.build}). The build function returns the AppModule shape — ` +\n 'register() / routes() / contributors() — wired with the resolved config.',\n )\n }\n\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): AppModule & TExtra => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate-in-place rather than spread: spread strips prototype methods\n // when build() returns a class instance, and the existing defineAdapter\n // pattern uses the same approach. `TExtra` properties survive because\n // they're own properties on the build result.\n const built = options.build(config, { name: instanceName, scoped })\n // AppModule has no `name` field today, but stamping the resolved\n // instance name onto the result lets diagnostics surface it without\n // adopter wiring. The cast keeps the public AppModule type\n // unchanged while the data is available for any framework code\n // that wants it.\n Object.defineProperty(built, '__moduleName', {\n value: instanceName,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n return built\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as ModuleFactory<TConfig, TExtra>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n Object.defineProperty(factory, 'definition', {\n value: Object.freeze({ ...options }),\n writable: false,\n enumerable: true,\n configurable: false,\n })\n\n return factory\n}\n","import type { AppModuleEntry } from './app-module'\n\n/**\n * Chainable registry returned by {@link defineModules}. Extends\n * `Array<AppModuleEntry>` so it drops into `bootstrap({ modules })`\n * unchanged — the framework loader already iterates the array, so a\n * `ModuleList` IS a valid `modules` value with no special handling.\n *\n * The added affordance is `.mount(module)` returning `this` so\n * adopters can build the list fluently:\n *\n * ```ts\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * bootstrap({ modules })\n * ```\n *\n * **Why not just use a plain array?** Two reasons. First, the\n * fluent shape reads more naturally for module-list construction —\n * adopters used to `definePlugin().scoped(...)` / `defineAdapter(...)`\n * see the same call-then-call pattern. Second, the registry is the\n * stable extension point: a future `.use(module)` for non-HTTP\n * modules (queues, cron, workers) lands as a method on `ModuleList`\n * without breaking the `Array<AppModuleEntry>` superclass contract.\n */\nexport class ModuleList extends Array<AppModuleEntry> {\n /**\n * Append a module to the list. Returns `this` for chaining.\n *\n * Accepts both class form (`SomeModule extends AppModule`) and\n * `defineModule` factory output — same as the underlying\n * `bootstrap({ modules })` array. The framework loader\n * discriminates `typeof entry === 'function'` at boot.\n */\n mount(module: AppModuleEntry): this {\n this.push(module)\n return this\n }\n}\n\n/**\n * Build a {@link ModuleList} for the application. Optional vararg\n * lets adopters seed the list inline; subsequent `.mount()` calls\n * append.\n *\n * @example\n * ```ts\n * import { bootstrap, defineModules } from '@forinda/kickjs'\n * import { HelloModule } from './modules/hello/hello.module'\n * import { AdminModule } from './modules/admin/admin.module'\n *\n * // Fluent — most common\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * // Or seeded inline + chained\n * const modules2 = defineModules(HelloModule()).mount(AdminModule())\n *\n * await bootstrap({ modules })\n * ```\n *\n * The returned value is an `AppModuleEntry[]` subclass — `bootstrap`\n * accepts it directly without any special-casing. For conditional /\n * dynamic registration at bootstrap time (env flags, runtime config,\n * tenant lists), use the `setup(registry)` callback on `bootstrap`\n * instead — both surfaces compose.\n */\nexport function defineModules(...initial: AppModuleEntry[]): ModuleList {\n const list = new ModuleList()\n for (const entry of initial) list.push(entry)\n return list\n}\n","import type { AppModuleEntry } from './app-module'\n\n/**\n * Imperative receiver passed to {@link KickPlugin.setup} and the\n * top-level `bootstrap({ setup })` callback so adopters can register\n * modules conditionally — based on env flags, runtime config,\n * tenant lists, etc. — instead of being limited to the static\n * `modules: [...]` array.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules: [HelloModule()], // static — always mounted\n * setup(registry) {\n * if (env.ENABLE_ADMIN) registry.mount(AdminModule())\n * for (const tenant of env.TENANTS) {\n * registry.mount(TenantModule.scoped(tenant.id, { id: tenant.id }))\n * }\n * },\n * })\n * ```\n *\n * **Currently exposes only `.mount(module)` — the HTTP-feature path.**\n * A future `.use(module)` for non-HTTP modules (queues, cron, workers,\n * DI-only seeds) is planned but not yet implemented; existing non-HTTP\n * modules continue returning `null` from `routes()` and registering\n * via `.mount()`.\n *\n * Same dispatch rules as the static `modules: [...]` array — class\n * entries get `new`-ed at boot, `defineModule` factory output is used\n * directly. The registry doesn't validate the entry shape itself; the\n * Application loader does the typeof discrimination at mount time.\n */\nexport interface ModuleRegistry {\n /**\n * Register a module for the full HTTP lifecycle: `register()` runs,\n * `import.meta.glob` decorator side-effects fire, `routes()` is\n * called and its result mounted, `contributors()` are merged at\n * `'module'` precedence and applied to the mounted routes.\n *\n * Modules are mounted in registration order (first call → first\n * mount). Order across sources is: plugin static modules → plugin\n * `setup()` calls → user static modules → user `setup()` callback.\n */\n mount(module: AppModuleEntry): void\n}\n\n/**\n * Internal collector implementing {@link ModuleRegistry}. Application\n * passes one of these to every `setup()` callback during bootstrap;\n * the collected `entries` array is then run through the same\n * class-vs-instance dispatch as the static `modules: [...]` array.\n *\n * Not exported from the public surface — adopters interact only\n * through the {@link ModuleRegistry} interface.\n */\nexport class MutableModuleRegistry implements ModuleRegistry {\n readonly entries: AppModuleEntry[] = []\n\n mount(module: AppModuleEntry): void {\n this.entries.push(module)\n }\n}\n","/**\n * Circuit Breaker pattern for external service calls.\n *\n * Protects your application from cascading failures when downstream\n * services are unhealthy by short-circuiting requests after a\n * configurable failure threshold.\n *\n * @example\n * ```ts\n * const breaker = new CircuitBreaker('payment-api', {\n * failureThreshold: 5,\n * resetTimeout: 30_000,\n * })\n *\n * const result = await breaker.execute(() => fetch('https://payment.example.com/charge'))\n * ```\n */\n\nexport type CircuitBreakerState = 'closed' | 'open' | 'half_open'\n\nexport interface CircuitBreakerOptions {\n /** Number of consecutive failures before the circuit opens. */\n failureThreshold: number\n /** Milliseconds to wait before transitioning from OPEN to HALF_OPEN. */\n resetTimeout: number\n /** Max requests allowed in HALF_OPEN state before deciding (default 1). */\n halfOpenMax?: number\n}\n\nexport interface CircuitBreakerStats {\n failures: number\n successes: number\n state: CircuitBreakerState\n lastFailure?: Date\n}\n\n/**\n * Error thrown when the circuit is open and calls are being rejected.\n */\nexport class CircuitOpenError extends Error {\n constructor(public readonly breakerName: string) {\n super(`Circuit breaker \"${breakerName}\" is open — request rejected`)\n this.name = 'CircuitOpenError'\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitBreakerState = 'closed'\n private failures = 0\n private successes = 0\n private halfOpenAttempts = 0\n private lastFailure?: Date\n private openedAt?: number\n\n private readonly failureThreshold: number\n private readonly resetTimeout: number\n private readonly halfOpenMax: number\n\n constructor(\n public readonly name: string,\n options: CircuitBreakerOptions,\n ) {\n this.failureThreshold = options.failureThreshold\n this.resetTimeout = options.resetTimeout\n this.halfOpenMax = options.halfOpenMax ?? 1\n }\n\n /**\n * Execute an async function through the circuit breaker.\n * Throws `CircuitOpenError` if the circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n this.maybeTransitionToHalfOpen()\n\n if (this.state === 'open') {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open' && this.halfOpenAttempts >= this.halfOpenMax) {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open') {\n this.halfOpenAttempts++\n }\n\n try {\n const result = await fn()\n this.onSuccess()\n return result\n } catch (error) {\n this.onFailure()\n throw error\n }\n }\n\n /** Returns the current circuit state. */\n getState(): CircuitBreakerState {\n this.maybeTransitionToHalfOpen()\n return this.state\n }\n\n /** Returns current statistics. */\n getStats(): CircuitBreakerStats {\n this.maybeTransitionToHalfOpen()\n return {\n failures: this.failures,\n successes: this.successes,\n state: this.state,\n ...(this.lastFailure ? { lastFailure: this.lastFailure } : {}),\n }\n }\n\n /** Manually reset the circuit breaker to CLOSED state. */\n reset(): void {\n this.state = 'closed'\n this.failures = 0\n this.successes = 0\n this.halfOpenAttempts = 0\n this.lastFailure = undefined\n this.openedAt = undefined\n }\n\n private onSuccess(): void {\n if (this.state === 'half_open') {\n // Recovery confirmed — close the circuit\n this.state = 'closed'\n this.failures = 0\n this.halfOpenAttempts = 0\n this.openedAt = undefined\n }\n this.successes++\n }\n\n private onFailure(): void {\n this.failures++\n this.lastFailure = new Date()\n\n if (this.state === 'half_open') {\n // Recovery failed — re-open\n this.state = 'open'\n this.openedAt = Date.now()\n this.halfOpenAttempts = 0\n } else if (this.state === 'closed' && this.failures >= this.failureThreshold) {\n this.state = 'open'\n this.openedAt = Date.now()\n }\n }\n\n private maybeTransitionToHalfOpen(): void {\n if (this.state === 'open' && this.openedAt != null) {\n if (Date.now() - this.openedAt >= this.resetTimeout) {\n this.state = 'half_open'\n this.halfOpenAttempts = 0\n }\n }\n }\n}\n","import 'reflect-metadata'\nimport { METADATA, type Constructor, type MaybePromise } from './interfaces'\nimport { pushClassMeta, pushMethodMeta } from './metadata'\nimport type { InjectionToken } from './token'\nimport type { ContextMeta, ExecutionContext, MetaValue } from './execution-context'\n\n/**\n * String-literal union of every key declared on the augmentable\n * {@link ContextMeta} interface. Resolves to `string` when the\n * project hasn't augmented `ContextMeta` yet (so a first-day project\n * keeps compiling), and narrows to the concrete keys once the\n * `declare module '@forinda/kickjs' { interface ContextMeta { ... } }`\n * blocks land.\n *\n * Used as the element type of `dependsOn`, so a typo like\n * `dependsOn: ['tenent']` becomes a TS error instead of a boot-time\n * `MissingContributorError`.\n */\nexport type ContextMetaKey = keyof ContextMeta extends never ? string : keyof ContextMeta & string\n\n/**\n * What a single `deps` entry is allowed to be — the runtime calls\n * `container.resolve(value)`, which accepts an injection token brand\n * or a constructor (decorated class). Anything else (a string literal,\n * an array, a plain object) silently fails at boot when the runner\n * tries to resolve it; constraining at the type level surfaces the\n * mistake in the editor instead.\n */\nexport type DepValue = InjectionToken<unknown> | Constructor<unknown>\n\n/**\n * Maps a `deps` declaration object to its resolved-instance shape.\n *\n * `deps: { db: DB_TOKEN, cache: CacheService }` produces\n * `{ db: Db, cache: CacheService }` — the runner resolves each value\n * against the DI container and hands the result to `resolve()`.\n */\nexport type ResolvedDeps<D extends Record<string, DepValue>> = {\n [K in keyof D]: D[K] extends InjectionToken<infer T>\n ? T\n : D[K] extends Constructor<infer T>\n ? T\n : never\n}\n\n/**\n * Spec passed to {@link defineContextDecorator}. Describes one entry in\n * the per-request contributor pipeline.\n *\n * @typeParam K - The {@link ContextMeta} key this contributor populates.\n * @typeParam D - DI dependency declaration shape (see {@link ResolvedDeps}).\n * @typeParam Ctx - Concrete execution-context type. Defaults to the abstract\n * {@link ExecutionContext}; HTTP contributors typically\n * default to `RequestContext` at the call site.\n */\nexport interface ContextDecoratorSpec<\n K extends string = string,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n /** ContextMeta key the resolved value is written to. */\n key: K\n /**\n * DI dependencies resolved by the runner before `resolve()` runs.\n * Map entry keys become property names on the resolved-deps argument\n * passed to `resolve()`.\n */\n deps?: D\n /**\n * Other contributor keys that must populate before this one runs.\n * Enforced via topo-sort at startup; missing deps and cycles fail boot.\n *\n * Typed against `keyof ContextMeta` once the project augments it — so\n * `dependsOn: ['tenent']` (typo) is a TS error, not a boot-time\n * `MissingContributorError`. Falls back to plain `string[]` when the\n * registry is empty (no augmentation yet) so first-day projects keep\n * compiling. Cast-as-any escape hatch:\n * `dependsOn: ['custom-key' as keyof ContextMeta]`.\n */\n dependsOn?: readonly ContextMetaKey[]\n /**\n * If `true`, a thrown `resolve` skips the contributor instead of\n * forwarding to the request error handler. The key is left unset\n * and downstream consumers see `ctx.get(key) === undefined`.\n */\n optional?: boolean\n /**\n * Default per-call params merged with the call-site params; call-site\n * wins. When omitted, params default to `{}` and the resolver\n * receives an empty object — back-compat with today's zero-arg\n * decorators.\n *\n * Param shape is intentionally unconstrained: plain data, functions,\n * and closures are all valid. Adopters who want runtime validation\n * should perform it inside `resolve()` — keeps the framework\n * validator-agnostic so projects can plug Zod / Valibot / hand-rolled\n * checks without the framework caring.\n *\n * **Required-field caveat.** The factory call signature accepts\n * `Partial<P>` so adopters can override only the fields they care\n * about. If your `P` declares a *required* field that isn't in\n * `paramDefaults`, the merged runtime value can be missing that\n * field even though TypeScript types `params` as `P` inside\n * `resolve()`. Mitigate by either:\n *\n * 1. Marking the field optional in `P` and `if`-checking inside\n * `resolve()`, or\n * 2. Providing a default for every required field in\n * `paramDefaults`.\n *\n * The framework can't enforce option 2 at compile time without\n * giving up the back-compat that lets `paramDefaults` be omitted\n * entirely for `P = Record<string, never>`.\n */\n paramDefaults?: Partial<P>\n /**\n * Hook invoked when `resolve()` throws and `optional` is `false`.\n * Returning a value writes it to `ctx.set(key, …)`; returning\n * `undefined` / `void` skips. Throwing inside the hook forwards\n * the new error to the request error handler.\n *\n * Async-permitted by design — adopters frequently want to\n * `await auditService.log(err)` or `await cache.fallback(...)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`).\n */\n onError?: (err: unknown, ctx: Ctx, params: P) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Compute and return the value to write into `ctx.set(key, …)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`). When the\n * decorator is applied zero-arg (`@Foo`), `params` equals\n * `paramDefaults` (or `{}` if defaults are omitted). When applied as\n * a factory (`@Foo({ ... })`), the call-site object is merged on top.\n */\n resolve: (ctx: Ctx, deps: ResolvedDeps<D>, params: P) => MaybePromise<MetaValue<K>>\n}\n\n/**\n * Internal, normalised registration produced by {@link defineContextDecorator}.\n *\n * Frozen and shared by every use site of the decorator (method, class,\n * module, adapter, global). The pipeline builder dedupes by `key` using\n * the precedence rule defined in §20.4 of `architecture.md`.\n */\nexport interface ContributorRegistration<\n K extends string = string,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n readonly key: K\n readonly deps: D\n readonly dependsOn: readonly ContextMetaKey[]\n readonly optional: boolean\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `onError`\n * exposes only `(err, ctx)`.\n */\n readonly onError?: (err: unknown, ctx: Ctx) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `resolve`\n * exposes only `(ctx, deps)`. Adopters who write their own\n * registrations (without `defineContextDecorator`) match this\n * shape directly — params are an implementation detail of the\n * decorator factory, not of the runtime pipeline.\n */\n readonly resolve: (ctx: Ctx, deps: ResolvedDeps<D>) => MaybePromise<MetaValue<K>>\n /**\n * Stack snapshot captured the moment the decorator was defined.\n * Used by the contributor-pipeline builder to point boot-time\n * errors (`MissingContributorError`, `ContributorCycleError`,\n * `DuplicateContributorError`) at the adopter file that declared\n * the broken contributor — instead of forcing them to grep for\n * the key string.\n *\n * Optional because adopters who hand-roll a `ContributorRegistration`\n * (without `defineContextDecorator`) may not have a useful stack to\n * attach. Format is the raw `Error.stack` string; consumers slice\n * the first non-framework frame for display.\n */\n readonly definedAt?: string\n}\n\n/**\n * Type-erased registration shape used in the public collection slots\n * (`AppModule.contributors?()`, `AppAdapter.contributors?()`,\n * `KickPlugin.contributors?()`, `bootstrap({ contributors })`).\n *\n * The narrow `ContributorRegistration` carries a `Ctx` parameter so\n * `resolve(ctx, deps)` is typed at the *definition* site, but `Ctx`\n * sits in a contravariant position on `resolve` — meaning a\n * `ContributorRegistration<…, RequestContext>` is *not* assignable to\n * `ContributorRegistration<…, ExecutionContext>` even though every\n * `RequestContext` is-a `ExecutionContext`. That's TS-sound but\n * useless: the runner only ever calls a contributor with the same\n * concrete ctx the route mounts under, so an HTTP-typed contributor\n * never sees a non-HTTP ctx. Erasing `Ctx` to `any` in the collection\n * type lets adopters store contributors typed against `RequestContext`\n * (or any future transport-specific ctx) in the same array without\n * casting.\n */\nexport type AnyContributorRegistration = ContributorRegistration<\n string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n>\n\n/** Convenience alias for `AnyContributorRegistration[]` collections. */\nexport type ContributorRegistrations =\n | AnyContributorRegistration[]\n | readonly AnyContributorRegistration[]\n\n/**\n * The decorator function returned by either a zero-arg\n * {@link ContextDecorator} application or a factory call. Overloaded\n * to satisfy both class and method decorator shapes — adopters apply\n * the same decorator to either site without TS rejecting the call.\n */\nexport interface ContextDecoratorTarget {\n /** Class decorator usage. */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /** Method decorator usage. */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n}\n\n/**\n * The value returned by {@link defineContextDecorator}. Callable as\n * either a class or method decorator (with `paramDefaults` applied),\n * as a factory (`@Foo({...})` returning a per-site decorator), or\n * via `.with(params)` for non-decorator registration sites (module\n * hooks, adapter hooks, bootstrap option). `.registration` exposes\n * the underlying frozen {@link ContributorRegistration} for the\n * default-params case.\n */\nexport interface ContextDecorator<\n K extends string = string,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n /**\n * Class decorator usage with `paramDefaults` — `@Foo class C {}`.\n * The class constructor is a `Function`, so this overload only\n * matches actual classes (not plain object literals — which\n * fall through to the factory overload below).\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /**\n * Method decorator usage with `paramDefaults` — `@Foo` over a\n * method. The required `propertyKey` distinguishes this from the\n * factory overload's single-object-arg call shape.\n */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n /**\n * Zero-arg factory usage — `@Foo()` returns a decorator with\n * `paramDefaults` applied. Equivalent in behaviour to the bare\n * `@Foo` form, exposed here so adopters who prefer \"always call the\n * factory\" don't need an `as any` cast.\n */\n (): ContextDecoratorTarget\n /**\n * Factory usage — `@Foo({ ... })` returns the actual decorator with\n * call-site params merged onto `paramDefaults`. Matches a single\n * params object; plain objects don't satisfy the `Function` /\n * 2-arg-minimum constraints above, so TS picks this overload.\n *\n * Accepts `Partial<P>` so adopters can override only the fields\n * they care about — anything missing falls back to `paramDefaults`\n * at runtime. Inside `resolve()`, `params` is the fully merged\n * `P`, never a `Partial<P>`.\n *\n * The returned decorator is overloaded for class AND method usage —\n * `@Foo({...}) class C {}` and `@Foo({...}) m() {}` both type-check.\n */\n (params: Partial<P>): ContextDecoratorTarget\n /**\n * Bare registration — uses `paramDefaults` only. Use this at non-\n * decorator registration sites (module / adapter / bootstrap) when\n * the call site doesn't need to override params.\n */\n readonly registration: ContributorRegistration<K, D, Ctx>\n /**\n * Build a registration with call-site params merged onto\n * `paramDefaults`. Use this at non-decorator registration sites\n * when the call site DOES want to override params:\n *\n * ```ts\n * bootstrap({\n * contributors: [LoadTenant.with({ source: 'subdomain' }).registration],\n * })\n * ```\n *\n * `Partial<P>` for the same reason as the factory overload — the\n * runtime merges with `paramDefaults`, so adopters only specify\n * fields they want to override.\n */\n with(params: Partial<P>): { readonly registration: ContributorRegistration<K, D, Ctx> }\n}\n\n/**\n * Define a typed context contributor.\n *\n * The returned function works as a method or class decorator:\n *\n * ```ts\n * const LoadTenant = defineContextDecorator({\n * key: 'tenant',\n * deps: { tenants: TenantsRepo },\n * resolve: (ctx, { tenants }) => tenants.findByHost(ctx.requestId),\n * })\n *\n * @LoadTenant\n * @Get('/me')\n * me(ctx: RequestContext) {\n * ctx.get('tenant') // typed via ContextMeta\n * }\n * ```\n *\n * For non-decorator registration (module/adapter/bootstrap), pass\n * `LoadTenant.registration` into the appropriate contributors list.\n *\n * Boot-time validation: a missing `resolve`, an empty `key`, or a\n * non-array `dependsOn` throws `TypeError` here at definition time\n * (typically module-load) rather than waiting for the first request.\n * See `architecture.md` §20 for the full pipeline plan.\n */\nexport function defineContextDecorator<\n K extends string,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n>(spec: ContextDecoratorSpec<K, D, P, Ctx>): ContextDecorator<K, D, P, Ctx> {\n // ---- Boot-time validation --------------------------------------\n // Surface mistakes the moment `defineContextDecorator` is called\n // (typically module-load time) instead of letting them ride to\n // request time as cryptic ContextMeta misses or container failures.\n // Cheap, ~15 lines, catches a whole class of definition-time bugs.\n if (spec === null || typeof spec !== 'object') {\n throw new TypeError(\n 'defineContextDecorator: spec must be an object literal. ' +\n 'See architecture.md §20.4 for the spec shape.',\n )\n }\n if (typeof spec.key !== 'string' || spec.key.length === 0) {\n const got = typeof spec.key === 'string' ? '\"\"' : typeof spec.key\n throw new TypeError(\n `defineContextDecorator: spec.key must be a non-empty string (got ${got}). ` +\n 'Pick a name that matches a key declared on `ContextMeta` (or one of the framework keys).',\n )\n }\n if (typeof spec.resolve !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.resolve is required and must be a function ` +\n `(got ${typeof spec.resolve}). Each contributor needs a resolver that produces ` +\n `the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`,\n )\n }\n if (spec.onError !== undefined && typeof spec.onError !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.onError must be a function when provided ` +\n `(got ${typeof spec.onError}).`,\n )\n }\n if (spec.dependsOn !== undefined && !Array.isArray(spec.dependsOn)) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided ` +\n `(got ${typeof spec.dependsOn}).`,\n )\n }\n if (\n Array.isArray(spec.dependsOn) &&\n spec.dependsOn.some((dep) => typeof dep !== 'string' || dep.length === 0)\n ) {\n // TS narrows dependsOn to `readonly ContextMetaKey[]` for typed\n // adopters, but JS callers (and TS callers who erase via `as any`)\n // can still slip through with `[42]` or `['']`. Surface it at\n // definition time as a TypeError instead of riding to first\n // request as a `MissingContributorError` on a phantom key.\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`,\n )\n }\n\n // Source-location capture. `new Error().stack` here records the\n // adopter's call site so boot-time errors (cycles, missing deps,\n // duplicate keys) can point at the file that declared the broken\n // decorator. Bad boot errors are why frameworks get abandoned —\n // a single stack frame turns \"what's a 'tenent'?\" into \"edit\n // src/contributors/tenant.ts line 42\".\n const definedAt = new Error().stack\n\n // Snapshot + freeze paramDefaults so callers can't mutate the spec\n // object post-definition and shift the merged params under our feet.\n // Same immutability boundary applied to `deps` and `dependsOn`.\n const defaults = Object.freeze({\n ...(spec.paramDefaults ?? ({} as Partial<P>)),\n }) as Readonly<Partial<P>>\n\n // Shared deps + dependsOn — frozen once and reused across every\n // registration produced by this decorator (one per call site).\n // `Object.freeze` is shallow — the values inside (DI tokens,\n // constructors) are intentionally untouched; freezing them would\n // prevent the container from doing legitimate per-instance\n // bookkeeping.\n //\n // The `({} as D)` cast: `spec.deps` is optional (`deps?: D`), so\n // the fallback to `{}` is required. When `D` defaults to\n // `Record<string, never>`, the cast is sound (`{}` matches). When\n // `D` is non-empty AND adopter omitted `deps`, the cast is unsound\n // — but the runner errors loudly the moment it tries to resolve a\n // missing dep, so the mistake surfaces at first request rather\n // than producing wrong-but-silent behaviour. The alternative\n // (forcing `deps` non-optional in the spec) would break ergonomics\n // for the common zero-deps case.\n const sharedDeps = Object.freeze({ ...(spec.deps ?? ({} as D)) }) as D\n const sharedDependsOn = Object.freeze([...(spec.dependsOn ?? [])])\n const sharedOptional = spec.optional ?? false\n\n /**\n * Build a frozen registration that closes over a specific\n * resolved-params object. The runner sees a normal\n * `(ctx, deps) => value` registration — params are baked into the\n * closure, invisible to the pipeline.\n */\n const buildRegistration = (params: P): ContributorRegistration<K, D, Ctx> => {\n // Shallow-freeze the captured params so a misbehaving `resolve()` /\n // `onError()` can't mutate the closure and bleed into later\n // requests through the same registration. Adopters who really need\n // mutable per-request state should write to `ctx.set(...)` from\n // inside the resolver instead of mutating params.\n const frozenParams = Object.freeze({ ...params }) as P\n const onError: ContributorRegistration<K, D, Ctx>['onError'] = spec.onError\n ? (err: unknown, ctx: Ctx) => spec.onError!(err, ctx, frozenParams)\n : undefined\n const resolve: ContributorRegistration<K, D, Ctx>['resolve'] = (\n ctx: Ctx,\n deps: ResolvedDeps<D>,\n ) => spec.resolve(ctx, deps, frozenParams)\n return Object.freeze({\n key: spec.key,\n deps: sharedDeps,\n dependsOn: sharedDependsOn,\n optional: sharedOptional,\n onError,\n resolve,\n definedAt,\n })\n }\n\n // Default-params registration — used for zero-arg decorator\n // applications and for the `.registration` accessor. Cloning\n // `defaults` so the closure can't be mutated through the spec.\n const defaultRegistration = buildRegistration({ ...defaults } as P)\n\n /**\n * Decide whether the decorator was called as a decorator\n * (`@Foo` / `@Foo(target, key)`) or as a factory\n * (`@Foo()` / `@Foo({...})`).\n *\n * - Class decorator: `(target)` where target is a constructor function.\n * - Method decorator: `(target, propertyKey, descriptor?)` where\n * target is a prototype object and propertyKey is a string/symbol.\n * - Factory (defaults): `()` — adopter wrote `@Foo()` (rare; usually\n * they write the bare `@Foo`). Returns a decorator using\n * `paramDefaults`.\n * - Factory (params): `(params)` where params is a plain object.\n *\n * The legacy decorators that ship with KickJS (`@Get('/path')`,\n * `@Cron('* * * *')`, etc.) use the same heuristic, so adopters\n * already know to pass plain object literals as factory args.\n */\n const isDecoratorCall = (args: unknown[]): boolean => {\n // Zero args is a factory call (`@Foo()`), not a direct decorator\n // application — TS never invokes a decorator with zero args.\n if (args.length === 0) return false\n if (args.length >= 2) return true\n const first = args[0]\n // Functions are class constructors at decoration time.\n if (typeof first === 'function') return true\n // Anything else (plain object, undefined, primitive) → factory call.\n return false\n }\n\n const applyAsDecorator = (\n registration: ContributorRegistration<K, D, Ctx>,\n target: object,\n propertyKey?: string | symbol,\n ): void => {\n if (propertyKey === undefined) {\n // Class decorator — `target` is the constructor.\n pushClassMeta(METADATA.CLASS_CONTRIBUTORS, target, registration)\n } else {\n // Method decorator — `target` is the prototype; we write to its\n // constructor so reads can use the controller class as the\n // lookup key, matching the convention used by @Middleware and\n // consumed by router-builder.ts.\n const constructor = (target as { constructor: object }).constructor\n pushMethodMeta(METADATA.METHOD_CONTRIBUTORS, constructor, String(propertyKey), registration)\n }\n }\n\n /**\n * `true` for `{...}` and `Object.create(null)`. Rejects every\n * built-in subclass (Map / Set / Date / Error / RegExp / Promise),\n * adopter classes, and arrays.\n */\n const isPlainObject = (value: object): boolean => {\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n }\n\n /**\n * Merge call-site `Partial<P>` over `paramDefaults`. Strict guard:\n * accepts only **plain object literals** (or `Object.create(null)`).\n * Reject everything else with a descriptive `TypeError`.\n *\n * Why strict? Object spread copies enumerable own properties only.\n * `{ ...new Date() }` and `{ ...new Map([...])}` both produce `{}`\n * — the params silently drop. Catching this at the boundary\n * surfaces the mistake instead of routing the request with empty\n * config. Adopters who genuinely need a class-instance shape can\n * destructure it themselves: `Foo({ ...new MyParams() })`.\n */\n const mergeParams = (override: unknown): P => {\n if (override === undefined) return { ...defaults } as P\n if (\n override === null ||\n typeof override !== 'object' ||\n Array.isArray(override) ||\n !isPlainObject(override)\n ) {\n const got =\n override === null\n ? 'null'\n : Array.isArray(override)\n ? 'array'\n : typeof override === 'object'\n ? `instance of ${(override as object).constructor?.name ?? 'unknown class'}`\n : typeof override\n throw new TypeError(\n `defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. ` +\n `Class instances and built-ins (Map, Date, etc) silently spread to empty objects — ` +\n `call the decorator with a spread object literal instead, e.g. ` +\n `\\`MyDecorator({ ...someInstance })\\`.`,\n )\n }\n return { ...defaults, ...(override as Partial<P>) } as P\n }\n\n // Overloaded function signatures so `decoratorOrFactory`'s type\n // matches `ContextDecorator`'s call shapes directly — no\n // `as unknown as` double-cast needed. The implementation signature\n // (`...args: unknown[]`) is hidden from callers; only the four\n // declared overloads are reachable.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n function decoratorOrFactory(target: Function): void\n function decoratorOrFactory(\n target: object,\n propertyKey: string | symbol,\n descriptor?: PropertyDescriptor,\n ): void\n function decoratorOrFactory(): ContextDecoratorTarget\n function decoratorOrFactory(params: Partial<P>): ContextDecoratorTarget\n function decoratorOrFactory(...args: unknown[]): unknown {\n if (isDecoratorCall(args)) {\n const [target, propertyKey] = args as [\n object,\n string | symbol | undefined,\n PropertyDescriptor?,\n ]\n applyAsDecorator(defaultRegistration, target, propertyKey)\n return undefined\n }\n // Factory call — capture merged params and return a decorator.\n const params = mergeParams(args[0])\n const registration = buildRegistration(params)\n return (target: object, propertyKey?: string | symbol) => {\n applyAsDecorator(registration, target, propertyKey)\n }\n }\n\n // Set a meaningful `.name` so `console.log(LoadTenant)` and stack\n // traces show `[Function: ContextDecorator(tenant)]` instead of\n // `[Function: decoratorOrFactory]`. Function.name is configurable\n // by default, so this works on the existing function reference.\n Object.defineProperty(decoratorOrFactory, 'name', {\n value: `ContextDecorator(${spec.key})`,\n writable: false,\n enumerable: false,\n configurable: true,\n })\n\n // `Object.assign` returns the intersection type, so the decorator\n // gets `.registration` + `.with` properties typed without the\n // previous `as unknown as ContextDecorator<...>` double cast.\n // Properties go on via assign (writable + enumerable defaults),\n // then `Object.freeze` locks the shape so adopters can't mutate\n // `decorator.registration = …` post-construction.\n const decorator = Object.assign(decoratorOrFactory, {\n registration: defaultRegistration,\n with: (params: Partial<P>) => ({\n registration: buildRegistration(mergeParams(params)),\n }),\n })\n\n return Object.freeze(decorator)\n}\n","/**\n * Errors thrown by the Context Contributor pipeline.\n *\n * All three are startup-time errors raised during pipeline build (topo-sort\n * + validation), not per-request — a misconfigured app fails to boot rather\n * than fails a request. Per the locked design in `architecture.md` §20.4,\n * the pipeline is validated once when each route is mounted and the\n * resolved order is cached on the route.\n */\n\n/**\n * A contributor declared `dependsOn: ['<key>']` but no other contributor\n * (at any precedence level) produces that key for the same route.\n *\n * @example\n * ```text\n * MissingContributorError: Missing context contributor 'tenant' required by 'project' on route GET /projects/:id\n * declared at src/contributors/load-project.ts:42:18\n * ```\n */\nexport class MissingContributorError extends Error {\n readonly key: string\n readonly dependent: string\n readonly route?: string\n readonly dependentDefinedAt?: string\n\n constructor(key: string, dependent: string, route?: string, dependentDefinedAt?: string) {\n const where = route ? ` on route ${route}` : ''\n const declaredAt = formatDeclaredAt(dependentDefinedAt)\n super(`Missing context contributor '${key}' required by '${dependent}'${where}${declaredAt}`)\n this.name = 'MissingContributorError'\n this.key = key\n this.dependent = dependent\n this.route = route\n this.dependentDefinedAt = dependentDefinedAt\n }\n}\n\n/**\n * Slice the first non-framework frame off a captured `Error.stack`\n * and format it as `\\n declared at <frame>` for inclusion in\n * boot-time error messages. Returns `''` when no useful frame is\n * available (hand-rolled registrations, missing stacks).\n */\nfunction formatDeclaredAt(stack: string | undefined): string {\n if (!stack) return ''\n const lines = stack.split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('at ')) continue\n // Skip frames inside this file or the decorator factory — the\n // adopter's call site is the next frame after those.\n if (trimmed.includes('context-decorator.')) continue\n if (trimmed.includes('context-errors.')) continue\n return `\\n declared at ${trimmed.replace(/^at /, '')}`\n }\n return ''\n}\n\n/**\n * Topo-sort detected a cycle in `dependsOn` declarations. The `cycle` field\n * lists the keys in the order they form the loop, with the first key\n * repeated at the end so the path reads naturally.\n *\n * @example\n * ```text\n * ContributorCycleError: Cycle in context contributors on route GET /projects/:id: tenant → project → tenant\n * ```\n */\nexport class ContributorCycleError extends Error {\n readonly cycle: readonly string[]\n readonly route?: string\n\n constructor(cycle: readonly string[], route?: string) {\n const where = route ? ` on route ${route}` : ''\n super(`Cycle in context contributors${where}: ${cycle.join(' → ')}`)\n this.name = 'ContributorCycleError'\n this.cycle = Object.freeze([...cycle])\n this.route = route\n }\n}\n\n/**\n * Two contributors at the same precedence level both produce the same key\n * for the same route. Precedence resolution (method > class > module >\n * adapter > global, per architecture.md §20.4) silently drops\n * lower-precedence duplicates; this error fires only when ties occur\n * within a single level — e.g., two method-level decorators declaring\n * `key: 'tenant'`.\n *\n * `sources` is best-effort identifying labels for each conflicting\n * registration (typically the decorator's call site or registering\n * module/adapter name).\n *\n * @example\n * ```text\n * DuplicateContributorError: Duplicate context contributor for key 'tenant' at the same precedence level. Sources: LoadTenantFromHeader, LoadTenantFromSubdomain\n * ```\n */\nexport class DuplicateContributorError extends Error {\n readonly key: string\n readonly sources: readonly string[]\n\n constructor(key: string, sources: readonly string[]) {\n super(\n `Duplicate context contributor for key '${key}' at the same precedence level. ` +\n `Sources: ${sources.join(', ')}`,\n )\n this.name = 'DuplicateContributorError'\n this.key = key\n this.sources = Object.freeze([...sources])\n }\n}\n","import type { AnyContributorRegistration } from './context-decorator'\nimport {\n ContributorCycleError,\n DuplicateContributorError,\n MissingContributorError,\n} from './context-errors'\n\n/**\n * Where a contributor was registered, ordered by precedence\n * (lower index wins). Mirrors `architecture.md` §20.4.\n *\n * Levels (highest → lowest precedence):\n * - `'method'` — `@LoadX` on a controller method.\n * - `'class'` — `@LoadX` on a controller class.\n * - `'module'` — returned by `AppModule.contributors?()`. Per-module scope.\n * - `'adapter'` — returned by `AppAdapter.contributors?()`. Cross-cutting, applies\n * to every module's routes.\n * - `'global'` — `ApplicationOptions.contributors`. App-wide default.\n */\nexport type ContributorSource = 'method' | 'class' | 'module' | 'adapter' | 'global'\n\n/** Precedence-ranked source list. Index = precedence; method (0) wins ties. */\nconst PRECEDENCE_ORDER: readonly ContributorSource[] = [\n 'method',\n 'class',\n 'module',\n 'adapter',\n 'global',\n]\n\n/**\n * Input row to {@link buildPipeline}. Pairs a registration with its origin\n * so dedup can apply the precedence rule and diagnostic errors can name\n * the offending site.\n *\n * `registration` is the type-erased {@link AnyContributorRegistration}\n * — the runner doesn't care about each registration's narrow `D` /\n * `Ctx` shape, and adopters frequently mix HTTP / transport-agnostic\n * contributors in the same `externalSources` / `contributors()`\n * array. The narrow generics from `defineContextDecorator` survive\n * at the *definition* site (where `resolve` is typed); they're erased\n * here so collections compose without `as any` casts.\n */\nexport interface SourcedRegistration {\n registration: AnyContributorRegistration\n source: ContributorSource\n /** Human label surfaced in DuplicateContributorError messages. */\n label?: string\n}\n\n/**\n * Resolved, validated, topo-sorted pipeline ready for execution.\n *\n * `contributors` is in execution order — every entry's `dependsOn`\n * keys appear earlier. `keys` is the set of all keys this pipeline\n * produces; the runner uses it for fast existence checks and the\n * builder uses it for missing-dep validation.\n */\nexport interface ContributorPipeline {\n readonly contributors: readonly AnyContributorRegistration[]\n readonly keys: ReadonlySet<string>\n}\n\nexport interface BuildPipelineOptions {\n /** Optional route identifier surfaced in diagnostic errors (e.g., 'GET /projects/:id'). */\n route?: string\n}\n\n/**\n * Build, validate, and topo-sort a contributor pipeline.\n *\n * Pipeline:\n *\n * 1. Group sources by precedence (method > class > module > adapter > global).\n * 2. Within each precedence level, two contributors with the same `key`\n * raise {@link DuplicateContributorError}.\n * 3. Across levels, the highest-precedence contributor wins each key\n * (lower-precedence duplicates are silently dropped — that is the\n * documented override mechanism, not an error).\n * 4. Every key referenced by `dependsOn` must be produced by the\n * surviving set, otherwise {@link MissingContributorError}.\n * 5. Kahn's algorithm topo-sorts the survivors. Any nodes left after\n * the queue drains form a cycle → {@link ContributorCycleError}\n * with a reconstructed path.\n *\n * Pure function — no DI, no I/O. Safe to call once at startup per\n * route and cache the result.\n */\nexport function buildPipeline(\n sources: readonly SourcedRegistration[],\n options: BuildPipelineOptions = {},\n): ContributorPipeline {\n const route = options.route\n\n // Step 1: group by source.\n const bySource = new Map<ContributorSource, SourcedRegistration[]>()\n for (const src of PRECEDENCE_ORDER) bySource.set(src, [])\n for (const entry of sources) bySource.get(entry.source)!.push(entry)\n\n // Step 2 + 3: dedup with precedence. We walk highest precedence first,\n // claim its keys, and drop lower-precedence rows that conflict.\n const winners = new Map<string, SourcedRegistration>()\n for (const src of PRECEDENCE_ORDER) {\n const rows = bySource.get(src)!\n const keysAtThisLevel = new Map<string, SourcedRegistration[]>()\n for (const row of rows) {\n const key = row.registration.key\n const existing = keysAtThisLevel.get(key) ?? []\n existing.push(row)\n keysAtThisLevel.set(key, existing)\n }\n for (const [key, rowsForKey] of keysAtThisLevel) {\n if (rowsForKey.length > 1) {\n const labels = rowsForKey.map((r, i) => r.label ?? `${src}#${i}`)\n throw new DuplicateContributorError(key, labels)\n }\n if (!winners.has(key)) winners.set(key, rowsForKey[0])\n }\n }\n\n const survivors = [...winners.values()].map((r) => r.registration)\n const allKeys = new Set(survivors.map((r) => r.key))\n\n // Step 4: dependsOn validation.\n for (const reg of survivors) {\n for (const dep of reg.dependsOn) {\n if (!allKeys.has(dep)) {\n throw new MissingContributorError(dep, reg.key, route, reg.definedAt)\n }\n }\n }\n\n // Step 5: Kahn topo-sort.\n const sorted = topoSort(survivors, route)\n\n return Object.freeze({\n contributors: Object.freeze(sorted),\n keys: allKeys,\n })\n}\n\n/**\n * Kahn's algorithm with deterministic ordering on ties.\n *\n * Nodes with no remaining incoming edges enter the queue in input\n * order. Cycles surface as nodes still present after the queue\n * drains; we reconstruct one cycle path for diagnostics.\n */\nfunction topoSort(\n registrations: readonly AnyContributorRegistration[],\n route: string | undefined,\n): AnyContributorRegistration[] {\n // indegree[key] = number of unresolved dependsOn entries for key\n const indegree = new Map<string, number>()\n // dependents[depKey] = list of registrations that depend on depKey\n const dependents = new Map<string, AnyContributorRegistration[]>()\n const byKey = new Map<string, AnyContributorRegistration>()\n\n for (const reg of registrations) {\n indegree.set(reg.key, reg.dependsOn.length)\n byKey.set(reg.key, reg)\n }\n for (const reg of registrations) {\n for (const dep of reg.dependsOn) {\n const list = dependents.get(dep) ?? []\n list.push(reg)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: registrations with zero indegree, preserving input order.\n const queue: AnyContributorRegistration[] = []\n for (const reg of registrations) {\n if (indegree.get(reg.key) === 0) queue.push(reg)\n }\n\n const sorted: AnyContributorRegistration[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.key) ?? []) {\n const next = (indegree.get(dependent.key) ?? 0) - 1\n indegree.set(dependent.key, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== registrations.length) {\n const remaining = registrations.filter((r) => (indegree.get(r.key) ?? 0) > 0)\n throw new ContributorCycleError(reconstructCycle(remaining, byKey), route)\n }\n\n return sorted\n}\n\n/**\n * Reconstruct one cycle path from the residual graph.\n *\n * Walks from any remaining node, following its first dependency that's\n * still in the residual set, until we revisit a node. The path returned\n * starts and ends with the same key so the message reads naturally:\n * `tenant → project → tenant`.\n */\nfunction reconstructCycle(\n residual: readonly AnyContributorRegistration[],\n byKey: ReadonlyMap<string, AnyContributorRegistration>,\n): string[] {\n const residualKeys = new Set(residual.map((r) => r.key))\n const start = residual[0].key\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor: string = start\n\n while (true) {\n const reg = byKey.get(cursor)!\n const nextDep = reg.dependsOn.find((d) => residualKeys.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n // Trim the prefix that isn't part of the cycle.\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n // Fallback — couldn't reconstruct a strict cycle, return what we walked.\n return path\n}\n","import type { Container } from './container'\nimport type { ContributorRegistration } from './context-decorator'\nimport type { ExecutionContext } from './execution-context'\nimport type { ContributorPipeline } from './contributor-pipeline'\n\nexport interface RunContributorsOptions {\n /** Pre-built, validated, topo-sorted pipeline. */\n pipeline: ContributorPipeline\n /** The execution context contributors read from and write into. */\n ctx: ExecutionContext\n /** DI container used to resolve declared `deps`. */\n container: Container\n}\n\n/**\n * Execute a built {@link ContributorPipeline} against an {@link ExecutionContext}.\n *\n * Sequential — one contributor at a time, in the topo order chosen at\n * pipeline-build time. Per `architecture.md` §20.10, V1 does not run\n * contributors in parallel even within the same topo level; that is a\n * V2 follow-up gated on profiling.\n *\n * ## Error matrix (architecture.md §20.9)\n *\n * | resolve outcome | optional | onError defined | runner behaviour |\n * |-----------------|----------|-----------------|--------------------------------------------------|\n * | throws | true | — | skip; `ctx.get(key)` remains undefined |\n * | throws | false | yes | call hook; returned value (if any) stored |\n * | throws | false | no | propagate the original error |\n * | hook throws | — | yes | propagate the new error |\n * | hook returns | — | yes | undefined/void → skip; value → `ctx.set(key, v)` |\n * | resolves | — | — | `ctx.set(key, value)` |\n *\n * Container.resolve() throwing while building `deps` is treated as a\n * resolve-side throw and flows through the same matrix — adopters who\n * need different behaviour for missing-DI vs. data-lookup failures can\n * wrap the container access inside their own `resolve()`.\n */\nexport async function runContributors(options: RunContributorsOptions): Promise<void> {\n const { pipeline, ctx, container } = options\n\n for (const reg of pipeline.contributors) {\n await runOne(reg, ctx, container)\n }\n}\n\nasync function runOne(\n reg: ContributorRegistration,\n ctx: ExecutionContext,\n container: Container,\n): Promise<void> {\n let value: unknown\n try {\n const deps = resolveDeps(reg, container)\n value = await reg.resolve(ctx, deps)\n } catch (err) {\n if (reg.optional) return\n if (reg.onError) {\n // Hook throws are unconditionally propagated — there is no\n // second-chance hook. Returning undefined/void from the hook\n // means \"skip\"; returning a value means \"use this instead\".\n const replacement = await reg.onError(err, ctx)\n if (replacement === undefined) return\n ctx.set(reg.key, replacement as never)\n return\n }\n throw err\n }\n ctx.set(reg.key, value as never)\n}\n\n/**\n * Resolve a contributor's declared dependencies through the container,\n * preserving the property names from the spec so `resolve(ctx, deps)`\n * receives exactly the shape the user wrote.\n *\n * Empty `deps` produces an empty object — `resolve()` always sees an\n * object, never `undefined`.\n */\nfunction resolveDeps(reg: ContributorRegistration, container: Container): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, token] of Object.entries(reg.deps)) {\n out[key] = container.resolve(token as never)\n }\n return out\n}\n","/**\n * Topological sort for plugins and adapters with `dependsOn` declarations.\n *\n * Mirrors the algorithm used by the Context Contributor pipeline\n * (`contributor-pipeline.ts:topoSort`) but operates on a name-keyed\n * `MountSortItem` shape rather than a `ContributorRegistration`. Kept\n * standalone instead of factored generic so that:\n *\n * 1. The contributor pipeline doesn't need refactoring (risk reduction).\n * 2. The error classes can carry plugin/adapter-specific diagnostic\n * fields (`kind`, `dependent`) without bending the contributor types.\n */\n\n/**\n * A plugin or adapter that participates in dependency-ordered mounting.\n * Both `KickPlugin` and `AppAdapter` satisfy this shape via their `name`\n * and `dependsOn` fields.\n */\nexport interface MountSortItem {\n /** Unique mount name within the plugin/adapter set being sorted. */\n name: string\n /**\n * Other plugin/adapter names that must mount before this one. Cycles\n * and missing names fail at boot — same fail-fast contract as the\n * Context Contributor pipeline (architecture.md §20.4).\n */\n dependsOn?: readonly string[]\n}\n\n/** What kind of mount set the sort is operating on — surfaces in error messages. */\nexport type MountKind = 'plugin' | 'adapter'\n\n/**\n * Two items in the same mount set declared the same `name`. Plugin/adapter\n * names must be unique so `dependsOn` references resolve unambiguously.\n */\nexport class DuplicateMountNameError extends Error {\n readonly kind: MountKind\n readonly mountName: string\n\n constructor(kind: MountKind, mountName: string) {\n super(`Duplicate ${kind} name '${mountName}' — every ${kind} must declare a unique name`)\n this.name = 'DuplicateMountNameError'\n this.kind = kind\n this.mountName = mountName\n }\n}\n\n/**\n * A `dependsOn` entry references a plugin/adapter that isn't in the\n * current set. The dependency might not be installed, might be misspelled,\n * or might live in a different mount set (plugin referencing an adapter,\n * or vice versa — they sort independently).\n */\nexport class MissingMountDepError extends Error {\n readonly kind: MountKind\n readonly missing: string\n readonly dependent: string\n\n constructor(kind: MountKind, missing: string, dependent: string) {\n super(\n `Missing ${kind} dependency '${missing}' required by '${dependent}' — ` +\n `is the ${kind} installed and named correctly?`,\n )\n this.name = 'MissingMountDepError'\n this.kind = kind\n this.missing = missing\n this.dependent = dependent\n }\n}\n\n/**\n * The `dependsOn` declarations form a cycle. Boot fails so the cycle\n * can't poison live traffic. The `cycle` field reports one path through\n * the offending nodes for diagnostics — formatted `A → B → C → A`.\n */\nexport class MountCycleError extends Error {\n readonly kind: MountKind\n readonly cycle: readonly string[]\n\n constructor(kind: MountKind, cycle: readonly string[]) {\n super(`Cycle in ${kind} dependsOn declarations: ${cycle.join(' → ')}`)\n this.name = 'MountCycleError'\n this.kind = kind\n this.cycle = Object.freeze([...cycle])\n }\n}\n\n/**\n * Topologically sort `items` so every item with `dependsOn: ['X']` mounts\n * after `X`. Items without `dependsOn` keep their input order via Kahn's\n * algorithm with a FIFO queue seeded in input order — preserves the\n * historical \"plugins/adapters mount in declaration order\" behaviour for\n * any set that doesn't use `dependsOn`.\n *\n * @param items - mountable items, e.g. plugins or adapters\n * @param kind - 'plugin' | 'adapter', used in error messages\n * @returns a new array in valid mount order\n * @throws {DuplicateMountNameError} two items share a name\n * @throws {MissingMountDepError} a `dependsOn` references an unknown name\n * @throws {MountCycleError} the dep graph contains a cycle\n */\nexport function mountSort<T extends MountSortItem>(items: readonly T[], kind: MountKind): T[] {\n const byName = new Map<string, T>()\n for (const item of items) {\n if (byName.has(item.name)) {\n throw new DuplicateMountNameError(kind, item.name)\n }\n byName.set(item.name, item)\n }\n\n // indegree[name] = number of unresolved deps for that item\n const indegree = new Map<string, number>()\n // dependents[depName] = items that depend on depName\n const dependents = new Map<string, T[]>()\n\n for (const item of items) {\n indegree.set(item.name, item.dependsOn?.length ?? 0)\n }\n for (const item of items) {\n for (const dep of item.dependsOn ?? []) {\n if (!byName.has(dep)) {\n throw new MissingMountDepError(kind, dep, item.name)\n }\n const list = dependents.get(dep) ?? []\n list.push(item)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: items with zero indegree, preserving input order.\n const queue: T[] = []\n for (const item of items) {\n if (indegree.get(item.name) === 0) queue.push(item)\n }\n\n const sorted: T[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.name) ?? []) {\n const next = (indegree.get(dependent.name) ?? 0) - 1\n indegree.set(dependent.name, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== items.length) {\n const remaining = items.filter((it) => (indegree.get(it.name) ?? 0) > 0)\n throw new MountCycleError(kind, reconstructCycle(remaining, byName))\n }\n\n return sorted\n}\n\n/**\n * Walk the residual graph to find one cycle path for diagnostics.\n * Starts from the first remaining node and follows the first dep that's\n * still in the residual set until we revisit a node. Returns a path\n * starting and ending with the same name so the message reads naturally\n * (`A → B → C → A`).\n */\nfunction reconstructCycle<T extends MountSortItem>(\n residual: readonly T[],\n byName: ReadonlyMap<string, T>,\n): string[] {\n const residualNames = new Set(residual.map((r) => r.name))\n const start = residual[0].name\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor = start\n\n while (true) {\n const item = byName.get(cursor)!\n const nextDep = item.dependsOn?.find((d) => residualNames.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n return path\n}\n","import type { Container } from './container'\nimport type { KickPlugin } from './plugin'\n\n/**\n * Options passed to {@link definePlugin}. Mirrors the spec in\n * `architecture.md` §21.3.1 + §21.2.2 (Phase A \"lite\").\n */\nexport interface DefinePluginOptions<TConfig> {\n /** Stable identity used for logging, `dependsOn` lookups, and `.scoped()` namespacing. */\n name: string\n\n /** Optional plugin version — surfaced to DevTools and `kick add` compatibility checks (Phase B). */\n version?: string\n\n /**\n * Peer-version ranges this plugin needs from the framework or other\n * plugins. Recorded as metadata; runtime enforcement lands in §21.4.4.\n */\n requires?: { kickjs?: string }\n\n /** Default config merged under any caller overrides. */\n defaults?: Partial<TConfig>\n\n /** Builds the underlying {@link KickPlugin} from the resolved config. */\n build(config: TConfig, ctx: BuildContext): Omit<KickPlugin, 'name'>\n}\n\n/**\n * Hook context handed to a plugin/adapter `build` function. `name` is the\n * resolved instance name (the bare definition name for the bare call,\n * or `${defName}:${scope}` for `.scoped()`). `scoped` is the discriminator\n * authors use to namespace DI tokens or resource keys.\n */\nexport interface BuildContext {\n name: string\n scoped: boolean\n}\n\n/**\n * Async-config form of a plugin or adapter — `inject` lists DI tokens to\n * resolve from the container, then `useFactory` produces the config.\n *\n * Plugins resolve this lazily inside `onReady`, so any module/adapter/\n * middleware/contributor a plugin contributes via `build` is **not\n * available** when `.async()` is used. Use the bare/`.scoped()` form for\n * plugins that need to register modules or middleware.\n */\nexport interface PluginAsyncOptions<TConfig> {\n inject?: ReadonlyArray<unknown>\n useFactory(...deps: any[]): TConfig | Promise<TConfig>\n}\n\n/**\n * Factory returned by {@link definePlugin}. Callable form is the singleton\n * (`AuthPlugin(config)`); `.scoped()` and `.async()` are the multi-instance\n * and deferred-config forms.\n */\nexport interface PluginFactory<TConfig> {\n (config?: Partial<TConfig>): KickPlugin\n scoped(scopeName: string, config?: Partial<TConfig>): KickPlugin\n async(opts: PluginAsyncOptions<TConfig>): KickPlugin\n /** Read-only access to the original definition — useful for DevTools introspection. */\n readonly definition: Readonly<DefinePluginOptions<TConfig>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build a {@link KickPlugin} factory from a typed definition. See\n * `architecture.md` §21.2.2 for the design rationale.\n *\n * @example\n * ```ts\n * const FlagsPlugin = definePlugin<FlagsConfig>({\n * name: 'FlagsPlugin',\n * defaults: { defaultTtl: 60_000 },\n * build(config) {\n * return {\n * register(container) {\n * container.registerInstance(FLAGS, makeProvider(config))\n * },\n * }\n * },\n * })\n *\n * bootstrap({ plugins: [FlagsPlugin({ provider: launchDarkly })] })\n * ```\n */\nexport function definePlugin<TConfig = Record<string, unknown>>(\n options: DefinePluginOptions<TConfig>,\n): PluginFactory<TConfig> {\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): KickPlugin => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate `name` on the build result instead of spreading — spread\n // strips prototype methods when `build()` returns a class instance.\n const built = options.build(config, { name: instanceName, scoped }) as KickPlugin\n built.name = instanceName\n return built\n }\n\n const buildAsync = (instanceName: string, opts: PluginAsyncOptions<TConfig>): KickPlugin => {\n let inner: KickPlugin | undefined\n\n const ensureInner = async (container: Container): Promise<KickPlugin> => {\n if (inner) return inner\n const deps = (opts.inject ?? []).map((tok) => container.resolve(tok as never))\n const config = await opts.useFactory(...deps)\n inner = buildSync(instanceName, false, config as Partial<TConfig>)\n return inner\n }\n\n return {\n name: instanceName,\n async onReady(container: Container) {\n const built = await ensureInner(container)\n // Run the inner plugin's register() now that we have the config —\n // late but better than never; `.async()` plugins skip the early\n // module/middleware contribution surface by design.\n built.register?.(container)\n await built.onReady?.(container)\n },\n async shutdown() {\n await inner?.shutdown?.()\n },\n }\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as PluginFactory<TConfig>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n factory.async = (opts: PluginAsyncOptions<TConfig>) => buildAsync(options.name, opts)\n\n Object.defineProperty(factory, 'definition', { value: Object.freeze({ ...options }) })\n\n return factory\n}\n","import type { AppAdapter, AdapterContext } from './adapter'\nimport type { BuildContext } from './define-plugin'\n\n/**\n * Options passed to {@link defineAdapter}. Mirrors {@link DefinePluginOptions}\n * exactly — see `architecture.md` §21.3.4 for the definePlugin/defineAdapter\n * symmetry rationale.\n *\n * The optional `TExtra` generic lets an adapter ship public methods\n * beyond the standard {@link AppAdapter} contract — they're preserved\n * on the returned adapter instance so external callers (tests, peer\n * adapters) can invoke them directly. See `OtelAdapter.applyRedaction`\n * for the canonical use case.\n */\nexport interface DefineAdapterOptions<TConfig, TExtra = unknown> {\n /**\n * Stable identity used for logging, `dependsOn` lookups, and `.scoped()`\n * namespacing. Required for adapters that participate in `dependsOn`\n * sort — error messages reference adapters by name.\n */\n name: string\n version?: string\n requires?: { kickjs?: string }\n defaults?: Partial<TConfig>\n build(config: TConfig, ctx: BuildContext): Omit<AppAdapter, 'name'> & TExtra\n}\n\n/**\n * Async-config form of an adapter. Identical shape to `PluginAsyncOptions`\n * but resolved during `beforeStart` (the earliest async hook in\n * `AppAdapter`) — meaning `middleware()` and `contributors()` from the inner\n * adapter are **not** picked up. Use the bare/`.scoped()` form when you need\n * those.\n */\nexport interface AdapterAsyncOptions<TConfig> {\n inject?: ReadonlyArray<unknown>\n useFactory(...deps: any[]): TConfig | Promise<TConfig>\n}\n\n/**\n * Factory returned by {@link defineAdapter}. Same surface as\n * {@link PluginFactory} (call / `.scoped()` / `.async()`) so adopters learn\n * one mental model for both primitives. Returned instances carry both\n * the standard {@link AppAdapter} contract and any `TExtra` extension\n * methods the `build` function exposed.\n */\nexport interface AdapterFactory<TConfig, TExtra = unknown> {\n (config?: Partial<TConfig>): AppAdapter & TExtra\n scoped(scopeName: string, config?: Partial<TConfig>): AppAdapter & TExtra\n async(opts: AdapterAsyncOptions<TConfig>): AppAdapter\n readonly definition: Readonly<DefineAdapterOptions<TConfig, TExtra>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build an {@link AppAdapter} factory from a typed definition. See\n * `architecture.md` §21.3.4 for migration guidance — most first-party\n * adapter packages collapse to a single `defineAdapter()` call after this\n * lands.\n *\n * @example\n * ```ts\n * const TenantAdapter = defineAdapter<TenantConfig>({\n * name: 'TenantAdapter',\n * defaults: { strategy: 'header', required: true },\n * build: (config) => ({\n * middleware: () => [tenantResolverMiddleware(config)],\n * beforeStart: ({ container }) => registerTenantFactory(container, config),\n * }),\n * })\n *\n * bootstrap({\n * adapters: [\n * TenantAdapter({ strategy: 'subdomain' }),\n * TenantAdapter.scoped('shard-eu', { headerName: 'x-eu-tenant' }),\n * ],\n * })\n * ```\n */\nexport function defineAdapter<TConfig = Record<string, unknown>, TExtra = unknown>(\n options: DefineAdapterOptions<TConfig, TExtra>,\n): AdapterFactory<TConfig, TExtra> {\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): AppAdapter & TExtra => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate `name` on the build result instead of spreading — spread\n // strips prototype methods when `build()` returns a class instance.\n // Extension methods declared by the build function (`TExtra`) survive\n // the mutation since they're own properties on the build result.\n const built = options.build(config, { name: instanceName, scoped }) as AppAdapter & TExtra\n ;(built as AppAdapter).name = instanceName\n return built\n }\n\n const buildAsync = (instanceName: string, opts: AdapterAsyncOptions<TConfig>): AppAdapter => {\n let inner: AppAdapter | undefined\n\n return {\n name: instanceName,\n async beforeStart(ctx: AdapterContext) {\n const deps = (opts.inject ?? []).map((tok) => ctx.container.resolve(tok as never))\n const config = await opts.useFactory(...deps)\n inner = buildSync(instanceName, false, config as Partial<TConfig>)\n await inner.beforeStart?.(ctx)\n },\n async afterStart(ctx: AdapterContext) {\n await inner?.afterStart?.(ctx)\n },\n async shutdown() {\n await inner?.shutdown?.()\n },\n async onHealthCheck() {\n return (await inner?.onHealthCheck?.()) ?? { name: instanceName, status: 'up' }\n },\n }\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as AdapterFactory<TConfig, TExtra>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n factory.async = (opts: AdapterAsyncOptions<TConfig>) => buildAsync(options.name, opts)\n\n Object.defineProperty(factory, 'definition', { value: Object.freeze({ ...options }) })\n\n return factory\n}\n","/**\n * Augmentation registry — runtime no-op + the canonical\n * `KickJsPluginRegistry` interface that `kick typegen` augments.\n *\n * See `architecture.md` §21.2.1 (typegen for `dependsOn`) and §21.3.3\n * (standardized augmentation registry) for the design rationale.\n *\n * @module @forinda/kickjs/core/augmentation\n */\n\n/**\n * Marker interface that lists every plugin/adapter in the project. The\n * keys are the literal `name` field passed to `defineAdapter` /\n * `definePlugin` (or declared on a `class implements AppAdapter`); the\n * values are the kind tag (`'plugin' | 'adapter'`).\n *\n * The framework ships this empty. `kick typegen` augments it from\n * source so `keyof KickJsPluginRegistry` resolves to a string-literal\n * union of every name in scope — and therefore so do `dependsOn`\n * declarations on plugins/adapters.\n */\nexport interface KickJsPluginRegistry {}\n\n/**\n * Resolves to a string-literal union of every registered plugin/adapter\n * name when the project's typegen has populated `KickJsPluginRegistry`,\n * or to `string` when the registry is empty (no typegen pass yet).\n *\n * Falling back to `string` keeps fresh projects compiling — the typegen\n * narrowing is a progressive enhancement, not a hard prerequisite.\n */\nexport type KickJsPluginName = keyof KickJsPluginRegistry extends never\n ? string\n : keyof KickJsPluginRegistry & string\n\n/**\n * Metadata attached to a `defineAugmentation` call. The fields are\n * documentation-only — `kick typegen` surfaces them in the generated\n * `.kickjs/types/augmentations.d.ts` so adopters can browse every\n * augmentable interface from one place.\n *\n * `description` and `example` may both be multi-line — typegen preserves\n * line breaks when rendering them as JSDoc. There is no upper limit on\n * the example size; entire shape definitions or worked snippets are fine.\n */\nexport interface AugmentationMeta {\n /**\n * Free-form description of what the augmentation customises. May be\n * multi-line — newlines are preserved in the generated JSDoc.\n */\n description?: string\n /**\n * TS snippet showing a typical shape. May be multi-line; rendered\n * inside a fenced code block in the generated catalogue. Use this\n * for anything from `'{ foo: string }'` up to a full interface body\n * — typegen no longer flattens newlines.\n */\n example?: string\n}\n\n/**\n * Advertise an augmentable interface so `kick typegen` can list it in\n * `.kickjs/types/augmentations.d.ts` for project-wide discovery.\n *\n * **This function is a runtime AND type-level no-op.** It does NOT\n * augment the interface for TypeScript. The actual augmentation is\n * the `declare module` block — `defineAugmentation` only adds an\n * entry to the typegen catalogue so other contributors / adopters\n * can find the interface and know its expected shape.\n *\n * Both calls are needed:\n *\n * ```ts\n * import { defineAugmentation } from '@forinda/kickjs'\n *\n * // (1) The actual TS augmentation — what `ctx.get('tenant')` reads.\n * declare module '@forinda/kickjs' {\n * interface ContextMeta {\n * tenant: { id: string; name: string }\n * }\n * }\n *\n * // (2) Catalogue entry — what `kick typegen` lists in\n * // `.kickjs/types/augmentations.d.ts` for browseability.\n * defineAugmentation('ContextMeta', {\n * description: 'Tenant resolved from x-tenant-id by TenantAdapter',\n * example: `{\n * tenant: {\n * id: string\n * name: string\n * plan: 'free' | 'pro' | 'enterprise'\n * featureFlags: Record<string, boolean>\n * }\n * }`,\n * })\n * ```\n *\n * Skip `defineAugmentation` if you don't care about the catalogue — the\n * `declare module` block alone is enough for runtime + type behaviour.\n */\nexport function defineAugmentation(name: string, meta?: AugmentationMeta): void {\n // No-op at runtime — `kick typegen` discovers the call statically and\n // emits the augmentation catalogue. Touch the args so esbuild / tsc\n // don't drop the call as dead code under aggressive minification\n // (the marker has to survive the build for static scanners that look\n // at compiled output, e.g. third-party tools).\n void name\n void meta\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"core-Cs7Djz9w.mjs","names":["mergeConfig","composeName","reconstructCycle","mergeConfig","composeName"],"sources":["../src/core/define-module.ts","../src/core/define-modules.ts","../src/core/module-registry.ts","../src/core/circuit-breaker.ts","../src/core/context-decorator.ts","../src/core/context-errors.ts","../src/core/contributor-pipeline.ts","../src/core/contributor-runner.ts","../src/core/mount-sort.ts","../src/core/define-plugin.ts","../src/core/define-adapter.ts","../src/core/augmentation.ts"],"sourcesContent":["import type { AppModule } from './app-module'\nimport type { BuildContext } from './define-plugin'\n\n/**\n * Options passed to {@link defineModule}. Mirrors {@link DefineAdapterOptions}\n * and {@link DefinePluginOptions} so adopters learn one mental model\n * across all three primitives.\n *\n * The optional `TExtra` generic lets a module ship public methods\n * beyond the standard {@link AppModule} contract — they're preserved\n * on the returned module instance so external callers (tests, sibling\n * modules) can invoke them directly.\n */\nexport interface DefineModuleOptions<TConfig, TExtra = unknown> {\n /**\n * Stable identity surfaced in diagnostics, route logs, and\n * `.scoped()` namespacing. Required because boot-time error\n * messages reference modules by name — anonymous modules make\n * cycles + missing-dep errors hard to localize.\n */\n name: string\n /** Optional version string surfaced in diagnostics. */\n version?: string\n /**\n * Optional kickjs version constraint. Enforced by the framework\n * when implemented; today it lives on the definition for\n * documentation and forward compatibility.\n */\n requires?: { kickjs?: string }\n /**\n * Default config merged with overrides at instantiation time\n * (`MyModule({ field: x })`). Call-site overrides win.\n */\n defaults?: Partial<TConfig>\n /**\n * Build the AppModule shape from the resolved config and context.\n * `ctx.name` is the resolved instance name (the bare definition\n * name for the default call, or `${name}:${scope}` for\n * `.scoped()`); `ctx.scoped` discriminates so the build function\n * can branch on namespacing if needed.\n *\n * Return any extension methods alongside the standard\n * {@link AppModule} surface — they're typed via `TExtra` and stay\n * accessible on the resulting instance.\n */\n build(config: TConfig, ctx: BuildContext): AppModule & TExtra\n}\n\n/**\n * Factory returned by {@link defineModule}. Mirrors {@link AdapterFactory}\n * — call (`MyModule({...})`), scope (`MyModule.scoped('public', {...})`),\n * inspect (`MyModule.definition`).\n *\n * `.async()` is intentionally **not** part of the module factory\n * surface. Module config has no async-resolution window: `register()`\n * and `routes()` both run synchronously during bootstrap, before any\n * adapter `beforeStart` hook fires. Adopters that need async-resolved\n * config push it into an adapter (which has `beforeStart`), then\n * inject the resolved value into the module via DI tokens.\n */\nexport interface ModuleFactory<TConfig, TExtra = unknown> {\n (config?: Partial<TConfig>): AppModule & TExtra\n scoped(scopeName: string, config?: Partial<TConfig>): AppModule & TExtra\n readonly definition: Readonly<DefineModuleOptions<TConfig, TExtra>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build an {@link AppModule} factory from a typed definition. Pairs\n * with {@link defineAdapter}, {@link definePlugin}, and\n * {@link defineContextDecorator} as the fourth `define*` primitive\n * — adopters write modules the same way they write adapters and\n * plugins.\n *\n * The returned factory produces real {@link AppModule} instances\n * that drop into `bootstrap({ modules })` directly. The legacy\n * class form (`class TasksModule implements AppModule { ... }`) keeps\n * working — `bootstrap` accepts either shape.\n *\n * @example\n * ```ts\n * interface TasksConfig {\n * scope: 'public' | 'admin'\n * }\n *\n * const TasksModule = defineModule<TasksConfig>({\n * name: 'TasksModule',\n * defaults: { scope: 'public' },\n * build: (config, { name }) => ({\n * register(container) {\n * container.registerInstance(`tasks:scope:${name}`, config.scope)\n * },\n * routes() {\n * return { path: `/${config.scope}/tasks`, router: buildTasksRouter() }\n * },\n * contributors() {\n * return [LoadTenant.registration]\n * },\n * }),\n * })\n *\n * bootstrap({\n * modules: [\n * TasksModule(), // public scope (defaults)\n * TasksModule.scoped('admin', { scope: 'admin' }),\n * ],\n * })\n * ```\n */\nexport function defineModule<TConfig = Record<string, unknown>, TExtra = unknown>(\n options: DefineModuleOptions<TConfig, TExtra>,\n): ModuleFactory<TConfig, TExtra> {\n if (options === null || typeof options !== 'object') {\n throw new TypeError(\n 'defineModule: options must be an object literal. ' +\n 'See architecture.md §21 for the spec shape.',\n )\n }\n if (typeof options.name !== 'string' || options.name.length === 0) {\n const got = typeof options.name === 'string' ? '\"\"' : typeof options.name\n throw new TypeError(\n `defineModule: options.name must be a non-empty string (got ${got}). ` +\n 'Module names appear in diagnostics and `.scoped()` namespacing — anonymous ' +\n 'modules make cycle / missing-dep errors hard to localize.',\n )\n }\n if (typeof options.build !== 'function') {\n throw new TypeError(\n `defineModule(${options.name}): options.build is required and must be a function ` +\n `(got ${typeof options.build}). The build function returns the AppModule shape — ` +\n 'register() / routes() / contributors() — wired with the resolved config.',\n )\n }\n\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): AppModule & TExtra => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate-in-place rather than spread: spread strips prototype methods\n // when build() returns a class instance, and the existing defineAdapter\n // pattern uses the same approach. `TExtra` properties survive because\n // they're own properties on the build result.\n const built = options.build(config, { name: instanceName, scoped })\n // AppModule has no `name` field today, but stamping the resolved\n // instance name onto the result lets diagnostics surface it without\n // adopter wiring. The cast keeps the public AppModule type\n // unchanged while the data is available for any framework code\n // that wants it.\n Object.defineProperty(built, '__moduleName', {\n value: instanceName,\n enumerable: false,\n writable: false,\n configurable: false,\n })\n return built\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as ModuleFactory<TConfig, TExtra>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n Object.defineProperty(factory, 'definition', {\n value: Object.freeze({ ...options }),\n writable: false,\n enumerable: true,\n configurable: false,\n })\n\n return factory\n}\n","import type { AppModuleEntry } from './app-module'\n\n/**\n * Chainable registry returned by {@link defineModules}. Extends\n * `Array<AppModuleEntry>` so it drops into `bootstrap({ modules })`\n * unchanged — the framework loader already iterates the array, so a\n * `ModuleList` IS a valid `modules` value with no special handling.\n *\n * The added affordance is `.mount(module)` returning `this` so\n * adopters can build the list fluently:\n *\n * ```ts\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * bootstrap({ modules })\n * ```\n *\n * **Why not just use a plain array?** Two reasons. First, the\n * fluent shape reads more naturally for module-list construction —\n * adopters used to `definePlugin().scoped(...)` / `defineAdapter(...)`\n * see the same call-then-call pattern. Second, the registry is the\n * stable extension point: a future `.use(module)` for non-HTTP\n * modules (queues, cron, workers) lands as a method on `ModuleList`\n * without breaking the `Array<AppModuleEntry>` superclass contract.\n */\nexport class ModuleList extends Array<AppModuleEntry> {\n /**\n * Append a module to the list. Returns `this` for chaining.\n *\n * Accepts both class form (`SomeModule extends AppModule`) and\n * `defineModule` factory output — same as the underlying\n * `bootstrap({ modules })` array. The framework loader\n * discriminates `typeof entry === 'function'` at boot.\n */\n mount(module: AppModuleEntry): this {\n this.push(module)\n return this\n }\n}\n\n/**\n * Build a {@link ModuleList} for the application. Optional vararg\n * lets adopters seed the list inline; subsequent `.mount()` calls\n * append.\n *\n * @example\n * ```ts\n * import { bootstrap, defineModules } from '@forinda/kickjs'\n * import { HelloModule } from './modules/hello/hello.module'\n * import { AdminModule } from './modules/admin/admin.module'\n *\n * // Fluent — most common\n * const modules = defineModules()\n * .mount(HelloModule())\n * .mount(AdminModule())\n *\n * // Or seeded inline + chained\n * const modules2 = defineModules(HelloModule()).mount(AdminModule())\n *\n * await bootstrap({ modules })\n * ```\n *\n * The returned value is an `AppModuleEntry[]` subclass — `bootstrap`\n * accepts it directly without any special-casing. For conditional /\n * dynamic registration at bootstrap time (env flags, runtime config,\n * tenant lists), use the `setup(registry)` callback on `bootstrap`\n * instead — both surfaces compose.\n */\nexport function defineModules(...initial: AppModuleEntry[]): ModuleList {\n const list = new ModuleList()\n for (const entry of initial) list.push(entry)\n return list\n}\n","import type { AppModuleEntry } from './app-module'\n\n/**\n * Imperative receiver passed to {@link KickPlugin.setup} and the\n * top-level `bootstrap({ setup })` callback so adopters can register\n * modules conditionally — based on env flags, runtime config,\n * tenant lists, etc. — instead of being limited to the static\n * `modules: [...]` array.\n *\n * @example\n * ```ts\n * bootstrap({\n * modules: [HelloModule()], // static — always mounted\n * setup(registry) {\n * if (env.ENABLE_ADMIN) registry.mount(AdminModule())\n * for (const tenant of env.TENANTS) {\n * registry.mount(TenantModule.scoped(tenant.id, { id: tenant.id }))\n * }\n * },\n * })\n * ```\n *\n * **Currently exposes only `.mount(module)` — the HTTP-feature path.**\n * A future `.use(module)` for non-HTTP modules (queues, cron, workers,\n * DI-only seeds) is planned but not yet implemented; existing non-HTTP\n * modules continue returning `null` from `routes()` and registering\n * via `.mount()`.\n *\n * Same dispatch rules as the static `modules: [...]` array — class\n * entries get `new`-ed at boot, `defineModule` factory output is used\n * directly. The registry doesn't validate the entry shape itself; the\n * Application loader does the typeof discrimination at mount time.\n */\nexport interface ModuleRegistry {\n /**\n * Register a module for the full HTTP lifecycle: `register()` runs,\n * `import.meta.glob` decorator side-effects fire, `routes()` is\n * called and its result mounted, `contributors()` are merged at\n * `'module'` precedence and applied to the mounted routes.\n *\n * Modules are mounted in registration order (first call → first\n * mount). Order across sources is: plugin static modules → plugin\n * `setup()` calls → user static modules → user `setup()` callback.\n */\n mount(module: AppModuleEntry): void\n}\n\n/**\n * Internal collector implementing {@link ModuleRegistry}. Application\n * passes one of these to every `setup()` callback during bootstrap;\n * the collected `entries` array is then run through the same\n * class-vs-instance dispatch as the static `modules: [...]` array.\n *\n * Not exported from the public surface — adopters interact only\n * through the {@link ModuleRegistry} interface.\n */\nexport class MutableModuleRegistry implements ModuleRegistry {\n readonly entries: AppModuleEntry[] = []\n\n mount(module: AppModuleEntry): void {\n this.entries.push(module)\n }\n}\n","/**\n * Circuit Breaker pattern for external service calls.\n *\n * Protects your application from cascading failures when downstream\n * services are unhealthy by short-circuiting requests after a\n * configurable failure threshold.\n *\n * @example\n * ```ts\n * const breaker = new CircuitBreaker('payment-api', {\n * failureThreshold: 5,\n * resetTimeout: 30_000,\n * })\n *\n * const result = await breaker.execute(() => fetch('https://payment.example.com/charge'))\n * ```\n */\n\nexport type CircuitBreakerState = 'closed' | 'open' | 'half_open'\n\nexport interface CircuitBreakerOptions {\n /** Number of consecutive failures before the circuit opens. */\n failureThreshold: number\n /** Milliseconds to wait before transitioning from OPEN to HALF_OPEN. */\n resetTimeout: number\n /** Max requests allowed in HALF_OPEN state before deciding (default 1). */\n halfOpenMax?: number\n}\n\nexport interface CircuitBreakerStats {\n failures: number\n successes: number\n state: CircuitBreakerState\n lastFailure?: Date\n}\n\n/**\n * Error thrown when the circuit is open and calls are being rejected.\n */\nexport class CircuitOpenError extends Error {\n constructor(public readonly breakerName: string) {\n super(`Circuit breaker \"${breakerName}\" is open — request rejected`)\n this.name = 'CircuitOpenError'\n }\n}\n\nexport class CircuitBreaker {\n private state: CircuitBreakerState = 'closed'\n private failures = 0\n private successes = 0\n private halfOpenAttempts = 0\n private lastFailure?: Date\n private openedAt?: number\n\n private readonly failureThreshold: number\n private readonly resetTimeout: number\n private readonly halfOpenMax: number\n\n constructor(\n public readonly name: string,\n options: CircuitBreakerOptions,\n ) {\n this.failureThreshold = options.failureThreshold\n this.resetTimeout = options.resetTimeout\n this.halfOpenMax = options.halfOpenMax ?? 1\n }\n\n /**\n * Execute an async function through the circuit breaker.\n * Throws `CircuitOpenError` if the circuit is open.\n */\n async execute<T>(fn: () => Promise<T>): Promise<T> {\n this.maybeTransitionToHalfOpen()\n\n if (this.state === 'open') {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open' && this.halfOpenAttempts >= this.halfOpenMax) {\n throw new CircuitOpenError(this.name)\n }\n\n if (this.state === 'half_open') {\n this.halfOpenAttempts++\n }\n\n try {\n const result = await fn()\n this.onSuccess()\n return result\n } catch (error) {\n this.onFailure()\n throw error\n }\n }\n\n /** Returns the current circuit state. */\n getState(): CircuitBreakerState {\n this.maybeTransitionToHalfOpen()\n return this.state\n }\n\n /** Returns current statistics. */\n getStats(): CircuitBreakerStats {\n this.maybeTransitionToHalfOpen()\n return {\n failures: this.failures,\n successes: this.successes,\n state: this.state,\n ...(this.lastFailure ? { lastFailure: this.lastFailure } : {}),\n }\n }\n\n /** Manually reset the circuit breaker to CLOSED state. */\n reset(): void {\n this.state = 'closed'\n this.failures = 0\n this.successes = 0\n this.halfOpenAttempts = 0\n this.lastFailure = undefined\n this.openedAt = undefined\n }\n\n private onSuccess(): void {\n if (this.state === 'half_open') {\n // Recovery confirmed — close the circuit\n this.state = 'closed'\n this.failures = 0\n this.halfOpenAttempts = 0\n this.openedAt = undefined\n }\n this.successes++\n }\n\n private onFailure(): void {\n this.failures++\n this.lastFailure = new Date()\n\n if (this.state === 'half_open') {\n // Recovery failed — re-open\n this.state = 'open'\n this.openedAt = Date.now()\n this.halfOpenAttempts = 0\n } else if (this.state === 'closed' && this.failures >= this.failureThreshold) {\n this.state = 'open'\n this.openedAt = Date.now()\n }\n }\n\n private maybeTransitionToHalfOpen(): void {\n if (this.state === 'open' && this.openedAt != null) {\n if (Date.now() - this.openedAt >= this.resetTimeout) {\n this.state = 'half_open'\n this.halfOpenAttempts = 0\n }\n }\n }\n}\n","import 'reflect-metadata'\nimport { METADATA, type Constructor, type MaybePromise } from './interfaces'\nimport { pushClassMeta, pushMethodMeta } from './metadata'\nimport type { InjectionToken } from './token'\nimport type { ContextMeta, ExecutionContext, MetaValue } from './execution-context'\n\n/**\n * String-literal union of every key declared on the augmentable\n * {@link ContextMeta} interface. Resolves to `string` when the\n * project hasn't augmented `ContextMeta` yet (so a first-day project\n * keeps compiling), and narrows to the concrete keys once the\n * `declare module '@forinda/kickjs' { interface ContextMeta { ... } }`\n * blocks land.\n *\n * Used as the element type of `dependsOn`, so a typo like\n * `dependsOn: ['tenent']` becomes a TS error instead of a boot-time\n * `MissingContributorError`.\n */\nexport type ContextMetaKey = keyof ContextMeta extends never ? string : keyof ContextMeta & string\n\n/**\n * What a single `deps` entry is allowed to be — the runtime calls\n * `container.resolve(value)`, which accepts an injection token brand\n * or a constructor (decorated class). Anything else (a string literal,\n * an array, a plain object) silently fails at boot when the runner\n * tries to resolve it; constraining at the type level surfaces the\n * mistake in the editor instead.\n */\nexport type DepValue = InjectionToken<unknown> | Constructor<unknown>\n\n/**\n * Maps a `deps` declaration object to its resolved-instance shape.\n *\n * `deps: { db: DB_TOKEN, cache: CacheService }` produces\n * `{ db: Db, cache: CacheService }` — the runner resolves each value\n * against the DI container and hands the result to `resolve()`.\n */\nexport type ResolvedDeps<D extends Record<string, DepValue>> = {\n [K in keyof D]: D[K] extends InjectionToken<infer T>\n ? T\n : D[K] extends Constructor<infer T>\n ? T\n : never\n}\n\n/**\n * Spec passed to {@link defineContextDecorator}. Describes one entry in\n * the per-request contributor pipeline.\n *\n * @typeParam K - The {@link ContextMeta} key this contributor populates.\n * @typeParam D - DI dependency declaration shape (see {@link ResolvedDeps}).\n * @typeParam Ctx - Concrete execution-context type. Defaults to the abstract\n * {@link ExecutionContext}; HTTP contributors typically\n * default to `RequestContext` at the call site.\n */\nexport interface ContextDecoratorSpec<\n K extends string = string,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n /** ContextMeta key the resolved value is written to. */\n key: K\n /**\n * DI dependencies resolved by the runner before `resolve()` runs.\n * Map entry keys become property names on the resolved-deps argument\n * passed to `resolve()`.\n */\n deps?: D\n /**\n * Other contributor keys that must populate before this one runs.\n * Enforced via topo-sort at startup; missing deps and cycles fail boot.\n *\n * Typed against `keyof ContextMeta` once the project augments it — so\n * `dependsOn: ['tenent']` (typo) is a TS error, not a boot-time\n * `MissingContributorError`. Falls back to plain `string[]` when the\n * registry is empty (no augmentation yet) so first-day projects keep\n * compiling. Cast-as-any escape hatch:\n * `dependsOn: ['custom-key' as keyof ContextMeta]`.\n */\n dependsOn?: readonly ContextMetaKey[]\n /**\n * If `true`, a thrown `resolve` skips the contributor instead of\n * forwarding to the request error handler. The key is left unset\n * and downstream consumers see `ctx.get(key) === undefined`.\n */\n optional?: boolean\n /**\n * Default per-call params merged with the call-site params; call-site\n * wins. When omitted, params default to `{}` and the resolver\n * receives an empty object — back-compat with today's zero-arg\n * decorators.\n *\n * Param shape is intentionally unconstrained: plain data, functions,\n * and closures are all valid. Adopters who want runtime validation\n * should perform it inside `resolve()` — keeps the framework\n * validator-agnostic so projects can plug Zod / Valibot / hand-rolled\n * checks without the framework caring.\n *\n * **Required-field caveat.** The factory call signature accepts\n * `Partial<P>` so adopters can override only the fields they care\n * about. If your `P` declares a *required* field that isn't in\n * `paramDefaults`, the merged runtime value can be missing that\n * field even though TypeScript types `params` as `P` inside\n * `resolve()`. Mitigate by either:\n *\n * 1. Marking the field optional in `P` and `if`-checking inside\n * `resolve()`, or\n * 2. Providing a default for every required field in\n * `paramDefaults`.\n *\n * The framework can't enforce option 2 at compile time without\n * giving up the back-compat that lets `paramDefaults` be omitted\n * entirely for `P = Record<string, never>`.\n */\n paramDefaults?: Partial<P>\n /**\n * Hook invoked when `resolve()` throws and `optional` is `false`.\n * Returning a value writes it to `ctx.set(key, …)`; returning\n * `undefined` / `void` skips. Throwing inside the hook forwards\n * the new error to the request error handler.\n *\n * Async-permitted by design — adopters frequently want to\n * `await auditService.log(err)` or `await cache.fallback(...)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`).\n */\n onError?: (err: unknown, ctx: Ctx, params: P) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Compute and return the value to write into `ctx.set(key, …)`.\n *\n * The third `params` argument carries the resolved per-call params\n * (call-site overrides merged on top of `paramDefaults`). When the\n * decorator is applied zero-arg (`@Foo`), `params` equals\n * `paramDefaults` (or `{}` if defaults are omitted). When applied as\n * a factory (`@Foo({ ... })`), the call-site object is merged on top.\n */\n resolve: (ctx: Ctx, deps: ResolvedDeps<D>, params: P) => MaybePromise<MetaValue<K>>\n}\n\n/**\n * Internal, normalised registration produced by {@link defineContextDecorator}.\n *\n * Frozen and shared by every use site of the decorator (method, class,\n * module, adapter, global). The pipeline builder dedupes by `key` using\n * the precedence rule defined in §20.4 of `architecture.md`.\n */\nexport interface ContributorRegistration<\n K extends string = string,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n readonly key: K\n readonly deps: D\n readonly dependsOn: readonly ContextMetaKey[]\n readonly optional: boolean\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `onError`\n * exposes only `(err, ctx)`.\n */\n readonly onError?: (err: unknown, ctx: Ctx) => MaybePromise<MetaValue<K> | undefined | void>\n /**\n * Per-call params from the spec are baked into the closure at\n * decorator-construction time, so the runner-facing `resolve`\n * exposes only `(ctx, deps)`. Adopters who write their own\n * registrations (without `defineContextDecorator`) match this\n * shape directly — params are an implementation detail of the\n * decorator factory, not of the runtime pipeline.\n */\n readonly resolve: (ctx: Ctx, deps: ResolvedDeps<D>) => MaybePromise<MetaValue<K>>\n /**\n * Stack snapshot captured the moment the decorator was defined.\n * Used by the contributor-pipeline builder to point boot-time\n * errors (`MissingContributorError`, `ContributorCycleError`,\n * `DuplicateContributorError`) at the adopter file that declared\n * the broken contributor — instead of forcing them to grep for\n * the key string.\n *\n * Optional because adopters who hand-roll a `ContributorRegistration`\n * (without `defineContextDecorator`) may not have a useful stack to\n * attach. Format is the raw `Error.stack` string; consumers slice\n * the first non-framework frame for display.\n */\n readonly definedAt?: string\n}\n\n/**\n * Type-erased registration shape used in the public collection slots\n * (`AppModule.contributors?()`, `AppAdapter.contributors?()`,\n * `KickPlugin.contributors?()`, `bootstrap({ contributors })`).\n *\n * The narrow `ContributorRegistration` carries a `Ctx` parameter so\n * `resolve(ctx, deps)` is typed at the *definition* site, but `Ctx`\n * sits in a contravariant position on `resolve` — meaning a\n * `ContributorRegistration<…, RequestContext>` is *not* assignable to\n * `ContributorRegistration<…, ExecutionContext>` even though every\n * `RequestContext` is-a `ExecutionContext`. That's TS-sound but\n * useless: the runner only ever calls a contributor with the same\n * concrete ctx the route mounts under, so an HTTP-typed contributor\n * never sees a non-HTTP ctx. Erasing `Ctx` to `any` in the collection\n * type lets adopters store contributors typed against `RequestContext`\n * (or any future transport-specific ctx) in the same array without\n * casting.\n */\nexport type AnyContributorRegistration = ContributorRegistration<\n string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n>\n\n/** Convenience alias for `AnyContributorRegistration[]` collections. */\nexport type ContributorRegistrations =\n | AnyContributorRegistration[]\n | readonly AnyContributorRegistration[]\n\n/**\n * The decorator function returned by either a zero-arg\n * {@link ContextDecorator} application or a factory call. Overloaded\n * to satisfy both class and method decorator shapes — adopters apply\n * the same decorator to either site without TS rejecting the call.\n */\nexport interface ContextDecoratorTarget {\n /** Class decorator usage. */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /** Method decorator usage. */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n}\n\n/**\n * The value returned by {@link defineContextDecorator}. Callable as\n * either a class or method decorator (with `paramDefaults` applied),\n * as a factory (`@Foo({...})` returning a per-site decorator), or\n * via `.with(params)` for non-decorator registration sites (module\n * hooks, adapter hooks, bootstrap option). `.registration` exposes\n * the underlying frozen {@link ContributorRegistration} for the\n * default-params case.\n */\nexport interface ContextDecorator<\n K extends string = string,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n> {\n /**\n * Class decorator usage with `paramDefaults` — `@Foo class C {}`.\n * The class constructor is a `Function`, so this overload only\n * matches actual classes (not plain object literals — which\n * fall through to the factory overload below).\n */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n (target: Function): void\n /**\n * Method decorator usage with `paramDefaults` — `@Foo` over a\n * method. The required `propertyKey` distinguishes this from the\n * factory overload's single-object-arg call shape.\n */\n (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void\n /**\n * Zero-arg factory usage — `@Foo()` returns a decorator with\n * `paramDefaults` applied. Equivalent in behaviour to the bare\n * `@Foo` form, exposed here so adopters who prefer \"always call the\n * factory\" don't need an `as any` cast.\n */\n (): ContextDecoratorTarget\n /**\n * Factory usage — `@Foo({ ... })` returns the actual decorator with\n * call-site params merged onto `paramDefaults`. Matches a single\n * params object; plain objects don't satisfy the `Function` /\n * 2-arg-minimum constraints above, so TS picks this overload.\n *\n * Accepts `Partial<P>` so adopters can override only the fields\n * they care about — anything missing falls back to `paramDefaults`\n * at runtime. Inside `resolve()`, `params` is the fully merged\n * `P`, never a `Partial<P>`.\n *\n * The returned decorator is overloaded for class AND method usage —\n * `@Foo({...}) class C {}` and `@Foo({...}) m() {}` both type-check.\n */\n (params: Partial<P>): ContextDecoratorTarget\n /**\n * Bare registration — uses `paramDefaults` only. Use this at non-\n * decorator registration sites (module / adapter / bootstrap) when\n * the call site doesn't need to override params.\n */\n readonly registration: ContributorRegistration<K, D, Ctx>\n /**\n * Build a registration with call-site params merged onto\n * `paramDefaults`. Use this at non-decorator registration sites\n * when the call site DOES want to override params:\n *\n * ```ts\n * bootstrap({\n * contributors: [LoadTenant.with({ source: 'subdomain' }).registration],\n * })\n * ```\n *\n * `Partial<P>` for the same reason as the factory overload — the\n * runtime merges with `paramDefaults`, so adopters only specify\n * fields they want to override.\n */\n with(params: Partial<P>): { readonly registration: ContributorRegistration<K, D, Ctx> }\n}\n\n/**\n * Define a typed context contributor.\n *\n * The returned function works as a method or class decorator:\n *\n * ```ts\n * const LoadTenant = defineContextDecorator({\n * key: 'tenant',\n * deps: { tenants: TenantsRepo },\n * resolve: (ctx, { tenants }) => tenants.findByHost(ctx.requestId),\n * })\n *\n * @LoadTenant\n * @Get('/me')\n * me(ctx: RequestContext) {\n * ctx.get('tenant') // typed via ContextMeta\n * }\n * ```\n *\n * For non-decorator registration (module/adapter/bootstrap), pass\n * `LoadTenant.registration` into the appropriate contributors list.\n *\n * Boot-time validation: a missing `resolve`, an empty `key`, or a\n * non-array `dependsOn` throws `TypeError` here at definition time\n * (typically module-load) rather than waiting for the first request.\n * See `architecture.md` §20 for the full pipeline plan.\n */\nexport function defineContextDecorator<\n K extends string,\n D extends Record<string, DepValue> = Record<string, never>,\n P extends Record<string, unknown> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n>(spec: ContextDecoratorSpec<K, D, P, Ctx>): ContextDecorator<K, D, P, Ctx> {\n // ---- Boot-time validation --------------------------------------\n // Surface mistakes the moment `defineContextDecorator` is called\n // (typically module-load time) instead of letting them ride to\n // request time as cryptic ContextMeta misses or container failures.\n // Cheap, ~15 lines, catches a whole class of definition-time bugs.\n if (spec === null || typeof spec !== 'object') {\n throw new TypeError(\n 'defineContextDecorator: spec must be an object literal. ' +\n 'See architecture.md §20.4 for the spec shape.',\n )\n }\n if (typeof spec.key !== 'string' || spec.key.length === 0) {\n const got = typeof spec.key === 'string' ? '\"\"' : typeof spec.key\n throw new TypeError(\n `defineContextDecorator: spec.key must be a non-empty string (got ${got}). ` +\n 'Pick a name that matches a key declared on `ContextMeta` (or one of the framework keys).',\n )\n }\n if (typeof spec.resolve !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.resolve is required and must be a function ` +\n `(got ${typeof spec.resolve}). Each contributor needs a resolver that produces ` +\n `the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`,\n )\n }\n if (spec.onError !== undefined && typeof spec.onError !== 'function') {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.onError must be a function when provided ` +\n `(got ${typeof spec.onError}).`,\n )\n }\n if (spec.dependsOn !== undefined && !Array.isArray(spec.dependsOn)) {\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided ` +\n `(got ${typeof spec.dependsOn}).`,\n )\n }\n if (\n Array.isArray(spec.dependsOn) &&\n spec.dependsOn.some((dep) => typeof dep !== 'string' || dep.length === 0)\n ) {\n // TS narrows dependsOn to `readonly ContextMetaKey[]` for typed\n // adopters, but JS callers (and TS callers who erase via `as any`)\n // can still slip through with `[42]` or `['']`. Surface it at\n // definition time as a TypeError instead of riding to first\n // request as a `MissingContributorError` on a phantom key.\n throw new TypeError(\n `defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`,\n )\n }\n\n // Source-location capture. `new Error().stack` here records the\n // adopter's call site so boot-time errors (cycles, missing deps,\n // duplicate keys) can point at the file that declared the broken\n // decorator. Bad boot errors are why frameworks get abandoned —\n // a single stack frame turns \"what's a 'tenent'?\" into \"edit\n // src/contributors/tenant.ts line 42\".\n const definedAt = new Error().stack\n\n // Snapshot + freeze paramDefaults so callers can't mutate the spec\n // object post-definition and shift the merged params under our feet.\n // Same immutability boundary applied to `deps` and `dependsOn`.\n const defaults = Object.freeze({\n ...(spec.paramDefaults ?? ({} as Partial<P>)),\n }) as Readonly<Partial<P>>\n\n // Shared deps + dependsOn — frozen once and reused across every\n // registration produced by this decorator (one per call site).\n // `Object.freeze` is shallow — the values inside (DI tokens,\n // constructors) are intentionally untouched; freezing them would\n // prevent the container from doing legitimate per-instance\n // bookkeeping.\n //\n // The `({} as D)` cast: `spec.deps` is optional (`deps?: D`), so\n // the fallback to `{}` is required. When `D` defaults to\n // `Record<string, never>`, the cast is sound (`{}` matches). When\n // `D` is non-empty AND adopter omitted `deps`, the cast is unsound\n // — but the runner errors loudly the moment it tries to resolve a\n // missing dep, so the mistake surfaces at first request rather\n // than producing wrong-but-silent behaviour. The alternative\n // (forcing `deps` non-optional in the spec) would break ergonomics\n // for the common zero-deps case.\n const sharedDeps = Object.freeze({ ...(spec.deps ?? ({} as D)) }) as D\n const sharedDependsOn = Object.freeze([...(spec.dependsOn ?? [])])\n const sharedOptional = spec.optional ?? false\n\n /**\n * Build a frozen registration that closes over a specific\n * resolved-params object. The runner sees a normal\n * `(ctx, deps) => value` registration — params are baked into the\n * closure, invisible to the pipeline.\n */\n const buildRegistration = (params: P): ContributorRegistration<K, D, Ctx> => {\n // Shallow-freeze the captured params so a misbehaving `resolve()` /\n // `onError()` can't mutate the closure and bleed into later\n // requests through the same registration. Adopters who really need\n // mutable per-request state should write to `ctx.set(...)` from\n // inside the resolver instead of mutating params.\n const frozenParams = Object.freeze({ ...params }) as P\n const onError: ContributorRegistration<K, D, Ctx>['onError'] = spec.onError\n ? (err: unknown, ctx: Ctx) => spec.onError!(err, ctx, frozenParams)\n : undefined\n const resolve: ContributorRegistration<K, D, Ctx>['resolve'] = (\n ctx: Ctx,\n deps: ResolvedDeps<D>,\n ) => spec.resolve(ctx, deps, frozenParams)\n return Object.freeze({\n key: spec.key,\n deps: sharedDeps,\n dependsOn: sharedDependsOn,\n optional: sharedOptional,\n onError,\n resolve,\n definedAt,\n })\n }\n\n // Default-params registration — used for zero-arg decorator\n // applications and for the `.registration` accessor. Cloning\n // `defaults` so the closure can't be mutated through the spec.\n const defaultRegistration = buildRegistration({ ...defaults } as P)\n\n /**\n * Decide whether the decorator was called as a decorator\n * (`@Foo` / `@Foo(target, key)`) or as a factory\n * (`@Foo()` / `@Foo({...})`).\n *\n * - Class decorator: `(target)` where target is a constructor function.\n * - Method decorator: `(target, propertyKey, descriptor?)` where\n * target is a prototype object and propertyKey is a string/symbol.\n * - Factory (defaults): `()` — adopter wrote `@Foo()` (rare; usually\n * they write the bare `@Foo`). Returns a decorator using\n * `paramDefaults`.\n * - Factory (params): `(params)` where params is a plain object.\n *\n * The legacy decorators that ship with KickJS (`@Get('/path')`,\n * `@Cron('* * * *')`, etc.) use the same heuristic, so adopters\n * already know to pass plain object literals as factory args.\n */\n const isDecoratorCall = (args: unknown[]): boolean => {\n // Zero args is a factory call (`@Foo()`), not a direct decorator\n // application — TS never invokes a decorator with zero args.\n if (args.length === 0) return false\n if (args.length >= 2) return true\n const first = args[0]\n // Functions are class constructors at decoration time.\n if (typeof first === 'function') return true\n // Anything else (plain object, undefined, primitive) → factory call.\n return false\n }\n\n const applyAsDecorator = (\n registration: ContributorRegistration<K, D, Ctx>,\n target: object,\n propertyKey?: string | symbol,\n ): void => {\n if (propertyKey === undefined) {\n // Class decorator — `target` is the constructor.\n pushClassMeta(METADATA.CLASS_CONTRIBUTORS, target, registration)\n } else {\n // Method decorator — `target` is the prototype; we write to its\n // constructor so reads can use the controller class as the\n // lookup key, matching the convention used by @Middleware and\n // consumed by router-builder.ts.\n const constructor = (target as { constructor: object }).constructor\n pushMethodMeta(METADATA.METHOD_CONTRIBUTORS, constructor, String(propertyKey), registration)\n }\n }\n\n /**\n * `true` for `{...}` and `Object.create(null)`. Rejects every\n * built-in subclass (Map / Set / Date / Error / RegExp / Promise),\n * adopter classes, and arrays.\n */\n const isPlainObject = (value: object): boolean => {\n const proto = Object.getPrototypeOf(value)\n return proto === Object.prototype || proto === null\n }\n\n /**\n * Merge call-site `Partial<P>` over `paramDefaults`. Strict guard:\n * accepts only **plain object literals** (or `Object.create(null)`).\n * Reject everything else with a descriptive `TypeError`.\n *\n * Why strict? Object spread copies enumerable own properties only.\n * `{ ...new Date() }` and `{ ...new Map([...])}` both produce `{}`\n * — the params silently drop. Catching this at the boundary\n * surfaces the mistake instead of routing the request with empty\n * config. Adopters who genuinely need a class-instance shape can\n * destructure it themselves: `Foo({ ...new MyParams() })`.\n */\n const mergeParams = (override: unknown): P => {\n if (override === undefined) return { ...defaults } as P\n if (\n override === null ||\n typeof override !== 'object' ||\n Array.isArray(override) ||\n !isPlainObject(override)\n ) {\n const got =\n override === null\n ? 'null'\n : Array.isArray(override)\n ? 'array'\n : typeof override === 'object'\n ? `instance of ${(override as object).constructor?.name ?? 'unknown class'}`\n : typeof override\n throw new TypeError(\n `defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. ` +\n `Class instances and built-ins (Map, Date, etc) silently spread to empty objects — ` +\n `call the decorator with a spread object literal instead, e.g. ` +\n `\\`MyDecorator({ ...someInstance })\\`.`,\n )\n }\n return { ...defaults, ...(override as Partial<P>) } as P\n }\n\n // Overloaded function signatures so `decoratorOrFactory`'s type\n // matches `ContextDecorator`'s call shapes directly — no\n // `as unknown as` double-cast needed. The implementation signature\n // (`...args: unknown[]`) is hidden from callers; only the four\n // declared overloads are reachable.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n function decoratorOrFactory(target: Function): void\n function decoratorOrFactory(\n target: object,\n propertyKey: string | symbol,\n descriptor?: PropertyDescriptor,\n ): void\n function decoratorOrFactory(): ContextDecoratorTarget\n function decoratorOrFactory(params: Partial<P>): ContextDecoratorTarget\n function decoratorOrFactory(...args: unknown[]): unknown {\n if (isDecoratorCall(args)) {\n const [target, propertyKey] = args as [\n object,\n string | symbol | undefined,\n PropertyDescriptor?,\n ]\n applyAsDecorator(defaultRegistration, target, propertyKey)\n return undefined\n }\n // Factory call — capture merged params and return a decorator.\n const params = mergeParams(args[0])\n const registration = buildRegistration(params)\n return (target: object, propertyKey?: string | symbol) => {\n applyAsDecorator(registration, target, propertyKey)\n }\n }\n\n // Set a meaningful `.name` so `console.log(LoadTenant)` and stack\n // traces show `[Function: ContextDecorator(tenant)]` instead of\n // `[Function: decoratorOrFactory]`. Function.name is configurable\n // by default, so this works on the existing function reference.\n Object.defineProperty(decoratorOrFactory, 'name', {\n value: `ContextDecorator(${spec.key})`,\n writable: false,\n enumerable: false,\n configurable: true,\n })\n\n // `Object.assign` returns the intersection type, so the decorator\n // gets `.registration` + `.with` properties typed without the\n // previous `as unknown as ContextDecorator<...>` double cast.\n // Properties go on via assign (writable + enumerable defaults),\n // then `Object.freeze` locks the shape so adopters can't mutate\n // `decorator.registration = …` post-construction.\n const decorator = Object.assign(decoratorOrFactory, {\n registration: defaultRegistration,\n with: (params: Partial<P>) => ({\n registration: buildRegistration(mergeParams(params)),\n }),\n })\n\n return Object.freeze(decorator)\n}\n","/**\n * Errors thrown by the Context Contributor pipeline.\n *\n * All three are startup-time errors raised during pipeline build (topo-sort\n * + validation), not per-request — a misconfigured app fails to boot rather\n * than fails a request. Per the locked design in `architecture.md` §20.4,\n * the pipeline is validated once when each route is mounted and the\n * resolved order is cached on the route.\n */\n\n/**\n * A contributor declared `dependsOn: ['<key>']` but no other contributor\n * (at any precedence level) produces that key for the same route.\n *\n * @example\n * ```text\n * MissingContributorError: Missing context contributor 'tenant' required by 'project' on route GET /projects/:id\n * declared at src/contributors/load-project.ts:42:18\n * ```\n */\nexport class MissingContributorError extends Error {\n readonly key: string\n readonly dependent: string\n readonly route?: string\n readonly dependentDefinedAt?: string\n\n constructor(key: string, dependent: string, route?: string, dependentDefinedAt?: string) {\n const where = route ? ` on route ${route}` : ''\n const declaredAt = formatDeclaredAt(dependentDefinedAt)\n super(`Missing context contributor '${key}' required by '${dependent}'${where}${declaredAt}`)\n this.name = 'MissingContributorError'\n this.key = key\n this.dependent = dependent\n this.route = route\n this.dependentDefinedAt = dependentDefinedAt\n }\n}\n\n/**\n * Slice the first non-framework frame off a captured `Error.stack`\n * and format it as `\\n declared at <frame>` for inclusion in\n * boot-time error messages. Returns `''` when no useful frame is\n * available (hand-rolled registrations, missing stacks).\n */\nfunction formatDeclaredAt(stack: string | undefined): string {\n if (!stack) return ''\n const lines = stack.split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('at ')) continue\n // Skip frames inside this file or the decorator factory — the\n // adopter's call site is the next frame after those.\n if (trimmed.includes('context-decorator.')) continue\n if (trimmed.includes('context-errors.')) continue\n return `\\n declared at ${trimmed.replace(/^at /, '')}`\n }\n return ''\n}\n\n/**\n * Topo-sort detected a cycle in `dependsOn` declarations. The `cycle` field\n * lists the keys in the order they form the loop, with the first key\n * repeated at the end so the path reads naturally.\n *\n * @example\n * ```text\n * ContributorCycleError: Cycle in context contributors on route GET /projects/:id: tenant → project → tenant\n * ```\n */\nexport class ContributorCycleError extends Error {\n readonly cycle: readonly string[]\n readonly route?: string\n\n constructor(cycle: readonly string[], route?: string) {\n const where = route ? ` on route ${route}` : ''\n super(`Cycle in context contributors${where}: ${cycle.join(' → ')}`)\n this.name = 'ContributorCycleError'\n this.cycle = Object.freeze([...cycle])\n this.route = route\n }\n}\n\n/**\n * Two contributors at the same precedence level both produce the same key\n * for the same route. Precedence resolution (method > class > module >\n * adapter > global, per architecture.md §20.4) silently drops\n * lower-precedence duplicates; this error fires only when ties occur\n * within a single level — e.g., two method-level decorators declaring\n * `key: 'tenant'`.\n *\n * `sources` is best-effort identifying labels for each conflicting\n * registration (typically the decorator's call site or registering\n * module/adapter name).\n *\n * @example\n * ```text\n * DuplicateContributorError: Duplicate context contributor for key 'tenant' at the same precedence level. Sources: LoadTenantFromHeader, LoadTenantFromSubdomain\n * ```\n */\nexport class DuplicateContributorError extends Error {\n readonly key: string\n readonly sources: readonly string[]\n\n constructor(key: string, sources: readonly string[]) {\n super(\n `Duplicate context contributor for key '${key}' at the same precedence level. ` +\n `Sources: ${sources.join(', ')}`,\n )\n this.name = 'DuplicateContributorError'\n this.key = key\n this.sources = Object.freeze([...sources])\n }\n}\n","import type { AnyContributorRegistration } from './context-decorator'\nimport {\n ContributorCycleError,\n DuplicateContributorError,\n MissingContributorError,\n} from './context-errors'\n\n/**\n * Where a contributor was registered, ordered by precedence\n * (lower index wins). Mirrors `architecture.md` §20.4.\n *\n * Levels (highest → lowest precedence):\n * - `'method'` — `@LoadX` on a controller method.\n * - `'class'` — `@LoadX` on a controller class.\n * - `'module'` — returned by `AppModule.contributors?()`. Per-module scope.\n * - `'adapter'` — returned by `AppAdapter.contributors?()`. Cross-cutting, applies\n * to every module's routes.\n * - `'global'` — `ApplicationOptions.contributors`. App-wide default.\n */\nexport type ContributorSource = 'method' | 'class' | 'module' | 'adapter' | 'global'\n\n/** Precedence-ranked source list. Index = precedence; method (0) wins ties. */\nconst PRECEDENCE_ORDER: readonly ContributorSource[] = [\n 'method',\n 'class',\n 'module',\n 'adapter',\n 'global',\n]\n\n/**\n * Input row to {@link buildPipeline}. Pairs a registration with its origin\n * so dedup can apply the precedence rule and diagnostic errors can name\n * the offending site.\n *\n * `registration` is the type-erased {@link AnyContributorRegistration}\n * — the runner doesn't care about each registration's narrow `D` /\n * `Ctx` shape, and adopters frequently mix HTTP / transport-agnostic\n * contributors in the same `externalSources` / `contributors()`\n * array. The narrow generics from `defineContextDecorator` survive\n * at the *definition* site (where `resolve` is typed); they're erased\n * here so collections compose without `as any` casts.\n */\nexport interface SourcedRegistration {\n registration: AnyContributorRegistration\n source: ContributorSource\n /** Human label surfaced in DuplicateContributorError messages. */\n label?: string\n}\n\n/**\n * Resolved, validated, topo-sorted pipeline ready for execution.\n *\n * `contributors` is in execution order — every entry's `dependsOn`\n * keys appear earlier. `keys` is the set of all keys this pipeline\n * produces; the runner uses it for fast existence checks and the\n * builder uses it for missing-dep validation.\n */\nexport interface ContributorPipeline {\n readonly contributors: readonly AnyContributorRegistration[]\n readonly keys: ReadonlySet<string>\n}\n\nexport interface BuildPipelineOptions {\n /** Optional route identifier surfaced in diagnostic errors (e.g., 'GET /projects/:id'). */\n route?: string\n}\n\n/**\n * Build, validate, and topo-sort a contributor pipeline.\n *\n * Pipeline:\n *\n * 1. Group sources by precedence (method > class > module > adapter > global).\n * 2. Within each precedence level, two contributors with the same `key`\n * raise {@link DuplicateContributorError}.\n * 3. Across levels, the highest-precedence contributor wins each key\n * (lower-precedence duplicates are silently dropped — that is the\n * documented override mechanism, not an error).\n * 4. Every key referenced by `dependsOn` must be produced by the\n * surviving set, otherwise {@link MissingContributorError}.\n * 5. Kahn's algorithm topo-sorts the survivors. Any nodes left after\n * the queue drains form a cycle → {@link ContributorCycleError}\n * with a reconstructed path.\n *\n * Pure function — no DI, no I/O. Safe to call once at startup per\n * route and cache the result.\n */\nexport function buildPipeline(\n sources: readonly SourcedRegistration[],\n options: BuildPipelineOptions = {},\n): ContributorPipeline {\n const route = options.route\n\n // Step 1: group by source.\n const bySource = new Map<ContributorSource, SourcedRegistration[]>()\n for (const src of PRECEDENCE_ORDER) bySource.set(src, [])\n for (const entry of sources) bySource.get(entry.source)!.push(entry)\n\n // Step 2 + 3: dedup with precedence. We walk highest precedence first,\n // claim its keys, and drop lower-precedence rows that conflict.\n const winners = new Map<string, SourcedRegistration>()\n for (const src of PRECEDENCE_ORDER) {\n const rows = bySource.get(src)!\n const keysAtThisLevel = new Map<string, SourcedRegistration[]>()\n for (const row of rows) {\n const key = row.registration.key\n const existing = keysAtThisLevel.get(key) ?? []\n existing.push(row)\n keysAtThisLevel.set(key, existing)\n }\n for (const [key, rowsForKey] of keysAtThisLevel) {\n if (rowsForKey.length > 1) {\n const labels = rowsForKey.map((r, i) => r.label ?? `${src}#${i}`)\n throw new DuplicateContributorError(key, labels)\n }\n if (!winners.has(key)) winners.set(key, rowsForKey[0])\n }\n }\n\n const survivors = [...winners.values()].map((r) => r.registration)\n const allKeys = new Set(survivors.map((r) => r.key))\n\n // Step 4: dependsOn validation.\n for (const reg of survivors) {\n for (const dep of reg.dependsOn) {\n if (!allKeys.has(dep)) {\n throw new MissingContributorError(dep, reg.key, route, reg.definedAt)\n }\n }\n }\n\n // Step 5: Kahn topo-sort.\n const sorted = topoSort(survivors, route)\n\n return Object.freeze({\n contributors: Object.freeze(sorted),\n keys: allKeys,\n })\n}\n\n/**\n * Kahn's algorithm with deterministic ordering on ties.\n *\n * Nodes with no remaining incoming edges enter the queue in input\n * order. Cycles surface as nodes still present after the queue\n * drains; we reconstruct one cycle path for diagnostics.\n */\nfunction topoSort(\n registrations: readonly AnyContributorRegistration[],\n route: string | undefined,\n): AnyContributorRegistration[] {\n // indegree[key] = number of unresolved dependsOn entries for key\n const indegree = new Map<string, number>()\n // dependents[depKey] = list of registrations that depend on depKey\n const dependents = new Map<string, AnyContributorRegistration[]>()\n const byKey = new Map<string, AnyContributorRegistration>()\n\n for (const reg of registrations) {\n indegree.set(reg.key, reg.dependsOn.length)\n byKey.set(reg.key, reg)\n }\n for (const reg of registrations) {\n for (const dep of reg.dependsOn) {\n const list = dependents.get(dep) ?? []\n list.push(reg)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: registrations with zero indegree, preserving input order.\n const queue: AnyContributorRegistration[] = []\n for (const reg of registrations) {\n if (indegree.get(reg.key) === 0) queue.push(reg)\n }\n\n const sorted: AnyContributorRegistration[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.key) ?? []) {\n const next = (indegree.get(dependent.key) ?? 0) - 1\n indegree.set(dependent.key, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== registrations.length) {\n const remaining = registrations.filter((r) => (indegree.get(r.key) ?? 0) > 0)\n throw new ContributorCycleError(reconstructCycle(remaining, byKey), route)\n }\n\n return sorted\n}\n\n/**\n * Reconstruct one cycle path from the residual graph.\n *\n * Walks from any remaining node, following its first dependency that's\n * still in the residual set, until we revisit a node. The path returned\n * starts and ends with the same key so the message reads naturally:\n * `tenant → project → tenant`.\n */\nfunction reconstructCycle(\n residual: readonly AnyContributorRegistration[],\n byKey: ReadonlyMap<string, AnyContributorRegistration>,\n): string[] {\n const residualKeys = new Set(residual.map((r) => r.key))\n const start = residual[0].key\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor: string = start\n\n while (true) {\n const reg = byKey.get(cursor)!\n const nextDep = reg.dependsOn.find((d) => residualKeys.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n // Trim the prefix that isn't part of the cycle.\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n // Fallback — couldn't reconstruct a strict cycle, return what we walked.\n return path\n}\n","import type { Container } from './container'\nimport type { ContributorRegistration } from './context-decorator'\nimport type { ExecutionContext } from './execution-context'\nimport type { ContributorPipeline } from './contributor-pipeline'\n\nexport interface RunContributorsOptions {\n /** Pre-built, validated, topo-sorted pipeline. */\n pipeline: ContributorPipeline\n /** The execution context contributors read from and write into. */\n ctx: ExecutionContext\n /** DI container used to resolve declared `deps`. */\n container: Container\n}\n\n/**\n * Execute a built {@link ContributorPipeline} against an {@link ExecutionContext}.\n *\n * Sequential — one contributor at a time, in the topo order chosen at\n * pipeline-build time. Per `architecture.md` §20.10, V1 does not run\n * contributors in parallel even within the same topo level; that is a\n * V2 follow-up gated on profiling.\n *\n * ## Error matrix (architecture.md §20.9)\n *\n * | resolve outcome | optional | onError defined | runner behaviour |\n * |-----------------|----------|-----------------|--------------------------------------------------|\n * | throws | true | — | skip; `ctx.get(key)` remains undefined |\n * | throws | false | yes | call hook; returned value (if any) stored |\n * | throws | false | no | propagate the original error |\n * | hook throws | — | yes | propagate the new error |\n * | hook returns | — | yes | undefined/void → skip; value → `ctx.set(key, v)` |\n * | resolves | — | — | `ctx.set(key, value)` |\n *\n * Container.resolve() throwing while building `deps` is treated as a\n * resolve-side throw and flows through the same matrix — adopters who\n * need different behaviour for missing-DI vs. data-lookup failures can\n * wrap the container access inside their own `resolve()`.\n */\nexport async function runContributors(options: RunContributorsOptions): Promise<void> {\n const { pipeline, ctx, container } = options\n\n for (const reg of pipeline.contributors) {\n await runOne(reg, ctx, container)\n }\n}\n\nasync function runOne(\n reg: ContributorRegistration,\n ctx: ExecutionContext,\n container: Container,\n): Promise<void> {\n let value: unknown\n try {\n const deps = resolveDeps(reg, container)\n value = await reg.resolve(ctx, deps)\n } catch (err) {\n if (reg.optional) return\n if (reg.onError) {\n // Hook throws are unconditionally propagated — there is no\n // second-chance hook. Returning undefined/void from the hook\n // means \"skip\"; returning a value means \"use this instead\".\n const replacement = await reg.onError(err, ctx)\n if (replacement === undefined) return\n ctx.set(reg.key, replacement as never)\n return\n }\n throw err\n }\n ctx.set(reg.key, value as never)\n}\n\n/**\n * Resolve a contributor's declared dependencies through the container,\n * preserving the property names from the spec so `resolve(ctx, deps)`\n * receives exactly the shape the user wrote.\n *\n * Empty `deps` produces an empty object — `resolve()` always sees an\n * object, never `undefined`.\n */\nfunction resolveDeps(reg: ContributorRegistration, container: Container): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, token] of Object.entries(reg.deps)) {\n out[key] = container.resolve(token as never)\n }\n return out\n}\n","/**\n * Topological sort for plugins and adapters with `dependsOn` declarations.\n *\n * Mirrors the algorithm used by the Context Contributor pipeline\n * (`contributor-pipeline.ts:topoSort`) but operates on a name-keyed\n * `MountSortItem` shape rather than a `ContributorRegistration`. Kept\n * standalone instead of factored generic so that:\n *\n * 1. The contributor pipeline doesn't need refactoring (risk reduction).\n * 2. The error classes can carry plugin/adapter-specific diagnostic\n * fields (`kind`, `dependent`) without bending the contributor types.\n */\n\n/**\n * A plugin or adapter that participates in dependency-ordered mounting.\n * Both `KickPlugin` and `AppAdapter` satisfy this shape via their `name`\n * and `dependsOn` fields.\n */\nexport interface MountSortItem {\n /** Unique mount name within the plugin/adapter set being sorted. */\n name: string\n /**\n * Other plugin/adapter names that must mount before this one. Cycles\n * and missing names fail at boot — same fail-fast contract as the\n * Context Contributor pipeline (architecture.md §20.4).\n */\n dependsOn?: readonly string[]\n}\n\n/** What kind of mount set the sort is operating on — surfaces in error messages. */\nexport type MountKind = 'plugin' | 'adapter'\n\n/**\n * Two items in the same mount set declared the same `name`. Plugin/adapter\n * names must be unique so `dependsOn` references resolve unambiguously.\n */\nexport class DuplicateMountNameError extends Error {\n readonly kind: MountKind\n readonly mountName: string\n\n constructor(kind: MountKind, mountName: string) {\n super(`Duplicate ${kind} name '${mountName}' — every ${kind} must declare a unique name`)\n this.name = 'DuplicateMountNameError'\n this.kind = kind\n this.mountName = mountName\n }\n}\n\n/**\n * A `dependsOn` entry references a plugin/adapter that isn't in the\n * current set. The dependency might not be installed, might be misspelled,\n * or might live in a different mount set (plugin referencing an adapter,\n * or vice versa — they sort independently).\n */\nexport class MissingMountDepError extends Error {\n readonly kind: MountKind\n readonly missing: string\n readonly dependent: string\n\n constructor(kind: MountKind, missing: string, dependent: string) {\n super(\n `Missing ${kind} dependency '${missing}' required by '${dependent}' — ` +\n `is the ${kind} installed and named correctly?`,\n )\n this.name = 'MissingMountDepError'\n this.kind = kind\n this.missing = missing\n this.dependent = dependent\n }\n}\n\n/**\n * The `dependsOn` declarations form a cycle. Boot fails so the cycle\n * can't poison live traffic. The `cycle` field reports one path through\n * the offending nodes for diagnostics — formatted `A → B → C → A`.\n */\nexport class MountCycleError extends Error {\n readonly kind: MountKind\n readonly cycle: readonly string[]\n\n constructor(kind: MountKind, cycle: readonly string[]) {\n super(`Cycle in ${kind} dependsOn declarations: ${cycle.join(' → ')}`)\n this.name = 'MountCycleError'\n this.kind = kind\n this.cycle = Object.freeze([...cycle])\n }\n}\n\n/**\n * Topologically sort `items` so every item with `dependsOn: ['X']` mounts\n * after `X`. Items without `dependsOn` keep their input order via Kahn's\n * algorithm with a FIFO queue seeded in input order — preserves the\n * historical \"plugins/adapters mount in declaration order\" behaviour for\n * any set that doesn't use `dependsOn`.\n *\n * @param items - mountable items, e.g. plugins or adapters\n * @param kind - 'plugin' | 'adapter', used in error messages\n * @returns a new array in valid mount order\n * @throws {DuplicateMountNameError} two items share a name\n * @throws {MissingMountDepError} a `dependsOn` references an unknown name\n * @throws {MountCycleError} the dep graph contains a cycle\n */\nexport function mountSort<T extends MountSortItem>(items: readonly T[], kind: MountKind): T[] {\n const byName = new Map<string, T>()\n for (const item of items) {\n if (byName.has(item.name)) {\n throw new DuplicateMountNameError(kind, item.name)\n }\n byName.set(item.name, item)\n }\n\n // indegree[name] = number of unresolved deps for that item\n const indegree = new Map<string, number>()\n // dependents[depName] = items that depend on depName\n const dependents = new Map<string, T[]>()\n\n for (const item of items) {\n indegree.set(item.name, item.dependsOn?.length ?? 0)\n }\n for (const item of items) {\n for (const dep of item.dependsOn ?? []) {\n if (!byName.has(dep)) {\n throw new MissingMountDepError(kind, dep, item.name)\n }\n const list = dependents.get(dep) ?? []\n list.push(item)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: items with zero indegree, preserving input order.\n const queue: T[] = []\n for (const item of items) {\n if (indegree.get(item.name) === 0) queue.push(item)\n }\n\n const sorted: T[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.name) ?? []) {\n const next = (indegree.get(dependent.name) ?? 0) - 1\n indegree.set(dependent.name, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== items.length) {\n const remaining = items.filter((it) => (indegree.get(it.name) ?? 0) > 0)\n throw new MountCycleError(kind, reconstructCycle(remaining, byName))\n }\n\n return sorted\n}\n\n/**\n * Walk the residual graph to find one cycle path for diagnostics.\n * Starts from the first remaining node and follows the first dep that's\n * still in the residual set until we revisit a node. Returns a path\n * starting and ending with the same name so the message reads naturally\n * (`A → B → C → A`).\n */\nfunction reconstructCycle<T extends MountSortItem>(\n residual: readonly T[],\n byName: ReadonlyMap<string, T>,\n): string[] {\n const residualNames = new Set(residual.map((r) => r.name))\n const start = residual[0].name\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor = start\n\n while (true) {\n const item = byName.get(cursor)!\n const nextDep = item.dependsOn?.find((d) => residualNames.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n return path\n}\n","import type { Container } from './container'\nimport type { KickPlugin } from './plugin'\n\n/**\n * Options passed to {@link definePlugin}. Mirrors the spec in\n * `architecture.md` §21.3.1 + §21.2.2 (Phase A \"lite\").\n */\nexport interface DefinePluginOptions<TConfig> {\n /** Stable identity used for logging, `dependsOn` lookups, and `.scoped()` namespacing. */\n name: string\n\n /** Optional plugin version — surfaced to DevTools and `kick add` compatibility checks (Phase B). */\n version?: string\n\n /**\n * Peer-version ranges this plugin needs from the framework or other\n * plugins. Recorded as metadata; runtime enforcement lands in §21.4.4.\n */\n requires?: { kickjs?: string }\n\n /** Default config merged under any caller overrides. */\n defaults?: Partial<TConfig>\n\n /** Builds the underlying {@link KickPlugin} from the resolved config. */\n build(config: TConfig, ctx: BuildContext): Omit<KickPlugin, 'name'>\n}\n\n/**\n * Hook context handed to a plugin/adapter `build` function. `name` is the\n * resolved instance name (the bare definition name for the bare call,\n * or `${defName}:${scope}` for `.scoped()`). `scoped` is the discriminator\n * authors use to namespace DI tokens or resource keys.\n */\nexport interface BuildContext {\n name: string\n scoped: boolean\n}\n\n/**\n * Async-config form of a plugin or adapter — `inject` lists DI tokens to\n * resolve from the container, then `useFactory` produces the config.\n *\n * Plugins resolve this lazily inside `onReady`, so any module/adapter/\n * middleware/contributor a plugin contributes via `build` is **not\n * available** when `.async()` is used. Use the bare/`.scoped()` form for\n * plugins that need to register modules or middleware.\n */\nexport interface PluginAsyncOptions<TConfig> {\n inject?: ReadonlyArray<unknown>\n useFactory(...deps: any[]): TConfig | Promise<TConfig>\n}\n\n/**\n * Factory returned by {@link definePlugin}. Callable form is the singleton\n * (`AuthPlugin(config)`); `.scoped()` and `.async()` are the multi-instance\n * and deferred-config forms.\n */\nexport interface PluginFactory<TConfig> {\n (config?: Partial<TConfig>): KickPlugin\n scoped(scopeName: string, config?: Partial<TConfig>): KickPlugin\n async(opts: PluginAsyncOptions<TConfig>): KickPlugin\n /** Read-only access to the original definition — useful for DevTools introspection. */\n readonly definition: Readonly<DefinePluginOptions<TConfig>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build a {@link KickPlugin} factory from a typed definition. See\n * `architecture.md` §21.2.2 for the design rationale.\n *\n * @example\n * ```ts\n * const FlagsPlugin = definePlugin<FlagsConfig>({\n * name: 'FlagsPlugin',\n * defaults: { defaultTtl: 60_000 },\n * build(config) {\n * return {\n * register(container) {\n * container.registerInstance(FLAGS, makeProvider(config))\n * },\n * }\n * },\n * })\n *\n * bootstrap({ plugins: [FlagsPlugin({ provider: launchDarkly })] })\n * ```\n */\nexport function definePlugin<TConfig = Record<string, unknown>>(\n options: DefinePluginOptions<TConfig>,\n): PluginFactory<TConfig> {\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): KickPlugin => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate `name` on the build result instead of spreading — spread\n // strips prototype methods when `build()` returns a class instance.\n const built = options.build(config, { name: instanceName, scoped }) as KickPlugin\n built.name = instanceName\n return built\n }\n\n const buildAsync = (instanceName: string, opts: PluginAsyncOptions<TConfig>): KickPlugin => {\n let inner: KickPlugin | undefined\n\n const ensureInner = async (container: Container): Promise<KickPlugin> => {\n if (inner) return inner\n const deps = (opts.inject ?? []).map((tok) => container.resolve(tok as never))\n const config = await opts.useFactory(...deps)\n inner = buildSync(instanceName, false, config as Partial<TConfig>)\n return inner\n }\n\n return {\n name: instanceName,\n async onReady(container: Container) {\n const built = await ensureInner(container)\n // Run the inner plugin's register() now that we have the config —\n // late but better than never; `.async()` plugins skip the early\n // module/middleware contribution surface by design.\n built.register?.(container)\n await built.onReady?.(container)\n },\n async shutdown() {\n await inner?.shutdown?.()\n },\n }\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as PluginFactory<TConfig>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n factory.async = (opts: PluginAsyncOptions<TConfig>) => buildAsync(options.name, opts)\n\n Object.defineProperty(factory, 'definition', { value: Object.freeze({ ...options }) })\n\n return factory\n}\n","import type { AppAdapter, AdapterContext } from './adapter'\nimport type { BuildContext } from './define-plugin'\n\n/**\n * Options passed to {@link defineAdapter}. Mirrors {@link DefinePluginOptions}\n * exactly — see `architecture.md` §21.3.4 for the definePlugin/defineAdapter\n * symmetry rationale.\n *\n * The optional `TExtra` generic lets an adapter ship public methods\n * beyond the standard {@link AppAdapter} contract — they're preserved\n * on the returned adapter instance so external callers (tests, peer\n * adapters) can invoke them directly. See `OtelAdapter.applyRedaction`\n * for the canonical use case.\n */\nexport interface DefineAdapterOptions<TConfig, TExtra = unknown> {\n /**\n * Stable identity used for logging, `dependsOn` lookups, and `.scoped()`\n * namespacing. Required for adapters that participate in `dependsOn`\n * sort — error messages reference adapters by name.\n */\n name: string\n version?: string\n requires?: { kickjs?: string }\n defaults?: Partial<TConfig>\n build(config: TConfig, ctx: BuildContext): Omit<AppAdapter, 'name'> & TExtra\n}\n\n/**\n * Async-config form of an adapter. Identical shape to `PluginAsyncOptions`\n * but resolved during `beforeStart` (the earliest async hook in\n * `AppAdapter`) — meaning `middleware()` and `contributors()` from the inner\n * adapter are **not** picked up. Use the bare/`.scoped()` form when you need\n * those.\n */\nexport interface AdapterAsyncOptions<TConfig> {\n inject?: ReadonlyArray<unknown>\n useFactory(...deps: any[]): TConfig | Promise<TConfig>\n}\n\n/**\n * Factory returned by {@link defineAdapter}. Same surface as\n * {@link PluginFactory} (call / `.scoped()` / `.async()`) so adopters learn\n * one mental model for both primitives. Returned instances carry both\n * the standard {@link AppAdapter} contract and any `TExtra` extension\n * methods the `build` function exposed.\n */\nexport interface AdapterFactory<TConfig, TExtra = unknown> {\n (config?: Partial<TConfig>): AppAdapter & TExtra\n scoped(scopeName: string, config?: Partial<TConfig>): AppAdapter & TExtra\n async(opts: AdapterAsyncOptions<TConfig>): AppAdapter\n readonly definition: Readonly<DefineAdapterOptions<TConfig, TExtra>>\n}\n\nconst mergeConfig = <TConfig>(\n defaults: Partial<TConfig> = {},\n overrides: Partial<TConfig> = {},\n): TConfig => ({ ...defaults, ...overrides }) as TConfig\n\nconst composeName = (base: string, scope: string): string => `${base}:${scope}`\n\n/**\n * Build an {@link AppAdapter} factory from a typed definition. See\n * `architecture.md` §21.3.4 for migration guidance — most first-party\n * adapter packages collapse to a single `defineAdapter()` call after this\n * lands.\n *\n * @example\n * ```ts\n * const TenantAdapter = defineAdapter<TenantConfig>({\n * name: 'TenantAdapter',\n * defaults: { strategy: 'header', required: true },\n * build: (config) => ({\n * middleware: () => [tenantResolverMiddleware(config)],\n * beforeStart: ({ container }) => registerTenantFactory(container, config),\n * }),\n * })\n *\n * bootstrap({\n * adapters: [\n * TenantAdapter({ strategy: 'subdomain' }),\n * TenantAdapter.scoped('shard-eu', { headerName: 'x-eu-tenant' }),\n * ],\n * })\n * ```\n */\nexport function defineAdapter<TConfig = Record<string, unknown>, TExtra = unknown>(\n options: DefineAdapterOptions<TConfig, TExtra>,\n): AdapterFactory<TConfig, TExtra> {\n const buildSync = (\n instanceName: string,\n scoped: boolean,\n overrides?: Partial<TConfig>,\n ): AppAdapter & TExtra => {\n const config = mergeConfig(options.defaults, overrides)\n // Mutate `name` on the build result instead of spreading — spread\n // strips prototype methods when `build()` returns a class instance.\n // Extension methods declared by the build function (`TExtra`) survive\n // the mutation since they're own properties on the build result.\n const built = options.build(config, { name: instanceName, scoped }) as AppAdapter & TExtra\n ;(built as AppAdapter).name = instanceName\n return built\n }\n\n const buildAsync = (instanceName: string, opts: AdapterAsyncOptions<TConfig>): AppAdapter => {\n let inner: AppAdapter | undefined\n\n return {\n name: instanceName,\n async beforeStart(ctx: AdapterContext) {\n const deps = (opts.inject ?? []).map((tok) => ctx.container.resolve(tok as never))\n const config = await opts.useFactory(...deps)\n inner = buildSync(instanceName, false, config as Partial<TConfig>)\n await inner.beforeStart?.(ctx)\n },\n async afterStart(ctx: AdapterContext) {\n await inner?.afterStart?.(ctx)\n },\n async shutdown() {\n await inner?.shutdown?.()\n },\n async onHealthCheck() {\n return (await inner?.onHealthCheck?.()) ?? { name: instanceName, status: 'up' }\n },\n }\n }\n\n const factory = ((config?: Partial<TConfig>) =>\n buildSync(options.name, false, config)) as AdapterFactory<TConfig, TExtra>\n\n factory.scoped = (scopeName: string, config?: Partial<TConfig>) =>\n buildSync(composeName(options.name, scopeName), true, config)\n\n factory.async = (opts: AdapterAsyncOptions<TConfig>) => buildAsync(options.name, opts)\n\n Object.defineProperty(factory, 'definition', { value: Object.freeze({ ...options }) })\n\n return factory\n}\n","/**\n * Augmentation registry — runtime no-op + the canonical\n * `KickJsPluginRegistry` interface that `kick typegen` augments.\n *\n * See `architecture.md` §21.2.1 (typegen for `dependsOn`) and §21.3.3\n * (standardized augmentation registry) for the design rationale.\n *\n * @module @forinda/kickjs/core/augmentation\n */\n\n/**\n * Marker interface that lists every plugin/adapter in the project. The\n * keys are the literal `name` field passed to `defineAdapter` /\n * `definePlugin` (or declared on a `class implements AppAdapter`); the\n * values are the kind tag (`'plugin' | 'adapter'`).\n *\n * The framework ships this empty. `kick typegen` augments it from\n * source so `keyof KickJsPluginRegistry` resolves to a string-literal\n * union of every name in scope — and therefore so do `dependsOn`\n * declarations on plugins/adapters.\n */\nexport interface KickJsPluginRegistry {}\n\n/**\n * Resolves to a string-literal union of every registered plugin/adapter\n * name when the project's typegen has populated `KickJsPluginRegistry`,\n * or to `string` when the registry is empty (no typegen pass yet).\n *\n * Falling back to `string` keeps fresh projects compiling — the typegen\n * narrowing is a progressive enhancement, not a hard prerequisite.\n */\nexport type KickJsPluginName = keyof KickJsPluginRegistry extends never\n ? string\n : keyof KickJsPluginRegistry & string\n\n/**\n * Metadata attached to a `defineAugmentation` call. The fields are\n * documentation-only — `kick typegen` surfaces them in the generated\n * `.kickjs/types/augmentations.d.ts` so adopters can browse every\n * augmentable interface from one place.\n *\n * `description` and `example` may both be multi-line — typegen preserves\n * line breaks when rendering them as JSDoc. There is no upper limit on\n * the example size; entire shape definitions or worked snippets are fine.\n */\nexport interface AugmentationMeta {\n /**\n * Free-form description of what the augmentation customises. May be\n * multi-line — newlines are preserved in the generated JSDoc.\n */\n description?: string\n /**\n * TS snippet showing a typical shape. May be multi-line; rendered\n * inside a fenced code block in the generated catalogue. Use this\n * for anything from `'{ foo: string }'` up to a full interface body\n * — typegen no longer flattens newlines.\n */\n example?: string\n}\n\n/**\n * Advertise an augmentable interface so `kick typegen` can list it in\n * `.kickjs/types/augmentations.d.ts` for project-wide discovery.\n *\n * **This function is a runtime AND type-level no-op.** It does NOT\n * augment the interface for TypeScript. The actual augmentation is\n * the `declare module` block — `defineAugmentation` only adds an\n * entry to the typegen catalogue so other contributors / adopters\n * can find the interface and know its expected shape.\n *\n * Both calls are needed:\n *\n * ```ts\n * import { defineAugmentation } from '@forinda/kickjs'\n *\n * // (1) The actual TS augmentation — what `ctx.get('tenant')` reads.\n * declare module '@forinda/kickjs' {\n * interface ContextMeta {\n * tenant: { id: string; name: string }\n * }\n * }\n *\n * // (2) Catalogue entry — what `kick typegen` lists in\n * // `.kickjs/types/augmentations.d.ts` for browseability.\n * defineAugmentation('ContextMeta', {\n * description: 'Tenant resolved from x-tenant-id by TenantAdapter',\n * example: `{\n * tenant: {\n * id: string\n * name: string\n * plan: 'free' | 'pro' | 'enterprise'\n * featureFlags: Record<string, boolean>\n * }\n * }`,\n * })\n * ```\n *\n * Skip `defineAugmentation` if you don't care about the catalogue — the\n * `declare module` block alone is enough for runtime + type behaviour.\n */\nexport function defineAugmentation(name: string, meta?: AugmentationMeta): void {\n // No-op at runtime — `kick typegen` discovers the call statically and\n // emits the augmentation catalogue. Touch the args so esbuild / tsc\n // don't drop the call as dead code under aggressive minification\n // (the marker has to survive the build for static scanners that look\n // at compiled output, e.g. third-party tools).\n void name\n void meta\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors-BfgqfLPd.d.mts","names":[],"sources":["../src/http/middleware/cors.ts"],"mappings":";;;;UAEiB,WAAA;;EAEf,MAAA,sBAA4B,MAAA,aAAmB,MAAM;EAF3B;EAI1B,OAAA;EAFqD;EAIrD,cAAA;EAJ4B;EAM5B,cAAA;EAJA;EAMA,WAAA;EAFA;EAIA,MAAA;EAAA;EAEA,SAAA;AAAA;AAAS;AAyCX;;;;;;;;;;;;;;;;;;AAWyD;;;;;AApD9C,iBAyCK,IAAA,CAAK,OAAA,GAAS,WAAA,
|
|
1
|
+
{"version":3,"file":"cors-BfgqfLPd.d.mts","names":[],"sources":["../src/http/middleware/cors.ts"],"mappings":";;;;UAEiB,WAAA;;EAEf,MAAA,sBAA4B,MAAA,aAAmB,MAAM;EAF3B;EAI1B,OAAA;EAFqD;EAIrD,cAAA;EAJ4B;EAM5B,cAAA;EAJA;EAMA,WAAA;EAFA;EAIA,MAAA;EAAA;EAEA,SAAA;AAAA;AAAS;AAyCX;;;;;;;;;;;;;;;;;;AAWyD;;;;;AApD9C,iBAyCK,IAAA,CAAK,OAAA,GAAS,WAAA,IAAgB,GAAA,EAW/B,OAAA,EAAO,GAAA,EAAO,QAAA,EAAQ,IAAA,EAAQ,YAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf-DiK9zept.d.mts","names":[],"sources":["../src/http/middleware/csrf.ts"],"mappings":";;;;UAGiB,WAAA;;EAEf,MAAA;EAF0B;EAI1B,MAAA;EAJ0B;EAM1B,OAAA;EAFA;EAIA,WAAA;EAAA;EAEA,WAAA;EAEA;EAAA,aAAA;IACE,QAAA;IACA,QAAA;IACA,MAAA;IACA,IAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;AA6CqD;;;;iBAhBzC,IAAA,CAAK,OAAA,GAAS,WAAA,
|
|
1
|
+
{"version":3,"file":"csrf-DiK9zept.d.mts","names":[],"sources":["../src/http/middleware/csrf.ts"],"mappings":";;;;UAGiB,WAAA;;EAEf,MAAA;EAF0B;EAI1B,MAAA;EAJ0B;EAM1B,OAAA;EAFA;EAIA,WAAA;EAAA;EAEA,WAAA;EAEA;EAAA,aAAA;IACE,QAAA;IACA,QAAA;IACA,MAAA;IACA,IAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;AA6CqD;;;;iBAhBzC,IAAA,CAAK,OAAA,GAAS,WAAA,IAAgB,GAAA,EAgB/B,OAAA,EAAO,GAAA,EAAO,QAAA,EAAQ,IAAA,EAAQ,YAAA,YAAY,QAAA,MAAA,MAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { l as ServiceOptions, n as BuilderOf } from "./interfaces-
|
|
3
|
-
import { i as KickJsRegistry, s as InjectionToken } from "./container-
|
|
2
|
+
import { l as ServiceOptions, n as BuilderOf } from "./interfaces-ClcFeDhd.mjs";
|
|
3
|
+
import { i as KickJsRegistry, s as InjectionToken } from "./container-B51bkskE.mjs";
|
|
4
4
|
//#region src/core/assets.d.ts
|
|
5
5
|
declare const ASSET_MANIFEST_VERSION: 1;
|
|
6
6
|
/**
|
|
@@ -268,11 +268,11 @@ interface RouteDefinition {
|
|
|
268
268
|
name?: string;
|
|
269
269
|
};
|
|
270
270
|
}
|
|
271
|
-
declare const Get: (path?: string, validation?: RouteDefinition[
|
|
272
|
-
declare const Post: (path?: string, validation?: RouteDefinition[
|
|
273
|
-
declare const Put: (path?: string, validation?: RouteDefinition[
|
|
274
|
-
declare const Delete: (path?: string, validation?: RouteDefinition[
|
|
275
|
-
declare const Patch: (path?: string, validation?: RouteDefinition[
|
|
271
|
+
declare const Get: (path?: string, validation?: RouteDefinition['validation']) => MethodDecorator;
|
|
272
|
+
declare const Post: (path?: string, validation?: RouteDefinition['validation']) => MethodDecorator;
|
|
273
|
+
declare const Put: (path?: string, validation?: RouteDefinition['validation']) => MethodDecorator;
|
|
274
|
+
declare const Delete: (path?: string, validation?: RouteDefinition['validation']) => MethodDecorator;
|
|
275
|
+
declare const Patch: (path?: string, validation?: RouteDefinition['validation']) => MethodDecorator;
|
|
276
276
|
interface ApiQueryParamsConfig {
|
|
277
277
|
/** Fields that can be used in filter queries (e.g., `?filter=status:eq:active`) */
|
|
278
278
|
filterable?: string[];
|
|
@@ -409,4 +409,4 @@ declare function withBuilder<C extends new (...args: any[]) => any>(ctor: C): C
|
|
|
409
409
|
};
|
|
410
410
|
//#endregion
|
|
411
411
|
export { Value as A, useAssets as B, Post as C, Repository as D, Put as E, KickAssets as F, UnknownAssetError as I, assets as L, withBuilder as M, ASSETS as N, RouteDefinition as O, ASSET_MANIFEST_VERSION as P, clearAssetCache as R, Patch as S, PropertyOrParameterDecorator as T, Get as _, Autowired as a, Middleware as b, ColumnApiQueryParamsConfig as c, Delete as d, Env as f, FileUploadConfig as g, FileUpload as h, AssetKey as i, normalizeApiQueryParamsConfig as j, Service as k, Component as l, FileTypeFilter as m, ApiQueryParamsConfig as n, BaseUploadOptions as o, EnvKey as p, Asset as r, Builder as s, ApiQueryParams as t, Controller as u, Inject as v, PostConstruct as w, MiddlewareHandler as x, Injectable as y, resolveAsset as z };
|
|
412
|
-
//# sourceMappingURL=decorators-
|
|
412
|
+
//# sourceMappingURL=decorators-KbaPUBXd.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators-KbaPUBXd.d.mts","names":[],"sources":["../src/core/assets.ts","../src/core/decorators.ts"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v5.
|
|
2
|
+
* @forinda/kickjs v5.13.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{
|
|
12
|
-
//# sourceMappingURL=env-
|
|
11
|
+
import{t as Container,v as __require}from"./container-CBeLvm_9.mjs";import{z}from"zod";function tryLoadDotenv(){try{__require(`dotenv`).config({override:!1,quiet:!0})}catch{}}tryLoadDotenv();const baseEnvSchema=z.object({PORT:z.coerce.number().default(3e3),NODE_ENV:z.enum([`development`,`production`,`test`]).default(`development`),LOG_LEVEL:z.string().default(`info`)});let cachedEnv=null,cachedSchema=null;function defineEnv(extend){let userSchema=extend(baseEnvSchema);return baseEnvSchema.extend(userSchema.shape)}function loadEnv(schema){let s=schema??cachedSchema??baseEnvSchema;return cachedEnv&&cachedSchema===s?cachedEnv:(cachedSchema=s,cachedEnv=s.parse(process.env),Container._envResolver=key=>cachedEnv?.[key],cachedEnv)}function getEnv(key,schema){return(schema?loadEnv(schema):loadEnv())[key]}function reloadEnv(){try{__require(`dotenv`).config({override:!0,quiet:!0})}catch{}cachedEnv=null,cachedSchema&&loadEnv(cachedSchema)}function resetEnvCache(){cachedEnv=null,cachedSchema=null}export{reloadEnv as a,loadEnv as i,defineEnv as n,resetEnvCache as o,getEnv as r,baseEnvSchema as t};
|
|
12
|
+
//# sourceMappingURL=env-B9_HjyB6.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-
|
|
1
|
+
{"version":3,"file":"env-B9_HjyB6.mjs","names":[],"sources":["../src/config/env.ts"],"sourcesContent":["import { z } from 'zod'\nimport { Container } from '../core/container'\nimport type { EnvKey } from '../core/decorators'\n\n/**\n * Lazily load `.env` via dotenv if it is installed in the consumer app.\n * `dotenv` is an **optional peer dependency** of `@forinda/kickjs` —\n * apps that want `.env` file support install it themselves (the CLI\n * generators add it to every scaffolded `package.json`). Apps that\n * inject env via the shell, Docker, or a secret manager can skip it\n * entirely and `process.env` is read as-is.\n *\n * We call `dotenv.config()` directly rather than `require('dotenv/config')`\n * so that a re-evaluation of this module (e.g. Vite SSR full-reload after\n * editing `.env`) can re-read the file. `dotenv/config` is a side-effect\n * shim that CJS caches aggressively, so the second `require` is a no-op\n * and new keys never reach `process.env` — the root cause of the\n * \"DATABASE_URL undefined until restart\" bug on Windows dev.\n */\nfunction tryLoadDotenv(): void {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const dotenv = require('dotenv')\n // `quiet: true` suppresses dotenv 17's tip banner that goes to stdout.\n // Required for CLI tools whose stdout is parsed by callers (`kick explain\n // --json`, etc) — the banner would otherwise corrupt the JSON output.\n dotenv.config({ override: false, quiet: true })\n } catch {\n // dotenv not installed — fall through, env vars must come from\n // the environment directly. This is a valid deployment style.\n }\n}\ntryLoadDotenv()\n\n/**\n * Base environment schema with common server variables.\n * Users extend this with their own application-specific vars.\n */\nexport const baseEnvSchema = z.object({\n // Server\n PORT: z.coerce.number().default(3000),\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n LOG_LEVEL: z.string().default('info'),\n})\n\n/** Cached env config to avoid re-parsing — keyed by schema reference */\nlet cachedEnv: any = null\nlet cachedSchema: any = null\n\n/**\n * Define a custom env schema. The base schema (`PORT`, `NODE_ENV`,\n * `LOG_LEVEL`) is always merged in, so users never need to remember to\n * extend it themselves — both styles below produce the same result and\n * both surface the base keys in `KickEnv`.\n *\n * Returns a Zod schema that validates `process.env`.\n *\n * @example\n * ```ts\n * // Style A — return a fresh object; base fields are merged automatically\n * export default defineEnv(() =>\n * z.object({\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * })\n * )\n *\n * // Style B — explicitly extend `base` (still works, identical result)\n * export default defineEnv((base) =>\n * base.extend({\n * DATABASE_URL: z.string().url(),\n * })\n * )\n * ```\n *\n * User-defined keys override base keys when they collide, so a project\n * can re-type `PORT` or tighten `NODE_ENV` without losing the rest of\n * the base shape.\n */\nexport function defineEnv<T extends z.ZodRawShape>(\n extend: (base: typeof baseEnvSchema) => z.ZodObject<T>,\n): z.ZodObject<typeof baseEnvSchema.shape & T> {\n const userSchema = extend(baseEnvSchema)\n // Always merge the base in. `extend` lets the user's shape override\n // any colliding base keys, which preserves the escape hatch for\n // projects that want to re-type `PORT` etc.\n return baseEnvSchema.extend(userSchema.shape) as z.ZodObject<typeof baseEnvSchema.shape & T>\n}\n\n/**\n * Parse and validate `process.env` against a Zod schema. Caches result per schema.\n *\n * **No-arg behaviour is sticky.** The first time `loadEnv(schema)` is called\n * with an extended schema, the cache holds that schema. Subsequent\n * `loadEnv()` calls (no arg) will reuse the cached extended schema rather\n * than falling back to `baseEnvSchema`.\n *\n * Stickiness matters because `ConfigService` and the `@Value()` decorator\n * both read through the no-arg form internally. Without it, resolving\n * either of them after a `loadEnv(extendedSchema)` call would silently\n * downgrade the env to the base shape and drop every user-defined key.\n *\n * To force a re-parse with a different schema, pass the new schema explicitly.\n * To start fresh, call `resetEnvCache()` (full reset) or `reloadEnv()`\n * (re-parse against the same schema after a `.env` file change).\n *\n * **Typing:**\n * - With an explicit schema → returns `z.infer<typeof schema>`.\n * - With no schema and `KickEnv` populated by typegen → returns\n * `KickEnv` (the project-wide typed env shape).\n * - With no schema and `KickEnv` empty → returns the base `Env`.\n */\nexport function loadEnv<T extends z.ZodObject<any>>(schema: T): z.infer<T>\nexport function loadEnv(): [EnvKey] extends [never] ? Env : KickEnv\nexport function loadEnv<T extends z.ZodObject<any>>(schema?: T): any {\n // Sticky: when called with no arg, prefer the most recently cached\n // schema over re-parsing with the base schema.\n const s = schema ?? cachedSchema ?? baseEnvSchema\n // Re-parse if schema changed or no cache yet\n if (cachedEnv && cachedSchema === s) return cachedEnv\n cachedSchema = s\n cachedEnv = s.parse(process.env)\n\n // Register env resolver so @Value() reads validated values\n Container._envResolver = (key: string) => cachedEnv?.[key]\n\n return cachedEnv\n}\n\n/**\n * Get a single typed environment variable value.\n *\n * Three forms:\n * - **No-arg, KickEnv populated** (`kick typegen` has run) → key is\n * constrained to known `KickEnv` keys; return type inferred from\n * the schema.\n * - **With explicit schema** → key constrained to that schema's keys;\n * return type is `z.infer<typeof schema>[K]`.\n * - **No-arg, KickEnv empty** → loose `string` key, `any` return.\n *\n * @example\n * ```ts\n * // After `kick typegen` (KickEnv populated):\n * const port = getEnv('PORT') // typed as number\n * const url = getEnv('DATABASE_URL') // typed as string\n *\n * // With an inline schema (legacy / one-off):\n * const dbUrl = getEnv('DATABASE_URL', envSchema)\n * ```\n */\nexport function getEnv<K extends EnvKey>(key: K): KickEnv[K]\nexport function getEnv<T extends z.ZodObject<any>, K extends string & keyof z.infer<T>>(\n key: K,\n schema: T,\n): z.infer<T>[K]\nexport function getEnv(key: string, schema?: z.ZodObject<any>): any {\n const env = schema ? loadEnv(schema) : (loadEnv() as Record<string, any>)\n return env[key]\n}\n\n/**\n * Reload env from `process.env`. Re-reads `.env` via dotenv (if\n * installed), then re-parses the result against the **same** schema\n * the project registered at startup. Called during HMR rebuild and by\n * `envWatchPlugin` to pick up `.env` file changes mid-session.\n *\n * Crucially, this preserves the registered schema. Wiping it would\n * silently downgrade `ConfigService` and `@Value()` back to the base\n * schema on the next read, so every user-defined key would start\n * resolving as `undefined` after the first `.env` save — which was the\n * original \"config disappears in dev\" bug.\n *\n * If you need to fully forget the schema (e.g. tests that swap schemas\n * between cases), call {@link resetEnvCache} instead.\n */\nexport function reloadEnv(): void {\n // Re-read .env file into process.env if dotenv is installed.\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require('dotenv').config({ override: true, quiet: true })\n } catch {\n // dotenv not installed — nothing to reload, env comes from the\n // environment directly.\n }\n\n // Drop the parsed snapshot but keep the schema. Re-parse eagerly so\n // existing consumers (including ConfigService getters and @Value\n // resolvers that may already hold references to the env state) see\n // the new values on their very next read instead of having to\n // re-trigger any cache-population step.\n cachedEnv = null\n if (cachedSchema) {\n loadEnv(cachedSchema)\n }\n}\n\n/**\n * Fully reset the env cache, including the registered schema. Use this\n * in tests that need to swap schemas between cases — `reloadEnv()`\n * deliberately preserves the schema to avoid the \"user keys vanish on\n * `.env` reload\" footgun.\n */\nexport function resetEnvCache(): void {\n cachedEnv = null\n cachedSchema = null\n}\n\nexport type Env = z.infer<typeof baseEnvSchema>\n"],"mappings":";;;;;;;;;;uFAmBA,SAAS,eAAsB,CAC7B,GAAI,CAMF,UAJuB,QAIlB,EAAE,OAAO,CAAE,SAAU,GAAO,MAAO,EAAK,CAAC,CAChD,MAAQ,CAGR,CACF,CACA,cAAc,EAMd,MAAa,cAAgB,EAAE,OAAO,CAEpC,KAAM,EAAE,OAAO,OAAO,EAAE,QAAQ,GAAI,EACpC,SAAU,EAAE,KAAK,CAAC,cAAe,aAAc,MAAM,CAAC,EAAE,QAAQ,aAAa,EAC7E,UAAW,EAAE,OAAO,EAAE,QAAQ,MAAM,CACtC,CAAC,EAGD,IAAI,UAAiB,KACjB,aAAoB,KAgCxB,SAAgB,UACd,OAC6C,CAC7C,IAAM,WAAa,OAAO,aAAa,EAIvC,OAAO,cAAc,OAAO,WAAW,KAAK,CAC9C,CA2BA,SAAgB,QAAoC,OAAiB,CAGnE,IAAM,EAAI,QAAU,cAAgB,cASpC,OAPI,WAAa,eAAiB,EAAU,WAC5C,aAAe,EACf,UAAY,EAAE,MAAM,QAAQ,GAAG,EAG/B,UAAU,aAAgB,KAAgB,YAAY,KAE/C,UACT,CA4BA,SAAgB,OAAO,IAAa,OAAgC,CAElE,OADY,OAAS,QAAQ,MAAM,EAAK,QAAQ,GACrC,IACb,CAiBA,SAAgB,WAAkB,CAEhC,GAAI,CAEF,UAAQ,QAAQ,EAAE,OAAO,CAAE,SAAU,GAAM,MAAO,EAAK,CAAC,CAC1D,MAAQ,CAGR,CAOA,UAAY,KACR,cACF,QAAQ,YAAY,CAExB,CAQA,SAAgB,eAAsB,CACpC,UAAY,KACZ,aAAe,IACjB"}
|