@forinda/kickjs 6.1.0 → 6.2.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.
Files changed (167) hide show
  1. package/README.md +14 -14
  2. package/dist/{adapter-D_uzU-QA.d.mts → adapter-DCfEFZMY.d.mts} +33 -10
  3. package/dist/adapter-DCfEFZMY.d.mts.map +1 -0
  4. package/dist/{app-module-CuAS6hJC.d.mts → app-module-BwYtACD6.d.mts} +3 -3
  5. package/dist/{app-module-CuAS6hJC.d.mts.map → app-module-BwYtACD6.d.mts.map} +1 -1
  6. package/dist/application-DS7t40r-.mjs +12 -0
  7. package/dist/application-DS7t40r-.mjs.map +1 -0
  8. package/dist/{application-CFenLjbY.d.mts → application-p8RRz7p6.d.mts} +4 -4
  9. package/dist/{application-CFenLjbY.d.mts.map → application-p8RRz7p6.d.mts.map} +1 -1
  10. package/dist/{bootstrap-DGUAqImq.mjs → bootstrap-DESuFm-t.mjs} +3 -3
  11. package/dist/{bootstrap-DGUAqImq.mjs.map → bootstrap-DESuFm-t.mjs.map} +1 -1
  12. package/dist/{bootstrap-DodgsiDG.d.mts → bootstrap-JXdD0wVC.d.mts} +2 -2
  13. package/dist/{bootstrap-DodgsiDG.d.mts.map → bootstrap-JXdD0wVC.d.mts.map} +1 -1
  14. package/dist/{cache-BSFDtsEG.d.mts → cache-CwZzIH0D.d.mts} +9 -11
  15. package/dist/cache-CwZzIH0D.d.mts.map +1 -0
  16. package/dist/config/index.d.mts +1 -1
  17. package/dist/config/index.mjs +2 -2
  18. package/dist/{config-bUVJOptu.mjs → config-CqN6u5CP.mjs} +3 -3
  19. package/dist/{config-bUVJOptu.mjs.map → config-CqN6u5CP.mjs.map} +1 -1
  20. package/dist/container-CkcVS_gA.mjs +108 -0
  21. package/dist/container-CkcVS_gA.mjs.map +1 -0
  22. package/dist/{container-TWOUyR_0.d.mts → container-iED1swkd.d.mts} +19 -3
  23. package/dist/container-iED1swkd.d.mts.map +1 -0
  24. package/dist/context-Cd-p7DfC.mjs +12 -0
  25. package/dist/context-Cd-p7DfC.mjs.map +1 -0
  26. package/dist/contributor-runner-BP9rOU_2.mjs +13 -0
  27. package/dist/contributor-runner-BP9rOU_2.mjs.map +1 -0
  28. package/dist/core/adapter.d.mts +1 -1
  29. package/dist/core/adapter.mjs +1 -1
  30. package/dist/core/app-module.d.mts +1 -1
  31. package/dist/core/app-module.mjs +1 -1
  32. package/dist/core/cache.d.mts +1 -1
  33. package/dist/core/cache.mjs +2 -2
  34. package/dist/core/cache.mjs.map +1 -1
  35. package/dist/core/container.d.mts +1 -1
  36. package/dist/core/container.mjs +2 -2
  37. package/dist/core/cron.mjs +2 -2
  38. package/dist/core/decorators.d.mts +2 -2
  39. package/dist/core/decorators.mjs +2 -2
  40. package/dist/core/decorators.mjs.map +1 -1
  41. package/dist/core/errors.mjs +1 -1
  42. package/dist/core/interfaces.d.mts +1 -1
  43. package/dist/core/interfaces.mjs +2 -2
  44. package/dist/core/interfaces.mjs.map +1 -1
  45. package/dist/core/logger.mjs +2 -2
  46. package/dist/core/logger.mjs.map +1 -1
  47. package/dist/core/path.mjs +1 -1
  48. package/dist/core/plugin.d.mts +1 -1
  49. package/dist/core/plugin.mjs +1 -1
  50. package/dist/core/reactivity.d.mts +1 -1
  51. package/dist/core/reactivity.mjs +2 -2
  52. package/dist/core/reactivity.mjs.map +1 -1
  53. package/dist/core-BBnRIW_2.mjs +12 -0
  54. package/dist/core-BBnRIW_2.mjs.map +1 -0
  55. package/dist/csrf-7nUXGTuz.d.mts.map +1 -1
  56. package/dist/csrf-CrChcN0K.mjs +12 -0
  57. package/dist/csrf-CrChcN0K.mjs.map +1 -0
  58. package/dist/{decorators-ejABLo9H.d.mts → decorators-DKjpujFp.d.mts} +13 -4
  59. package/dist/decorators-DKjpujFp.d.mts.map +1 -0
  60. package/dist/disposables-msQG0AHt.mjs +12 -0
  61. package/dist/disposables-msQG0AHt.mjs.map +1 -0
  62. package/dist/env-CrvPEW3K.mjs +13 -0
  63. package/dist/{env-CqRVEtoa.mjs.map → env-CrvPEW3K.mjs.map} +1 -1
  64. package/dist/handler-BMUwLMu_.mjs +12 -0
  65. package/dist/handler-BMUwLMu_.mjs.map +1 -0
  66. package/dist/http/application.d.mts +1 -1
  67. package/dist/http/application.mjs +2 -2
  68. package/dist/http/bootstrap.d.mts +1 -1
  69. package/dist/http/bootstrap.mjs +2 -2
  70. package/dist/http/context.d.mts +2 -2
  71. package/dist/http/context.mjs +2 -2
  72. package/dist/http/middleware/cors.mjs +1 -1
  73. package/dist/http/middleware/csrf.mjs +2 -3
  74. package/dist/http/middleware/error-handler.mjs +2 -2
  75. package/dist/http/middleware/helmet.mjs +1 -1
  76. package/dist/http/middleware/rate-limit.mjs +2 -2
  77. package/dist/http/middleware/rate-limit.mjs.map +1 -1
  78. package/dist/http/middleware/request-id.mjs +2 -2
  79. package/dist/http/middleware/request-id.mjs.map +1 -1
  80. package/dist/http/middleware/request-logger.mjs +2 -2
  81. package/dist/http/middleware/session.mjs +2 -2
  82. package/dist/http/middleware/session.mjs.map +1 -1
  83. package/dist/http/middleware/spa.d.mts +1 -1
  84. package/dist/http/middleware/spa.mjs +2 -2
  85. package/dist/http/middleware/spa.mjs.map +1 -1
  86. package/dist/http/middleware/upload.d.mts +2 -2
  87. package/dist/http/middleware/upload.mjs +3 -3
  88. package/dist/http/middleware/upload.mjs.map +1 -1
  89. package/dist/http/middleware/validate.mjs +2 -2
  90. package/dist/http/middleware/validate.mjs.map +1 -1
  91. package/dist/http/middleware/views.d.mts +1 -1
  92. package/dist/http/middleware/views.mjs +2 -2
  93. package/dist/http/query/index.mjs +2 -2
  94. package/dist/http/router-builder.d.mts +1 -1
  95. package/dist/http/router-builder.mjs +2 -2
  96. package/dist/http/router-builder.mjs.map +1 -1
  97. package/dist/http/runtimes/fastify.d.mts +1 -1
  98. package/dist/http/runtimes/fastify.d.mts.map +1 -1
  99. package/dist/http/runtimes/fastify.mjs +3 -3
  100. package/dist/http/runtimes/fastify.mjs.map +1 -1
  101. package/dist/http/runtimes/h3-web.d.mts +52 -0
  102. package/dist/http/runtimes/h3-web.d.mts.map +1 -0
  103. package/dist/http/runtimes/h3-web.mjs +12 -0
  104. package/dist/http/runtimes/h3-web.mjs.map +1 -0
  105. package/dist/http/runtimes/h3.d.mts +10 -4
  106. package/dist/http/runtimes/h3.d.mts.map +1 -1
  107. package/dist/http/runtimes/h3.mjs +2 -2
  108. package/dist/http/runtimes/h3.mjs.map +1 -1
  109. package/dist/{index-zIcb4wjg.d.mts → index-ChK5rOgq.d.mts} +2 -2
  110. package/dist/{index-zIcb4wjg.d.mts.map → index-ChK5rOgq.d.mts.map} +1 -1
  111. package/dist/index.d.mts +16 -15
  112. package/dist/index.mjs +2 -2
  113. package/dist/index.mjs.map +1 -1
  114. package/dist/{interfaces-DbTyKGdS.d.mts → interfaces-CayXPOAf.d.mts} +2 -1
  115. package/dist/{interfaces-DbTyKGdS.d.mts.map → interfaces-CayXPOAf.d.mts.map} +1 -1
  116. package/dist/{metadata-3QjXqAh_.mjs → metadata-WlPWz6fi.mjs} +2 -2
  117. package/dist/{metadata-3QjXqAh_.mjs.map → metadata-WlPWz6fi.mjs.map} +1 -1
  118. package/dist/module-registry-DmTQjSdD.mjs +12 -0
  119. package/dist/module-registry-DmTQjSdD.mjs.map +1 -0
  120. package/dist/{query-0FYLK2Mg.mjs → query-B2w_8bwe.mjs} +2 -2
  121. package/dist/{query-0FYLK2Mg.mjs.map → query-B2w_8bwe.mjs.map} +1 -1
  122. package/dist/rate-limit-oLL5EPUf.d.mts.map +1 -1
  123. package/dist/{reactivity-DWmLcN0A.d.mts → reactivity-XL0fFDvG.d.mts} +10 -16
  124. package/dist/reactivity-XL0fFDvG.d.mts.map +1 -0
  125. package/dist/reply-Dh15A_0R.mjs +12 -0
  126. package/dist/reply-Dh15A_0R.mjs.map +1 -0
  127. package/dist/reply-Elm9mc8Z.d.mts +63 -0
  128. package/dist/reply-Elm9mc8Z.d.mts.map +1 -0
  129. package/dist/request-id-IDobbxU-.d.mts.map +1 -1
  130. package/dist/rolldown-runtime-C4pdyXoH.mjs +11 -0
  131. package/dist/{router-builder-Bh3qSiDz.d.mts → router-builder-BHTp3zL3.d.mts} +2 -2
  132. package/dist/router-builder-BHTp3zL3.d.mts.map +1 -0
  133. package/dist/session-DSRA-PUX.d.mts.map +1 -1
  134. package/dist/{spa-BdvZFXJz.d.mts → spa-D8I4YnpL.d.mts} +2 -2
  135. package/dist/{spa-BdvZFXJz.d.mts.map → spa-D8I4YnpL.d.mts.map} +1 -1
  136. package/dist/{upload-Bc_rBRtE.d.mts → upload-CUyZ0zNt.d.mts} +9 -7
  137. package/dist/upload-CUyZ0zNt.d.mts.map +1 -0
  138. package/dist/upload-config-B6vGWI0_.mjs +12 -0
  139. package/dist/upload-config-B6vGWI0_.mjs.map +1 -0
  140. package/dist/validate-DoZDOhCi.d.mts.map +1 -1
  141. package/dist/{views-DcTVDiOh.d.mts → views-PFf-CBvN.d.mts} +7 -5
  142. package/dist/views-PFf-CBvN.d.mts.map +1 -0
  143. package/dist/web.d.mts +150 -0
  144. package/dist/web.d.mts.map +1 -0
  145. package/dist/web.mjs +12 -0
  146. package/dist/web.mjs.map +1 -0
  147. package/package.json +24 -15
  148. package/dist/adapter-D_uzU-QA.d.mts.map +0 -1
  149. package/dist/application-D5VVcJFf.mjs +0 -12
  150. package/dist/application-D5VVcJFf.mjs.map +0 -1
  151. package/dist/cache-BSFDtsEG.d.mts.map +0 -1
  152. package/dist/container-9AvE4y3u.mjs +0 -108
  153. package/dist/container-9AvE4y3u.mjs.map +0 -1
  154. package/dist/container-TWOUyR_0.d.mts.map +0 -1
  155. package/dist/context-CXLrVAx5.mjs +0 -12
  156. package/dist/context-CXLrVAx5.mjs.map +0 -1
  157. package/dist/core-IrvhPT6O.mjs +0 -13
  158. package/dist/core-IrvhPT6O.mjs.map +0 -1
  159. package/dist/decorators-ejABLo9H.d.mts.map +0 -1
  160. package/dist/env-CqRVEtoa.mjs +0 -13
  161. package/dist/http/middleware/csrf.mjs.map +0 -1
  162. package/dist/reactivity-DWmLcN0A.d.mts.map +0 -1
  163. package/dist/request-scope-D5j8kWQ7.mjs +0 -12
  164. package/dist/request-scope-D5j8kWQ7.mjs.map +0 -1
  165. package/dist/router-builder-Bh3qSiDz.d.mts.map +0 -1
  166. package/dist/upload-Bc_rBRtE.d.mts.map +0 -1
  167. package/dist/views-DcTVDiOh.d.mts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v6.1.0
2
+ * @forinda/kickjs v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- const LEVEL_RANK={trace:10,debug:20,info:30,warn:40,error:50,fatal:60,silent:100};function thresholdRank(){return LEVEL_RANK[(process.env.LOG_LEVEL??`info`).toLowerCase()]??LEVEL_RANK.info}var ConsoleLoggerProvider=class ConsoleLoggerProvider{prefix;useColor;constructor(prefix){this.prefix=prefix??``,this.useColor=typeof process<`u`&&process.stdout?.isTTY===!0&&!process.env.NO_COLOR}fmt(level,color,msg){let tag=this.prefix?`[${this.prefix}]`:``;return this.useColor?`${color}${level}\x1b[0m ${tag?`\x1b[36m${tag}\x1b[0m `:``}${msg}`:`${level} ${tag?`${tag} `:``}${msg}`}enabled(rank){return rank>=thresholdRank()}info(msg,...args){this.enabled(LEVEL_RANK.info)&&console.log(this.fmt(`INFO`,`\x1B[32m`,msg),...args)}warn(msg,...args){this.enabled(LEVEL_RANK.warn)&&console.warn(this.fmt(`WARN`,`\x1B[33m`,msg),...args)}error(msg,...args){this.enabled(LEVEL_RANK.error)&&console.error(this.fmt(`ERROR`,`\x1B[31m`,msg),...args)}debug(msg,...args){this.enabled(LEVEL_RANK.debug)&&console.debug(this.fmt(`DEBUG`,`\x1B[90m`,msg),...args)}trace(msg,...args){this.enabled(LEVEL_RANK.trace)&&console.trace(this.fmt(`TRACE`,`\x1B[90m`,msg),...args)}fatal(msg,...args){this.enabled(LEVEL_RANK.fatal)&&console.error(this.fmt(`FATAL`,`\x1B[1m\x1B[31m`,msg),...args)}child(bindings){return new ConsoleLoggerProvider(bindings.component)}};let activeProvider=new ConsoleLoggerProvider;const loggerCache=new Map;var Logger=class Logger{_provider;_providerVersion;_name;static _providerVersion=0;constructor(name){this._name=name,this._providerVersion=Logger._providerVersion,this._provider=name?activeProvider.child({component:name}):activeProvider}get provider(){return this._providerVersion!==Logger._providerVersion&&(this._providerVersion=Logger._providerVersion,this._provider=this._name?activeProvider.child({component:this._name}):activeProvider),this._provider}static setProvider(provider){activeProvider=provider,Logger._providerVersion++,loggerCache.clear()}static getProvider(){return activeProvider}static resetProvider(){activeProvider=new ConsoleLoggerProvider,Logger._providerVersion++,loggerCache.clear()}static for(name){let cached=loggerCache.get(name);return cached||(cached=new Logger(name),loggerCache.set(name,cached)),cached}child(name){return new Logger(name)}info(msg,...args){this.provider.info(msg,...args)}warn(msg,...args){this.provider.warn(msg,...args)}error(msgOrObj,msg,...args){typeof msgOrObj==`string`?this.provider.error(msgOrObj):msg?this.provider.error(msg,...args):this.provider.error(String(msgOrObj))}debug(msg,...args){this.provider.debug(msg,...args)}trace(msg,...args){this.provider.trace?this.provider.trace(msg,...args):this.provider.debug(msg,...args)}fatal(msg,...args){this.provider.fatal?this.provider.fatal(msg,...args):this.provider.error(msg,...args)}};function createLogger(name){return Logger.for(name)}export{ConsoleLoggerProvider,Logger,createLogger};
11
+ const LEVEL_RANK={trace:10,debug:20,info:30,warn:40,error:50,fatal:60,silent:100};function thresholdRank(){let level=(process.env.LOG_LEVEL??`info`).toLowerCase();return LEVEL_RANK[level]??LEVEL_RANK.info}var ConsoleLoggerProvider=class ConsoleLoggerProvider{prefix;useColor;constructor(prefix){this.prefix=prefix??``,this.useColor=typeof process<`u`&&process.stdout?.isTTY===!0&&!process.env.NO_COLOR}fmt(level,color,msg){let tag=this.prefix?`[${this.prefix}]`:``;return this.useColor?`${color}${level}\x1b[0m ${tag?`\x1b[36m${tag}\x1b[0m `:``}${msg}`:`${level} ${tag?`${tag} `:``}${msg}`}enabled(rank){return rank>=thresholdRank()}info(msg,...args){this.enabled(LEVEL_RANK.info)&&console.log(this.fmt(`INFO`,`\x1B[32m`,msg),...args)}warn(msg,...args){this.enabled(LEVEL_RANK.warn)&&console.warn(this.fmt(`WARN`,`\x1B[33m`,msg),...args)}error(msg,...args){this.enabled(LEVEL_RANK.error)&&console.error(this.fmt(`ERROR`,`\x1B[31m`,msg),...args)}debug(msg,...args){this.enabled(LEVEL_RANK.debug)&&console.debug(this.fmt(`DEBUG`,`\x1B[90m`,msg),...args)}trace(msg,...args){this.enabled(LEVEL_RANK.trace)&&console.trace(this.fmt(`TRACE`,`\x1B[90m`,msg),...args)}fatal(msg,...args){this.enabled(LEVEL_RANK.fatal)&&console.error(this.fmt(`FATAL`,`\x1B[1m\x1B[31m`,msg),...args)}child(bindings){return new ConsoleLoggerProvider(bindings.component)}};let activeProvider=new ConsoleLoggerProvider;const loggerCache=new Map;var Logger=class Logger{_provider;_providerVersion;_name;static _providerVersion=0;constructor(name){this._name=name,this._providerVersion=Logger._providerVersion,this._provider=name?activeProvider.child({component:name}):activeProvider}get provider(){return this._providerVersion!==Logger._providerVersion&&(this._providerVersion=Logger._providerVersion,this._provider=this._name?activeProvider.child({component:this._name}):activeProvider),this._provider}static setProvider(provider){activeProvider=provider,Logger._providerVersion++,loggerCache.clear()}static getProvider(){return activeProvider}static resetProvider(){activeProvider=new ConsoleLoggerProvider,Logger._providerVersion++,loggerCache.clear()}static for(name){let cached=loggerCache.get(name);return cached||(cached=new Logger(name),loggerCache.set(name,cached)),cached}child(name){return new Logger(name)}info(msg,...args){this.provider.info(msg,...args)}warn(msg,...args){this.provider.warn(msg,...args)}error(msgOrObj,msg,...args){typeof msgOrObj==`string`?this.provider.error(msgOrObj):msg?this.provider.error(msg,...args):this.provider.error(String(msgOrObj))}debug(msg,...args){this.provider.debug(msg,...args)}trace(msg,...args){this.provider.trace?this.provider.trace(msg,...args):this.provider.debug(msg,...args)}fatal(msg,...args){this.provider.fatal?this.provider.fatal(msg,...args):this.provider.error(msg,...args)}};function createLogger(name){return Logger.for(name)}export{ConsoleLoggerProvider,Logger,createLogger};
12
12
  //# sourceMappingURL=logger.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.mjs","names":[],"sources":["../../src/core/logger.ts"],"sourcesContent":["// ── LoggerProvider interface ───────────────────────────────────────────\n\n/**\n * Pluggable logger backend.\n *\n * Implement this interface to plug in any logging library (pino,\n * winston, bunyan, console, etc.). The framework only ever calls the\n * methods declared here — it doesn't know or care which logger is\n * underneath.\n *\n * Default: `ConsoleLoggerProvider` (zero runtime deps).\n *\n * @example\n * ```ts\n * import pino from 'pino'\n * import { Logger, type LoggerProvider } from '@forinda/kickjs'\n *\n * class PinoProvider implements LoggerProvider {\n * constructor(private p = pino()) {}\n * info(msg, ...args) { this.p.info(msg, ...args) }\n * warn(msg, ...args) { this.p.warn(msg, ...args) }\n * error(msg, ...args) { this.p.error(msg, ...args) }\n * debug(msg, ...args) { this.p.debug(msg, ...args) }\n * child({ component }) { return new PinoProvider(this.p.child({ component })) }\n * }\n *\n * Logger.setProvider(new PinoProvider())\n * ```\n *\n * See `docs/guide/logging.md` for Pino, Winston, and silent-logger recipes.\n */\nexport interface LoggerProvider {\n info(msg: string, ...args: any[]): void\n warn(msg: string, ...args: any[]): void\n error(msg: string, ...args: any[]): void\n debug(msg: string, ...args: any[]): void\n trace?(msg: string, ...args: any[]): void\n fatal?(msg: string, ...args: any[]): void\n /** Return a child provider scoped to the given component name */\n child(bindings: { component: string }): LoggerProvider\n}\n\n// ── Log levels ─────────────────────────────────────────────────────────\n\n/** Severity ordering — a message prints only when its rank ≥ the threshold. */\nconst LEVEL_RANK: Record<string, number> = {\n trace: 10,\n debug: 20,\n info: 30,\n warn: 40,\n error: 50,\n fatal: 60,\n silent: 100,\n}\n\n/**\n * Resolve the active threshold rank from `LOG_LEVEL` (default `info`).\n * Read per-call so a late `process.env.LOG_LEVEL` assignment still\n * takes effect. An unrecognised value falls back to `info`.\n */\nfunction thresholdRank(): number {\n const level = (process.env.LOG_LEVEL ?? 'info').toLowerCase()\n return LEVEL_RANK[level] ?? LEVEL_RANK.info\n}\n\n// ── ConsoleLoggerProvider (default) ────────────────────────────────────\n\n/**\n * Default provider — emits through `console.*` methods. Zero deps.\n *\n * Respects the `LOG_LEVEL` env var (default `info`): messages below the\n * threshold are dropped, so the verbose startup detail (the route table,\n * DI wiring, HMR ticks — all `debug`) stays quiet by default and only\n * appears with `LOG_LEVEL=debug`. `error`/`fatal` always print unless\n * `LOG_LEVEL=silent`.\n *\n * Swap it for pino, winston, bunyan, or anything else by implementing\n * the `LoggerProvider` interface and calling `Logger.setProvider()`\n * before `bootstrap()`.\n */\nexport class ConsoleLoggerProvider implements LoggerProvider {\n private prefix: string\n private useColor: boolean\n\n constructor(prefix?: string) {\n this.prefix = prefix ?? ''\n this.useColor =\n typeof process !== 'undefined' && process.stdout?.isTTY === true && !process.env.NO_COLOR\n }\n\n private fmt(level: string, color: string, msg: string): string {\n const tag = this.prefix ? `[${this.prefix}]` : ''\n if (this.useColor) {\n return `${color}${level}\\x1b[0m ${tag ? `\\x1b[36m${tag}\\x1b[0m ` : ''}${msg}`\n }\n return `${level} ${tag ? `${tag} ` : ''}${msg}`\n }\n\n /** Should a message at `rank` print under the current `LOG_LEVEL`? */\n private enabled(rank: number): boolean {\n return rank >= thresholdRank()\n }\n\n info(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.info)) console.log(this.fmt('INFO', '\\x1b[32m', msg), ...args)\n }\n warn(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.warn)) console.warn(this.fmt('WARN', '\\x1b[33m', msg), ...args)\n }\n error(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.error)) console.error(this.fmt('ERROR', '\\x1b[31m', msg), ...args)\n }\n debug(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.debug)) console.debug(this.fmt('DEBUG', '\\x1b[90m', msg), ...args)\n }\n trace(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.trace)) console.trace(this.fmt('TRACE', '\\x1b[90m', msg), ...args)\n }\n fatal(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.fatal))\n console.error(this.fmt('FATAL', '\\x1b[1m\\x1b[31m', msg), ...args)\n }\n child(bindings: { component: string }): LoggerProvider {\n return new ConsoleLoggerProvider(bindings.component)\n }\n}\n\n// ── Active provider ────────────────────────────────────────────────────\n\nlet activeProvider: LoggerProvider = new ConsoleLoggerProvider()\n\n/** Cache of named loggers to avoid creating duplicates */\nconst loggerCache = new Map<string, Logger>()\n\n/**\n * Named logger with component context.\n *\n * @example\n * ```ts\n * // Static factory — preferred\n * const log = Logger.for('UserService')\n * log.info('User created')\n * // Output: [UserService] User created\n *\n * // Shorthand function\n * const log = createLogger('OrderService')\n *\n * // Pluggable backend — see docs/guide/logging.md\n * Logger.setProvider(new MyCustomProvider())\n *\n * // Injectable — auto-named from class\n * class MyService {\n * @Autowired() private logger!: Logger\n * // or inject a named one:\n * private log = Logger.for('MyService')\n * }\n * ```\n */\nexport class Logger {\n private _provider: LoggerProvider\n private _providerVersion: number\n private _name?: string\n\n /** Incremented on every setProvider/resetProvider so instances know to refresh */\n private static _providerVersion = 0\n\n constructor(name?: string) {\n this._name = name\n this._providerVersion = Logger._providerVersion\n this._provider = name ? activeProvider.child({ component: name }) : activeProvider\n }\n\n /** Re-derive the provider if setProvider() was called since construction/last access */\n private get provider(): LoggerProvider {\n if (this._providerVersion !== Logger._providerVersion) {\n this._providerVersion = Logger._providerVersion\n this._provider = this._name ? activeProvider.child({ component: this._name }) : activeProvider\n }\n return this._provider\n }\n\n /**\n * Replace the logging backend for **all** Logger instances created after\n * this call. Clears the logger cache so subsequent `Logger.for()` calls\n * pick up the new provider.\n *\n * @example\n * ```ts\n * Logger.setProvider(new MyWinstonProvider())\n * ```\n */\n static setProvider(provider: LoggerProvider): void {\n activeProvider = provider\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Return the currently active provider (useful for testing) */\n static getProvider(): LoggerProvider {\n return activeProvider\n }\n\n /**\n * Reset the provider back to the default `ConsoleLoggerProvider`.\n * Primarily intended for test teardown.\n */\n static resetProvider(): void {\n activeProvider = new ConsoleLoggerProvider()\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Create or retrieve a cached named logger */\n static for(name: string): Logger {\n let cached = loggerCache.get(name)\n if (!cached) {\n cached = new Logger(name)\n loggerCache.set(name, cached)\n }\n return cached\n }\n\n /** Create a child logger with a sub-component name */\n child(name: string): Logger {\n return new Logger(name)\n }\n\n info(msg: string, ...args: any[]) {\n this.provider.info(msg, ...args)\n }\n\n warn(msg: string, ...args: any[]) {\n this.provider.warn(msg, ...args)\n }\n\n error(msgOrObj: any, msg?: string, ...args: any[]) {\n if (typeof msgOrObj === 'string') {\n this.provider.error(msgOrObj)\n } else if (msg) {\n this.provider.error(msg, ...args)\n } else {\n this.provider.error(String(msgOrObj))\n }\n }\n\n debug(msg: string, ...args: any[]) {\n this.provider.debug(msg, ...args)\n }\n\n trace(msg: string, ...args: any[]) {\n if (this.provider.trace) this.provider.trace(msg, ...args)\n else this.provider.debug(msg, ...args)\n }\n\n fatal(msg: string, ...args: any[]) {\n if (this.provider.fatal) this.provider.fatal(msg, ...args)\n else this.provider.error(msg, ...args)\n }\n}\n\n/** Shorthand for Logger.for(name) */\nexport function createLogger(name: string): Logger {\n return Logger.for(name)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"logger.mjs","names":[],"sources":["../../src/core/logger.ts"],"sourcesContent":["// ── LoggerProvider interface ───────────────────────────────────────────\n\n/**\n * Pluggable logger backend.\n *\n * Implement this interface to plug in any logging library (pino,\n * winston, bunyan, console, etc.). The framework only ever calls the\n * methods declared here — it doesn't know or care which logger is\n * underneath.\n *\n * Default: `ConsoleLoggerProvider` (zero runtime deps).\n *\n * @example\n * ```ts\n * import pino from 'pino'\n * import { Logger, type LoggerProvider } from '@forinda/kickjs'\n *\n * class PinoProvider implements LoggerProvider {\n * constructor(private p = pino()) {}\n * info(msg, ...args) { this.p.info(msg, ...args) }\n * warn(msg, ...args) { this.p.warn(msg, ...args) }\n * error(msg, ...args) { this.p.error(msg, ...args) }\n * debug(msg, ...args) { this.p.debug(msg, ...args) }\n * child({ component }) { return new PinoProvider(this.p.child({ component })) }\n * }\n *\n * Logger.setProvider(new PinoProvider())\n * ```\n *\n * See `docs/guide/logging.md` for Pino, Winston, and silent-logger recipes.\n */\nexport interface LoggerProvider {\n info(msg: string, ...args: any[]): void\n warn(msg: string, ...args: any[]): void\n error(msg: string, ...args: any[]): void\n debug(msg: string, ...args: any[]): void\n trace?(msg: string, ...args: any[]): void\n fatal?(msg: string, ...args: any[]): void\n /** Return a child provider scoped to the given component name */\n child(bindings: { component: string }): LoggerProvider\n}\n\n// ── Log levels ─────────────────────────────────────────────────────────\n\n/** Severity ordering — a message prints only when its rank ≥ the threshold. */\nconst LEVEL_RANK: Record<string, number> = {\n trace: 10,\n debug: 20,\n info: 30,\n warn: 40,\n error: 50,\n fatal: 60,\n silent: 100,\n}\n\n/**\n * Resolve the active threshold rank from `LOG_LEVEL` (default `info`).\n * Read per-call so a late `process.env.LOG_LEVEL` assignment still\n * takes effect. An unrecognised value falls back to `info`.\n */\nfunction thresholdRank(): number {\n const level = (process.env.LOG_LEVEL ?? 'info').toLowerCase()\n return LEVEL_RANK[level] ?? LEVEL_RANK.info\n}\n\n// ── ConsoleLoggerProvider (default) ────────────────────────────────────\n\n/**\n * Default provider — emits through `console.*` methods. Zero deps.\n *\n * Respects the `LOG_LEVEL` env var (default `info`): messages below the\n * threshold are dropped, so the verbose startup detail (the route table,\n * DI wiring, HMR ticks — all `debug`) stays quiet by default and only\n * appears with `LOG_LEVEL=debug`. `error`/`fatal` always print unless\n * `LOG_LEVEL=silent`.\n *\n * Swap it for pino, winston, bunyan, or anything else by implementing\n * the `LoggerProvider` interface and calling `Logger.setProvider()`\n * before `bootstrap()`.\n */\nexport class ConsoleLoggerProvider implements LoggerProvider {\n private prefix: string\n private useColor: boolean\n\n constructor(prefix?: string) {\n this.prefix = prefix ?? ''\n this.useColor =\n typeof process !== 'undefined' && process.stdout?.isTTY === true && !process.env.NO_COLOR\n }\n\n private fmt(level: string, color: string, msg: string): string {\n const tag = this.prefix ? `[${this.prefix}]` : ''\n if (this.useColor) {\n return `${color}${level}\\x1b[0m ${tag ? `\\x1b[36m${tag}\\x1b[0m ` : ''}${msg}`\n }\n return `${level} ${tag ? `${tag} ` : ''}${msg}`\n }\n\n /** Should a message at `rank` print under the current `LOG_LEVEL`? */\n private enabled(rank: number): boolean {\n return rank >= thresholdRank()\n }\n\n info(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.info)) console.log(this.fmt('INFO', '\\x1b[32m', msg), ...args)\n }\n warn(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.warn)) console.warn(this.fmt('WARN', '\\x1b[33m', msg), ...args)\n }\n error(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.error)) console.error(this.fmt('ERROR', '\\x1b[31m', msg), ...args)\n }\n debug(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.debug)) console.debug(this.fmt('DEBUG', '\\x1b[90m', msg), ...args)\n }\n trace(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.trace)) console.trace(this.fmt('TRACE', '\\x1b[90m', msg), ...args)\n }\n fatal(msg: string, ...args: any[]) {\n if (this.enabled(LEVEL_RANK.fatal))\n console.error(this.fmt('FATAL', '\\x1b[1m\\x1b[31m', msg), ...args)\n }\n child(bindings: { component: string }): LoggerProvider {\n return new ConsoleLoggerProvider(bindings.component)\n }\n}\n\n// ── Active provider ────────────────────────────────────────────────────\n\nlet activeProvider: LoggerProvider = new ConsoleLoggerProvider()\n\n/** Cache of named loggers to avoid creating duplicates */\nconst loggerCache = new Map<string, Logger>()\n\n/**\n * Named logger with component context.\n *\n * @example\n * ```ts\n * // Static factory — preferred\n * const log = Logger.for('UserService')\n * log.info('User created')\n * // Output: [UserService] User created\n *\n * // Shorthand function\n * const log = createLogger('OrderService')\n *\n * // Pluggable backend — see docs/guide/logging.md\n * Logger.setProvider(new MyCustomProvider())\n *\n * // Injectable — auto-named from class\n * class MyService {\n * @Autowired() private logger!: Logger\n * // or inject a named one:\n * private log = Logger.for('MyService')\n * }\n * ```\n */\nexport class Logger {\n private _provider: LoggerProvider\n private _providerVersion: number\n private _name?: string\n\n /** Incremented on every setProvider/resetProvider so instances know to refresh */\n private static _providerVersion = 0\n\n constructor(name?: string) {\n this._name = name\n this._providerVersion = Logger._providerVersion\n this._provider = name ? activeProvider.child({ component: name }) : activeProvider\n }\n\n /** Re-derive the provider if setProvider() was called since construction/last access */\n private get provider(): LoggerProvider {\n if (this._providerVersion !== Logger._providerVersion) {\n this._providerVersion = Logger._providerVersion\n this._provider = this._name ? activeProvider.child({ component: this._name }) : activeProvider\n }\n return this._provider\n }\n\n /**\n * Replace the logging backend for **all** Logger instances created after\n * this call. Clears the logger cache so subsequent `Logger.for()` calls\n * pick up the new provider.\n *\n * @example\n * ```ts\n * Logger.setProvider(new MyWinstonProvider())\n * ```\n */\n static setProvider(provider: LoggerProvider): void {\n activeProvider = provider\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Return the currently active provider (useful for testing) */\n static getProvider(): LoggerProvider {\n return activeProvider\n }\n\n /**\n * Reset the provider back to the default `ConsoleLoggerProvider`.\n * Primarily intended for test teardown.\n */\n static resetProvider(): void {\n activeProvider = new ConsoleLoggerProvider()\n Logger._providerVersion++\n loggerCache.clear()\n }\n\n /** Create or retrieve a cached named logger */\n static for(name: string): Logger {\n let cached = loggerCache.get(name)\n if (!cached) {\n cached = new Logger(name)\n loggerCache.set(name, cached)\n }\n return cached\n }\n\n /** Create a child logger with a sub-component name */\n child(name: string): Logger {\n return new Logger(name)\n }\n\n info(msg: string, ...args: any[]) {\n this.provider.info(msg, ...args)\n }\n\n warn(msg: string, ...args: any[]) {\n this.provider.warn(msg, ...args)\n }\n\n error(msgOrObj: any, msg?: string, ...args: any[]) {\n if (typeof msgOrObj === 'string') {\n this.provider.error(msgOrObj)\n } else if (msg) {\n this.provider.error(msg, ...args)\n } else {\n this.provider.error(String(msgOrObj))\n }\n }\n\n debug(msg: string, ...args: any[]) {\n this.provider.debug(msg, ...args)\n }\n\n trace(msg: string, ...args: any[]) {\n if (this.provider.trace) this.provider.trace(msg, ...args)\n else this.provider.debug(msg, ...args)\n }\n\n fatal(msg: string, ...args: any[]) {\n if (this.provider.fatal) this.provider.fatal(msg, ...args)\n else this.provider.error(msg, ...args)\n }\n}\n\n/** Shorthand for Logger.for(name) */\nexport function createLogger(name: string): Logger {\n return Logger.for(name)\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v6.1.0
2
+ * @forinda/kickjs v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,3 +1,3 @@
1
1
 
2
- import { jt as KickPlugin } from "../adapter-D_uzU-QA.mjs";
2
+ import { Pt as KickPlugin } from "../adapter-DCfEFZMY.mjs";
3
3
  export { KickPlugin };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v6.1.0
2
+ * @forinda/kickjs v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,3 +1,3 @@
1
1
 
2
- import { a as isRef, c as toRefs, i as computed, l as unref, n as Ref, o as reactive, r as WatchOptions, s as ref, t as ComputedRef, u as watch } from "../reactivity-DWmLcN0A.mjs";
2
+ import { a as isRef, c as toRefs, i as computed, l as unref, n as Ref, o as reactive, r as WatchOptions, s as ref, t as ComputedRef, u as watch } from "../reactivity-XL0fFDvG.mjs";
3
3
  export { ComputedRef, Ref, WatchOptions, computed, isRef, reactive, ref, toRefs, unref, watch };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v6.1.0
2
+ * @forinda/kickjs v6.2.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- let activeEffect=null;const targetMap=new WeakMap;function track(target,key){if(!activeEffect)return;let depsMap=targetMap.get(target);depsMap||(depsMap=new Map,targetMap.set(target,depsMap));let deps=depsMap.get(key);deps||(deps=new Set,depsMap.set(key,deps)),deps.add(activeEffect)}function trigger(target,key){let depsMap=targetMap.get(target);if(!depsMap)return;let deps=depsMap.get(key);if(deps)for(let effect of deps)effect()}function ref(initialValue){let subscribers=new Set,wrapper={_value:initialValue};return{get value(){return track(wrapper,`_value`),wrapper._value},set value(newValue){let oldValue=wrapper._value;if(!Object.is(oldValue,newValue)){wrapper._value=newValue,trigger(wrapper,`_value`);for(let fn of subscribers)fn(newValue,oldValue)}},subscribe(fn){return subscribers.add(fn),()=>subscribers.delete(fn)},toJSON(){return wrapper._value}}}function computed(getter){let cached,dirty=!0,wrapper={_value:void 0},effect=()=>{dirty=!0,trigger(wrapper,`_value`)},recompute=()=>{if(dirty){let prev=activeEffect;activeEffect=effect;try{cached=getter()}finally{activeEffect=prev}wrapper._value=cached,dirty=!1}return cached};return{get value(){return track(wrapper,`_value`),recompute()},toJSON(){return recompute()}}}function watch(source,callback,options){let oldValue,stopped=!1,getter=typeof source==`function`?source:()=>source.value,job=()=>{if(stopped)return;let newValue=getter();Object.is(newValue,oldValue)||(callback(newValue,oldValue),oldValue=newValue)},prev=activeEffect;activeEffect=job;try{oldValue=getter()}finally{activeEffect=prev}return options?.immediate&&callback(oldValue,void 0),()=>{stopped=!0}}function reactive(target){return new Proxy(target,{get(obj,key,receiver){let value=Reflect.get(obj,key,receiver);return typeof key==`symbol`?value:(track(obj,key),typeof value==`object`&&value&&!Array.isArray(value)?reactive(value):value)},set(obj,key,value,receiver){let oldValue=Reflect.get(obj,key,receiver),result=Reflect.set(obj,key,value,receiver);return!Object.is(oldValue,value)&&typeof key!=`symbol`&&trigger(obj,key),result}})}function isRef(value){return typeof value==`object`&&!!value&&`subscribe`in value&&`value`in value}function unref(value){return isRef(value)?value.value:value}function toRefs(obj){let result={};for(let key in obj)result[key]=ref(obj[key]);return result}export{computed,isRef,reactive,ref,toRefs,unref,watch};
11
+ let activeEffect=null;const targetMap=new WeakMap;function createEffect(fn){let effect=fn;return effect.deps=new Set,effect}function cleanupEffect(effect){for(let depSet of effect.deps)depSet.delete(effect);effect.deps.clear()}function runTracked(effect,fn){cleanupEffect(effect);let prev=activeEffect;activeEffect=effect;try{return fn()}finally{activeEffect=prev}}function track(target,key){if(!activeEffect)return;let depsMap=targetMap.get(target);depsMap||(depsMap=new Map,targetMap.set(target,depsMap));let deps=depsMap.get(key);deps||(deps=new Set,depsMap.set(key,deps)),deps.add(activeEffect),activeEffect.deps.add(deps)}function trigger(target,key){let depsMap=targetMap.get(target);if(!depsMap)return;let deps=depsMap.get(key);if(!(!deps||deps.size===0))for(let effect of Array.from(deps))effect()}function ref(initialValue){let subscribers=new Set,wrapper={_value:initialValue};return{get value(){return track(wrapper,`_value`),wrapper._value},set value(newValue){let oldValue=wrapper._value;if(!Object.is(oldValue,newValue)){wrapper._value=newValue,trigger(wrapper,`_value`);for(let fn of subscribers)fn(newValue,oldValue)}},subscribe(fn){return subscribers.add(fn),()=>subscribers.delete(fn)},toJSON(){return wrapper._value}}}function computed(getter){let cached,dirty=!0,disposed=!1,wrapper={_value:void 0},effect=createEffect(()=>{dirty=!0,trigger(wrapper,`_value`)}),recompute=()=>{if(disposed){let prev=activeEffect;activeEffect=null;try{return getter()}finally{activeEffect=prev}}return dirty&&=(cached=runTracked(effect,getter),wrapper._value=cached,!1),cached};return{get value(){return track(wrapper,`_value`),recompute()},dispose(){disposed=!0,cleanupEffect(effect)},toJSON(){return recompute()}}}function watch(source,callback,options){let oldValue,stopped=!1,getter=typeof source==`function`?source:()=>source.value,job=createEffect(()=>{if(stopped)return;let newValue=runTracked(job,getter);Object.is(newValue,oldValue)||(callback(newValue,oldValue),oldValue=newValue)});return oldValue=runTracked(job,getter),options?.immediate&&callback(oldValue,void 0),()=>{stopped=!0,cleanupEffect(job)}}const proxyCache=new WeakMap;function reactive(target){let existing=proxyCache.get(target);if(existing)return existing;let proxy=new Proxy(target,{get(obj,key,receiver){let value=Reflect.get(obj,key,receiver);return typeof key==`symbol`?value:(track(obj,key),typeof value==`object`&&value&&!Array.isArray(value)?reactive(value):value)},set(obj,key,value,receiver){let oldValue=Reflect.get(obj,key,receiver),result=Reflect.set(obj,key,value,receiver);return!Object.is(oldValue,value)&&typeof key!=`symbol`&&trigger(obj,key),result}});return proxyCache.set(target,proxy),proxy}function isRef(value){return typeof value==`object`&&!!value&&`subscribe`in value&&`value`in value}function unref(value){return isRef(value)?value.value:value}function toRefs(obj){let result={};for(let key in obj)result[key]=ref(obj[key]);return result}export{computed,isRef,reactive,ref,toRefs,unref,watch};
12
12
  //# sourceMappingURL=reactivity.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"reactivity.mjs","names":[],"sources":["../../src/core/reactivity.ts"],"sourcesContent":["/**\n * Lightweight reactivity system inspired by Vue 3's composition API.\n * Provides ref(), computed(), watch(), and reactive() for observable\n * backend state — config, metrics, health, circuit breakers.\n */\n\n// ── Dependency Tracking ─────────────────────────────────────────────────\n\ntype Effect = () => void\nlet activeEffect: Effect | null = null\nconst targetMap = new WeakMap<object, Map<string | symbol, Set<Effect>>>()\n\nfunction track(target: object, key: string | symbol): void {\n if (!activeEffect) return\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n let deps = depsMap.get(key)\n if (!deps) {\n deps = new Set()\n depsMap.set(key, deps)\n }\n deps.add(activeEffect)\n}\n\nfunction trigger(target: object, key: string | symbol): void {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n const deps = depsMap.get(key)\n if (!deps) return\n for (const effect of deps) {\n effect()\n }\n}\n\n// ── ref() ────────────────────────────────────────────────────────────────\n\nexport interface Ref<T = any> {\n value: T\n /** Subscribe to changes. Returns unsubscribe function. */\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void\n /**\n * Auto-unwrap on `JSON.stringify` — returns the underlying value\n * so refs serialize transparently inside larger payloads (introspect\n * snapshots, devtools state, request responses). Equivalent to\n * `unref(ref)` but invoked implicitly by the JSON pipeline.\n */\n toJSON(): T\n}\n\n/**\n * Create a reactive reference. Reading `.value` tracks the dependency,\n * writing `.value` triggers watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * count.value++ // triggers watchers\n * ```\n */\nexport function ref<T>(initialValue: T): Ref<T> {\n const subscribers = new Set<(newValue: T, oldValue: T) => void>()\n const wrapper = { _value: initialValue }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return wrapper._value\n },\n set value(newValue: T) {\n const oldValue = wrapper._value\n if (Object.is(oldValue, newValue)) return\n wrapper._value = newValue\n trigger(wrapper, '_value')\n for (const fn of subscribers) {\n fn(newValue, oldValue)\n }\n },\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n },\n /**\n * Auto-unwrap on `JSON.stringify`. Without this, serializing a ref\n * leaks the wrapper shape (`{\"value\": …, \"subscribe\": …}`) instead\n * of the underlying value — particularly painful for adopters who\n * keep adapter / plugin state in refs and surface it through\n * `introspect()`. Returning `value` here means\n * `JSON.stringify({ count: ref(0) })` produces `{\"count\":0}` as\n * expected, without callers having to `.value`-unwrap by hand.\n */\n toJSON(): T {\n return wrapper._value\n },\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────\n\nexport interface ComputedRef<T = any> {\n readonly value: T\n /**\n * Auto-unwrap on `JSON.stringify` — see {@link Ref.toJSON} for the\n * motivation. Triggers a recompute when the cached value is stale,\n * same as reading `.value`.\n */\n toJSON(): T\n}\n\n/**\n * Create a computed value that auto-recalculates when its reactive\n * dependencies change. Cached until a dependency triggers.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const doubled = computed(() => count.value * 2)\n * count.value = 5\n * console.log(doubled.value) // 10\n * ```\n */\nexport function computed<T>(getter: () => T): ComputedRef<T> {\n let cached: T\n let dirty = true\n const wrapper = { _value: undefined as T }\n\n const effect: Effect = () => {\n dirty = true\n trigger(wrapper, '_value')\n }\n\n const recompute = (): T => {\n if (dirty) {\n const prev = activeEffect\n activeEffect = effect\n try {\n cached = getter()\n } finally {\n activeEffect = prev\n }\n wrapper._value = cached\n dirty = false\n }\n return cached\n }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return recompute()\n },\n /**\n * Auto-unwrap on `JSON.stringify`, matching {@link ref}'s behavior\n * so adopters can drop a computed straight into a JSON-bound\n * payload (introspect snapshot, devtools state) and get the value\n * rather than the wrapper shape.\n *\n * Computed values cache through the same dirty-flag the getter\n * uses, so calling `toJSON` is at worst one recompute when stale —\n * identical cost to reading `.value`.\n */\n toJSON(): T {\n return recompute()\n },\n }\n}\n\n// ── watch() ──────────────────────────────────────────────────────────────\n\nexport interface WatchOptions {\n /** Run the callback immediately with the current value (default: false) */\n immediate?: boolean\n}\n\ntype WatchSource<T> = Ref<T> | ComputedRef<T> | (() => T)\n\n/**\n * Watch a reactive source and run a callback when it changes.\n * Returns a stop function to unsubscribe.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const stop = watch(count, (newVal, oldVal) => {\n * console.log(`Changed from ${oldVal} to ${newVal}`)\n * })\n * count.value = 1 // logs: \"Changed from 0 to 1\"\n * stop() // no more callbacks\n * ```\n */\nexport function watch<T>(\n source: WatchSource<T>,\n callback: (newValue: T, oldValue: T) => void,\n options?: WatchOptions,\n): () => void {\n let oldValue: T\n let stopped = false\n\n const getter = typeof source === 'function' ? source : () => source.value\n\n const job: Effect = () => {\n if (stopped) return\n const newValue = getter()\n if (!Object.is(newValue, oldValue)) {\n callback(newValue, oldValue)\n oldValue = newValue\n }\n }\n\n // Initial run to establish tracking\n const prev = activeEffect\n activeEffect = job\n try {\n oldValue = getter()\n } finally {\n activeEffect = prev\n }\n\n if (options?.immediate) {\n callback(oldValue, undefined as T)\n }\n\n return () => {\n stopped = true\n }\n}\n\n// ── reactive() ───────────────────────────────────────────────────────────\n\n/**\n * Create a deeply reactive proxy around a plain object. Property reads\n * are tracked, property writes trigger watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const state = reactive({ users: 0, errors: 0 })\n * const errorRate = computed(() =>\n * state.users > 0 ? state.errors / state.users : 0\n * )\n * state.users = 100\n * state.errors = 5\n * console.log(errorRate.value) // 0.05\n * ```\n */\nexport function reactive<T extends Record<string, any>>(target: T): T {\n return new Proxy(target, {\n get(obj, key, receiver) {\n const value = Reflect.get(obj, key, receiver)\n if (typeof key === 'symbol') return value\n track(obj, key)\n // Deep reactivity for nested objects\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return reactive(value)\n }\n return value\n },\n set(obj, key, value, receiver) {\n const oldValue = Reflect.get(obj, key, receiver)\n const result = Reflect.set(obj, key, value, receiver)\n if (!Object.is(oldValue, value) && typeof key !== 'symbol') {\n trigger(obj, key)\n }\n return result\n },\n })\n}\n\n// ── Utility ──────────────────────────────────────────────────────────────\n\n/** Check if a value is a Ref */\nexport function isRef(value: any): value is Ref {\n return value !== null && typeof value === 'object' && 'subscribe' in value && 'value' in value\n}\n\n/** Unwrap a ref or return the value as-is */\nexport function unref<T>(value: Ref<T> | T): T {\n return isRef(value) ? value.value : value\n}\n\n/** Convert all properties of an object to refs */\nexport function toRefs<T extends Record<string, any>>(obj: T): { [K in keyof T]: Ref<T[K]> } {\n const result = {} as any\n for (const key in obj) {\n result[key] = ref(obj[key])\n }\n return result\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reactivity.mjs","names":[],"sources":["../../src/core/reactivity.ts"],"sourcesContent":["/**\n * Lightweight reactivity system inspired by Vue 3's composition API.\n * Provides ref(), computed(), watch(), and reactive() for observable\n * backend state — config, metrics, health, circuit breakers.\n */\n\n// ── Dependency Tracking ─────────────────────────────────────────────────\n//\n// Effects carry back-references to every dep Set they were added to, so\n// stopping a watcher / disposing a computed — or simply re-running an\n// effect whose getter reads different keys this time — removes it from the\n// old Sets instead of leaving dead subscriptions to accumulate (and be\n// invoked!) forever. Same cleanup discipline as Vue 3's ReactiveEffect.\n\ninterface Effect {\n (): void\n /** Dep Sets this effect is currently registered in (for cleanup). */\n deps: Set<Set<Effect>>\n}\n\nlet activeEffect: Effect | null = null\nconst targetMap = new WeakMap<object, Map<string | symbol, Set<Effect>>>()\n\nfunction createEffect(fn: () => void): Effect {\n const effect = fn as Effect\n effect.deps = new Set()\n return effect\n}\n\n/** Remove the effect from every dep Set it was tracked into. */\nfunction cleanupEffect(effect: Effect): void {\n for (const depSet of effect.deps) depSet.delete(effect)\n effect.deps.clear()\n}\n\n/**\n * Run `fn` with `effect` as the active tracking target, first clearing the\n * effect's previous subscriptions so conditional reads don't leave stale deps.\n */\nfunction runTracked<T>(effect: Effect, fn: () => T): T {\n cleanupEffect(effect)\n const prev = activeEffect\n activeEffect = effect\n try {\n return fn()\n } finally {\n activeEffect = prev\n }\n}\n\nfunction track(target: object, key: string | symbol): void {\n if (!activeEffect) return\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n let deps = depsMap.get(key)\n if (!deps) {\n deps = new Set()\n depsMap.set(key, deps)\n }\n deps.add(activeEffect)\n activeEffect.deps.add(deps)\n}\n\nfunction trigger(target: object, key: string | symbol): void {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n const deps = depsMap.get(key)\n if (!deps || deps.size === 0) return\n // Iterate a snapshot — a triggered effect re-tracks (mutating `deps`)\n // during its run, which would otherwise corrupt this iteration.\n for (const effect of Array.from(deps)) {\n effect()\n }\n}\n\n// ── ref() ────────────────────────────────────────────────────────────────\n\nexport interface Ref<T = any> {\n value: T\n /** Subscribe to changes. Returns unsubscribe function. */\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void\n /**\n * Auto-unwrap on `JSON.stringify` — returns the underlying value\n * so refs serialize transparently inside larger payloads (introspect\n * snapshots, devtools state, request responses). Equivalent to\n * `unref(ref)` but invoked implicitly by the JSON pipeline.\n */\n toJSON(): T\n}\n\n/**\n * Create a reactive reference. Reading `.value` tracks the dependency,\n * writing `.value` triggers watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * count.value++ // triggers watchers\n * ```\n */\nexport function ref<T>(initialValue: T): Ref<T> {\n const subscribers = new Set<(newValue: T, oldValue: T) => void>()\n const wrapper = { _value: initialValue }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return wrapper._value\n },\n set value(newValue: T) {\n const oldValue = wrapper._value\n if (Object.is(oldValue, newValue)) return\n wrapper._value = newValue\n trigger(wrapper, '_value')\n for (const fn of subscribers) {\n fn(newValue, oldValue)\n }\n },\n subscribe(fn: (newValue: T, oldValue: T) => void): () => void {\n subscribers.add(fn)\n return () => subscribers.delete(fn)\n },\n /**\n * Auto-unwrap on `JSON.stringify`. Without this, serializing a ref\n * leaks the wrapper shape (`{\"value\": …, \"subscribe\": …}`) instead\n * of the underlying value — particularly painful for adopters who\n * keep adapter / plugin state in refs and surface it through\n * `introspect()`. Returning `value` here means\n * `JSON.stringify({ count: ref(0) })` produces `{\"count\":0}` as\n * expected, without callers having to `.value`-unwrap by hand.\n */\n toJSON(): T {\n return wrapper._value\n },\n }\n}\n\n// ── computed() ───────────────────────────────────────────────────────────\n\nexport interface ComputedRef<T = any> {\n readonly value: T\n /**\n * Auto-unwrap on `JSON.stringify` — see {@link Ref.toJSON} for the\n * motivation. Triggers a recompute when the cached value is stale,\n * same as reading `.value`.\n */\n toJSON(): T\n /**\n * Unsubscribe this computed from its reactive sources. Call when the\n * computed's lifetime is shorter than its sources' (e.g. created\n * per-request/per-tenant against app-lifetime refs) — without it the\n * internal effect stays pinned in the sources' dep Sets forever.\n * After disposal, reading `.value` still works but recomputes on every\n * access and no longer tracks.\n */\n dispose(): void\n}\n\n/**\n * Create a computed value that auto-recalculates when its reactive\n * dependencies change. Cached until a dependency triggers.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const doubled = computed(() => count.value * 2)\n * count.value = 5\n * console.log(doubled.value) // 10\n * ```\n */\nexport function computed<T>(getter: () => T): ComputedRef<T> {\n let cached: T\n let dirty = true\n let disposed = false\n const wrapper = { _value: undefined as T }\n\n const effect = createEffect(() => {\n dirty = true\n trigger(wrapper, '_value')\n })\n\n const recompute = (): T => {\n // Disposed: plain evaluation with tracking SUSPENDED — neither this\n // computed's effect nor any OUTER active effect (a watcher reading\n // `.value`) may attach to the sources through a disposed computed;\n // otherwise disposal wouldn't actually sever the subscription chain.\n if (disposed) {\n const prev = activeEffect\n activeEffect = null\n try {\n return getter()\n } finally {\n activeEffect = prev\n }\n }\n if (dirty) {\n cached = runTracked(effect, getter)\n wrapper._value = cached\n dirty = false\n }\n return cached\n }\n\n return {\n get value(): T {\n track(wrapper, '_value')\n return recompute()\n },\n dispose(): void {\n disposed = true\n cleanupEffect(effect)\n },\n /**\n * Auto-unwrap on `JSON.stringify`, matching {@link ref}'s behavior\n * so adopters can drop a computed straight into a JSON-bound\n * payload (introspect snapshot, devtools state) and get the value\n * rather than the wrapper shape.\n *\n * Computed values cache through the same dirty-flag the getter\n * uses, so calling `toJSON` is at worst one recompute when stale —\n * identical cost to reading `.value`.\n */\n toJSON(): T {\n return recompute()\n },\n }\n}\n\n// ── watch() ──────────────────────────────────────────────────────────────\n\nexport interface WatchOptions {\n /** Run the callback immediately with the current value (default: false) */\n immediate?: boolean\n}\n\ntype WatchSource<T> = Ref<T> | ComputedRef<T> | (() => T)\n\n/**\n * Watch a reactive source and run a callback when it changes.\n * Returns a stop function to unsubscribe.\n *\n * @example\n * ```ts\n * const count = ref(0)\n * const stop = watch(count, (newVal, oldVal) => {\n * console.log(`Changed from ${oldVal} to ${newVal}`)\n * })\n * count.value = 1 // logs: \"Changed from 0 to 1\"\n * stop() // no more callbacks\n * ```\n */\nexport function watch<T>(\n source: WatchSource<T>,\n callback: (newValue: T, oldValue: T) => void,\n options?: WatchOptions,\n): () => void {\n let oldValue: T\n let stopped = false\n\n const getter = typeof source === 'function' ? source : () => source.value\n\n const job = createEffect(() => {\n if (stopped) return\n // Re-run the getter UNDER tracking so deps are refreshed each run —\n // a conditional getter (`cond ? a.value : b.value`) drops the branch\n // it no longer reads instead of staying subscribed to it forever.\n const newValue = runTracked(job, getter)\n if (!Object.is(newValue, oldValue)) {\n callback(newValue, oldValue)\n oldValue = newValue\n }\n })\n\n // Initial run to establish tracking\n oldValue = runTracked(job, getter)\n\n if (options?.immediate) {\n callback(oldValue, undefined as T)\n }\n\n return () => {\n // Detach from every dep Set — a flag alone would leave the dead effect\n // referenced (and invoked) by its sources for their entire lifetime.\n stopped = true\n cleanupEffect(job)\n }\n}\n\n// ── reactive() ───────────────────────────────────────────────────────────\n\n/**\n * Create a deeply reactive proxy around a plain object. Property reads\n * are tracked, property writes trigger watchers and computed recalculations.\n *\n * @example\n * ```ts\n * const state = reactive({ users: 0, errors: 0 })\n * const errorRate = computed(() =>\n * state.users > 0 ? state.errors / state.users : 0\n * )\n * state.users = 100\n * state.errors = 5\n * console.log(errorRate.value) // 0.05\n * ```\n */\n// Memoize proxies per target: repeated reads of the same nested object must\n// return the SAME proxy — otherwise every access allocates a fresh Proxy and\n// referential identity breaks (`state.nested !== state.nested`).\nconst proxyCache = new WeakMap<object, any>()\n\nexport function reactive<T extends Record<string, any>>(target: T): T {\n const existing = proxyCache.get(target)\n if (existing) return existing as T\n const proxy = new Proxy(target, {\n get(obj, key, receiver) {\n const value = Reflect.get(obj, key, receiver)\n if (typeof key === 'symbol') return value\n track(obj, key)\n // Deep reactivity for nested objects\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return reactive(value)\n }\n return value\n },\n set(obj, key, value, receiver) {\n const oldValue = Reflect.get(obj, key, receiver)\n const result = Reflect.set(obj, key, value, receiver)\n if (!Object.is(oldValue, value) && typeof key !== 'symbol') {\n trigger(obj, key)\n }\n return result\n },\n })\n proxyCache.set(target, proxy)\n return proxy\n}\n\n// ── Utility ──────────────────────────────────────────────────────────────\n\n/** Check if a value is a Ref */\nexport function isRef(value: any): value is Ref {\n return value !== null && typeof value === 'object' && 'subscribe' in value && 'value' in value\n}\n\n/** Unwrap a ref or return the value as-is */\nexport function unref<T>(value: Ref<T> | T): T {\n return isRef(value) ? value.value : value\n}\n\n/** Convert all properties of an object to refs */\nexport function toRefs<T extends Record<string, any>>(obj: T): { [K in keyof T]: Ref<T[K]> } {\n const result = {} as any\n for (const key in obj) {\n result[key] = ref(obj[key])\n }\n return result\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @forinda/kickjs v6.2.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import{t as __require}from"./rolldown-runtime-C4pdyXoH.mjs";import{METADATA}from"./core/interfaces.mjs";import{i as createToken,l as _setAssetResolver}from"./container-CkcVS_gA.mjs";import"./core/logger.mjs";import{c as pushClassMeta,l as pushMethodMeta}from"./metadata-WlPWz6fi.mjs";import"./core/decorators.mjs";import"./core/cron.mjs";import"./core/cache.mjs";import"reflect-metadata";import{existsSync,readFileSync,readdirSync}from"node:fs";import{isAbsolute,join,resolve,sep}from"node:path";const mergeConfig$2=(defaults={},overrides={})=>({...defaults,...overrides}),composeName$2=(base,scope)=>`${base}:${scope}`;function defineModule(options){if(typeof options!=`object`||!options)throw TypeError(`defineModule: options must be an object literal. See architecture.md §21 for the spec shape.`);if(typeof options.name!=`string`||options.name.length===0){let got=typeof options.name==`string`?`""`:typeof options.name;throw TypeError(`defineModule: options.name must be a non-empty string (got ${got}). Module names appear in diagnostics and \`.scoped()\` namespacing — anonymous modules make cycle / missing-dep errors hard to localize.`)}if(typeof options.build!=`function`)throw TypeError(`defineModule(${options.name}): options.build is required and must be a function (got ${typeof options.build}). The build function returns the AppModule shape — register() / routes() / contributors() — wired with the resolved config.`);let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig$2(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return Object.defineProperty(built,"__moduleName",{value:instanceName,enumerable:!1,writable:!1,configurable:!1}),built},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName$2(options.name,scopeName),!0,config),Object.defineProperty(factory,"definition",{value:Object.freeze({...options}),writable:!1,enumerable:!0,configurable:!1}),factory}var ModuleList=class extends Array{mount(module){return this.push(module),this}};function defineModules(...initial){let list=new ModuleList;for(let entry of initial)list.push(entry);return list}var CircuitOpenError=class extends Error{breakerName;constructor(breakerName){super(`Circuit breaker "${breakerName}" is open — request rejected`),this.breakerName=breakerName,this.name=`CircuitOpenError`}},CircuitBreaker=class{name;state=`closed`;failures=0;successes=0;halfOpenAttempts=0;lastFailure;openedAt;failureThreshold;resetTimeout;halfOpenMax;constructor(name,options){this.name=name,this.failureThreshold=options.failureThreshold,this.resetTimeout=options.resetTimeout,this.halfOpenMax=options.halfOpenMax??1}async execute(fn){if(this.maybeTransitionToHalfOpen(),this.state===`open`||this.state===`half_open`&&this.halfOpenAttempts>=this.halfOpenMax)throw new CircuitOpenError(this.name);this.state===`half_open`&&this.halfOpenAttempts++;try{let result=await fn();return this.onSuccess(),result}catch(error){throw this.onFailure(),error}}getState(){return this.maybeTransitionToHalfOpen(),this.state}getStats(){return this.maybeTransitionToHalfOpen(),{failures:this.failures,successes:this.successes,state:this.state,...this.lastFailure?{lastFailure:this.lastFailure}:{}}}reset(){this.state=`closed`,this.failures=0,this.successes=0,this.halfOpenAttempts=0,this.lastFailure=void 0,this.openedAt=void 0}onSuccess(){this.state===`half_open`&&(this.state=`closed`,this.failures=0,this.halfOpenAttempts=0,this.openedAt=void 0),this.successes++}onFailure(){this.failures++,this.lastFailure=new Date,this.state===`half_open`?(this.state=`open`,this.openedAt=Date.now(),this.halfOpenAttempts=0):this.state===`closed`&&this.failures>=this.failureThreshold&&(this.state=`open`,this.openedAt=Date.now())}maybeTransitionToHalfOpen(){this.state===`open`&&this.openedAt!=null&&Date.now()-this.openedAt>=this.resetTimeout&&(this.state=`half_open`,this.halfOpenAttempts=0)}};function defineContextDecoratorImpl(spec){if(typeof spec!=`object`||!spec)throw TypeError(`defineContextDecorator: spec must be an object literal. See architecture.md §20.4 for the spec shape.`);if(typeof spec.key!=`string`||spec.key.length===0){let got=typeof spec.key==`string`?`""`:typeof spec.key;throw TypeError(`defineContextDecorator: spec.key must be a non-empty string (got ${got}). Pick a name that matches a key declared on \`ContextMeta\` (or one of the framework keys).`)}if(typeof spec.resolve!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.resolve is required and must be a function (got ${typeof spec.resolve}). Each contributor needs a resolver that produces the value to write into ctx.set(${JSON.stringify(spec.key)}, …).`);if(spec.onError!==void 0&&typeof spec.onError!=`function`)throw TypeError(`defineContextDecorator(${spec.key}): spec.onError must be a function when provided (got ${typeof spec.onError}).`);if(spec.dependsOn!==void 0&&!Array.isArray(spec.dependsOn))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn must be an array when provided (got ${typeof spec.dependsOn}).`);if(Array.isArray(spec.dependsOn)&&spec.dependsOn.some(dep=>typeof dep!=`string`||dep.length===0))throw TypeError(`defineContextDecorator(${spec.key}): spec.dependsOn entries must be non-empty strings.`);let definedAt=Error().stack,defaults=Object.freeze({...spec.paramDefaults??{}}),sharedDeps=Object.freeze({...spec.deps??{}}),sharedDependsOn=Object.freeze([...spec.dependsOn??[]]),sharedOptional=spec.optional??!1,buildRegistration=params=>{let frozenParams=Object.freeze({...params}),onError=spec.onError?(err,ctx)=>spec.onError(err,ctx,frozenParams):void 0;return Object.freeze({key:spec.key,deps:sharedDeps,dependsOn:sharedDependsOn,optional:sharedOptional,onError,resolve:(ctx,deps)=>spec.resolve(ctx,deps,frozenParams),definedAt})},defaultRegistration=buildRegistration({...defaults}),isDecoratorCall=args=>args.length===0?!1:args.length>=2||typeof args[0]==`function`,applyAsDecorator=(registration,target,propertyKey)=>{if(propertyKey===void 0)pushClassMeta(METADATA.CLASS_CONTRIBUTORS,target,registration);else{let constructor=target.constructor;pushMethodMeta(METADATA.METHOD_CONTRIBUTORS,constructor,String(propertyKey),registration)}},isPlainObject=value=>{let proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null},mergeParams=override=>{if(override===void 0)return{...defaults};if(typeof override!=`object`||!override||Array.isArray(override)||!isPlainObject(override)){let got=override===null?`null`:Array.isArray(override)?`array`:typeof override==`object`?`instance of ${override.constructor?.name??`unknown class`}`:typeof override;throw TypeError(`defineContextDecorator(${spec.key}): factory call requires a plain object literal, got ${got}. Class instances and built-ins (Map, Date, etc) silently spread to empty objects — call the decorator with a spread object literal instead, e.g. \`MyDecorator({ ...someInstance })\`.`)}return{...defaults,...override}};function decoratorOrFactory(...args){if(isDecoratorCall(args)){let[target,propertyKey]=args;applyAsDecorator(defaultRegistration,target,propertyKey);return}let params=mergeParams(args[0]),registration=buildRegistration(params);return(target,propertyKey)=>{applyAsDecorator(registration,target,propertyKey)}}Object.defineProperty(decoratorOrFactory,"name",{value:`ContextDecorator(${spec.key})`,writable:!1,enumerable:!1,configurable:!0});let decorator=Object.assign(decoratorOrFactory,{registration:defaultRegistration,with:params=>({registration:buildRegistration(mergeParams(params))})});return Object.freeze(decorator)}const defineContextDecorator=Object.freeze(Object.assign(defineContextDecoratorImpl,{withParams:()=>spec=>defineContextDecoratorImpl(spec)}));var DuplicateMountNameError=class extends Error{kind;mountName;constructor(kind,mountName){super(`Duplicate ${kind} name '${mountName}' — every ${kind} must declare a unique name`),this.name=`DuplicateMountNameError`,this.kind=kind,this.mountName=mountName}},MissingMountDepError=class extends Error{kind;missing;dependent;constructor(kind,missing,dependent){super(`Missing ${kind} dependency '${missing}' required by '${dependent}' — is the ${kind} installed and named correctly?`),this.name=`MissingMountDepError`,this.kind=kind,this.missing=missing,this.dependent=dependent}},MountCycleError=class extends Error{kind;cycle;constructor(kind,cycle){super(`Cycle in ${kind} dependsOn declarations: ${cycle.join(` → `)}`),this.name=`MountCycleError`,this.kind=kind,this.cycle=Object.freeze([...cycle])}};function mountSort(items,kind){let byName=new Map;for(let item of items){if(byName.has(item.name))throw new DuplicateMountNameError(kind,item.name);byName.set(item.name,item)}let indegree=new Map,dependents=new Map;for(let item of items)indegree.set(item.name,item.dependsOn?.length??0);for(let item of items)for(let dep of item.dependsOn??[]){if(!byName.has(dep))throw new MissingMountDepError(kind,dep,item.name);let list=dependents.get(dep)??[];list.push(item),dependents.set(dep,list)}let queue=[];for(let item of items)indegree.get(item.name)===0&&queue.push(item);let sorted=[];for(;queue.length>0;){let node=queue.shift();sorted.push(node);for(let dependent of dependents.get(node.name)??[]){let next=(indegree.get(dependent.name)??0)-1;indegree.set(dependent.name,next),next===0&&queue.push(dependent)}}if(sorted.length!==items.length)throw new MountCycleError(kind,reconstructCycle(items.filter(it=>(indegree.get(it.name)??0)>0),byName));return sorted}function reconstructCycle(residual,byName){let residualNames=new Set(residual.map(r=>r.name)),start=residual[0].name,path=[start],seen=new Set([start]),cursor=start;for(;;){let nextDep=byName.get(cursor).dependsOn?.find(d=>residualNames.has(d));if(nextDep===void 0)break;if(seen.has(nextDep)){let cycleStart=path.indexOf(nextDep);return[...path.slice(cycleStart),nextDep]}path.push(nextDep),seen.add(nextDep),cursor=nextDep}return path}const mergeConfig$1=(defaults={},overrides={})=>({...defaults,...overrides}),composeName$1=(base,scope)=>`${base}:${scope}`;function definePlugin(options){let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig$1(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return built.name=instanceName,built},buildAsync=(instanceName,opts)=>{let inner,ensureInner=async container=>{if(inner)return inner;let deps=(opts.inject??[]).map(tok=>container.resolve(tok)),config=await opts.useFactory(...deps);return inner=buildSync(instanceName,!1,config),inner};return{name:instanceName,async onReady(container){let built=await ensureInner(container);built.register?.(container),await built.onReady?.(container)},async shutdown(){await inner?.shutdown?.()}}},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName$1(options.name,scopeName),!0,config),factory.async=opts=>buildAsync(options.name,opts),Object.defineProperty(factory,"definition",{value:Object.freeze({...options})}),factory}const mergeConfig=(defaults={},overrides={})=>({...defaults,...overrides}),composeName=(base,scope)=>`${base}:${scope}`;function defineAdapter(options){let buildSync=(instanceName,scoped,overrides)=>{let config=mergeConfig(options.defaults,overrides),built=options.build(config,{name:instanceName,scoped});return built.name=instanceName,built},buildAsync=(instanceName,opts)=>{let inner;return{name:instanceName,async beforeStart(ctx){let deps=(opts.inject??[]).map(tok=>ctx.container.resolve(tok)),config=await opts.useFactory(...deps);inner=buildSync(instanceName,!1,config),await inner.beforeStart?.(ctx)},async afterStart(ctx){await inner?.afterStart?.(ctx)},async shutdown(){await inner?.shutdown?.()},async onHealthCheck(){return await inner?.onHealthCheck?.()??{name:instanceName,status:`up`}}}},factory=(config=>buildSync(options.name,!1,config));return factory.scoped=(scopeName,config)=>buildSync(composeName(options.name,scopeName),!0,config),factory.async=opts=>buildAsync(options.name,opts),Object.defineProperty(factory,"definition",{value:Object.freeze({...options})}),factory}function defineAugmentation(name,meta){}function groupAssetKeys(namespace,paths,opts={}){let strategy=opts.strategy??`auto`;if(strategy===`with-extension`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${rel}`})),collisionGroupsResolved:0};if(strategy===`strip`)return{pairs:paths.map(rel=>({rel,key:`${namespace}/${stripExt(rel)}`})),collisionGroupsResolved:0};let groups=new Map;for(let rel of paths){let strippedKey=`${namespace}/${stripExt(rel)}`,arr=groups.get(strippedKey);arr?arr.push(rel):groups.set(strippedKey,[rel])}let collisionGroupsResolved=0,keyByRel=new Map;for(let[strippedKey,rels]of groups)if(rels.length===1)keyByRel.set(rels[0],strippedKey);else{collisionGroupsResolved+=1;for(let rel of rels)keyByRel.set(rel,`${namespace}/${rel}`)}return{pairs:paths.map(rel=>({rel,key:keyByRel.get(rel)??`${namespace}/${rel}`})),collisionGroupsResolved}}function stripExt(path){let lastSlash=path.lastIndexOf(`/`),filename=lastSlash>=0?path.slice(lastSlash+1):path,lastDot=filename.lastIndexOf(`.`);return lastDot<=0?path:(lastSlash>=0?path.slice(0,lastSlash+1):``)+filename.slice(0,lastDot)}const ASSET_MANIFEST_VERSION=1,ASSETS=createToken(`kick/assets/Map`);var UnknownAssetError=class extends Error{namespace;key;constructor(namespace,key){super(`Unknown asset '${namespace}/${key}'. Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`),this.name=`UnknownAssetError`,this.namespace=namespace,this.key=key}};let manifestCache;function toPosixPath(p){return p.replaceAll(`\\`,`/`)}function resolveAsset(namespace,key){let resolved=loadManifest();if(!resolved)throw new UnknownAssetError(namespace,key);let entry=resolved.manifest.entries[`${namespace}/${key}`];if(!entry)throw new UnknownAssetError(namespace,key);return toPosixPath(isAbsolute(entry)?entry:resolve(resolved.root,entry))}function clearAssetCache(){manifestCache=void 0}function loadManifest(){return process.env.NODE_ENV===`production`?(manifestCache===void 0&&(manifestCache=discoverManifest()),manifestCache):discoverManifest()}function discoverManifest(){let envRoot=process.env.KICK_ASSETS_ROOT;if(envRoot){let fromEnv=readBuiltManifest(envRoot);if(fromEnv)return fromEnv}let cwd=process.cwd(),config=loadConfigSync(cwd);if(process.env.NODE_ENV!==`production`){let fresh=synthesiseDevManifest(cwd,config);if(fresh)return fresh}let configOutDir=config?.build?.outDir;if(configOutDir&&!STANDARD_OUT_DIRS.includes(configOutDir)){let found=readBuiltManifest(resolve(cwd,configOutDir));if(found)return found}for(let candidate of STANDARD_OUT_DIRS){let found=readBuiltManifest(join(cwd,candidate));if(found)return found}return synthesiseDevManifest(cwd,config)}const STANDARD_OUT_DIRS=[`dist`,`build`,`out`];function readBuiltManifest(dir){let path=join(dir,`.kickjs-assets.json`);if(!existsSync(path))return null;try{let raw=readFileSync(path,`utf-8`),parsed=JSON.parse(raw);return parsed.version!==1||!parsed.entries||typeof parsed.entries!=`object`?null:{manifest:parsed,root:resolve(dir)}}catch{return null}}function synthesiseDevManifest(cwd,preloadedConfig){let config=preloadedConfig===void 0?loadConfigSync(cwd):preloadedConfig;if(!config?.assetMap)return null;let entries={};for(let[namespace,entry]of Object.entries(config.assetMap)){if(!entry||typeof entry.src!=`string`)continue;let srcAbs=resolve(cwd,entry.src);if(!existsSync(srcAbs))continue;let collected=[];walkSync(srcAbs,srcAbs,(relPath,absPath)=>{entry.glob&&!matchesGlobLite(relPath,entry.glob,namespace)||collected.push({rel:relPath,abs:absPath})}),collected.sort((a,b)=>a.rel.localeCompare(b.rel));let{pairs,collisionGroupsResolved}=groupAssetKeys(namespace,collected.map(c=>c.rel),{strategy:entry.keys??`auto`}),absByRel=new Map(collected.map(c=>[c.rel,c.abs]));for(let{rel,key}of pairs){let abs=absByRel.get(rel);abs&&(entries[key]=abs)}collisionGroupsResolved>0&&process.env.NODE_ENV!==`production`&&console.log(`[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions (set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`)}return{manifest:{version:1,entries},root:cwd}}const warnedGlobs=new Set;function matchesGlobLite(relPath,pattern,namespace){if(pattern===`**/*`||pattern===`**`)return!0;let singleExt=/^\*\*\/\*\.(\w+)$/.exec(pattern);if(singleExt)return relPath.endsWith(`.${singleExt[1]}`);let braceExt=/^\*\*\/\*\.\{([^}]+)\}$/.exec(pattern);if(braceExt)return braceExt[1].split(`,`).map(e=>e.trim()).some(ext=>relPath.endsWith(`.${ext}`));let tag=`${namespace}::${pattern}`;return warnedGlobs.has(tag)||(warnedGlobs.add(tag),console.warn(`[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`)),!0}function walkSync(root,dir,onFile){let entries;try{entries=readdirSync(dir,{withFileTypes:!0})}catch{return}for(let entry of entries){if(entry.name===`node_modules`||entry.name.startsWith(`.`))continue;let full=join(dir,entry.name);entry.isDirectory()?walkSync(root,full,onFile):entry.isFile()&&onFile(full.slice(root.length+1).split(sep).join(`/`),full)}}function loadConfigSync(cwd){let jsonPath=join(cwd,`kick.config.json`);if(existsSync(jsonPath))try{return JSON.parse(readFileSync(jsonPath,`utf-8`))}catch{return null}for(let filename of[`kick.config.cjs`,`kick.config.js`]){let path=join(cwd,filename);if(existsSync(path))try{let{createRequire}=__require(`node:module`),mod=createRequire(join(cwd,`package.json`))(path);return mod.default??mod??null}catch{continue}}let snapshotPath=join(cwd,`.kickjs`,`kick.config.json`);if(existsSync(snapshotPath))try{return JSON.parse(readFileSync(snapshotPath,`utf-8`))}catch{return null}return null}const PROMISE_DETECTION_KEYS=new Set([`then`]),PASSTHROUGH_KEYS=new Set([`toString`,`valueOf`,`constructor`,`prototype`,`name`,`length`,`asymmetricMatch`,`nodeType`,Symbol.toPrimitive,Symbol.toStringTag,Symbol.iterator,Symbol.asyncIterator]);function createNamespaceProxy(parts){return new Proxy(function(){},{apply(){if(parts.length<2)throw new UnknownAssetError(parts[0]??``,``);let[namespace,...rest]=parts;return resolveAsset(namespace,rest.join(`/`))},get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([...parts,prop])}}})}function createAssetProxy(){return new Proxy({},{get(t,prop){if(!PROMISE_DETECTION_KEYS.has(prop)){if(PASSTHROUGH_KEYS.has(prop))return Reflect.get(t,prop);if(typeof prop!=`symbol`)return createNamespaceProxy([prop])}}})}const assets=createAssetProxy();function useAssets(){return assets}_setAssetResolver(resolveAsset);export{defineModule as S,defineContextDecorator as _,clearAssetCache as a,ModuleList as b,groupAssetKeys as c,defineAdapter as d,definePlugin as f,mountSort as g,MountCycleError as h,assets as i,stripExt as l,MissingMountDepError as m,ASSET_MANIFEST_VERSION as n,resolveAsset as o,DuplicateMountNameError as p,UnknownAssetError as r,useAssets as s,ASSETS as t,defineAugmentation as u,CircuitBreaker as v,defineModules as x,CircuitOpenError as y};
12
+ //# sourceMappingURL=core-BBnRIW_2.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-BBnRIW_2.mjs","names":["mergeConfig","composeName","mergeConfig","composeName"],"sources":["../src/core/define-module.ts","../src/core/define-modules.ts","../src/core/circuit-breaker.ts","../src/core/context-decorator.ts","../src/core/mount-sort.ts","../src/core/define-plugin.ts","../src/core/define-adapter.ts","../src/core/augmentation.ts","../src/core/asset-keys.ts","../src/core/assets.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","/**\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 { ContextKeys, ContextMeta, ExecutionContext, MetaValue } from './execution-context'\n\n/**\n * String-literal union of every known context key — the union of the\n * keys declared on {@link ContextKeys} (key-only registry) and\n * {@link ContextMeta} (value-type registry). Resolves to `string` when\n * the project hasn't augmented either yet (so a first-day project keeps\n * compiling), and narrows to the concrete keys once the\n * `declare module '@forinda/kickjs' { interface ContextKeys/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 *\n * Unioning BOTH registries is deliberate: it means adding a value type\n * via `ContextMeta` automatically makes that key a valid `dependsOn`\n * target, and you can register a dependsOn-able key in `ContextKeys`\n * without being forced to give it a value type. (Before the split,\n * `dependsOn` was keyed off `ContextMeta` alone, so augmenting\n * `ContextMeta` for some keys broke `dependsOn` for every key you\n * hadn't added there.)\n */\ntype KnownContextKey = keyof ContextMeta | keyof ContextKeys\nexport type ContextMetaKey = [KnownContextKey] extends [never] ? string : KnownContextKey & 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 {@link ContextMetaKey} — the union of `keyof ContextMeta`\n * and `keyof ContextKeys` — once the project augments either, so\n * `dependsOn: ['tenent']` (typo) is a TS error, not a boot-time\n * `MissingContributorError`. Falls back to plain `string[]` when both\n * registries are empty (no augmentation yet) so first-day projects keep\n * compiling. Cast escape hatch: `dependsOn: ['custom-key' as ContextMetaKey]`.\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 * Curried params-first helper for {@link defineContextDecorator}.\n *\n * Solves the partial-inference problem: TS generics are positional, so\n * the moment an adopter wants to specify the per-call params shape\n * `P`, they're forced to spell `K` and `D` by hand — losing the\n * automatic `deps` inference that drives the `(ctx, deps, params) =>`\n * resolver signature:\n *\n * ```ts\n * // ❌ Positional generics — must repeat the deps type, and `D` no\n * // longer flows from `spec.deps` into `resolve(ctx, deps, ...)`.\n * defineContextDecorator<'tenant', { repo: typeof TENANT_REPO }, MyParams>({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO },\n * resolve: (ctx, { repo }, params) => ...,\n * })\n *\n * // ✅ Curried form — only `P` is spelled; `K`, `D`, `Ctx` infer.\n * defineContextDecorator.withParams<MyParams>()({\n * key: 'tenant',\n * deps: { repo: TENANT_REPO }, // D inferred\n * resolve: (ctx, { repo }, params) => ..., // deps + params both typed\n * })\n * ```\n *\n * Use this whenever the contributor takes call-site params; use the\n * positional form when there are no params (the common case).\n */\nexport interface DefineContextDecoratorWithParams<P extends Record<string, unknown>> {\n <\n K extends string,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx>,\n ): ContextDecorator<K, D, P, Ctx>\n}\n\n/**\n * The public shape of {@link defineContextDecorator}. Combines the\n * original positional-generic call signature with the curried\n * `.withParams<P>()` helper that fixes partial-inference for the\n * parameterised case.\n */\nexport interface DefineContextDecoratorFn {\n <\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 >(\n spec: ContextDecoratorSpec<K, D, P, Ctx>,\n ): ContextDecorator<K, D, P, Ctx>\n\n /**\n * Curried entry point. Spell only the per-call params shape `P`;\n * `K`, `D`, and `Ctx` are inferred from the spec passed to the\n * returned function. See {@link DefineContextDecoratorWithParams}\n * for the rationale.\n */\n withParams<P extends Record<string, unknown>>(): DefineContextDecoratorWithParams<P>\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 *\n * **Parameterised contributors:** TS generics are positional, so to\n * specify `P` (the per-call params shape) with this signature you\n * also have to spell `K` and `D` — which loses automatic `deps`\n * inference. Use {@link DefineContextDecoratorFn.withParams} instead:\n * `defineContextDecorator.withParams<MyParams>()(spec)`.\n */\nfunction defineContextDecoratorImpl<\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/**\n * Public export — the positional-generic implementation plus the\n * curried `.withParams<P>()` helper attached as a method. Adopters\n * pick whichever form fits the call site:\n *\n * - **Positional** — `defineContextDecorator(spec)` when there are\n * no per-call params (the common case). `K`, `D`, `Ctx` infer.\n * - **Curried** — `defineContextDecorator.withParams<P>()(spec)`\n * when the contributor takes params; spell only `P`, the rest\n * still infer.\n *\n * Frozen so adopters can't reassign `.withParams` at runtime.\n */\nexport const defineContextDecorator: DefineContextDecoratorFn = Object.freeze(\n Object.assign(defineContextDecoratorImpl, {\n withParams: <P extends Record<string, unknown>>(): DefineContextDecoratorWithParams<P> => {\n return <\n K extends string,\n D extends Record<string, DepValue> = Record<string, never>,\n Ctx extends ExecutionContext = ExecutionContext,\n >(\n spec: ContextDecoratorSpec<K, D, P, Ctx>,\n ): ContextDecorator<K, D, P, Ctx> => defineContextDecoratorImpl<K, D, P, Ctx>(spec)\n },\n }),\n) as DefineContextDecoratorFn\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 *\n * @deprecated Low value in practice: it does nothing at runtime or at\n * the type level — the `declare module` block alone gives you the\n * augmentation, and the generated `.kickjs/types/kick__augmentations.d.ts`\n * catalogue it feeds is documentation-only. Prefer a plain\n * `declare module '@forinda/kickjs' { ... }` block and (optionally) a\n * JSDoc comment on your own interface. `defineAugmentation` and the\n * `kick/augmentations` typegen plugin will be removed in a future major.\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","// Shared asset-key strategy — used by the build pipeline, the\n// runtime dev resolver, and the typegen emitter so all three layers\n// agree on what the manifest looks like.\n//\n// Three strategies:\n//\n// - `'strip'` — drop the final extension. `pages/index.pug` →\n// `pages/index`. Best for namespaces where every basename is\n// unique (mail templates, pdf invoices). Shortest autocomplete.\n//\n// - `'with-extension'` — keep the extension. `pages/index.pug` →\n// `pages/index.pug`. Required when extension siblings coexist.\n//\n// - `'auto'` (default) — strip when safe, keep extensions when not.\n// The grouping pass collects files by stripped basename; groups\n// of one keep the stripped key, groups of more than one keep the\n// full path. Backward compatible for projects with no\n// extension-siblings; lossless for projects that have them.\n//\n// The function returns the path / key pairs in walk order (input\n// order preserved) so callers can iterate without re-sorting. Sort\n// in the caller if a stable manifest order is needed.\n\nexport type AssetKeyStrategy = 'auto' | 'strip' | 'with-extension'\n\nexport interface AssetKeyPair {\n /** Source-relative path the file was discovered at. */\n rel: string\n /** Final logical key for the manifest, including the namespace prefix. */\n key: string\n}\n\nexport interface GroupAssetKeysOptions {\n /**\n * Strategy applied to this namespace. Default `'auto'`.\n *\n * Adopters override per-namespace via `assetMap.<ns>.keys` in\n * kick.config.ts when they want uniform behaviour (e.g. always\n * include the extension regardless of collisions).\n */\n strategy?: AssetKeyStrategy\n}\n\nexport interface GroupAssetKeysResult {\n pairs: AssetKeyPair[]\n /**\n * Number of collision groups resolved by keeping extensions.\n * Surfaces as an informational log (\"auto-resolved N collisions\")\n * so adopters know whether they're relying on auto-mode or not.\n * Always 0 for `'strip'` (overwrites) and `'with-extension'`\n * (extensions always preserved, no collision possible).\n */\n collisionGroupsResolved: number\n}\n\n/**\n * Compute final manifest keys for a namespace's discovered paths,\n * given the chosen strategy. Pure function — no fs / config / globals\n * involved.\n *\n * @param namespace assetMap key — e.g. `'mails'`, `'pages'`.\n * @param paths source-relative file paths (POSIX separators) discovered\n * under the namespace's `src` directory.\n * @param opts grouping options.\n */\nexport function groupAssetKeys(\n namespace: string,\n paths: readonly string[],\n opts: GroupAssetKeysOptions = {},\n): GroupAssetKeysResult {\n const strategy = opts.strategy ?? 'auto'\n\n if (strategy === 'with-extension') {\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${rel}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n if (strategy === 'strip') {\n // Strict strip — last-walk-order wins on collision; no\n // collision tracking here because the docs document this as the\n // adopter's choice. The caller (build / runtime / typegen) can\n // still warn if it wants; this function just shapes keys.\n return {\n pairs: paths.map((rel) => ({ rel, key: `${namespace}/${stripExt(rel)}` })),\n collisionGroupsResolved: 0,\n }\n }\n\n // strategy === 'auto' — group by stripped basename, keep\n // extensions only on collision groups.\n const groups = new Map<string, string[]>()\n for (const rel of paths) {\n const strippedKey = `${namespace}/${stripExt(rel)}`\n const arr = groups.get(strippedKey)\n if (arr) {\n arr.push(rel)\n } else {\n groups.set(strippedKey, [rel])\n }\n }\n\n let collisionGroupsResolved = 0\n // Build a per-rel decision table so we preserve input ordering in\n // the output (manifest writers rely on stable ordering).\n const keyByRel = new Map<string, string>()\n for (const [strippedKey, rels] of groups) {\n if (rels.length === 1) {\n keyByRel.set(rels[0], strippedKey)\n } else {\n collisionGroupsResolved += 1\n for (const rel of rels) {\n keyByRel.set(rel, `${namespace}/${rel}`)\n }\n }\n }\n\n return {\n pairs: paths.map((rel) => ({\n rel,\n key: keyByRel.get(rel) ?? `${namespace}/${rel}`,\n })),\n collisionGroupsResolved,\n }\n}\n\n/** Strip the final extension from a path. `'mails/welcome.ejs'` → `'mails/welcome'`. */\nexport function stripExt(path: string): string {\n // Inline the logic instead of importing extname so this module\n // stays usable in pure-browser contexts. Same algorithm: find the\n // last dot AFTER the last separator, slice up to it.\n const lastSlash = path.lastIndexOf('/')\n const filename = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n const lastDot = filename.lastIndexOf('.')\n if (lastDot <= 0) return path // no ext, or hidden file like `.env`\n const dirPart = lastSlash >= 0 ? path.slice(0, lastSlash + 1) : ''\n return dirPart + filename.slice(0, lastDot)\n}\n","/**\n * Asset manager runtime (assets-plan.md PR 3).\n *\n * Three accessor surfaces over one resolver engine:\n *\n * - `assets.x.y()` — Proxy ambient, autocomplete-friendly default.\n * - `useAssets()` — hook returning the same Proxy; mockable per-call.\n * - `resolveAsset(ns, k)` — string resolver for dynamic dispatch.\n *\n * Resolution path:\n *\n * 1. Load the build-time manifest (`<outDir>/.kickjs-assets.json`)\n * when present. Manifest existence = \"we're running from a built\n * dist\". Fast path; mapped paths returned verbatim.\n * 2. Otherwise (dev / test) walk the assetMap src directories from\n * `kick.config.{json,js,cjs}` and synthesise the same manifest\n * shape in memory. First call pays the walk cost; subsequent\n * calls hit the cache.\n * 3. `KICK_ASSETS_ROOT` env var overrides everything — the resolver\n * treats the path as the manifest directory and skips discovery.\n *\n * @module @forinda/kickjs/core/assets\n */\n\nimport { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { isAbsolute, join, resolve, sep } from 'node:path'\nimport { createToken, type InjectionToken } from './token'\nimport { groupAssetKeys } from './asset-keys'\n\nexport const ASSET_MANIFEST_VERSION = 1 as const\n\ninterface AssetManifest {\n version: typeof ASSET_MANIFEST_VERSION\n entries: Record<string, string>\n}\n\ninterface ResolvedManifest {\n manifest: AssetManifest\n root: string\n}\n\n/**\n * Augmentable interface — `kick typegen` (assets-plan.md PR 4) emits\n * a `declare global` augmentation listing every namespace + key for\n * the project's `assetMap`. Adopters who haven't run typegen yet\n * still get a runtime-correct Proxy; only typed autocomplete waits\n * on the augmentation.\n */\nexport interface KickAssets {}\n\n/**\n * DI token for the `assets` Proxy. Adopter code that wants to\n * receive assets via constructor injection registers via\n * `container.registerInstance(ASSETS, useAssets())` in `bootstrap()`\n * and injects with `@Inject(ASSETS)`.\n */\nexport const ASSETS: InjectionToken<KickAssets> = createToken<KickAssets>('kick/assets/Map')\n\nexport class UnknownAssetError extends Error {\n readonly namespace: string\n readonly key: string\n constructor(namespace: string, key: string) {\n super(\n `Unknown asset '${namespace}/${key}'. ` +\n `Make sure the namespace exists in kick.config.ts assetMap and that the file is present in the source directory.`,\n )\n this.name = 'UnknownAssetError'\n this.namespace = namespace\n this.key = key\n }\n}\n\nlet manifestCache: ResolvedManifest | null | undefined = undefined\n\n/**\n * Convert a filesystem path to its posix (forward-slash) form. The\n * asset manifest and the public `resolveAsset` / `assets.x.y()` /\n * `useAssets()` contract returns posix paths on every platform so\n * adopters can:\n *\n * - pass the result to `express.static` / `res.sendFile` / `ejs.renderFile`\n * without worrying about Windows vs. posix separators\n * - splice the path into URLs (`href`, `src`, CDN keys) without\n * replacing `\\` with `/` by hand\n * - compare two paths from `resolveAsset` for equality on any host\n *\n * Node's `fs.*` and Express's path-handling APIs accept either\n * separator on Windows, so this normalisation is safe — the only\n * adopter code it could break is something explicitly parsing\n * Windows backslashes back out of the result, which would already be\n * brittle.\n */\nfunction toPosixPath(p: string): string {\n return p.replaceAll('\\\\', '/')\n}\n\nexport function resolveAsset(namespace: string, key: string): string {\n const resolved = loadManifest()\n if (!resolved) {\n throw new UnknownAssetError(namespace, key)\n }\n const entry = resolved.manifest.entries[`${namespace}/${key}`]\n if (!entry) {\n throw new UnknownAssetError(namespace, key)\n }\n const abs = isAbsolute(entry) ? entry : resolve(resolved.root, entry)\n return toPosixPath(abs)\n}\n\n/** Reset the manifest cache. Tests use this; production code shouldn't need it. */\nexport function clearAssetCache(): void {\n manifestCache = undefined\n}\n\nfunction loadManifest(): ResolvedManifest | null {\n // In development we deliberately skip the cache so adding or removing\n // a template file (e.g. dropping `welcome-back.ejs` into\n // `src/templates/mails/`) takes effect without a server restart. The\n // synthesise-dev-manifest path walks ~10 files; the cost is negligible\n // compared to the user-visible surprise of \"I added a file but\n // assets.x.y() throws\". Production keeps the cache because the built\n // manifest under `dist/.kickjs-assets.json` doesn't change at runtime.\n if (process.env.NODE_ENV !== 'production') {\n return discoverManifest()\n }\n if (manifestCache !== undefined) return manifestCache\n manifestCache = discoverManifest()\n return manifestCache\n}\n\nfunction discoverManifest(): ResolvedManifest | null {\n // `KICK_ASSETS_ROOT` is an explicit, deliberate override — it wins in\n // both dev and prod.\n const envRoot = process.env.KICK_ASSETS_ROOT\n if (envRoot) {\n const fromEnv = readBuiltManifest(envRoot)\n if (fromEnv) return fromEnv\n }\n\n const cwd = process.cwd()\n const config = loadConfigSync(cwd)\n\n // In dev, prefer a live walk of the source tree over any built\n // manifest left on disk by an earlier `kick build`. Otherwise a stale\n // `dist/.kickjs-assets.json` shadows the live `src/` tree: a freshly\n // added template file would resolve to the old map (or throw\n // UnknownAssetError) until the next rebuild — exactly the surprise the\n // cache-skip in `loadManifest` exists to avoid. `synthesiseDevManifest`\n // returns null only when there's no `assetMap` config at all, in which\n // case we fall through to the built-manifest probes below.\n if (process.env.NODE_ENV !== 'production') {\n const fresh = synthesiseDevManifest(cwd, config)\n if (fresh) return fresh\n }\n\n // Honour `kick.config.build.outDir` BEFORE the standard probe list\n // so adopters with non-default Vite outputs (e.g. `output/`,\n // `target/`, anything else) don't have to set KICK_ASSETS_ROOT\n // every time. Cached config load — same loader the dev fallback\n // uses. Skip when outDir matches one of the standard candidates;\n // it'd be probed redundantly otherwise.\n const configOutDir = config?.build?.outDir\n if (configOutDir && !(STANDARD_OUT_DIRS as readonly string[]).includes(configOutDir)) {\n const found = readBuiltManifest(resolve(cwd, configOutDir))\n if (found) return found\n }\n\n for (const candidate of STANDARD_OUT_DIRS) {\n const found = readBuiltManifest(join(cwd, candidate))\n if (found) return found\n }\n\n return synthesiseDevManifest(cwd, config)\n}\n\nconst STANDARD_OUT_DIRS = ['dist', 'build', 'out'] as const\n\nfunction readBuiltManifest(dir: string): ResolvedManifest | null {\n const path = join(dir, '.kickjs-assets.json')\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as Partial<AssetManifest>\n if (parsed.version !== ASSET_MANIFEST_VERSION) return null\n if (!parsed.entries || typeof parsed.entries !== 'object') return null\n return { manifest: parsed as AssetManifest, root: resolve(dir) }\n } catch {\n return null\n }\n}\n\ninterface AssetMapConfigShape {\n assetMap?: Record<\n string,\n {\n src: string\n dest?: string\n glob?: string\n keys?: 'auto' | 'strip' | 'with-extension'\n }\n >\n build?: { outDir?: string }\n}\n\nfunction synthesiseDevManifest(\n cwd: string,\n preloadedConfig?: AssetMapConfigShape | null,\n): ResolvedManifest | null {\n const config = preloadedConfig === undefined ? loadConfigSync(cwd) : preloadedConfig\n if (!config?.assetMap) return null\n\n const entries: Record<string, string> = {}\n // Walk order is filesystem-dependent on Linux/macOS, so we collect\n // every match per-namespace first, then sort + feed `groupAssetKeys`\n // for stable manifest output. Build pipeline does the same — both\n // layers agree on the resulting key set.\n for (const [namespace, entry] of Object.entries(config.assetMap)) {\n if (!entry || typeof entry.src !== 'string') continue\n const srcAbs = resolve(cwd, entry.src)\n if (!existsSync(srcAbs)) continue\n\n const collected: Array<{ rel: string; abs: string }> = []\n walkSync(srcAbs, srcAbs, (relPath, absPath) => {\n if (entry.glob && !matchesGlobLite(relPath, entry.glob, namespace)) return\n collected.push({ rel: relPath, abs: absPath })\n })\n collected.sort((a, b) => a.rel.localeCompare(b.rel))\n\n const { pairs, collisionGroupsResolved } = groupAssetKeys(\n namespace,\n collected.map((c) => c.rel),\n { strategy: entry.keys ?? 'auto' },\n )\n const absByRel = new Map(collected.map((c) => [c.rel, c.abs]))\n for (const { rel, key } of pairs) {\n const abs = absByRel.get(rel)\n if (abs) entries[key] = abs\n }\n if (collisionGroupsResolved > 0 && process.env.NODE_ENV !== 'production') {\n // Dev-only nudge — production runs read the build manifest where\n // the collision was already resolved at build time.\n console.log(\n `[kickjs/assets] '${namespace}': ${collisionGroupsResolved} basename collision(s) auto-resolved by keeping extensions ` +\n `(set assetMap.${namespace}.keys to 'strip' or 'with-extension' to override).`,\n )\n }\n }\n\n return { manifest: { version: ASSET_MANIFEST_VERSION, entries }, root: cwd }\n}\n\nconst warnedGlobs = new Set<string>()\n\n/**\n * Tiny glob matcher used only by dev manifest synthesis. Covers the\n * common assetMap glob patterns (`**\\/*`, `**\\/*.ext`,\n * `**\\/*.{a,b}`). Anything exotic prints a one-time warning + falls\n * through to `**\\/*` semantics rather than silently dropping files.\n *\n * The build pipeline (`packages/cli/src/asset-manager/build.ts`) uses\n * the full `glob` package; adopters who hit the dev-mode warning\n * should run `kick build:assets` to produce a real manifest.\n */\nfunction matchesGlobLite(relPath: string, pattern: string, namespace: string): boolean {\n if (pattern === '**/*' || pattern === '**') return true\n const singleExt = /^\\*\\*\\/\\*\\.(\\w+)$/.exec(pattern)\n if (singleExt) return relPath.endsWith(`.${singleExt[1]}`)\n const braceExt = /^\\*\\*\\/\\*\\.\\{([^}]+)\\}$/.exec(pattern)\n if (braceExt) {\n const exts = braceExt[1].split(',').map((e) => e.trim())\n return exts.some((ext) => relPath.endsWith(`.${ext}`))\n }\n const tag = `${namespace}::${pattern}`\n if (!warnedGlobs.has(tag)) {\n warnedGlobs.add(tag)\n console.warn(\n `[kickjs/assets] Dev-mode glob '${pattern}' on '${namespace}' isn't recognised by the lite matcher. ` +\n `Falling back to '**/*'. Run 'kick build:assets' to use the full glob engine.`,\n )\n }\n return true\n}\n\nfunction walkSync(root: string, dir: string, onFile: (rel: string, abs: string) => void): void {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n walkSync(root, full, onFile)\n } else if (entry.isFile()) {\n const rel = full\n .slice(root.length + 1)\n .split(sep)\n .join('/')\n onFile(rel, full)\n }\n }\n}\n\n/**\n * Sync `kick.config.{json,cjs,js}` loader for dev manifest\n * synthesis. Avoids dynamic `import()` (would force every assets\n * lookup to be async). TypeScript configs (`kick.config.ts`) are\n * skipped here — adopters who use them should run `kick build:assets`\n * to produce a real manifest the resolver can read.\n */\nfunction loadConfigSync(cwd: string): AssetMapConfigShape | null {\n const jsonPath = join(cwd, 'kick.config.json')\n if (existsSync(jsonPath)) {\n try {\n return JSON.parse(readFileSync(jsonPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n for (const filename of ['kick.config.cjs', 'kick.config.js']) {\n const path = join(cwd, filename)\n if (!existsSync(path)) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { createRequire } = require('node:module') as typeof import('node:module')\n const req = createRequire(join(cwd, 'package.json'))\n const mod = req(path) as { default?: AssetMapConfigShape } & AssetMapConfigShape\n return mod.default ?? mod ?? null\n } catch {\n continue\n }\n }\n\n // Fallback: the CLI snapshot of a transpiled `kick.config.ts`. We\n // can't transpile TS synchronously on the asset hot path, so the CLI\n // (`loadKickConfig`) mirrors the JSON-serialisable `assetMap` +\n // `build.outDir` into `.kickjs/kick.config.json` whenever it loads a\n // `.ts`/`.js` config. This is what makes `assets.x.y()` resolve in\n // dev for `.ts`-config projects before the first production build.\n const snapshotPath = join(cwd, '.kickjs', 'kick.config.json')\n if (existsSync(snapshotPath)) {\n try {\n return JSON.parse(readFileSync(snapshotPath, 'utf-8')) as AssetMapConfigShape\n } catch {\n return null\n }\n }\n return null\n}\n\n/**\n * Keys we MUST return `undefined` for: returning anything truthy\n * here causes Promise resolution to treat the Proxy as a thenable\n * and `await` it (resolving to whatever `then.call(...)` produces).\n * Two-element list deliberately — every other coercion-related key\n * goes through {@link PASSTHROUGH_KEYS} below so `String(assets)`,\n * template literals, and `util.inspect` keep working.\n */\nconst PROMISE_DETECTION_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>(['then'])\n\n/**\n * Keys we let fall through to the proxy target (a function-shaped\n * object). The default `Object.prototype` implementations of\n * `toString` / `valueOf` etc. are correct for these — returning\n * `undefined` here would break `String(assets)` and template\n * literals (`${assets}`) because `Symbol.toPrimitive` falling through\n * to a missing `toString` throws. Adopters that hit `assets.toString`\n * get `'function () {}'` back, which is fine — the contract is\n * \"asset access returns paths\"; everything else is incidental.\n */\nconst PASSTHROUGH_KEYS: ReadonlySet<string | symbol> = new Set<string | symbol>([\n 'toString',\n 'valueOf',\n 'constructor',\n 'prototype',\n 'name',\n 'length',\n 'asymmetricMatch',\n 'nodeType',\n Symbol.toPrimitive,\n Symbol.toStringTag,\n Symbol.iterator,\n Symbol.asyncIterator,\n])\n\nfunction createNamespaceProxy(parts: readonly string[]): unknown {\n const target = function () {} as unknown as Record<string, unknown>\n return new Proxy(target, {\n apply() {\n if (parts.length < 2) {\n throw new UnknownAssetError(parts[0] ?? '', '')\n }\n const [namespace, ...rest] = parts\n return resolveAsset(namespace, rest.join('/'))\n },\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([...parts, prop])\n },\n })\n}\n\nfunction createAssetProxy(): KickAssets {\n return new Proxy({} as Record<string, unknown>, {\n get(t, prop) {\n if (PROMISE_DETECTION_KEYS.has(prop)) return undefined\n if (PASSTHROUGH_KEYS.has(prop)) return Reflect.get(t, prop)\n if (typeof prop === 'symbol') return undefined\n return createNamespaceProxy([prop])\n },\n }) as unknown as KickAssets\n}\n\n/**\n * Variant A — the ambient Proxy. Default accessor for static call\n * sites where the namespace + key are literals known at compile time.\n *\n * @example\n * ```ts\n * import { assets } from '@forinda/kickjs'\n * const path = assets.mails.welcome()\n * ```\n */\nexport const assets: KickAssets = createAssetProxy()\n\n/**\n * Variant B — the hook accessor. Returns the same Proxy as `assets`;\n * factory shape exists for testability + DI. Mock via\n * `vi.mock('@forinda/kickjs', () => ({ useAssets: () => fakeAssets }))`.\n *\n * @example\n * ```ts\n * import { useAssets } from '@forinda/kickjs'\n * class MailService {\n * private assets = useAssets()\n * send(name: string) {\n * return ejs.renderFile(this.assets.mails[name](), {})\n * }\n * }\n * ```\n */\nexport function useAssets(): KickAssets {\n return assets\n}\n\n// Install this module's resolver into the container's asset slot — the\n// container deliberately avoids importing this file (edge purity; see\n// asset-resolver-slot.ts). Runs at module scope so any node app that loads\n// the asset manager gets full @Asset injection.\nimport { _setAssetResolver } from './asset-resolver-slot'\n_setAssetResolver(resolveAsset)\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"csrf-7nUXGTuz.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
+ {"version":3,"file":"csrf-7nUXGTuz.d.mts","names":[],"sources":["../src/http/middleware/csrf.ts"],"mappings":";;;;UAIiB,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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @forinda/kickjs v6.2.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ function randomHex(bytes){let buf=crypto.getRandomValues(new Uint8Array(bytes));return Array.from(buf,b=>b.toString(16).padStart(2,`0`)).join(``)}function csrf(options={}){let cookieName=options.cookie??`_csrf`,headerName=options.header??`x-csrf-token`,protectedMethods=new Set((options.methods??[`POST`,`PUT`,`PATCH`,`DELETE`]).map(m=>m.toUpperCase())),ignorePaths=new Set(options.ignorePaths??[]),tokenLength=options.tokenLength??32,cookieOpts={httpOnly:!0,sameSite:`strict`,secure:process.env.NODE_ENV===`production`,path:`/`,...options.cookieOptions};return(req,res,next)=>{let token=(req.cookies||{})[cookieName];if(token||(token=randomHex(tokenLength),res.cookie(cookieName,token,cookieOpts)),!protectedMethods.has(req.method.toUpperCase())||ignorePaths.has(req.path))return next();let headerToken=req.headers[headerName];if(!headerToken||headerToken!==token)return res.status(403).json({message:`CSRF token mismatch`});next()}}export{randomHex as n,csrf as t};
12
+ //# sourceMappingURL=csrf-CrChcN0K.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csrf-CrChcN0K.mjs","names":[],"sources":["../src/core/web-crypto.ts","../src/http/middleware/csrf.ts"],"sourcesContent":["/**\n * Web Crypto helpers — portable across node/bun/deno/workers via\n * `globalThis.crypto`. The Web Crypto migration lives here so the request\n * path never imports `node:crypto` (edge-runtime portability, see\n * `web-standards-edge-design.md` P0).\n */\n\n/** Random hex string — replacement for `randomBytes(n).toString('hex')`. */\nexport function randomHex(bytes: number): string {\n const buf = crypto.getRandomValues(new Uint8Array(bytes))\n return Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n","import type { Request, Response, NextFunction } from 'express'\n\nimport { randomHex } from '../../core/web-crypto'\n\nexport interface CsrfOptions {\n /** Cookie name for the CSRF token (default: '_csrf') */\n cookie?: string\n /** Header name to check for the token (default: 'x-csrf-token') */\n header?: string\n /** HTTP methods that require CSRF validation (default: POST, PUT, PATCH, DELETE) */\n methods?: string[]\n /** Paths to exclude from CSRF checks (e.g. webhooks) */\n ignorePaths?: string[]\n /** Token byte length before hex encoding (default: 32 = 64 hex chars) */\n tokenLength?: number\n /** Cookie options */\n cookieOptions?: {\n httpOnly?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n secure?: boolean\n path?: string\n }\n}\n\n/**\n * Double-submit cookie CSRF protection middleware.\n *\n * On every request, sets a CSRF token cookie. For state-changing methods\n * (POST, PUT, PATCH, DELETE), validates that the request header matches\n * the cookie value.\n *\n * @example\n * ```ts\n * import { csrf } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middleware: [\n * cookieParser(),\n * csrf(),\n * // ... other middleware\n * ],\n * })\n * ```\n *\n * Client usage:\n * 1. Read the `_csrf` cookie value\n * 2. Send it in the `x-csrf-token` header on every mutating request\n */\nexport function csrf(options: CsrfOptions = {}) {\n const cookieName = options.cookie ?? '_csrf'\n const headerName = options.header ?? 'x-csrf-token'\n const protectedMethods = new Set(\n (options.methods ?? ['POST', 'PUT', 'PATCH', 'DELETE']).map((m) => m.toUpperCase()),\n )\n const ignorePaths = new Set(options.ignorePaths ?? [])\n const tokenLength = options.tokenLength ?? 32\n const cookieOpts = {\n httpOnly: true,\n sameSite: 'strict' as const,\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n ...options.cookieOptions,\n }\n\n return (req: Request, res: Response, next: NextFunction) => {\n // Generate or reuse CSRF token\n const cookies = (req as any).cookies || {}\n let token = cookies[cookieName]\n\n if (!token) {\n token = randomHex(tokenLength)\n res.cookie(cookieName, token, cookieOpts)\n }\n\n // Skip validation for safe methods and ignored paths\n if (!protectedMethods.has(req.method.toUpperCase())) {\n return next()\n }\n\n if (ignorePaths.has(req.path)) {\n return next()\n }\n\n // Validate: header token must match cookie token\n const headerToken = req.headers[headerName] as string | undefined\n\n if (!headerToken || headerToken !== token) {\n return res.status(403).json({\n message: 'CSRF token mismatch',\n })\n }\n\n next()\n }\n}\n"],"mappings":";;;;;;;;;;AAQA,SAAgB,UAAU,MAAuB,CAC/C,IAAM,IAAM,OAAO,gBAAgB,IAAI,WAAW,KAAK,CAAC,EACxD,OAAO,MAAM,KAAK,IAAM,GAAM,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CACxE,CCsCA,SAAgB,KAAK,QAAuB,CAAC,EAAG,CAC9C,IAAM,WAAa,QAAQ,QAAU,QAC/B,WAAa,QAAQ,QAAU,eAC/B,iBAAmB,IAAI,KAC1B,QAAQ,SAAW,CAAC,OAAQ,MAAO,QAAS,QAAQ,EAAA,CAAG,IAAK,GAAM,EAAE,YAAY,CAAC,CACpF,EACM,YAAc,IAAI,IAAI,QAAQ,aAAe,CAAC,CAAC,EAC/C,YAAc,QAAQ,aAAe,GACrC,WAAa,CACjB,SAAU,GACV,SAAU,SACV,OAAQ,QAAQ,IAAI,WAAa,aACjC,KAAM,IACN,GAAG,QAAQ,aACb,EAEA,OAAQ,IAAc,IAAe,OAAuB,CAG1D,IAAI,OADa,IAAY,SAAW,CAAC,EAAA,CACrB,YAYpB,GAVK,QACH,MAAQ,UAAU,WAAW,EAC7B,IAAI,OAAO,WAAY,MAAO,UAAU,GAItC,CAAC,iBAAiB,IAAI,IAAI,OAAO,YAAY,CAAC,GAI9C,YAAY,IAAI,IAAI,IAAI,EAC1B,OAAO,KAAK,EAId,IAAM,YAAc,IAAI,QAAQ,YAEhC,GAAI,CAAC,aAAe,cAAgB,MAClC,OAAO,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK,CAC1B,QAAS,qBACX,CAAC,EAGH,KAAK,CACP,CACF"}