@forinda/kickjs 8.5.0 → 8.7.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 (165) hide show
  1. package/dist/{adapter-989ssCBx.d.mts → adapter-DK8WDonT.d.mts} +138 -12
  2. package/dist/adapter-DK8WDonT.d.mts.map +1 -0
  3. package/dist/{app-module-OQsddkor.d.mts → app-module-DCA65GZZ.d.mts} +2 -2
  4. package/dist/{app-module-OQsddkor.d.mts.map → app-module-DCA65GZZ.d.mts.map} +1 -1
  5. package/dist/{application-BRvykQ6h.d.mts → application-B6Gp1nqK.d.mts} +113 -16
  6. package/dist/application-B6Gp1nqK.d.mts.map +1 -0
  7. package/dist/application-D2scwBz-.mjs +12 -0
  8. package/dist/application-D2scwBz-.mjs.map +1 -0
  9. package/dist/bootstrap-Dds2PVAn.mjs +12 -0
  10. package/dist/bootstrap-Dds2PVAn.mjs.map +1 -0
  11. package/dist/cache-C0qL0NSD.d.mts.map +1 -1
  12. package/dist/{client-ip-CW7HwSOm.mjs → client-ip-BuY7_pDD.mjs} +2 -2
  13. package/dist/{client-ip-CW7HwSOm.mjs.map → client-ip-BuY7_pDD.mjs.map} +1 -1
  14. package/dist/config/index.d.mts +1 -1
  15. package/dist/config/index.mjs +2 -2
  16. package/dist/{config-Cajrc3qr.mjs → config-DBebCpg_.mjs} +3 -3
  17. package/dist/{config-Cajrc3qr.mjs.map → config-DBebCpg_.mjs.map} +1 -1
  18. package/dist/container-Cqow_bOU.mjs +132 -0
  19. package/dist/container-Cqow_bOU.mjs.map +1 -0
  20. package/dist/context-CHuuc9r6.mjs +12 -0
  21. package/dist/context-CHuuc9r6.mjs.map +1 -0
  22. package/dist/{context-errors-Cj2_aDaj.mjs → context-errors-VduncZFQ.mjs} +2 -2
  23. package/dist/{context-errors-Cj2_aDaj.mjs.map → context-errors-VduncZFQ.mjs.map} +1 -1
  24. package/dist/{contributor-runner-DQCZLpvk.mjs → contributor-runner-BoVekHyj.mjs} +3 -3
  25. package/dist/{contributor-runner-DQCZLpvk.mjs.map → contributor-runner-BoVekHyj.mjs.map} +1 -1
  26. package/dist/core/adapter.d.mts +2 -2
  27. package/dist/core/adapter.mjs +1 -1
  28. package/dist/core/app-module.d.mts +1 -1
  29. package/dist/core/app-module.mjs +1 -1
  30. package/dist/core/cache.mjs +2 -2
  31. package/dist/core/cache.mjs.map +1 -1
  32. package/dist/core/container.mjs +2 -2
  33. package/dist/core/cron.d.mts +2 -2
  34. package/dist/core/cron.mjs +2 -2
  35. package/dist/core/cron.mjs.map +1 -1
  36. package/dist/core/decorators.d.mts +1 -1
  37. package/dist/core/decorators.mjs +2 -2
  38. package/dist/core/decorators.mjs.map +1 -1
  39. package/dist/core/errors.mjs +1 -1
  40. package/dist/core/interfaces.mjs +1 -1
  41. package/dist/core/logger.mjs +1 -1
  42. package/dist/core/path.mjs +1 -1
  43. package/dist/core/plugin.d.mts +1 -1
  44. package/dist/core/plugin.mjs +1 -1
  45. package/dist/core/reactivity.mjs +1 -1
  46. package/dist/{core-DZmGThrF.mjs → core-Chc61ahe.mjs} +3 -3
  47. package/dist/{core-DZmGThrF.mjs.map → core-Chc61ahe.mjs.map} +1 -1
  48. package/dist/cron-BTGytNg-.d.mts +122 -0
  49. package/dist/cron-BTGytNg-.d.mts.map +1 -0
  50. package/dist/{csrf-DDwUOF45.d.mts → csrf-COFjRrUz.d.mts} +3 -3
  51. package/dist/{csrf-DDwUOF45.d.mts.map → csrf-COFjRrUz.d.mts.map} +1 -1
  52. package/dist/{csrf-DPPIM72I.mjs → csrf-D-8W1iv7.mjs} +3 -3
  53. package/dist/csrf-D-8W1iv7.mjs.map +1 -0
  54. package/dist/{decorate-DHyinlPY.mjs → decorate-D9n58F0b.mjs} +1 -1
  55. package/dist/{decorators-mg0qJ6Qc.d.mts → decorators-DOcWsXci.d.mts} +2 -2
  56. package/dist/{decorators-mg0qJ6Qc.d.mts.map → decorators-DOcWsXci.d.mts.map} +1 -1
  57. package/dist/{define-adapter-0asM4v0B.mjs → define-adapter-BJunWqi0.mjs} +2 -2
  58. package/dist/{define-adapter-0asM4v0B.mjs.map → define-adapter-BJunWqi0.mjs.map} +1 -1
  59. package/dist/{disposables-BYYy0y_z.mjs → disposables-BFIEp5VA.mjs} +2 -2
  60. package/dist/{disposables-BYYy0y_z.mjs.map → disposables-BFIEp5VA.mjs.map} +1 -1
  61. package/dist/{env-DWP_2owJ.mjs → env-8BLpzy-M.mjs} +3 -3
  62. package/dist/{env-DWP_2owJ.mjs.map → env-8BLpzy-M.mjs.map} +1 -1
  63. package/dist/{error-handler-BNpeUs-0.d.mts → error-handler-DxF2EogM.d.mts} +2 -2
  64. package/dist/{error-handler-BNpeUs-0.d.mts.map → error-handler-DxF2EogM.d.mts.map} +1 -1
  65. package/dist/{handler-B4MQmJR5.mjs → handler-MSvgGVk0.mjs} +3 -3
  66. package/dist/handler-MSvgGVk0.mjs.map +1 -0
  67. package/dist/http/application.d.mts +2 -2
  68. package/dist/http/application.mjs +2 -2
  69. package/dist/http/bootstrap.d.mts +1 -1
  70. package/dist/http/bootstrap.mjs +2 -2
  71. package/dist/http/context.d.mts +2 -2
  72. package/dist/http/context.mjs +2 -2
  73. package/dist/http/middleware/cors.mjs +1 -1
  74. package/dist/http/middleware/csrf.d.mts +1 -1
  75. package/dist/http/middleware/csrf.mjs +2 -2
  76. package/dist/http/middleware/error-handler.d.mts +1 -1
  77. package/dist/http/middleware/error-handler.mjs +2 -2
  78. package/dist/http/middleware/helmet.mjs +1 -1
  79. package/dist/http/middleware/rate-limit.d.mts +1 -1
  80. package/dist/http/middleware/rate-limit.mjs +2 -2
  81. package/dist/http/middleware/request-id.mjs +1 -1
  82. package/dist/http/middleware/request-logger.mjs +2 -2
  83. package/dist/http/middleware/session.mjs +2 -2
  84. package/dist/http/middleware/spa.d.mts +1 -1
  85. package/dist/http/middleware/spa.mjs +2 -2
  86. package/dist/http/middleware/upload.d.mts +1 -1
  87. package/dist/http/middleware/upload.mjs +2 -2
  88. package/dist/http/middleware/validate.mjs +1 -1
  89. package/dist/http/middleware/views.d.mts +1 -1
  90. package/dist/http/middleware/views.mjs +2 -2
  91. package/dist/http/query/index.mjs +2 -2
  92. package/dist/http/router-builder.d.mts +1 -1
  93. package/dist/http/router-builder.mjs +2 -2
  94. package/dist/http/runtimes/fastify.d.mts +1 -1
  95. package/dist/http/runtimes/fastify.d.mts.map +1 -1
  96. package/dist/http/runtimes/fastify.mjs +3 -3
  97. package/dist/http/runtimes/fastify.mjs.map +1 -1
  98. package/dist/http/runtimes/h3-web.d.mts +1 -1
  99. package/dist/http/runtimes/h3-web.d.mts.map +1 -1
  100. package/dist/http/runtimes/h3-web.mjs +2 -2
  101. package/dist/http/runtimes/h3-web.mjs.map +1 -1
  102. package/dist/http/runtimes/h3.d.mts +1 -6
  103. package/dist/http/runtimes/h3.d.mts.map +1 -1
  104. package/dist/http/runtimes/h3.mjs +2 -2
  105. package/dist/http/runtimes/h3.mjs.map +1 -1
  106. package/dist/{index-Btz1W0X5.d.mts → index-ClP0EE9p.d.mts} +2 -2
  107. package/dist/{index-Btz1W0X5.d.mts.map → index-ClP0EE9p.d.mts.map} +1 -1
  108. package/dist/index.d.mts +14 -14
  109. package/dist/index.mjs +2 -2
  110. package/dist/index.mjs.map +1 -1
  111. package/dist/{kv-stores-Dijnf9sF.d.mts → kv-stores-B-1WZ2Fh.d.mts} +36 -6
  112. package/dist/kv-stores-B-1WZ2Fh.d.mts.map +1 -0
  113. package/dist/{kv-stores-D2MKbvcu.mjs → kv-stores-BDcvhJBw.mjs} +3 -3
  114. package/dist/{kv-stores-D2MKbvcu.mjs.map → kv-stores-BDcvhJBw.mjs.map} +1 -1
  115. package/dist/{metadata-Ceheg5tj.mjs → metadata-BClL-Eb0.mjs} +2 -2
  116. package/dist/{metadata-Ceheg5tj.mjs.map → metadata-BClL-Eb0.mjs.map} +1 -1
  117. package/dist/{module-registry-RHo93bJa.mjs → module-registry-kVXS3peW.mjs} +2 -2
  118. package/dist/{module-registry-RHo93bJa.mjs.map → module-registry-kVXS3peW.mjs.map} +1 -1
  119. package/dist/observers-C5Jid4FV.mjs +12 -0
  120. package/dist/observers-C5Jid4FV.mjs.map +1 -0
  121. package/dist/{query-CWwMw9E6.mjs → query-1Q69dPhY.mjs} +2 -2
  122. package/dist/{query-CWwMw9E6.mjs.map → query-1Q69dPhY.mjs.map} +1 -1
  123. package/dist/{rate-limit-D5FHbYKG.d.mts → rate-limit-Bwed3jOV.d.mts} +2 -2
  124. package/dist/{rate-limit-D5FHbYKG.d.mts.map → rate-limit-Bwed3jOV.d.mts.map} +1 -1
  125. package/dist/{respond-CI4CCFG4.mjs → respond-DBRvpEI5.mjs} +2 -2
  126. package/dist/{respond-CI4CCFG4.mjs.map → respond-DBRvpEI5.mjs.map} +1 -1
  127. package/dist/{route-flag-BMlwj3Nk.d.mts → route-flag-B1lQht6v.d.mts} +14 -2
  128. package/dist/{route-flag-BMlwj3Nk.d.mts.map → route-flag-B1lQht6v.d.mts.map} +1 -1
  129. package/dist/{route-flag-B7t2wGJ2.mjs → route-flag-BCBTtP_4.mjs} +3 -3
  130. package/dist/route-flag-BCBTtP_4.mjs.map +1 -0
  131. package/dist/{route-policy-MQkq3QEO.mjs → route-policy-FeJHstvF.mjs} +2 -2
  132. package/dist/route-policy-FeJHstvF.mjs.map +1 -0
  133. package/dist/{router-builder-CK7_6DLH.mjs → router-builder-Brup4e82.mjs} +3 -3
  134. package/dist/{router-builder-CK7_6DLH.mjs.map → router-builder-Brup4e82.mjs.map} +1 -1
  135. package/dist/{router-builder-DPrRk4Pv.d.mts → router-builder-CUs9Gq4X.d.mts} +4 -4
  136. package/dist/{router-builder-DPrRk4Pv.d.mts.map → router-builder-CUs9Gq4X.d.mts.map} +1 -1
  137. package/dist/{runtime-B3V_u8FS.mjs → runtime-CV0SS-X5.mjs} +3 -3
  138. package/dist/runtime-CV0SS-X5.mjs.map +1 -0
  139. package/dist/{upload-DwGoAQ6R.d.mts → upload-BukLMawc.d.mts} +3 -3
  140. package/dist/{upload-DwGoAQ6R.d.mts.map → upload-BukLMawc.d.mts.map} +1 -1
  141. package/dist/{upload-config-C49VFeoh.mjs → upload-config-BBp5QfWQ.mjs} +2 -2
  142. package/dist/{upload-config-C49VFeoh.mjs.map → upload-config-BBp5QfWQ.mjs.map} +1 -1
  143. package/dist/web.d.mts +23 -8
  144. package/dist/web.d.mts.map +1 -1
  145. package/dist/web.mjs +2 -2
  146. package/dist/web.mjs.map +1 -1
  147. package/package.json +17 -12
  148. package/dist/adapter-989ssCBx.d.mts.map +0 -1
  149. package/dist/application-BFKE8ac5.mjs +0 -12
  150. package/dist/application-BFKE8ac5.mjs.map +0 -1
  151. package/dist/application-BRvykQ6h.d.mts.map +0 -1
  152. package/dist/bootstrap-CPRgpW3i.mjs +0 -12
  153. package/dist/bootstrap-CPRgpW3i.mjs.map +0 -1
  154. package/dist/container-Cj2hqlfm.mjs +0 -117
  155. package/dist/container-Cj2hqlfm.mjs.map +0 -1
  156. package/dist/context-B7N5oE7S.mjs +0 -12
  157. package/dist/context-B7N5oE7S.mjs.map +0 -1
  158. package/dist/cron-DOOtTY4q.d.mts +0 -30
  159. package/dist/cron-DOOtTY4q.d.mts.map +0 -1
  160. package/dist/csrf-DPPIM72I.mjs.map +0 -1
  161. package/dist/handler-B4MQmJR5.mjs.map +0 -1
  162. package/dist/kv-stores-Dijnf9sF.d.mts.map +0 -1
  163. package/dist/route-flag-B7t2wGJ2.mjs.map +0 -1
  164. package/dist/route-policy-MQkq3QEO.mjs.map +0 -1
  165. package/dist/runtime-B3V_u8FS.mjs.map +0 -1
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @forinda/kickjs v8.7.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{METADATA}from"./core/interfaces.mjs";import{createLogger}from"./core/logger.mjs";import{i as getMetaRecord,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,r as getMetaMap,t as getClassMeta}from"./metadata-BClL-Eb0.mjs";import"reflect-metadata";let impl=null;function _setAssetResolver(fn){impl=fn}function resolveAssetViaSlot(namespace,key){if(!impl)throw Error(`@Asset('${namespace}/${key}'): the asset manager is not loaded. Import it (any import from '@forinda/kickjs' loads it) — note @Asset reads from the filesystem and is not supported on edge runtimes.`);return impl(namespace,key)}var KickError=class extends Error{code;summary;cause;fix;docsUrl;context;constructor(init){super(formatKickError(init,{color:!1})),this.name=`KickError`,this.code=init.code,this.summary=init.summary,init.cause!==void 0&&(this.cause=init.cause),init.fix!==void 0&&(this.fix=init.fix),init.docsUrl!==void 0&&(this.docsUrl=init.docsUrl),init.context!==void 0&&(this.context=init.context)}};function ansiEnabled(){return process.env.NO_COLOR?!1:process.env.FORCE_COLOR?!0:process.stderr?.isTTY===!0}function paint(text,code,enabled){return enabled?`[${code}m${text}`:text}const c={red:(s,enabled)=>paint(s,`31`,enabled),bold:(s,enabled)=>paint(s,`1`,enabled),dim:(s,enabled)=>paint(s,`2`,enabled),cyan:(s,enabled)=>paint(s,`36`,enabled),blue:(s,enabled)=>paint(s,`34`,enabled),underline:(s,enabled)=>paint(s,`4`,enabled)};function formatKickError(err,options={}){let color=options.color??ansiEnabled(),lines=[],code=c.bold(c.red(err.code,color),color);if(lines.push(`${code}: ${err.summary}`),err.cause){lines.push(``,` ${c.bold(c.dim(`Cause:`,color),color)}`);for(let line of err.cause.split(`
12
+ `))lines.push(` ${highlightInline(line,color)}`)}if(err.fix){lines.push(``,` ${c.bold(c.dim(`Fix:`,color),color)}`);for(let line of err.fix.split(`
13
+ `))lines.push(` ${highlightInline(line,color)}`)}return err.docsUrl&&(lines.push(``,` ${c.bold(c.dim(`Docs:`,color),color)}`),lines.push(` ${c.underline(c.blue(err.docsUrl,color),color)}`)),lines.join(`
14
+ `)}function highlightInline(line,color){return color?line.replace(/`([^`]+)`/g,(_,inner)=>`\`${c.cyan(inner,!0)}\``):line}const DOCS_BASE=`https://kickjs.app`;function noProviderError(tokenName){return new KickError({code:`KICK001`,summary:`No provider for ${tokenName}`,cause:`\`${tokenName}\` was requested from the DI container but no binding is registered.
15
+ This usually means one of:
16
+ • The class is decorated with @Service() / @Repository() / @Controller(),
17
+ but its enclosing module isn't passed to bootstrap({ modules: [...] }).
18
+ • The class isn't decorated at all (decorators register the binding).
19
+ • You're injecting a token (created with createToken()) that nothing
20
+ provides — add a Container.register(TOKEN, ...) call or a module that
21
+ binds it.`,fix:`If \`${tokenName}\` lives in a module, add the module to bootstrap:
22
+
23
+ bootstrap({
24
+ modules: [
25
+ UsersModule, // add this
26
+ OtherModule,
27
+ ],
28
+ })
29
+
30
+ If it's a custom token, register it explicitly:
31
+
32
+ const TENANT_REPO = createToken<TenantRepo>('TENANT_REPO')
33
+ Container.getInstance().register(TENANT_REPO, { useClass: PrismaTenantRepo })`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#registering-services`,context:{token:tokenName}})}function requestScopeMiddlewareMissingError(tokenName){return new KickError({code:`KICK002`,summary:`Cannot resolve REQUEST-scoped "${tokenName}" — request scope middleware not mounted`,cause:`\`${tokenName}\` has \`scope: Scope.REQUEST\` but no AsyncLocalStorage frame is wired.
34
+ The framework's request-scope middleware sets up that frame; without it,
35
+ REQUEST-scoped resolutions have nowhere to cache the per-request instance.`,fix:`Add \`requestScopeMiddleware()\` to your global middleware pipeline,
36
+ or let the framework auto-mount it by using \`contextStore: 'auto'\`
37
+ (the default — only check this if you set it to 'manual' explicitly):
38
+
39
+ bootstrap({
40
+ modules,
41
+ // contextStore: 'auto', // default; no need to set
42
+ })
43
+
44
+ If you set \`contextStore: 'manual'\` deliberately (rare), you own the
45
+ ALS wrapping — call \`requestStore.run({...}, () => ...)\` around the
46
+ code that resolves the binding.`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#scopes`,context:{token:tokenName}})}function requestScopeOutsideRequestError(tokenName){return new KickError({code:`KICK003`,summary:`Cannot resolve REQUEST-scoped "${tokenName}" outside an HTTP request`,cause:`\`${tokenName}\` has \`scope: Scope.REQUEST\` and was resolved outside
47
+ any HTTP request context. The request-scope middleware is wired, but
48
+ the call happened before a request started (e.g. in a top-level
49
+ import, a constructor, a cron job, a test without setup).`,fix:`If the caller is request-scoped (controller, request-scoped service),
50
+ make sure the call happens inside the request lifecycle, not at module
51
+ load time.
52
+
53
+ In tests, wrap setup in an ALS frame manually:
54
+
55
+ import { requestStore } from '@forinda/kickjs'
56
+
57
+ requestStore.run(
58
+ { requestId: 'test', instances: new Map(), values: new Map() },
59
+ () => {
60
+ const svc = container.resolve(MyRequestScopedService)
61
+ // ...
62
+ },
63
+ )
64
+
65
+ If the caller is a SINGLETON, change \`${tokenName}\` to TRANSIENT or
66
+ SINGLETON scope, or inject a factory instead of the instance directly.`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#scopes`,context:{token:tokenName}})}function envValueMissingError(envKey){return new KickError({code:`KICK004`,summary:`@Value('${envKey}'): environment variable not set and no default provided`,cause:`\`@Value('${envKey}')\` resolved but the env var isn't present and the
67
+ decorator didn't declare a default. Possible reasons:
68
+ • The variable is genuinely missing from your environment / .env file.
69
+ • Your \`src/env.ts\` calls \`loadEnv(envSchema)\` but \`src/index.ts\`
70
+ doesn't import \`./env\` before \`bootstrap()\`. Without that import
71
+ the schema never runs and ConfigService stays empty — \`@Value\`
72
+ only finds the var via the \`process.env\` fallback.`,fix:`Either set the variable in your shell / .env file:
73
+
74
+ ${envKey}=...
75
+
76
+ Or add a default at the decorator:
77
+
78
+ @Value('${envKey}', { default: 'fallback' })
79
+ private value!: string
80
+
81
+ If you're using a schema, make sure \`src/env.ts\` is imported from
82
+ \`src/index.ts\` BEFORE bootstrap runs:
83
+
84
+ // src/index.ts
85
+ import 'reflect-metadata'
86
+ import './env' // ← this line is required
87
+ import { bootstrap } from '@forinda/kickjs'
88
+ // ...`,docsUrl:`${DOCS_BASE}/guide/configuration#wiring-the-schema-at-startup`,context:{envKey}})}function moduleRouteMissingControllerError(mountPath){return new KickError({code:`KICK005`,summary:`Module route at ${mountPath} requires either 'controller' or 'router'`,cause:`A module's \`routes()\` returned an entry for \`${mountPath}\` but didn't
89
+ specify how to build the router. The framework needs either a decorated
90
+ controller class (it calls \`buildRoutes()\` internally) or a hand-built
91
+ Express Router.`,fix:`Pass \`controller:\` for the common case:
92
+
93
+ routes() {
94
+ return {
95
+ path: '${mountPath}',
96
+ controller: UsersController,
97
+ }
98
+ }
99
+
100
+ Or pass \`router:\` for full control:
101
+
102
+ routes() {
103
+ return {
104
+ path: '${mountPath}',
105
+ router: myExpressRouter,
106
+ }
107
+ }`,docsUrl:`${DOCS_BASE}/guide/modules`,context:{mountPath}})}function duplicateRouteError(method,path,owner,prior){return new KickError({code:`KICK006`,summary:`Duplicate route: ${method} ${path} is registered twice`,cause:`\`${owner}\` registers \`${method} ${path}\`, but that verb + path is
108
+ already claimed by \`${prior}\`. Only one handler can win at dispatch
109
+ time, and which one depends on the engine — while \`kick typegen\` and
110
+ the typed client may describe the other. Param names are ignored when
111
+ comparing (\`/:id\` and \`/:taskId\` are the same route).`,fix:`Change one of the two paths, remove the duplicate handler, or mount
112
+ the second controller under a different module path / version:
113
+
114
+ routes() {
115
+ return { path: '/tasks', controller: TasksController, version: 2 }
116
+ }`,docsUrl:`${DOCS_BASE}/guide/modules`,context:{method,path,owner,prior}})}function http2UnsupportedRuntimeError(runtime){return new KickError({code:`KICK007`,summary:`The ${runtime} runtime cannot serve HTTP/2`,cause:`\`bootstrap({ server: { http2: true } })\` asks for an HTTP/2 server, but the
117
+ \`${runtime}\` runtime does not support Node's HTTP/2 compatibility layer —
118
+ requests would hang instead of being answered.`,fix:`Pick one:
119
+ • Switch to a runtime that serves HTTP/2:
120
+ bootstrap({ runtime: fastifyRuntime(), server: { tls, http2: true } })
121
+ (or h3Runtime()).
122
+ • Drop \`http2\` and keep HTTPS over HTTP/1.1: server: { tls }.
123
+ • Terminate HTTP/2 at your proxy / load balancer and serve HTTP/1.1 to it.`,docsUrl:`${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,context:{runtime}})}function http2RequiresTlsError(){return new KickError({code:`KICK008`,summary:`server.http2 requires server.tls`,cause:"`bootstrap({ server: { http2: true } })` was set without `tls`. Browsers only\nnegotiate HTTP/2 over TLS, and KickJS does not serve cleartext HTTP/2 (h2c).",fix:`Pass the certificate and key alongside http2:
124
+
125
+ server: {
126
+ tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },
127
+ http2: true,
128
+ }
129
+
130
+ Or remove \`http2\` if TLS is terminated in front of the app.`,docsUrl:`${DOCS_BASE}/guide/http-runtimes#https-and-http-2`})}function tlsCredentialsError(problem){return new KickError({code:`KICK009`,summary:`server.tls is not usable: ${problem}`,cause:`\`bootstrap({ server: { tls } })\` was given TLS options the server cannot use:
131
+ ${problem}. Node would start anyway and fail every TLS handshake.`,fix:"Give the server an identity:\n • `key` and `cert` together (PEM, matching each other), or\n • `pfx` (with `passphrase` if it is encrypted), or\n • `SNICallback` / `pskCallback` for per-host or PSK setups.\n\n server: { tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') } }",docsUrl:`${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,context:{problem}})}const INJECTION_TOKEN=Symbol.for(`@kickjs/InjectionToken`);function createToken(name){return Object.freeze({[INJECTION_TOKEN]:!0,name})}function isInjectionToken(value){return typeof value==`object`&&!!value&&value[INJECTION_TOKEN]===!0}const log=createLogger(`Container`);function createReg(fields){return{kind:`unknown`,resolveCount:0,postConstructStatus:`skipped`,dependencies:[],...fields}}function tokenName(token){return typeof token==`symbol`?token.toString():isInjectionToken(token)?token.name:token?.name||String(token)}function getPersistentStore(){return globalThis.__kickjs_persistent||(globalThis.__kickjs_persistent={factories:[],instances:[],resolvedInstances:new Map}),globalThis.__kickjs_persistent}var Container=class Container{static instance;registrations=new Map;resolving=new Set;changeListeners=new Set;pendingChanges=[];notifyTimer=null;static DEBOUNCE_MS=50;static _onReady=null;static _onReset=null;static _onCreate=null;static _envResolver=null;isGlobal=!1;static _requestStoreProvider=null;static getInstance(){return Container.instance||(Container.instance=new Container,Container.instance.isGlobal=!0),Container._onReady&&=(Container._onReady(Container.instance),null),Container.instance}static reset(){Container.instance=new Container,Container.instance.isGlobal=!0,Container._onReset?.(Container.instance);let store=getPersistentStore();for(let{token,factory,scope}of store.factories)if(!Container.instance.has(token)){let name=tokenName(token),existing=store.resolvedInstances.get(name);Container.instance.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existing,resolveCount:+!!existing,postConstructStatus:existing?`completed`:`skipped`}))}for(let{token,instance}of store.instances)Container.instance.has(token)||Container.instance.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1}))}static create(){let container=new Container;return Container._onCreate?.(container),container}register(token,target,scope=`singleton`){let kind=getClassMeta(METADATA.CLASS_KIND,target,`unknown`),reg=createReg({target,scope,kind,dependencies:this.extractDependencies(target)});this.registrations.set(token,reg),typeof token==`function`&&token.name&&this.registrations.set(`__hmr__${token.name}`,reg),this.emit(token,`registered`,kind)}registerFactory(token,factory,scope=`singleton`){let store=getPersistentStore(),name=tokenName(token),factoryUnchanged=(this.isGlobal?store.factories.find(r=>tokenName(r.token)===name):void 0)?.factory===factory,existingInstance=this.isGlobal&&factoryUnchanged?store.resolvedInstances.get(name):void 0;if(this.isGlobal&&!factoryUnchanged&&store.resolvedInstances.delete(name),this.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existingInstance,resolveCount:+!!existingInstance,postConstructStatus:existingInstance?`completed`:`skipped`})),this.isGlobal){let idx=store.factories.findIndex(r=>tokenName(r.token)===name);idx>=0?store.factories[idx]={token,factory,scope}:store.factories.push({token,factory,scope})}this.emit(name,`registered`,`factory`)}registerInstance(token,instance){let store=getPersistentStore(),name=tokenName(token);if(this.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1,firstResolvedAt:Date.now(),lastResolvedAt:Date.now()})),this.isGlobal){store.resolvedInstances.set(name,instance);let idx=store.instances.findIndex(r=>tokenName(r.token)===name);idx>=0?store.instances[idx]={token,instance}:store.instances.push({token,instance})}this.emit(name,`registered`,`instance`)}has(token){return this.registrations.has(token)}getRegistrations(){let entries=[];for(let[token,reg]of this.registrations)entries.push({token:tokenName(token),scope:reg.scope,kind:reg.kind,instantiated:reg.scope===`singleton`?reg.instance!==void 0:reg.resolveCount>0,resolveCount:reg.resolveCount,lastResolvedAt:reg.lastResolvedAt,firstResolvedAt:reg.firstResolvedAt,resolveDurationMs:reg.resolveDurationMs,postConstructStatus:reg.postConstructStatus,dependencies:reg.dependencies});return entries}resolve(token){let reg=this.registrations.get(token);if(!reg&&typeof token==`function`&&token.name&&(reg=this.registrations.get(`__hmr__${token.name}`)),!reg)throw noProviderError(tokenName(token));if(reg.scope===`singleton`&&reg.instance!==void 0)return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),reg.instance;if(reg.scope===`request`){if(!Container._requestStoreProvider)throw requestScopeMiddlewareMissingError(tokenName(token));let store=Container._requestStoreProvider();if(!store)throw requestScopeOutsideRequestError(tokenName(token));if(store.values.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),store.values.get(token);if(store.instances.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),store.instances.get(token);if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||(reg.firstResolvedAt=Date.now()),store.instances.set(token,instance),this.emit(token,`resolved`,reg.kind),instance}let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||(reg.firstResolvedAt=Date.now()),store.instances.set(token,instance),this.emit(token,`resolved`,reg.kind),instance}if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||(reg.firstResolvedAt=Date.now()),reg.scope===`singleton`&&(reg.instance=instance,this.isGlobal&&getPersistentStore().resolvedInstances.set(tokenName(token),instance)),this.emit(token,`resolved`,reg.kind),instance}if(this.resolving.has(token)){let chain=[...this.resolving].map(tokenName);throw chain.push(tokenName(token)),Error(`Circular dependency detected: ${chain.join(` -> `)}`)}this.resolving.add(token);try{let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||(reg.firstResolvedAt=Date.now()),reg.scope===`singleton`&&(reg.instance=instance),this.emit(token,`resolved`,reg.kind),instance}finally{this.resolving.delete(token)}}bootstrap(){}onChange(callback){return this.changeListeners.add(callback),()=>this.changeListeners.delete(callback)}invalidate(token){let name=tokenName(token),reg=this.registrations.get(token)??this.registrations.get(`__hmr__${name}`);if(reg&&!getPersistentStore().resolvedInstances.has(name)){reg.instance=void 0,reg.resolveCount=0,reg.postConstructStatus=`skipped`,this.emit(name,`invalidated`,reg.kind);for(let[depToken,depReg]of this.registrations)depReg.dependencies.includes(name)&&this.invalidate(depToken)}}emit(rawToken,event,kind){if(this.changeListeners.size===0)return;let token=tokenName(rawToken),existing=this.pendingChanges.find(p=>p.token===token&&p.event===event);existing?existing.timestamp=Date.now():this.pendingChanges.push({token,event,kind,timestamp:Date.now()}),this.notifyTimer&&clearTimeout(this.notifyTimer),this.notifyTimer=setTimeout(()=>{let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}},Container.DEBOUNCE_MS),this.notifyTimer.unref?.()}flushChanges(){this.notifyTimer&&=(clearTimeout(this.notifyTimer),null);let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}}buildPlan(reg){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,reg.target,[]),injectTokens=getMetaRecord(METADATA.INJECT,reg.target),maxInjectIndex=Object.keys(injectTokens).length>0?Math.max(...Object.keys(injectTokens).map(Number))+1:0,paramCount=Math.max(paramTypes.length,maxInjectIndex),ctorTokens=[];for(let index=0;index<paramCount;index++){let token=injectTokens[index]||paramTypes[index];if(!token)throw Error(`Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). Add @Inject(Token) to every constructor parameter.`);if(reg.scope===`singleton`){let depReg=this.registrations.get(token);if(depReg&&depReg.scope===`request`){let parent=tokenName(reg.target),dep=tokenName(token),fix=getClassMetaOrUndefined(METADATA.CLASS_KIND,reg.target)===`controller`?`@Controller() is always SINGLETON, so inject "${dep}" with @Autowired() instead of the constructor.`:`Give "${parent}" TRANSIENT or REQUEST scope (e.g. @Service({ scope: Scope.REQUEST })), or inject "${dep}" with @Autowired() instead of the constructor.`;throw Error(`Cannot inject REQUEST-scoped "${dep}" into SINGLETON "${parent}". Singletons outlive requests. ${fix}`)}}ctorTokens.push(token)}let autowiredProps=getMetaMap(METADATA.AUTOWIRED,reg.target.prototype),autowired=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,reg.target.prototype,prop);resolvedToken&&autowired.push([prop,resolvedToken])}let values=[...getMetaMap(METADATA.VALUE,reg.target.prototype).entries()],assetProps=getMetaMap(METADATA.ASSET,reg.target.prototype),assets=[];for(let[prop,config]of assetProps){let slashIdx=config.assetKey.indexOf(`/`);if(slashIdx===-1)throw Error(`@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`);assets.push([prop,{namespace:config.assetKey.slice(0,slashIdx),key:config.assetKey.slice(slashIdx+1)}])}return{ctorTokens,autowired,values,assets,postConstruct:getClassMetaOrUndefined(METADATA.POST_CONSTRUCT,reg.target.prototype)}}createInstance(reg){let plan=reg.plan??=this.buildPlan(reg),args=[];for(let token of plan.ctorTokens)args.push(this.resolve(token));let instance=new reg.target(...args);this.injectProperties(instance,plan);let postConstruct=plan.postConstruct;if(postConstruct&&typeof instance[postConstruct]==`function`)try{let result=instance[postConstruct]();result&&typeof result.then==`function`?(reg.postConstructStatus=`pending`,result.then(()=>{reg.postConstructStatus=`completed`}).catch(()=>{reg.postConstructStatus=`failed`}),log.warn(`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise but the container is synchronous. The async operation will not be awaited.`)):reg.postConstructStatus=`completed`}catch(err){reg.postConstructStatus=`failed`,log.error(err,`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`)}return instance}extractDependencies(target){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,target,[]),injectTokens=getMetaRecord(METADATA.INJECT,target),ctorDeps=paramTypes.map((type,index)=>tokenName(injectTokens[index]||type)),autowiredProps=getMetaMap(METADATA.AUTOWIRED,target.prototype),propDeps=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&propDeps.push(tokenName(resolvedToken))}return[...new Set([...ctorDeps,...propDeps])]}injectProperties(instance,plan){for(let[prop,resolvedToken]of plan.autowired)Object.defineProperty(instance,prop,{get:()=>{let value=this.resolve(resolvedToken);return this.registrations.get(resolvedToken)?.scope===`singleton`&&Object.defineProperty(instance,prop,{value,enumerable:!0,configurable:!0,writable:!1}),value},enumerable:!0,configurable:!0});for(let[prop,config]of plan.values)Object.defineProperty(instance,prop,{get(){if(Container._envResolver){let val=Container._envResolver(config.envKey);if(val!==void 0)return val}let val=process.env[config.envKey];if(val!==void 0)return val;if(config.defaultValue!==void 0)return config.defaultValue;throw envValueMissingError(config.envKey)},enumerable:!0,configurable:!0});for(let[prop,{namespace,key}]of plan.assets)Object.defineProperty(instance,prop,{get(){return resolveAssetViaSlot(namespace,key)},enumerable:!0,configurable:!0})}};export{isInjectionToken as a,http2UnsupportedRuntimeError as c,KickError as d,formatKickError as f,createToken as i,moduleRouteMissingControllerError as l,tokenName as n,duplicateRouteError as o,_setAssetResolver as p,INJECTION_TOKEN as r,http2RequiresTlsError as s,Container as t,tlsCredentialsError as u};
132
+ //# sourceMappingURL=container-Cqow_bOU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-Cqow_bOU.mjs","names":["resolveAsset"],"sources":["../src/core/asset-resolver-slot.ts","../src/core/kick-error.ts","../src/core/kick-errors.ts","../src/core/token.ts","../src/core/container.ts"],"sourcesContent":["/**\n * Indirection slot between the DI container's `@Asset` property injection\n * and the asset manager. The container must NOT import `./assets` directly —\n * its eager `node:fs`/`node:path` imports would poison the edge-safe\n * `@forinda/kickjs/web` entry graph (every graph includes the container).\n *\n * `./assets` registers the real resolver at module scope, so any node app\n * that imports the asset manager (the core barrel does) gets full `@Asset`\n * behavior; an edge bundle that never imports it gets a clear error on\n * first `@Asset` property access instead of a broken bundle.\n */\n\ntype AssetResolver = (namespace: string, key: string) => unknown\n\nlet impl: AssetResolver | null = null\n\n/** Called by `./assets` at module scope to install the real resolver. */\nexport function _setAssetResolver(fn: AssetResolver): void {\n impl = fn\n}\n\n/** Resolve an asset through the slot — throws when no asset manager is loaded. */\nexport function resolveAssetViaSlot(namespace: string, key: string): unknown {\n if (!impl) {\n throw new Error(\n `@Asset('${namespace}/${key}'): the asset manager is not loaded. ` +\n \"Import it (any import from '@forinda/kickjs' loads it) — note @Asset \" +\n 'reads from the filesystem and is not supported on edge runtimes.',\n )\n }\n return impl(namespace, key)\n}\n","/**\n * Structured framework error with code, cause, and actionable fix hint.\n *\n * The goal is \"the developer who hits this knows what to do next.\" A\n * cryptic `Error('No binding found for: UserService')` becomes a\n * multi-line explanation pointing at the likely cause, the exact code\n * change to apply, and a docs link.\n *\n * `.message` carries the full multi-line plain-text body so Node's\n * default `Error.toString()` and unhandled-exception printing still\n * surface the helpful version. Call {@link formatKickError} to render a\n * colorized version for terminals.\n *\n * Per-error factories live in `kick-errors.ts` — those are the public\n * entry points framework code uses. `KickError` is exported so adopters\n * can build their own structured errors using the same shape.\n */\nexport interface KickErrorInit {\n /** Short stable identifier (e.g. `KICK001`, `NO_PROVIDER`). */\n code: string\n /** One-line headline. */\n summary: string\n /** Multi-line explanation of why this happened. */\n cause?: string\n /** Multi-line actionable fix — show exact code if possible. */\n fix?: string\n /** Absolute URL to the docs page that covers this error. */\n docsUrl?: string\n /** Arbitrary structured fields for log consumers / debug tooling. */\n context?: Record<string, unknown>\n}\n\nexport class KickError extends Error {\n public readonly code: string\n public readonly summary: string\n public readonly cause?: string\n public readonly fix?: string\n public readonly docsUrl?: string\n public readonly context?: Record<string, unknown>\n\n constructor(init: KickErrorInit) {\n super(formatKickError(init, { color: false }))\n this.name = 'KickError'\n this.code = init.code\n this.summary = init.summary\n if (init.cause !== undefined) this.cause = init.cause\n if (init.fix !== undefined) this.fix = init.fix\n if (init.docsUrl !== undefined) this.docsUrl = init.docsUrl\n if (init.context !== undefined) this.context = init.context\n }\n}\n\n// ── ANSI colour helpers ────────────────────────────────────────────────\n\n/**\n * `true` when the runtime can render ANSI escape codes on stderr.\n * Honors `NO_COLOR` (https://no-color.org) and `FORCE_COLOR`.\n */\nfunction ansiEnabled(): boolean {\n if (process.env.NO_COLOR) return false\n if (process.env.FORCE_COLOR) return true\n // Node sets `isTTY` on the writable streams when stdout/stderr is a\n // terminal. KickError output goes to stderr conceptually (it's an\n // error), so we follow that stream's affordances.\n return process.stderr?.isTTY === true\n}\n\n// Use the Unicode-escape form (`\u001b`) rather than the hex-escape (`\\x1b`)\n// for the ANSI ESC byte — both produce the same character, but oxlint's\n// `no-control-character` rule flags the hex form as \"unexpected control\n// character in string literal\". Functionally identical, lint-clean.\nconst ESC = '\u001b['\nconst RESET = `${ESC}0m`\n\nfunction paint(text: string, code: string, enabled: boolean): string {\n return enabled ? `${ESC}${code}m${text}${RESET}` : text\n}\n\nconst c = {\n red: (s: string, enabled: boolean) => paint(s, '31', enabled),\n bold: (s: string, enabled: boolean) => paint(s, '1', enabled),\n dim: (s: string, enabled: boolean) => paint(s, '2', enabled),\n cyan: (s: string, enabled: boolean) => paint(s, '36', enabled),\n blue: (s: string, enabled: boolean) => paint(s, '34', enabled),\n underline: (s: string, enabled: boolean) => paint(s, '4', enabled),\n}\n\n// ── Formatter ─────────────────────────────────────────────────────────\n\nexport interface FormatKickErrorOptions {\n /**\n * Render ANSI colour codes. Defaults to TTY detection (`NO_COLOR` /\n * `FORCE_COLOR` env vars honoured). Pass `false` for log files,\n * structured loggers, or HTTP response bodies.\n */\n color?: boolean\n}\n\n/**\n * Render a {@link KickErrorInit} (or {@link KickError} instance) into a\n * multi-line human-readable string. Identical layout regardless of\n * colour — the colour flag only toggles ANSI codes.\n */\nexport function formatKickError(\n err: KickErrorInit | KickError,\n options: FormatKickErrorOptions = {},\n): string {\n const color = options.color ?? ansiEnabled()\n const lines: string[] = []\n\n // Headline: `KICK001: No provider for UserService`\n const code = c.bold(c.red(err.code, color), color)\n lines.push(`${code}: ${err.summary}`)\n\n if (err.cause) {\n lines.push('', ` ${c.bold(c.dim('Cause:', color), color)}`)\n for (const line of err.cause.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.fix) {\n lines.push('', ` ${c.bold(c.dim('Fix:', color), color)}`)\n for (const line of err.fix.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.docsUrl) {\n lines.push('', ` ${c.bold(c.dim('Docs:', color), color)}`)\n lines.push(` ${c.underline(c.blue(err.docsUrl, color), color)}`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Highlight inline `code spans` in a line of cause/fix text. Anything\n * between backticks gets cyan when colour is enabled; backticks stay\n * in the output so the plain-text rendering still distinguishes\n * literals.\n */\nfunction highlightInline(line: string, color: boolean): string {\n if (!color) return line\n return line.replace(/`([^`]+)`/g, (_, inner: string) => `\\`${c.cyan(inner, true)}\\``)\n}\n","/**\n * Catalog of framework errors with structured cause + fix hints.\n *\n * Each factory function returns a {@link KickError} with a stable\n * `code` (KICK001, KICK002, …), a human-readable summary, an\n * explanation of the likely cause, an actionable fix, and a docs URL.\n *\n * Adding a new error: pick the next free code, add the factory below,\n * call it from the framework site that used to throw a bare `Error`.\n * Keep the public function name descriptive — `noProviderError`,\n * `envValueMissingError` — so call sites grep cleanly.\n *\n * Codes are stable. Reordering or renumbering breaks adopters whose\n * tools key on `err.code`.\n */\nimport { KickError } from './kick-error'\n\nconst DOCS_BASE = 'https://kickjs.app'\n\n// ── DI container ──────────────────────────────────────────────────────\n\n/**\n * KICK001 — No provider registered for a token.\n *\n * Thrown by `Container.resolve()` when no binding can be found for the\n * requested token. The most common cause is forgetting to add the\n * enclosing module to `bootstrap({ modules })`.\n */\nexport function noProviderError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK001',\n summary: `No provider for ${tokenName}`,\n cause: `\\`${tokenName}\\` was requested from the DI container but no binding is registered.\nThis usually means one of:\n • The class is decorated with @Service() / @Repository() / @Controller(),\n but its enclosing module isn't passed to bootstrap({ modules: [...] }).\n • The class isn't decorated at all (decorators register the binding).\n • You're injecting a token (created with createToken()) that nothing\n provides — add a Container.register(TOKEN, ...) call or a module that\n binds it.`,\n fix: `If \\`${tokenName}\\` lives in a module, add the module to bootstrap:\n\n bootstrap({\n modules: [\n UsersModule, // add this\n OtherModule,\n ],\n })\n\nIf it's a custom token, register it explicitly:\n\n const TENANT_REPO = createToken<TenantRepo>('TENANT_REPO')\n Container.getInstance().register(TENANT_REPO, { useClass: PrismaTenantRepo })`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#registering-services`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK002 — REQUEST-scoped binding resolved without a request store\n * provider configured.\n */\nexport function requestScopeMiddlewareMissingError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK002',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" — request scope middleware not mounted`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` but no AsyncLocalStorage frame is wired.\nThe framework's request-scope middleware sets up that frame; without it,\nREQUEST-scoped resolutions have nowhere to cache the per-request instance.`,\n fix: `Add \\`requestScopeMiddleware()\\` to your global middleware pipeline,\nor let the framework auto-mount it by using \\`contextStore: 'auto'\\`\n(the default — only check this if you set it to 'manual' explicitly):\n\n bootstrap({\n modules,\n // contextStore: 'auto', // default; no need to set\n })\n\nIf you set \\`contextStore: 'manual'\\` deliberately (rare), you own the\nALS wrapping — call \\`requestStore.run({...}, () => ...)\\` around the\ncode that resolves the binding.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK003 — REQUEST-scoped binding resolved outside any HTTP request\n * context (no ALS store active).\n */\nexport function requestScopeOutsideRequestError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK003',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" outside an HTTP request`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` and was resolved outside\nany HTTP request context. The request-scope middleware is wired, but\nthe call happened before a request started (e.g. in a top-level\nimport, a constructor, a cron job, a test without setup).`,\n fix: `If the caller is request-scoped (controller, request-scoped service),\nmake sure the call happens inside the request lifecycle, not at module\nload time.\n\nIn tests, wrap setup in an ALS frame manually:\n\n import { requestStore } from '@forinda/kickjs'\n\n requestStore.run(\n { requestId: 'test', instances: new Map(), values: new Map() },\n () => {\n const svc = container.resolve(MyRequestScopedService)\n // ...\n },\n )\n\nIf the caller is a SINGLETON, change \\`${tokenName}\\` to TRANSIENT or\nSINGLETON scope, or inject a factory instead of the instance directly.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n// ── Config / env wiring ───────────────────────────────────────────────\n\n/**\n * KICK004 — `@Value('X')` resolved but the env var isn't set and no\n * default was provided.\n *\n * This is the canonical \"you forgot to wire env\" symptom — surfaces\n * when ConfigService can't find the key. The fix usually involves\n * either setting the env var or wiring `loadEnv(envSchema)` at startup.\n */\nexport function envValueMissingError(envKey: string): KickError {\n return new KickError({\n code: 'KICK004',\n summary: `@Value('${envKey}'): environment variable not set and no default provided`,\n cause: `\\`@Value('${envKey}')\\` resolved but the env var isn't present and the\ndecorator didn't declare a default. Possible reasons:\n • The variable is genuinely missing from your environment / .env file.\n • Your \\`src/env.ts\\` calls \\`loadEnv(envSchema)\\` but \\`src/index.ts\\`\n doesn't import \\`./env\\` before \\`bootstrap()\\`. Without that import\n the schema never runs and ConfigService stays empty — \\`@Value\\`\n only finds the var via the \\`process.env\\` fallback.`,\n fix: `Either set the variable in your shell / .env file:\n\n ${envKey}=...\n\nOr add a default at the decorator:\n\n @Value('${envKey}', { default: 'fallback' })\n private value!: string\n\nIf you're using a schema, make sure \\`src/env.ts\\` is imported from\n\\`src/index.ts\\` BEFORE bootstrap runs:\n\n // src/index.ts\n import 'reflect-metadata'\n import './env' // ← this line is required\n import { bootstrap } from '@forinda/kickjs'\n // ...`,\n docsUrl: `${DOCS_BASE}/guide/configuration#wiring-the-schema-at-startup`,\n context: { envKey },\n })\n}\n\n// ── Module setup ──────────────────────────────────────────────────────\n\n/**\n * KICK005 — A module's `routes()` return value declares a mount path\n * but provides neither a `controller` class nor a pre-built `router`.\n */\nexport function moduleRouteMissingControllerError(mountPath: string): KickError {\n return new KickError({\n code: 'KICK005',\n summary: `Module route at ${mountPath} requires either 'controller' or 'router'`,\n cause: `A module's \\`routes()\\` returned an entry for \\`${mountPath}\\` but didn't\nspecify how to build the router. The framework needs either a decorated\ncontroller class (it calls \\`buildRoutes()\\` internally) or a hand-built\nExpress Router.`,\n fix: `Pass \\`controller:\\` for the common case:\n\n routes() {\n return {\n path: '${mountPath}',\n controller: UsersController,\n }\n }\n\nOr pass \\`router:\\` for full control:\n\n routes() {\n return {\n path: '${mountPath}',\n router: myExpressRouter,\n }\n }`,\n docsUrl: `${DOCS_BASE}/guide/modules`,\n context: { mountPath },\n })\n}\n\n/**\n * KICK006 — Two handlers claim the same HTTP verb + mounted path. The\n * engine would silently dispatch one of them (Express/h3: first wins,\n * Fastify: its own throw), while typegen and the typed client may\n * describe the other — so this fails at boot instead.\n */\nexport function duplicateRouteError(\n method: string,\n path: string,\n owner: string,\n prior: string,\n): KickError {\n return new KickError({\n code: 'KICK006',\n summary: `Duplicate route: ${method} ${path} is registered twice`,\n cause: `\\`${owner}\\` registers \\`${method} ${path}\\`, but that verb + path is\nalready claimed by \\`${prior}\\`. Only one handler can win at dispatch\ntime, and which one depends on the engine — while \\`kick typegen\\` and\nthe typed client may describe the other. Param names are ignored when\ncomparing (\\`/:id\\` and \\`/:taskId\\` are the same route).`,\n fix: `Change one of the two paths, remove the duplicate handler, or mount\nthe second controller under a different module path / version:\n\n routes() {\n return { path: '/tasks', controller: TasksController, version: 2 }\n }`,\n docsUrl: `${DOCS_BASE}/guide/modules`,\n context: { method, path, owner, prior },\n })\n}\n\n// ── Server ────────────────────────────────────────────────────────────\n\n/**\n * KICK007 — `server.http2` on a runtime that cannot serve HTTP/2. Express 5\n * does not run on Node's HTTP/2 compatibility layer (requests hang and it\n * throws internally), so this fails at boot rather than on the first request.\n */\nexport function http2UnsupportedRuntimeError(runtime: string): KickError {\n return new KickError({\n code: 'KICK007',\n summary: `The ${runtime} runtime cannot serve HTTP/2`,\n cause: `\\`bootstrap({ server: { http2: true } })\\` asks for an HTTP/2 server, but the\n\\`${runtime}\\` runtime does not support Node's HTTP/2 compatibility layer —\nrequests would hang instead of being answered.`,\n fix: `Pick one:\n • Switch to a runtime that serves HTTP/2:\n bootstrap({ runtime: fastifyRuntime(), server: { tls, http2: true } })\n (or h3Runtime()).\n • Drop \\`http2\\` and keep HTTPS over HTTP/1.1: server: { tls }.\n • Terminate HTTP/2 at your proxy / load balancer and serve HTTP/1.1 to it.`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n context: { runtime },\n })\n}\n\n/**\n * KICK008 — `server.http2` without `server.tls`. Browsers only speak HTTP/2\n * over TLS; cleartext HTTP/2 (h2c) is not offered.\n */\nexport function http2RequiresTlsError(): KickError {\n return new KickError({\n code: 'KICK008',\n summary: 'server.http2 requires server.tls',\n cause: `\\`bootstrap({ server: { http2: true } })\\` was set without \\`tls\\`. Browsers only\nnegotiate HTTP/2 over TLS, and KickJS does not serve cleartext HTTP/2 (h2c).`,\n fix: `Pass the certificate and key alongside http2:\n\n server: {\n tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },\n http2: true,\n }\n\nOr remove \\`http2\\` if TLS is terminated in front of the app.`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n })\n}\n\n/**\n * KICK009 — `server.tls` cannot identify the server. Node starts an HTTPS /\n * HTTP/2 server without a certificate (or with a key that does not match it)\n * and then fails every TLS handshake, so this is checked at boot instead.\n */\nexport function tlsCredentialsError(problem: string): KickError {\n return new KickError({\n code: 'KICK009',\n summary: `server.tls is not usable: ${problem}`,\n cause: `\\`bootstrap({ server: { tls } })\\` was given TLS options the server cannot use:\n${problem}. Node would start anyway and fail every TLS handshake.`,\n fix: `Give the server an identity:\n • \\`key\\` and \\`cert\\` together (PEM, matching each other), or\n • \\`pfx\\` (with \\`passphrase\\` if it is encrypted), or\n • \\`SNICallback\\` / \\`pskCallback\\` for per-host or PSK setups.\n\n server: { tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') } }`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n context: { problem },\n })\n}\n","/**\n * Collision-safe injection tokens with phantom type parameters.\n *\n * `createToken<T>(name)` is the recommended way to declare any DI token\n * that isn't a class. Each call returns a unique frozen object — even if\n * two files write `createToken<X>('Logger')`, they produce two distinct\n * references that the container treats as different tokens. Collisions\n * become impossible by construction, not by convention.\n *\n * The phantom type parameter `T` lets `container.resolve(token)` infer\n * the correct return type without any codegen.\n *\n * ## Hardening layers\n *\n * KickJS has four DI token kinds, listed from safest to riskiest:\n *\n * 1. **Class identity** — `container.resolve(UserService)`. Reference\n * equality, never collides. Type-safe via `Constructor<T>` overload.\n * 2. **`createToken<T>(name)`** — frozen object identity, never collides.\n * Type-safe via the `InjectionToken<T>` overload.\n * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`\n * is interned and CAN collide. Discouraged.\n * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.\n * Reserved for legacy code and runtime-computed token names.\n *\n * Prefer (1) and (2). Use (4) only when you have to.\n *\n * @example\n * ```ts\n * // src/tokens.ts\n * export const DATABASE_URL = createToken<string>('config.database.url')\n * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')\n *\n * // bootstrap\n * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)\n *\n * // anywhere else\n * const url = container.resolve(DATABASE_URL) // typed as `string`\n * ```\n *\n * @module @forinda/kickjs/core/token\n */\n\n/**\n * Symbol marker stamped on every InjectionToken so the container can\n * detect them at runtime regardless of which package created them.\n *\n * Uses `Symbol.for()` so cross-package isolation (different copies of\n * `@forinda/kickjs` loaded under different module identities) still\n * shares the same marker.\n */\nexport const INJECTION_TOKEN: unique symbol = Symbol.for('@kickjs/InjectionToken') as never\n\n/**\n * A type-safe DI token. Each `createToken()` call returns a unique frozen\n * object identified by reference, not by the `name` string. Two\n * `createToken<X>('foo')` calls in different files produce two distinct\n * tokens that the container treats independently.\n *\n * The phantom `_type` parameter is never assigned at runtime — it exists\n * solely to thread the type through `container.resolve(token)`.\n */\nexport interface InjectionToken<T> {\n readonly [INJECTION_TOKEN]: true\n /** Descriptive name used in error messages, logs, and devtools */\n readonly name: string\n /** Phantom — never assigned at runtime */\n readonly _type?: T\n}\n\n/**\n * Create a collision-safe injection token.\n *\n * The returned token is a frozen object identified by reference, not by\n * the `name` string. The `name` is used only for error messages, logs,\n * and debugging.\n */\nexport function createToken<T>(name: string): InjectionToken<T> {\n return Object.freeze({\n [INJECTION_TOKEN]: true as const,\n name,\n })\n}\n\n/** Type guard — returns `true` if `value` is an InjectionToken */\nexport function isInjectionToken(value: unknown): value is InjectionToken<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[INJECTION_TOKEN] === true\n )\n}\n","import 'reflect-metadata'\nimport {\n Constructor,\n Scope,\n METADATA,\n type ClassKind,\n type PostConstructStatus,\n} from './interfaces'\n// Slot indirection, NOT `./assets` — that module's eager node:fs import\n// would poison the edge-safe web entry graph (see asset-resolver-slot.ts).\nimport { resolveAssetViaSlot as resolveAsset } from './asset-resolver-slot'\nimport {\n envValueMissingError,\n noProviderError,\n requestScopeMiddlewareMissingError,\n requestScopeOutsideRequestError,\n} from './kick-errors'\nimport { createLogger } from './logger'\nimport {\n getClassMeta,\n getClassMetaOrUndefined,\n getMetaMap,\n getMetaRecord,\n getMethodMetaOrUndefined,\n} from './metadata'\nimport { isInjectionToken, type InjectionToken } from './token'\n\nconst log = createLogger('Container')\n\n/** Internal registration entry that tracks how a dependency should be resolved */\ninterface Registration {\n target: Constructor\n scope: Scope\n instance?: any\n factory?: () => any\n // Observability fields\n kind: ClassKind\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n /** Lazily built instantiation plan — see {@link Container.buildPlan}. */\n plan?: InstantiationPlan\n}\n\n/**\n * Precomputed instantiation recipe for a class. Class metadata is immutable\n * after definition, so the Reflect reads (5 per create) and the derived\n * structures only need computing ONCE — previously they re-ran on every\n * REQUEST-scoped resolve (i.e. every request) and every TRANSIENT resolve.\n */\ninterface InstantiationPlan {\n /** Ordered, validated constructor parameter tokens. */\n ctorTokens: any[]\n /** `@Autowired` properties: [property, resolved token]. */\n autowired: Array<[string, any]>\n /** `@Value` properties: [property, env config]. */\n values: Array<[string, { envKey: string; defaultValue?: any }]>\n /** `@Asset` properties: [property, pre-split namespace/key]. */\n assets: Array<[string, { namespace: string; key: string }]>\n /** `@PostConstruct` method name, if any. */\n postConstruct?: string | symbol\n}\n\n/** Create a Registration with observability defaults */\nfunction createReg(\n fields: Partial<Registration> & Pick<Registration, 'target' | 'scope'>,\n): Registration {\n return {\n kind: 'unknown',\n resolveCount: 0,\n postConstructStatus: 'skipped',\n dependencies: [],\n ...fields,\n }\n}\n\n/** Format a token for display in error messages */\nexport function tokenName(token: any): string {\n if (typeof token === 'symbol') return token.toString()\n if (isInjectionToken(token)) return token.name\n return token?.name || String(token)\n}\n\n// ── Persistent Store ────────────────────────────────────────────────────\n// globalThis-based storage that survives BOTH Container.reset() AND Vite's\n// ssrLoadModule() re-evaluation. When Vite re-evaluates a module, the\n// Container class gets a new identity (new statics). But globalThis persists.\n//\n// This is how DB connections, Redis clients, WsAdapter instances, and any\n// other stateful bindings survive HMR without being dropped.\n//\n// Pattern from: React Router (globalThis for state), Vinxi (globalThis.app)\n\n// ── Change Event System ─────────────────────────────────────────────────\n// Batched (50ms debounce) so `kick g module` creating 10+ files emits ONE\n// notification, not 10. Any adapter can subscribe: Swagger, DevTools, WS, etc.\n\n/** A single container change event */\nexport interface ContainerChangeEvent {\n token: string\n event: 'registered' | 'resolved' | 'invalidated'\n kind?: ClassKind\n timestamp: number\n}\n\n/** Listener that receives batched change events */\nexport type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void\n\n/**\n * Type-safe registry of string DI tokens. Augmented by `kick typegen` —\n * the generated `.kickjs/types/registry.d.ts` adds entries like:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface KickJsRegistry {\n * UserService: import('../../src/modules/users/user.service').UserService\n * }\n * }\n * ```\n *\n * Once typegen has run, `container.resolve('UserService')` returns the\n * correct type instead of `any`. Class-token resolution\n * (`container.resolve(UserService)`) is already type-safe via the\n * `Constructor<T>` overload and does not need typegen.\n */\nexport interface KickJsRegistry {}\n\ninterface PersistentStore {\n factories: Array<{ token: any; factory: () => any; scope: Scope }>\n instances: Array<{ token: any; instance: any }>\n /** Resolved instances keyed by string name — survives class identity changes */\n resolvedInstances: Map<string, any>\n}\n\nfunction getPersistentStore(): PersistentStore {\n if (!(globalThis as any).__kickjs_persistent) {\n ;(globalThis as any).__kickjs_persistent = {\n factories: [],\n instances: [],\n resolvedInstances: new Map(),\n } satisfies PersistentStore\n }\n return (globalThis as any).__kickjs_persistent\n}\n\n/**\n * Inversion-of-Control (IoC) container that manages dependency registration,\n * resolution, and lifecycle. Implements the Singleton pattern so all parts of\n * the application share a single container instance.\n *\n * Supports constructor injection, property injection (@Autowired),\n * factory registrations, and lifecycle hooks (@PostConstruct).\n *\n * Persistent state (DB connections, Redis, etc.) is stored on globalThis so\n * it survives both Container.reset() and Vite module re-evaluation.\n */\nexport class Container {\n private static instance: Container\n private registrations = new Map<any, Registration>()\n private resolving = new Set<any>()\n\n // ── Reactive change tracking ──────────────────────────────────────\n private changeListeners = new Set<ContainerChangeListener>()\n private pendingChanges: ContainerChangeEvent[] = []\n private notifyTimer: ReturnType<typeof setTimeout> | null = null\n private static readonly DEBOUNCE_MS = 50\n\n /** Callback set by the decorators module to flush pending registrations */\n static _onReady: ((container: Container) => void) | null = null\n /** Callback invoked on reset so decorators can update their container reference */\n static _onReset: ((container: Container) => void) | null = null\n\n /**\n * Seeds a NEWLY CREATED container with the decorator registrations, without\n * adopting it as the global one. `_onReset` does both; an isolated container\n * must only have the first half, or it would capture every subsequent\n * decorator registration in the process.\n */\n static _onCreate: ((container: Container) => void) | null = null\n /**\n * Environment resolver for @Value decorator. Set by the unified\n * `@forinda/kickjs` config layer to return Zod-validated, type-coerced\n * env values instead of raw process.env strings.\n */\n static _envResolver: ((key: string) => any) | null = null\n /**\n * Request store provider for REQUEST-scoped DI. Set by the HTTP layer\n * inside `@forinda/kickjs` to return the current request's\n * AsyncLocalStorage store. Returns { instances: Map, values: Map } or\n * null if outside a request.\n */\n /**\n * Whether this container is the global singleton.\n *\n * The persistent store exists to carry the GLOBAL container's registrations\n * across HMR and module re-evaluation. An isolated container is per-test and\n * must neither write to it — a `registerInstance` override was replayed into\n * every later `Container.reset()` — nor read from it, which would pull global\n * state into a container created to be free of it.\n */\n private isGlobal = false\n\n static _requestStoreProvider:\n | (() => { instances: Map<any, any>; values: Map<any, any> } | null)\n | null = null\n\n static getInstance(): Container {\n if (!Container.instance) {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n }\n // Flush any decorator registrations that queued before the container existed\n if (Container._onReady) {\n Container._onReady(Container.instance)\n Container._onReady = null\n }\n return Container.instance\n }\n\n /**\n * Resets the container by replacing the singleton with a fresh instance.\n * Persistent registrations (DB, Redis, etc.) are replayed from globalThis\n * so they survive both reset() and Vite module re-evaluation.\n */\n static reset(): void {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n // Notify decorators so they update their container reference\n Container._onReset?.(Container.instance)\n // Replay persistent registrations from globalThis store\n const store = getPersistentStore()\n for (const { token, factory, scope } of store.factories) {\n if (!Container.instance.has(token)) {\n // Check if we have a previously resolved instance for this token\n const name = tokenName(token)\n const existing = store.resolvedInstances.get(name)\n Container.instance.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existing, // Reuse resolved instance if available\n resolveCount: existing ? 1 : 0,\n postConstructStatus: existing ? 'completed' : 'skipped',\n }),\n )\n }\n }\n for (const { token, instance } of store.instances) {\n if (!Container.instance.has(token)) {\n Container.instance.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n }),\n )\n }\n }\n }\n\n /**\n * Create an isolated container instance (not the global singleton).\n * Useful for concurrent tests that must not share DI state.\n *\n * @example\n * ```ts\n * const container = Container.create()\n * container.register(UserService, UserService)\n * const svc = container.resolve(UserService)\n * ```\n */\n static create(): Container {\n const container = new Container()\n // Decorators register at class-definition time, into whichever container\n // was global then. Without this replay an isolated container is missing\n // every @Service / @Repository / @Controller in the process — and\n // `isolated: true` is what the generated docs recommend for test\n // isolation, so the failure reached adopters as `No provider for X`.\n Container._onCreate?.(container)\n return container\n }\n\n /** Register a class constructor under the given token */\n register(token: any, target: Constructor, scope: Scope = Scope.SINGLETON): void {\n const kind = getClassMeta<ClassKind>(METADATA.CLASS_KIND, target, 'unknown')\n const dependencies = this.extractDependencies(target)\n const reg = createReg({ target, scope, kind, dependencies })\n this.registrations.set(token, reg)\n // Store a name-based fallback so HMR class re-creation (new identity)\n // can still resolve by the original class name.\n if (typeof token === 'function' && token.name) {\n this.registrations.set(`__hmr__${token.name}`, reg)\n }\n this.emit(token, 'registered', kind)\n }\n\n /** Register a factory function under the given token */\n registerFactory(token: any, factory: () => any, scope: Scope = Scope.SINGLETON): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n // Check if we already have a resolved instance from a previous HMR cycle.\n // Only reuse if the factory hasn't changed (same reference). When a new\n // factory is provided (e.g. swapping implementations), clear the stale\n // instance so the new factory runs on next resolve.\n // Reads are global-only too. An isolated container adopting a cached\n // instance from the global store would be contaminated on creation — the\n // same leak, in the other direction.\n const existingEntry = this.isGlobal\n ? store.factories.find((r) => tokenName(r.token) === name)\n : undefined\n const factoryUnchanged = existingEntry?.factory === factory\n const existingInstance =\n this.isGlobal && factoryUnchanged ? store.resolvedInstances.get(name) : undefined\n\n // Clear stale resolved instance when factory changes\n if (this.isGlobal && !factoryUnchanged) {\n store.resolvedInstances.delete(name)\n }\n\n this.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existingInstance,\n resolveCount: existingInstance ? 1 : 0,\n postConstructStatus: existingInstance ? 'completed' : 'skipped',\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only, or an isolated container's factories are replayed\n // into the next Container.reset().\n if (this.isGlobal) {\n const idx = store.factories.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.factories[idx] = { token, factory, scope }\n } else {\n store.factories.push({ token, factory, scope })\n }\n }\n this.emit(name, 'registered', 'factory')\n }\n\n /** Register a pre-constructed singleton instance */\n registerInstance(token: any, instance: any): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n\n this.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n firstResolvedAt: Date.now(),\n lastResolvedAt: Date.now(),\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only. An isolated one that wrote here had its overrides\n // replayed into the next Container.reset().\n if (this.isGlobal) {\n store.resolvedInstances.set(name, instance)\n const idx = store.instances.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.instances[idx] = { token, instance }\n } else {\n store.instances.push({ token, instance })\n }\n }\n this.emit(name, 'registered', 'instance')\n }\n\n /** Check if a binding exists for the given token */\n has(token: any): boolean {\n return this.registrations.has(token)\n }\n\n /** Return a snapshot of all registered tokens with their scope and instantiation status */\n getRegistrations(): Array<{\n token: string\n scope: string\n kind: ClassKind\n instantiated: boolean\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n }> {\n const entries: Array<ReturnType<Container['getRegistrations']>[number]> = []\n for (const [token, reg] of this.registrations) {\n entries.push({\n token: tokenName(token),\n scope: reg.scope,\n kind: reg.kind,\n // Singletons check cached instance; transients check resolveCount\n instantiated:\n reg.scope === Scope.SINGLETON ? reg.instance !== undefined : reg.resolveCount > 0,\n resolveCount: reg.resolveCount,\n lastResolvedAt: reg.lastResolvedAt,\n firstResolvedAt: reg.firstResolvedAt,\n resolveDurationMs: reg.resolveDurationMs,\n postConstructStatus: reg.postConstructStatus,\n dependencies: reg.dependencies,\n })\n }\n return entries\n }\n\n /**\n * Resolve a dependency by its token.\n *\n * Four overloads, in order of preference:\n * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.\n * Collision-safe by construction; no codegen required.\n * - **Class constructor** → returns an instance of that class. Always\n * type-safe; no codegen required.\n * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →\n * returns the augmented type.\n * - **Anything else** → returns `any`. Use this only for legacy code or\n * string tokens that haven't been registered with typegen yet.\n */\n resolve<T>(token: InjectionToken<T>): T\n resolve<T>(token: Constructor<T>): T\n resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K]\n resolve<T = any>(token: any): T\n resolve<T = any>(token: any): T {\n let reg = this.registrations.get(token)\n // HMR fallback: when Vite re-evaluates a module, decorated classes get new\n // identity. Try resolving by class name if the primary token lookup fails.\n if (!reg && typeof token === 'function' && token.name) {\n reg = this.registrations.get(`__hmr__${token.name}`)\n }\n if (!reg) {\n throw noProviderError(tokenName(token))\n }\n\n if (reg.scope === Scope.SINGLETON && reg.instance !== undefined) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return reg.instance\n }\n\n // REQUEST scope: one instance per HTTP request, cached in AsyncLocalStorage\n if (reg.scope === Scope.REQUEST) {\n if (!Container._requestStoreProvider) {\n throw requestScopeMiddlewareMissingError(tokenName(token))\n }\n const store = Container._requestStoreProvider()\n if (!store) {\n throw requestScopeOutsideRequestError(tokenName(token))\n }\n // Check for pre-registered request values (user, tenant, etc.)\n if (store.values.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.values.get(token) as T\n }\n // Check per-request instance cache\n if (store.instances.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.instances.get(token) as T\n }\n // Factory-registered binding: invoke the factory once per request\n // and cache the result alongside class-instantiated REQUEST tokens.\n // Without this branch, registerFactory(token, fn, Scope.REQUEST)\n // silently falls through to createInstance(reg) which `new`s the\n // factory's placeholder `target: Object` and returns an empty {}\n // — the factory function never runs.\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n // Create instance and cache for this request only\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n // Persist resolved factory instances so they survive module\n // re-evaluation — global container only, for the same reason.\n if (this.isGlobal) {\n getPersistentStore().resolvedInstances.set(tokenName(token), instance)\n }\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n }\n\n if (this.resolving.has(token)) {\n const chain = [...this.resolving].map(tokenName)\n chain.push(tokenName(token))\n throw new Error(`Circular dependency detected: ${chain.join(' -> ')}`)\n }\n this.resolving.add(token)\n\n try {\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n } finally {\n this.resolving.delete(token)\n }\n }\n\n /** Lifecycle hook called during Application.setup() after module registration */\n bootstrap(): void {\n // Reserved for future use — adapters and modules register via\n // container.register(), registerFactory(), and registerInstance().\n }\n\n // ── Reactive Change API ─────────────────────────────────────────────\n\n /**\n * Subscribe to container changes. Returns an unsubscribe function.\n *\n * Changes are batched (50ms debounce) so bulk operations like\n * `kick g module` creating 10+ files emit ONE batch, not 10 events.\n *\n * Any adapter can subscribe:\n * - SwaggerAdapter: re-build spec when controllers change\n * - DevToolsAdapter: push SSE events to dashboard\n * - WsAdapter: re-discover namespaces when controllers change\n * - OtelAdapter: emit tracing spans for DI resolution\n * - Custom user adapters\n *\n * @example\n * ```ts\n * const unsub = container.onChange((changes) => {\n * for (const c of changes) {\n * console.log(`${c.event}: ${c.token} (${c.kind})`)\n * }\n * })\n * // later:\n * unsub()\n * ```\n */\n onChange(callback: ContainerChangeListener): () => void {\n this.changeListeners.add(callback)\n return () => this.changeListeners.delete(callback)\n }\n\n /**\n * Invalidate a specific registration. Clears the cached instance so the\n * next resolve() creates a fresh one. Also invalidates dependents\n * (anything that injected this token) by walking the dependency graph.\n *\n * Called by the Vite HMR plugin when a module file changes.\n * Skips persistent registrations (DB connections, etc.).\n */\n invalidate(token: any): void {\n const name = tokenName(token)\n // Try both the token itself and the HMR fallback name\n const reg = this.registrations.get(token) ?? this.registrations.get(`__hmr__${name}`)\n if (!reg) return\n\n // Don't invalidate persistent registrations (DB, Redis, etc.)\n const store = getPersistentStore()\n if (store.resolvedInstances.has(name)) return\n\n // Clear cached instance — next resolve() will re-create\n reg.instance = undefined\n reg.resolveCount = 0\n reg.postConstructStatus = 'skipped'\n this.emit(name, 'invalidated', reg.kind)\n\n // Walk dependency graph — invalidate anything that injected this token\n for (const [depToken, depReg] of this.registrations) {\n if (depReg.dependencies.includes(name)) {\n this.invalidate(depToken)\n }\n }\n }\n\n /**\n * Batch-emit a change event (debounced, flushed after 50ms of quiet).\n * Takes the RAW token — `tokenName()` is only computed after the\n * zero-listener guard, so the hot cached-singleton resolve path never pays\n * the name derivation when nobody is watching (the production default).\n */\n private emit(rawToken: any, event: ContainerChangeEvent['event'], kind?: ClassKind): void {\n // Zero-listener fast path. Nobody subscribes to container changes unless\n // DevTools (or a test) calls `onChange` — the production default. Without\n // this guard, every `resolve()` (including the hot cached-singleton path)\n // would scan `pendingChanges`, push an entry, and reschedule a `setTimeout`\n // only for the eventual flush to iterate an empty listener set and discard\n // the batch. Short-circuiting here keeps `resolve()` allocation- and\n // timer-free when no one is watching. When a listener attaches later,\n // subsequent emits work exactly as before — past resolves had no observer\n // to notify anyway.\n if (this.changeListeners.size === 0) return\n const token = tokenName(rawToken)\n\n // Dedupe within the pending batch: a high-throughput TRANSIENT\n // token that resolves 1000× per request would otherwise queue\n // 1000 identical 'resolved' events. The downstream listener only\n // cares that the token's state changed, so collapse to one entry\n // (the earliest timestamp wins; the latest update is reflected\n // in resolveCount/lastResolvedAt on the registration itself,\n // which the listener reads at notify time).\n const existing = this.pendingChanges.find((p) => p.token === token && p.event === event)\n if (existing) {\n existing.timestamp = Date.now()\n } else {\n this.pendingChanges.push({ token, event, kind, timestamp: Date.now() })\n }\n\n if (this.notifyTimer) clearTimeout(this.notifyTimer)\n this.notifyTimer = setTimeout(() => {\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }, Container.DEBOUNCE_MS)\n // Don't let a pending debounce window hold the event loop open — flush is\n // best-effort telemetry, not critical work.\n this.notifyTimer.unref?.()\n }\n\n /** Flush pending change events immediately (useful in tests) */\n flushChanges(): void {\n if (this.notifyTimer) {\n clearTimeout(this.notifyTimer)\n this.notifyTimer = null\n }\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }\n\n /**\n * Build the one-time instantiation plan for a registration. Runs on the\n * FIRST createInstance for the class — the metadata it reads is immutable\n * after class definition, so every later create (REQUEST scope: every\n * request) skips all Reflect reads and re-derivation.\n *\n * Scope validation (SINGLETON must not ctor-inject REQUEST) lives here\n * too: it's a static property of the (parent, dep) registration pair, so\n * checking it once at plan build is equivalent to the old per-create loop.\n */\n private buildPlan(reg: Registration): InstantiationPlan {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, reg.target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, reg.target)\n\n // When emitDecoratorMetadata is unavailable (tsx, ts-node without\n // SWC), paramTypes is empty even though @Inject set tokens on\n // specific constructor indices. Derive the constructor arity from\n // the @Inject metadata so constructor injection still works.\n const maxInjectIndex =\n Object.keys(injectTokens).length > 0\n ? Math.max(...Object.keys(injectTokens).map(Number)) + 1\n : 0\n const paramCount = Math.max(paramTypes.length, maxInjectIndex)\n\n const ctorTokens: any[] = []\n for (let index = 0; index < paramCount; index++) {\n const token = injectTokens[index] || paramTypes[index]\n if (!token) {\n throw new Error(\n `Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. ` +\n `No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). ` +\n `Add @Inject(Token) to every constructor parameter.`,\n )\n }\n // Scope validation: SINGLETON cannot inject REQUEST-scoped dependencies\n if (reg.scope === Scope.SINGLETON) {\n const depReg = this.registrations.get(token)\n if (depReg && depReg.scope === Scope.REQUEST) {\n // The fix depends on the parent: @Controller() takes no options and\n // is always SINGLETON, so \"change the parent's scope\" is advice a\n // controller cannot follow. @Autowired works for every parent — its\n // getter re-resolves REQUEST deps per access (injectProperties).\n const parent = tokenName(reg.target)\n const dep = tokenName(token)\n const kind = getClassMetaOrUndefined<ClassKind>(METADATA.CLASS_KIND, reg.target)\n const fix =\n kind === 'controller'\n ? `@Controller() is always SINGLETON, so inject \"${dep}\" with @Autowired() instead of the constructor.`\n : `Give \"${parent}\" TRANSIENT or REQUEST scope (e.g. @Service({ scope: Scope.REQUEST })), ` +\n `or inject \"${dep}\" with @Autowired() instead of the constructor.`\n throw new Error(\n `Cannot inject REQUEST-scoped \"${dep}\" into SINGLETON \"${parent}\". ` +\n `Singletons outlive requests. ${fix}`,\n )\n }\n }\n ctorTokens.push(token)\n }\n\n // @Autowired properties — resolve each property's token once.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, reg.target.prototype)\n const autowired: InstantiationPlan['autowired'] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, reg.target.prototype, prop)\n if (resolvedToken) autowired.push([prop, resolvedToken])\n }\n\n // @Value properties.\n const valueProps = getMetaMap<string, { envKey: string; defaultValue?: any }>(\n METADATA.VALUE,\n reg.target.prototype,\n )\n const values: InstantiationPlan['values'] = [...valueProps.entries()]\n\n // @Asset properties — pre-split namespace/key (and fail fast on bad keys).\n const assetProps = getMetaMap<string, { assetKey: string }>(\n METADATA.ASSET,\n reg.target.prototype,\n )\n const assets: InstantiationPlan['assets'] = []\n for (const [prop, config] of assetProps) {\n const slashIdx = config.assetKey.indexOf('/')\n if (slashIdx === -1) {\n throw new Error(\n `@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`,\n )\n }\n assets.push([\n prop,\n { namespace: config.assetKey.slice(0, slashIdx), key: config.assetKey.slice(slashIdx + 1) },\n ])\n }\n\n const postConstruct = getClassMetaOrUndefined<string | symbol>(\n METADATA.POST_CONSTRUCT,\n reg.target.prototype,\n )\n\n return { ctorTokens, autowired, values, assets, postConstruct }\n }\n\n private createInstance(reg: Registration): any {\n const plan = (reg.plan ??= this.buildPlan(reg))\n\n const args: any[] = []\n for (const token of plan.ctorTokens) {\n args.push(this.resolve(token))\n }\n\n const instance = new reg.target(...args)\n\n // Property injection via @Autowired / @Value / @Asset\n this.injectProperties(instance, plan)\n\n // @PostConstruct lifecycle hook\n const postConstruct = plan.postConstruct\n if (postConstruct && typeof instance[postConstruct] === 'function') {\n try {\n const result = instance[postConstruct]()\n if (result && typeof result.then === 'function') {\n reg.postConstructStatus = 'pending'\n ;(result as Promise<void>)\n .then(() => {\n reg.postConstructStatus = 'completed'\n })\n .catch(() => {\n reg.postConstructStatus = 'failed'\n })\n log.warn(\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise ` +\n `but the container is synchronous. The async operation will not be awaited.`,\n )\n } else {\n reg.postConstructStatus = 'completed'\n }\n } catch (err) {\n reg.postConstructStatus = 'failed'\n log.error(\n err,\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`,\n )\n }\n }\n\n return instance\n }\n\n /**\n * Extract every dependency token name a class declares — both\n * constructor params (`@Inject` + emitted `design:paramtypes`) and\n * `@Autowired` properties (read lazily at injection time, but\n * captured here so devtools surfaces a complete dependency graph).\n *\n * Without the property branch, classes that rely entirely on\n * `@Autowired` show `dependencies: []` in the introspection panel\n * even though they have real DI deps — see /_debug topology.\n */\n private extractDependencies(target: Constructor): string[] {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, target)\n const ctorDeps = paramTypes.map((type, index) => {\n const token = injectTokens[index] || type\n return tokenName(token)\n })\n\n // @Autowired properties live on `target.prototype`. Token may be\n // explicit (`@Autowired('SOME_TOKEN')`) or fall back to the\n // emitted `design:type` metadata of the property.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n const propDeps: string[] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) propDeps.push(tokenName(resolvedToken))\n }\n\n // De-dupe — a class can declare the same token in both surfaces.\n return [...new Set([...ctorDeps, ...propDeps])]\n }\n\n private injectProperties(instance: any, plan: InstantiationPlan): void {\n // @Autowired — lazy DI property injection. The getter re-resolves per\n // access by design (a SINGLETON may lazily read a REQUEST-scoped dep at\n // call time). For SINGLETON deps that laziness buys nothing after the\n // first read — the container returns the same cached instance forever —\n // so the getter memoizes itself into a plain data property then. REQUEST\n // and TRANSIENT deps keep the live getter (memoizing would pin the first\n // request's instance across requests).\n for (const [prop, resolvedToken] of plan.autowired) {\n Object.defineProperty(instance, prop, {\n get: () => {\n const value = this.resolve(resolvedToken)\n const depReg = this.registrations.get(resolvedToken)\n if (depReg?.scope === Scope.SINGLETON) {\n Object.defineProperty(instance, prop, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return value\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Value — lazy environment variable injection\n for (const [prop, config] of plan.values) {\n Object.defineProperty(instance, prop, {\n get() {\n // Use the registered env resolver if available (wired by the\n // config layer in @forinda/kickjs). Returns Zod-validated,\n // type-coerced values (e.g. PORT as number).\n if (Container._envResolver) {\n const val = Container._envResolver(config.envKey)\n if (val !== undefined) return val\n }\n\n // Fallback to raw process.env for apps that haven't called loadEnv()\n const val = process.env[config.envKey]\n if (val !== undefined) return val\n if (config.defaultValue !== undefined) return config.defaultValue\n throw envValueMissingError(config.envKey)\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Asset — lazy asset path injection. Same lazy-\n // getter pattern as @Value: resolve on every property access via\n // the asset manager's cached resolver, not at class instantiation.\n // namespace/key were pre-split at plan build.\n for (const [prop, { namespace, key }] of plan.assets) {\n Object.defineProperty(instance, prop, {\n get() {\n return resolveAsset(namespace, key)\n },\n enumerable: true,\n configurable: true,\n })\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @forinda/kickjs v8.7.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{createLogger}from"./core/logger.mjs";import{r as reportError}from"./observers-C5Jid4FV.mjs";import{normalizeProblem}from"./core/errors.mjs";import{t as ROUTE_SLOT}from"./route-flag-BCBTtP_4.mjs";import{r as MissingContextValueError}from"./context-errors-VduncZFQ.mjs";import{t as resolveClientIp}from"./client-ip-BuY7_pDD.mjs";import{a as parseQuery}from"./query-1Q69dPhY.mjs";import{AsyncLocalStorage}from"node:async_hooks";const requestStore=new AsyncLocalStorage;function getRequestStore(){let store=requestStore.getStore();if(!store)throw Error(`No active request context. REQUEST-scoped services can only be resolved during an HTTP request.`);return store}function getRequestValue(key){return requestStore.getStore()?.values.get(key)}const log=createLogger(`waitUntil`),pending=new Set;function waitUntil(promise){let requestId=requestStore.getStore()?.requestId,tracked=Promise.resolve(promise).then(()=>{},err=>{log.error({err,requestId},`Background work passed to waitUntil() failed`),reportError(err,{source:`background`,requestId})});pending.add(tracked),tracked.finally(()=>pending.delete(tracked))}async function settleBackgroundWork(){for(;pending.size>0;)await Promise.all(pending)}function pendingBackgroundWork(){return pending.size}function forwardBackgroundWork(platform){platform?.waitUntil?.(settleBackgroundWork())}const signalControllerKey=Symbol.for(`kickjs.requestContext.signalController`),readOnlyProxyCacheKey=Symbol.for(`kickjs.requestContext.readOnlyProxyCache`);function isPlainObjectOrArray(v){if(typeof v!=`object`||!v)return!1;if(Array.isArray(v))return!0;let proto=Object.getPrototypeOf(v);return proto===Object.prototype||proto===null}function makeReadOnlyProxy(target,req,label){if(process.env.NODE_ENV===`production`||!isPlainObjectOrArray(target))return target;let reqObj=req,cache=reqObj[readOnlyProxyCacheKey];cache||(cache=new WeakMap,reqObj[readOnlyProxyCacheKey]=cache);let targetObj=target,cached=cache.get(targetObj);if(cached)return cached;let warnWrite=(verb,prop)=>{console.warn(`[kickjs] Attempted to ${verb} \`ctx.${label}.${String(prop)}\` — request data is read-only. Stash computed values via \`ctx.set('key', value)\` or a Context Contributor, or reach \`ctx.req.${label}\` if you genuinely need the raw mutable handle.`)},proxy=new Proxy(targetObj,{get(t,prop,receiver){let value=Reflect.get(t,prop,receiver);return isPlainObjectOrArray(value)?makeReadOnlyProxy(value,req,`${label}.${String(prop)}`):value},set(_t,prop){return warnWrite(`assign`,prop),!0},deleteProperty(_t,prop){return warnWrite(`delete`,prop),!0},defineProperty(_t,prop){return warnWrite(`Object.defineProperty on`,prop),!0}});return cache.set(targetObj,proxy),proxy}var RequestContext=class{req;res;next;_response;get route(){return this.req?.[ROUTE_SLOT]}constructor(req,res,next,responseDriver){this.req=req,this.res=res,this.next=next,this._response=responseDriver??res}_qsMemo;_problem;metadataReadOnly(){return requestStore.getStore()?.values??null}metadataForWrite(){let store=requestStore.getStore();if(store)return store.values;throw Error(`RequestContext.set called outside an AsyncLocalStorage frame. Mount requestScopeMiddleware() before constructing RequestContext, or wrap test setup in requestStore.run({ requestId, instances: new Map(), values: new Map() }, () => ...).`)}get body(){return makeReadOnlyProxy(this.req.body,this.req,`body`)}get params(){return makeReadOnlyProxy(this.req.params,this.req,`params`)}get query(){return makeReadOnlyProxy(this.req.query,this.req,`query`)}get headers(){return makeReadOnlyProxy(this.req.headers,this.req,`headers`)}get requestId(){return this.req.requestId??this.req.headers[`x-request-id`]}get signal(){let req=this.req,cached=req[signalControllerKey];if(cached)return cached.signal;let ctrl=new AbortController;req[signalControllerKey]=ctrl;let abort=()=>ctrl.abort(`request closed`);return this.req.once(`close`,abort),this._response.once(`close`,abort),ctrl.signal}get session(){return this.req.session}get user(){return this.metadataReadOnly()?.get(`user`)??this.req.user}get tenantId(){return this.user?.tenantId}get roles(){let u=this.user;return u?.tenantRoles??u?.roles??[]}qs(fieldConfig,options){let memo=this._qsMemo;if(memo&&memo.config===fieldConfig&&memo.options===options)return memo.result;let effective={...options,onReject:options?.onReject??(rejection=>{let id=this.requestId?`[${this.requestId}] `:``;console.warn(`${id}query ${rejection.kind} '${rejection.field}' rejected (${rejection.reason})`)})},result=parseQuery(this.req.query,fieldConfig,effective);return this._qsMemo={config:fieldConfig,options,result},result}get file(){let f=this.req.file;return f?makeReadOnlyProxy(f,this.req,`file`):void 0}get files(){let f=this.req.files;if(Array.isArray(f))return makeReadOnlyProxy(f,this.req,`files`)}get(key){return this.metadataReadOnly()?.get(key)}require(key){let value=this.metadataReadOnly()?.get(key);if(value===void 0)throw new MissingContextValueError(key,this.routeLabel());return value}routeLabel(){let req=this.req,method=typeof req?.method==`string`?req.method:void 0,url=typeof req?.url==`string`?req.url:void 0;return method&&url?`${method} ${url}`:method??url}set(key,value){this.metadataForWrite().set(key,value)}setHeader(name,value){return this._response.setHeader(name,value),this}json(data,status=200){return this._response.status(status).json(data)}created(data){return this._response.status(201).json(data)}noContent(){return this._response.status(204).end()}notFound(message=`Not Found`){return this._response.status(404).json({message})}badRequest(message){return this._response.status(400).json({message})}get problem(){if(this._problem)return this._problem;let send=input=>this.sendProblem(input);return send.badRequest=(input={})=>this.sendProblem({status:400,...input}),send.unauthorized=(input={})=>this.sendProblem({status:401,...input}),send.forbidden=(input={})=>this.sendProblem({status:403,...input}),send.notFound=(input={})=>this.sendProblem({status:404,...input}),send.conflict=(input={})=>this.sendProblem({status:409,...input}),send.unprocessable=(input={})=>this.sendProblem({status:422,...input}),send.tooManyRequests=(input={})=>this.sendProblem({status:429,...input}),send.internal=(input={})=>this.sendProblem({status:500,...input}),send.validation=(issues,input={})=>{let errors=(issues||[]).map(issue=>({field:issue.path?.join?.(`.`)??``,message:issue.message??``,code:issue.code}));return this.sendProblem({status:422,detail:issues?.[0]?.message,...input,errors})},this._problem=send,this._problem}sendProblem(input){let body=normalizeProblem(input);return this._response.setHeader(`Content-Type`,`application/problem+json`),this._response.status(body.status).json(body)}get ip(){return resolveClientIp(this.req)}redirect(url,status=302){return this._response.status(status).setHeader(`location`,url),this._response.end()}html(content,status=200){return this._response.status(status).type(`text/html; charset=utf-8`).send(content)}download(buffer,filename,contentType=`application/octet-stream`){return this._response.setHeader(`Content-Disposition`,`attachment; filename="${filename}"`),this._response.setHeader(`Content-Type`,contentType),this._response.send(buffer)}render(template,data={}){return this._response.render(template,data)}async paginate(fetcher,fieldConfig){let parsed=this.qs(fieldConfig),{data,total}=await fetcher(parsed),{page,limit}=parsed.pagination,totalPages=Math.ceil(total/limit)||1,response={data,meta:{page,limit,total,totalPages,hasNext:page<totalPages,hasPrev:page>1}};return this.json(response),response}async sendResponse(response){let headers={};response.headers.forEach((value,name)=>{name!==`set-cookie`&&(headers[name]=value)});let cookies=response.headers.getSetCookie();if(cookies.length>0&&(headers[`set-cookie`]=cookies),!response.body){this._response.status(response.status);for(let[name,value]of Object.entries(headers))this._response.setHeader(name,value);this._response.send(``);return}this._response.writeHead(response.status,headers),this._response.flushHeaders();let reader=response.body.getReader(),stop=()=>{reader.cancel().catch(()=>{})};this.signal.addEventListener(`abort`,stop,{once:!0});try{for(;;){let{done,value}=await reader.read();if(done)break;this._response.write(Buffer.from(value))===!1&&!this.signal.aborted&&await new Promise(resolve=>{let resume=()=>{this.signal.removeEventListener(`abort`,resume),resolve()};this._response.once(`drain`,resume),this.signal.addEventListener(`abort`,resume,{once:!0})})}}finally{this.signal.removeEventListener(`abort`,stop),this._response.end()}}waitUntil(promise){waitUntil(promise)}sse(){this._response.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`,"X-Accel-Buffering":`no`}),this._response.flushHeaders();let closeCallbacks=[];return this.req.on(`close`,()=>{for(let cb of closeCallbacks)cb()}),{send:(data,event,id)=>{id&&this._response.write(`id: ${id}\n`),event&&this._response.write(`event: ${event}\n`),this._response.write(`data: ${JSON.stringify(data)}\n\n`)},comment:text=>{this._response.write(`: ${text}\n\n`)},onClose:fn=>{closeCallbacks.push(fn)},close:()=>{this._response.end()}}}};export{waitUntil as a,requestStore as c,settleBackgroundWork as i,forwardBackgroundWork as n,getRequestStore as o,pendingBackgroundWork as r,getRequestValue as s,RequestContext as t};
12
+ //# sourceMappingURL=context-CHuuc9r6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-CHuuc9r6.mjs","names":[],"sources":["../src/http/request-store.ts","../src/http/background.ts","../src/http/context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { ContextMetaKey, MetaValue } from '../core/execution-context'\n\n/** Per-request storage for REQUEST-scoped DI and request context propagation */\nexport interface RequestStore {\n /** Unique request identifier */\n requestId: string\n /**\n * Per-request singleton cache for REQUEST-scoped services. Keys are\n * heterogeneous (class constructors, `InjectionToken` objects, raw\n * strings), so the map type stays `Map<any, any>`.\n */\n instances: Map<any, any>\n /**\n * Per-request key/value bag. Canonical backing store for\n * `RequestContext.set/get` and the Context Contributor pipeline (#107)\n * since Phase 3. Anything written here is visible to every concrete\n * execution context constructed for the same request, regardless of\n * which middleware/contributor wrote it.\n *\n * Keys are constrained to `string` per the locked decision in\n * `architecture.md` §20.12 (#4); the value type is `unknown` because\n * the concrete shape at each key is determined by the consumer's\n * `ContextMeta` augmentation, not by the store itself.\n *\n * Prefer the {@link getRequestValue} helper over reaching into this\n * map directly — it threads the augmented `ContextMeta` shape through\n * `MetaValue<K>` so consumers get typed reads without an `as` cast.\n * Writes should flow through `ctx.set(key, value)` or a Context\n * Contributor's return value — see the `setRequestValue` note below\n * for why this package intentionally does NOT ship a service-level\n * write helper.\n */\n values: Map<string, unknown>\n /** Set once {@link disposeRequestStore} has run — guards double-dispose. */\n disposed?: boolean\n}\n\n/** AsyncLocalStorage instance shared across the framework */\nexport const requestStore = new AsyncLocalStorage<RequestStore>()\n\n/**\n * Get the current request store. Throws if called outside a request context.\n * Used by the Container to resolve REQUEST-scoped dependencies.\n */\nexport function getRequestStore(): RequestStore {\n const store = requestStore.getStore()\n if (!store) {\n throw new Error(\n 'No active request context. REQUEST-scoped services can only be resolved during an HTTP request.',\n )\n }\n return store\n}\n\n/**\n * Read a per-request `ContextMeta`-keyed value from outside a controller\n * (services, repositories, anywhere with no `ctx` reference). Returns\n * the augmented type for the key — `MetaValue<K>` — so consumers get the\n * same static safety as `ctx.get(key)` without holding the context.\n *\n * Returns `undefined` outside an active request frame (background jobs,\n * startup code, tests without `requestScopeMiddleware()`) — the helper\n * is intentionally null-tolerant so calling it from a service that\n * runs in both request and non-request paths doesn't throw.\n *\n * @example\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface ContextMeta { tenant: { id: string; name: string } }\n * }\n *\n * @Service()\n * class AuditService {\n * log(action: string) {\n * const tenant = getRequestValue('tenant') // typed: { id; name } | undefined\n * db.audit.insert({ action, tenantId: tenant?.id })\n * }\n * }\n * ```\n */\nexport function getRequestValue<K extends ContextMetaKey>(key: K): MetaValue<K> | undefined {\n return requestStore.getStore()?.values.get(key) as MetaValue<K> | undefined\n}\n\n// Intentionally NO `setRequestValue` export.\n//\n// Writes are deliberately context-scoped — only the controlled write\n// surfaces should mutate the per-request Map:\n//\n// 1. A Context Contributor's `resolve()` / `onError()` return value\n// (the runner does `ctx.set(reg.key, value)` on the contributor's\n// behalf). This is the canonical way to populate a key.\n// 2. A controller / middleware / contributor that holds a\n// `RequestContext` instance can call `ctx.set(key, value)`\n// directly. The write is auditable inline at the call site.\n//\n// Letting arbitrary services reach in and mutate the store from\n// anywhere produces \"spooky action at a distance\" — keys appear in\n// the request bag without an obvious source, and tracing which\n// service polluted what becomes a grep exercise. Services that need\n// to publish per-request state should return the value to the caller\n// (controller or contributor) and let *that* layer write it via\n// `ctx.set`. If a future need genuinely requires a service-level\n// write surface, expose a narrow function (`recordTrace`,\n// `markStartTime`) on a service that captures the side effect, not a\n// generic `setRequestValue`.\n","/**\n * Work that outlives the response — `waitUntil(promise)`.\n *\n * A handler that fires off an audit log, an email, or an analytics call and\n * answers without awaiting it leaves that promise unowned: on serverless the\n * platform freezes or kills the instance once the response is sent, and on a\n * Node server `SIGTERM` exits before it settles. `waitUntil` registers the\n * promise so something waits for it:\n *\n * - on the Node server, `shutdown()` awaits it (within `shutdownTimeout`);\n * - through `createHandler()` and the web entry, it is handed to the\n * platform's own `waitUntil` (Workers `ctx`, Netlify `context`, Vercel's\n * `waitUntil`) when you pass it in.\n *\n * Process-wide, like the DI container: one app per process.\n *\n * @module @forinda/kickjs/http/background\n */\nimport { createLogger } from '../core/logger'\nimport { reportError } from '../core/observers'\nimport { requestStore } from './request-store'\n\nconst log = createLogger('waitUntil')\nconst pending = new Set<Promise<void>>()\n\n/**\n * Keep `promise` alive past the response. A rejection is logged (with the\n * request id when called during a request), never left unhandled.\n *\n * @example\n * ```ts\n * @Post('/')\n * async create(ctx: RequestContext) {\n * const order = await this.orders.create(ctx.body)\n * ctx.waitUntil(this.mailer.sendReceipt(order)) // or waitUntil(...) outside a handler\n * return order\n * }\n * ```\n */\nexport function waitUntil(promise: Promise<unknown>): void {\n const requestId = requestStore.getStore()?.requestId\n const tracked: Promise<void> = Promise.resolve(promise).then(\n () => {},\n (err: unknown) => {\n log.error({ err, requestId }, 'Background work passed to waitUntil() failed')\n reportError(err, { source: 'background', requestId })\n },\n )\n pending.add(tracked)\n void tracked.finally(() => pending.delete(tracked))\n}\n\n/**\n * Resolve once every registered promise has settled — including work\n * registered while waiting. Never rejects.\n */\nexport async function settleBackgroundWork(): Promise<void> {\n while (pending.size > 0) await Promise.all(pending)\n}\n\n/** How many `waitUntil` promises have not settled yet. */\nexport function pendingBackgroundWork(): number {\n return pending.size\n}\n\n/**\n * The platform hook `createHandler()` and the web entry forward background\n * work to. Cloudflare Workers' `ctx`, Netlify's `context`, and `{ waitUntil }`\n * from `@vercel/functions` all have this shape.\n */\nexport interface PlatformContext {\n waitUntil?(promise: Promise<unknown>): void\n}\n\n/** Hand every pending (and later-registered) promise to the platform. */\nexport function forwardBackgroundWork(platform: PlatformContext | undefined): void {\n platform?.waitUntil?.(settleBackgroundWork())\n}\n","/// <reference types=\"multer\" />\nimport type { Request, NextFunction } from 'express'\nimport { ROUTE_SLOT } from '../core/route-flag'\nimport type { ActiveRuntime, MatchedRoute, RuntimeResponse } from './runtime'\nimport {\n type ContextMetaKey,\n type ExecutionContext,\n type MetaValue,\n} from '../core/execution-context'\nimport { normalizeProblem, type ProblemDetails, type ValidationError } from '../core/errors'\nimport { MissingContextValueError } from '../core/context-errors'\nimport { requestStore } from './request-store'\nimport { waitUntil } from './background'\nimport { resolveClientIp, type ClientRequestLike } from './client-ip'\nimport {\n parseQuery,\n type ParseQueryOptions,\n type QueryFieldConfig,\n type PaginatedResponse,\n type TypedParsedQuery,\n} from './query'\n\n/**\n * Input shape for the typed problem-detail convenience methods on\n * {@link ProblemSender} (`ctx.problem.notFound`, `.badRequest`, etc.) —\n * RFC 9457 details minus the `status`, which each method pre-fills.\n */\ntype ProblemInput = Omit<ProblemDetails, 'status'>\n\n/**\n * Callable response helper for RFC 9457 — Problem Details responses.\n * Returned by `ctx.problem`. Invoke directly for a fully-specified\n * payload, or use the named convenience methods to pre-fill `status` +\n * `title` for the common codes.\n *\n * Each call sets `Content-Type: application/problem+json` and serializes\n * the canonical shape (defaulting `type` to `about:blank` and `title` to\n * the IANA reason phrase per RFC 9457 §3.1.1 / §3.1.4).\n *\n * @example\n * ```ts\n * ctx.problem({\n * type: 'https://api.example.com/problems/out-of-credit',\n * status: 403,\n * detail: 'Your balance is 30, but that costs 50.',\n * balance: 30, // extension per §3.2\n * })\n *\n * ctx.problem.notFound({ detail: 'User abc not found' })\n * ctx.problem.validation(zodResult.error.issues)\n * ```\n */\ninterface ProblemSender {\n (input: ProblemDetails): RuntimeResponse\n /** 400 Bad Request — RFC 9457. */\n badRequest(input?: ProblemInput): RuntimeResponse\n /** 401 Unauthorized — RFC 9457. */\n unauthorized(input?: ProblemInput): RuntimeResponse\n /** 403 Forbidden — RFC 9457. */\n forbidden(input?: ProblemInput): RuntimeResponse\n /** 404 Not Found — RFC 9457. */\n notFound(input?: ProblemInput): RuntimeResponse\n /** 409 Conflict — RFC 9457. */\n conflict(input?: ProblemInput): RuntimeResponse\n /** 422 Unprocessable Entity — RFC 9457. */\n unprocessable(input?: ProblemInput): RuntimeResponse\n /** 429 Too Many Requests — RFC 9457. */\n tooManyRequests(input?: ProblemInput): RuntimeResponse\n /** 500 Internal Server Error — RFC 9457. */\n internal(input?: ProblemInput): RuntimeResponse\n /**\n * 422 with Zod issues serialized into the `errors` extension per\n * RFC 9457 §3.2's array convention. Pass `error.issues` from a Zod\n * `safeParse` failure or `error.issues` from a thrown ZodError.\n */\n validation(issues: any[], input?: ProblemInput): RuntimeResponse\n}\n\n/**\n * Recursively marks every property of `T` as `readonly`. Preserves\n * `Function`, `Date`, and `RegExp` unchanged, and converts `Map` / `Set`\n * to `ReadonlyMap` / `ReadonlySet` so their mutating methods (`set`,\n * `delete`, `add`, `clear`) are removed from the public surface.\n * Tuples keep their positional element types — only their per-element\n * mutability is locked.\n *\n * Exposed as a utility for adopters who want to opt into compile-time\n * sealing of their own shapes. **No longer applied to\n * `RequestContext.{body,params,query,headers,file,files}`** — that\n * broke TS narrowing / discriminated-union drilldowns on typed request\n * payloads. Runtime read-only enforcement now lives in\n * {@link makeReadOnlyProxy} (dev-only Proxy that warns on writes,\n * no-op in production).\n *\n * Type-only. No runtime cost. `DeepReadonly<any>` is `any` (TS\n * distributes conditional types over `any`).\n */\nexport type DeepReadonly<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends Date | RegExp\n ? T\n : T extends Map<infer K, infer V>\n ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>\n : T extends Set<infer V>\n ? ReadonlySet<DeepReadonly<V>>\n : T extends readonly (infer U)[]\n ? // Distinguish a regular array from a tuple. A regular `T[]` widens\n // its `length` to `number`; a tuple's `length` is a literal\n // (e.g. `2`). The literal branch maps over `keyof T` so each\n // positional slot keeps its own type — `DeepReadonly<[1, 2]>` →\n // `readonly [1, 2]`, not `readonly (1 | 2)[]`.\n number extends T['length']\n ? ReadonlyArray<DeepReadonly<U>>\n : { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T extends object\n ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T\n\n/**\n * Re-export the augmentable {@link ContextMeta} registry from core/.\n * Declared in `core/execution-context.ts` so non-HTTP transports\n * (WS/queue/cron, V2) share one declaration site. Apps still augment\n * via the canonical `declare module '@forinda/kickjs'` form.\n */\nexport type { ContextMeta } from '../core/execution-context'\n\n/**\n * Symbol key under which the per-request `AbortController` backing\n * `RequestContext.signal` is cached on the Express `req` object.\n * Shared across every `RequestContext` instance built for the same\n * request (router-builder constructs separate wrappers for\n * middleware, the contributor pipeline, and the main handler — they\n * all need the same signal).\n */\nconst signalControllerKey = Symbol.for('kickjs.requestContext.signalController')\n\n/**\n * Symbol key under which per-target read-only Proxy wrappers are\n * cached on the Express `req` object. Used by {@link makeReadOnlyProxy}\n * so repeat access of `ctx.body` / `ctx.params` / etc. returns the same\n * Proxy instance (cheap, and stable under `===` for adopters comparing\n * values across middleware).\n */\nconst readOnlyProxyCacheKey = Symbol.for('kickjs.requestContext.readOnlyProxyCache')\n\n/**\n * `true` iff `v` is a plain object (`{}` / `Object.create(null)`) or an\n * array — the two shapes JSON.parse / Express body-parser produce and\n * the only ones the read-only Proxy recurses into. Class instances,\n * `Date`, `RegExp`, `Map`, `Set`, `Buffer`, streams, and functions\n * pass through unwrapped so their method dispatch and `instanceof`\n * checks keep working.\n */\nfunction isPlainObjectOrArray(v: unknown): v is object {\n if (v === null || typeof v !== 'object') return false\n if (Array.isArray(v)) return true\n const proto = Object.getPrototypeOf(v)\n return proto === Object.prototype || proto === null\n}\n\n/**\n * In dev, wrap `target` in a `Proxy` whose `set` / `deleteProperty` /\n * `defineProperty` traps log a warning and otherwise leave the\n * underlying object untouched. A `get` trap recursively wraps nested\n * plain objects and arrays on access, so deep mutations\n * (`ctx.body.user.name = 'x'`, `ctx.files[0].fieldname = 'y'`,\n * `ctx.body.tags.push(...)`) all surface a warning — matching the\n * prior `DeepReadonly<T>` contract at runtime instead of at the type\n * level. In production (`process.env.NODE_ENV === 'production'`) the\n * proxy is bypassed entirely and `target` is returned as-is so the\n * hot path stays zero-cost.\n *\n * Why a Proxy instead of `DeepReadonly<T>` at the type level: the\n * recursive conditional type interfered with TS narrowing (notably\n * discriminated unions on `ctx.body`) and slowed type-checking on\n * deeply-nested Zod-inferred shapes. Compile-time sealing is gone;\n * runtime warning catches the same class of bug during dev and tests.\n *\n * Trap returns `true` so strict-mode call sites (which is all of them\n * — ES modules are strict) don't *throw* on assignment — they just\n * get a warning + the underlying value stays untouched. Adopters\n * discover the violation when the subsequent read returns the\n * original value, not the one they \"set\".\n *\n * Targets are cached per-`req` under {@link readOnlyProxyCacheKey} so\n * (1) repeat getter access returns the same proxy (`ctx.body ===\n * ctx.body`, `ctx.body.user === ctx.body.user`); and (2) cyclic refs\n * (`body.self = body`) terminate via the cache hit on the second\n * visit. Primitives, `null`, and non-plain values pass through.\n */\nfunction makeReadOnlyProxy<T>(target: T, req: object, label: string): T {\n if (process.env.NODE_ENV === 'production') return target\n if (!isPlainObjectOrArray(target)) return target\n\n type Cache = WeakMap<object, unknown>\n const reqObj = req as Record<symbol, Cache | undefined>\n let cache = reqObj[readOnlyProxyCacheKey]\n if (!cache) {\n cache = new WeakMap()\n reqObj[readOnlyProxyCacheKey] = cache\n }\n const targetObj = target as unknown as object\n const cached = cache.get(targetObj)\n if (cached) return cached as T\n\n const warnWrite = (verb: string, prop: PropertyKey) => {\n console.warn(\n `[kickjs] Attempted to ${verb} \\`ctx.${label}.${String(prop)}\\` — ` +\n `request data is read-only. ` +\n `Stash computed values via \\`ctx.set('key', value)\\` or a Context Contributor, ` +\n `or reach \\`ctx.req.${label}\\` if you genuinely need the raw mutable handle.`,\n )\n }\n\n const proxy = new Proxy(targetObj, {\n get(t, prop, receiver) {\n const value = Reflect.get(t, prop, receiver)\n // Recurse only into plain objects / arrays. Functions (incl.\n // array methods like `.push`) pass through with `receiver` as\n // their `this`, so internal `this[i] = x` writes still hit the\n // `set` trap and warn.\n if (isPlainObjectOrArray(value)) {\n return makeReadOnlyProxy(value, req, `${label}.${String(prop)}`)\n }\n return value\n },\n set(_t, prop) {\n warnWrite('assign', prop)\n return true\n },\n deleteProperty(_t, prop) {\n warnWrite('delete', prop)\n return true\n },\n defineProperty(_t, prop) {\n warnWrite('Object.defineProperty on', prop)\n return true\n },\n })\n cache.set(targetObj, proxy)\n return proxy as T\n}\n\n/**\n * The shape of a single route entry in `KickRoutes`. Generated by\n * `kick typegen` and consumed via the `Ctx<T>` helper. Each field is\n * `unknown` by default and is filled in as the typegen learns more\n * about the route (URL params from the path, body/query from validation\n * schemas, etc.).\n */\nexport interface RouteShape {\n params?: unknown\n body?: unknown\n query?: unknown\n response?: unknown\n /**\n * Union of context keys `kick typegen` proved are populated for this\n * route — the keys of every context contributor applied at method or\n * class level. Drives {@link RequestContext.require} narrowing, so\n * calling `ctx.require()` for a key this route doesn't carry is a\n * compile error instead of a request-time throw.\n *\n * `string` (the default) means \"not proven\", not \"no keys\". Typegen\n * emits `string` whenever it cannot see the full picture — a\n * contributor registered at module / adapter / bootstrap level, or an\n * unrecognised decorator that might bundle contributors of its own.\n * Narrowing an unprovable route would produce compile errors for keys\n * that really are present.\n */\n contextKeys?: string\n}\n\n/**\n * Unified request/response abstraction passed to every controller method.\n * Shields handlers from raw Express objects and provides convenience methods.\n *\n * Implements {@link ExecutionContext} so the transport-agnostic Context\n * Contributor pipeline (#107) can run against this concrete HTTP context\n * the same way it will run against future WS / queue / cron contexts.\n */\nexport class RequestContext<\n TBody = any,\n TParams = any,\n TQuery = any,\n /**\n * Context keys proven present for this route. Defaults to\n * {@link ContextMetaKey} — every key the project has registered — which\n * collapses to `string` when neither registry has been augmented, so a\n * handler typed as plain `RequestContext` keeps today's behaviour. That\n * default is also the deliberate escape hatch when typegen's view of a\n * route is incomplete.\n */\n TKeys extends ContextMetaKey = ContextMetaKey,\n> implements ExecutionContext<TKeys> {\n /**\n * Engine-agnostic response driver the response helpers write through. Under\n * the Express runtime it IS `res` (Express's Response satisfies\n * {@link RuntimeResponse} structurally); other runtimes pass a thin wrapper\n * over their native reply as the optional fourth argument.\n */\n private readonly _response: RuntimeResponse\n\n /**\n * The route this request matched: its method, path, controller, handler name\n * and resolved {@link MatchedRoute.flags}.\n *\n * Available to anything running inside the matched route — route middleware,\n * contributors, guards, the handler. Global middleware runs before route\n * matching, so it sees `undefined` there.\n *\n * ```ts\n * if (ctx.route?.flags.has('auth.public')) return next()\n * ```\n */\n get route(): MatchedRoute | undefined {\n return (this.req as unknown as Record<symbol, MatchedRoute | undefined>)?.[ROUTE_SLOT]\n }\n\n constructor(\n public readonly req: ActiveRuntime['request'],\n public readonly res: ActiveRuntime['response'],\n public readonly next: NextFunction,\n responseDriver?: RuntimeResponse,\n ) {\n // Under the Express runtime `res` IS a RuntimeResponse (structural); other\n // runtimes pass an explicit driver wrapping their native reply.\n this._response = responseDriver ?? (res as RuntimeResponse)\n }\n\n /**\n * Memo of the most recent {@link qs} call this request. The query\n * object is immutable for a request's lifetime, so a repeat call with\n * the same `(fieldConfig, options)` references returns the cached\n * parse instead of re-walking the filter/sort strings.\n */\n private _qsMemo?: { config: unknown; options: unknown; result: unknown }\n\n /** Memoized {@link problem} sender — built lazily on first access. */\n private _problem?: ProblemSender\n\n /**\n * Read-side accessor for the per-request metadata map.\n *\n * Returns the AsyncLocalStorage map (`requestStore.getStore().values`)\n * shared with the Context Contributor pipeline when an ALS frame is\n * active. Returns `null` outside any frame — read-side callers\n * (`ctx.get`, `ctx.user`, `ctx.tenantId`, `ctx.roles`) treat that as\n * \"no metadata\" and fall through to their `req.*` defaults. Reads\n * never throw because outside an ALS frame nobody could have written\n * via `ctx.set` anyway, so an empty answer is the truthful one.\n */\n private metadataReadOnly(): Map<string, any> | null {\n return (requestStore.getStore()?.values as Map<string, any> | undefined) ?? null\n }\n\n /**\n * Write-side accessor for the per-request metadata map.\n *\n * Returns the same map as {@link metadataReadOnly} when an ALS frame\n * is active. Throws otherwise — writing to a fallback map outside\n * the ALS frame would silently lose data because other call sites\n * (downstream contributors, request-logger, etc.) read from the\n * canonical store and would never see it. Mount\n * `requestScopeMiddleware()` (Application does this for you in\n * `'auto'` mode) or wrap your test setup in\n * `requestStore.run({...}, () => …)` to fix.\n */\n private metadataForWrite(): Map<string, any> {\n const store = requestStore.getStore()\n if (store) return store.values as Map<string, any>\n throw new Error(\n 'RequestContext.set called outside an AsyncLocalStorage frame. ' +\n 'Mount requestScopeMiddleware() before constructing RequestContext, ' +\n 'or wrap test setup in requestStore.run({ requestId, instances: new Map(), values: new Map() }, () => ...).',\n )\n }\n\n // ── Request Data ────────────────────────────────────────────────────\n\n /**\n * Parsed request body. Wrapped in a dev-only Proxy that warns when\n * adopters try to mutate Zod-validated payloads in place — compute\n * new values and stash them via `ctx.set('key', value)` or a Context\n * Contributor instead. Reach for `ctx.req.body` if you genuinely\n * need the raw, mutable Express handle (rare).\n *\n * In production the Proxy is bypassed and `this.req.body` is\n * returned as-is — no runtime cost on the hot path.\n */\n get body(): TBody {\n return makeReadOnlyProxy<TBody>(this.req.body, this.req, 'body')\n }\n\n /** Path parameters parsed from the route — read-only, see {@link body}. */\n get params(): TParams {\n return makeReadOnlyProxy<TParams>(this.req.params as TParams, this.req, 'params')\n }\n\n /** Parsed query string — read-only, see {@link body}. */\n get query(): TQuery {\n return makeReadOnlyProxy<TQuery>(this.req.query as TQuery, this.req, 'query')\n }\n\n /** Inbound HTTP headers — read-only, see {@link body}. */\n get headers(): Request['headers'] {\n return makeReadOnlyProxy<Request['headers']>(this.req.headers, this.req, 'headers')\n }\n\n get requestId(): string | undefined {\n return (this.req as any).requestId ?? (this.req.headers['x-request-id'] as string | undefined)\n }\n\n /**\n * `AbortSignal` that fires when the underlying HTTP request is\n * closed — either because the client disconnected before the\n * response was sent, or because Node's socket-close event ran\n * after a normal response. Multiple `RequestContext` wrappers for\n * the same request (router-builder constructs one per\n * middleware/contributor pipeline / main handler) share a single\n * `AbortController` cached on the underlying `req` object, so the\n * signal is stable across the request lifecycle.\n *\n * Thread it through anything that takes an `AbortSignal` so the\n * work cancels as soon as the client gives up — e.g. a kickjs-db\n * relational query:\n *\n * ```ts\n * @Get('/:id/full')\n * async show(ctx: RequestContext) {\n * const row = await this.repo.findFullById(ctx.params.id, ctx.signal)\n * if (!row) return ctx.notFound()\n * ctx.json(row)\n * }\n * ```\n *\n * The repo passes `signal` to `db.query.<table>.findUnique({ signal })`;\n * if the client disconnects mid-flight, the in-flight query rejects\n * with `RelationalQueryCancelledError` instead of consuming the\n * connection until completion.\n *\n * Implementation note — both `req.on('close')` and `res.on('close')`\n * are wired so the signal aborts regardless of whether the close\n * event lands on the request stream or the response stream first.\n * Subsequent fires are no-ops (`AbortController.abort()` is\n * idempotent).\n */\n get signal(): AbortSignal {\n const req = this.req as Request & { [signalControllerKey]?: AbortController }\n const cached = req[signalControllerKey]\n if (cached) return cached.signal\n\n const ctrl = new AbortController()\n req[signalControllerKey] = ctrl\n const abort = () => ctrl.abort('request closed')\n this.req.once('close', abort)\n this._response.once('close', abort)\n return ctrl.signal\n }\n\n /** Session data (requires session middleware) */\n get session(): any {\n return (this.req as any).session\n }\n\n /**\n * The authenticated user set by AuthAdapter.\n * Reads from the per-request metadata store first (`ctx.set('user', ...)`),\n * then falls back to `req.user` (set directly by AuthAdapter middleware).\n *\n * Extend the `AuthUser` interface via module augmentation in your own auth\n * module — see the BYO Auth recipe in the docs\n * to get full typing.\n *\n * @example\n * ```ts\n * @Get('/me')\n * @Authenticated()\n * getProfile(ctx: RequestContext) {\n * const user = ctx.user\n * if (!user) return ctx.notFound()\n * return ctx.json({ user })\n * }\n * ```\n */\n get user(): MetaValue<'user', Record<string, any>> | undefined {\n return this.metadataReadOnly()?.get('user') ?? (this.req as any).user\n }\n\n /**\n * Tenant identifier for multi-tenant apps. Reads `user.tenantId` —\n * populated either by `AuthAdapter.testMode({ tenantId })`, a tenant\n * resolver middleware, or manually on the strategy's `mapPayload`.\n *\n * Augment `AuthUser` (or `ContextMeta['user']`) to widen the return\n * type if your user shape guarantees a non-`string | undefined` id.\n */\n get tenantId(): string | undefined {\n const u = this.user as Record<string, any> | undefined\n return u?.tenantId\n }\n\n /**\n * Effective role list for the current user. Prefers `user.tenantRoles`\n * (populated by `AuthAdapterOptions.roleResolver` under a tenant) and\n * falls back to `user.roles`. Returns an empty array when the user is\n * absent or the shape has neither field.\n */\n get roles(): string[] {\n const u = this.user as Record<string, any> | undefined\n return u?.tenantRoles ?? u?.roles ?? []\n }\n\n // ── Query String Parsing ───────────────────────────────────────────\n\n /**\n * Parse the request query string into structured filters, sort, pagination, and search.\n * Pass the result to an ORM query builder adapter (Drizzle, Prisma, Sequelize, etc.).\n *\n * Generic over the field config: when passed an inline literal (or\n * `as const`), `filters[].field` and `sort[].field` are narrowed to\n * the exact whitelisted field names. Pass nothing or a non-literal\n * config and you get the loose `string` fallback.\n *\n * @param fieldConfig - Optional whitelist for filterable, sortable, and searchable fields\n * @param options - Limits (`maxLimit`, `defaultLimit`, `maxSearchLength`) and an\n * `onReject` hook. By default a rejected/over-broad field is logged via\n * `console.warn` with the request id; pass an explicit `onReject` to override\n * (e.g. throw to return a 400), or `onReject: () => {}` to silence.\n *\n * @example\n * ```ts\n * @Get('/')\n * async list(ctx: RequestContext) {\n * const parsed = ctx.qs({\n * filterable: ['status', 'priority'],\n * sortable: ['createdAt', 'title'],\n * } as const)\n * parsed.filters[0].field // typed as 'status' | 'priority'\n * parsed.sort[0].field // typed as 'createdAt' | 'title'\n * }\n * ```\n */\n qs<TConfig extends QueryFieldConfig | undefined = undefined>(\n fieldConfig?: TConfig,\n options?: ParseQueryOptions,\n ): TypedParsedQuery<TConfig> {\n // Cheap per-request memo — repeat calls with the same args (the\n // common controller pattern) skip re-parsing. `req.query` is fixed\n // for the request's lifetime, so caching is always sound.\n const memo = this._qsMemo\n if (memo && memo.config === fieldConfig && memo.options === options) {\n return memo.result as TypedParsedQuery<TConfig>\n }\n const effective: ParseQueryOptions = {\n ...options,\n onReject:\n options?.onReject ??\n ((rejection) => {\n const id = this.requestId ? `[${this.requestId}] ` : ''\n console.warn(\n `${id}query ${rejection.kind} '${rejection.field}' rejected (${rejection.reason})`,\n )\n }),\n }\n const result = parseQuery<TConfig>(\n this.req.query as Record<string, any>,\n fieldConfig,\n effective,\n )\n this._qsMemo = { config: fieldConfig, options, result }\n return result\n }\n\n // ── File Uploads ────────────────────────────────────────────────────\n\n /**\n * Single uploaded file (requires `@FileUpload({ mode: 'single' })`).\n * Wrapped in a dev-only Proxy that warns on mutation — see {@link body}.\n * File metadata is set by the upload middleware and shouldn't be\n * mutated downstream.\n */\n get file(): Express.Multer.File | undefined {\n const f = this.req.file\n return f ? makeReadOnlyProxy<Express.Multer.File>(f, this.req, 'file') : undefined\n }\n\n /**\n * Array of uploaded files (requires `@FileUpload({ mode: 'array' })`).\n * The array (and each entry in dev) is wrapped in a read-only Proxy\n * that warns on `push`/`pop` and per-file mutation. Use\n * `ctx.req.files` if you genuinely need the mutable Express handle.\n */\n get files(): Express.Multer.File[] | undefined {\n // multer's `Request['files']` is a union — array (mode: 'array' /\n // 'any') or `{ [fieldname]: File[] }` (mode: 'fields'). The\n // `@FileUpload({ mode: 'array' })` decorator only sets the array\n // shape, but `Request['files']` is the broad union. Narrow back to\n // `File[]` here for the common-case getter; adopters using\n // `mode: 'fields'` can read `ctx.req.files` directly for the\n // dictionary shape.\n const f = this.req.files\n if (!Array.isArray(f)) return undefined\n return makeReadOnlyProxy<Express.Multer.File[]>(f, this.req, 'files')\n }\n\n // ── Metadata Store ──────────────────────────────────────────────────\n\n /**\n * Read a value from the per-request metadata store.\n *\n * The key is constrained to {@link ContextMetaKey} — the keys registered in\n * `ContextMeta` / `ContextKeys` — so a typo is a compile error rather than a\n * permanent `undefined`. Projects that have augmented neither registry get\n * `string`, exactly as before.\n *\n * Constrained by `ContextMetaKey` and deliberately NOT by `TKeys`: \"does this\n * key exist in the app\" is a different question from \"does this route carry\n * it\", and narrowing by the latter would turn an incomplete typegen view into\n * false errors for keys written by middleware. Presence is `require`'s job;\n * this always returns `| undefined`.\n *\n * When `ContextMeta` has a matching key the return type is inferred\n * automatically. For ad-hoc keys, pass a generic: `ctx.get<MyType>('custom')`.\n */\n get<K extends ContextMetaKey>(key: K): MetaValue<K> | undefined {\n return this.metadataReadOnly()?.get(key) as MetaValue<K> | undefined\n }\n\n /**\n * Read a value that a contributor is expected to have produced, and\n * throw {@link MissingContextValueError} when it hasn't.\n *\n * Use this instead of `ctx.get(key)!` anywhere the value is a\n * precondition rather than an optional extra — permissions, tenants,\n * the resolved subject of the route. The non-null assertion compiles\n * whether or not the producing contributor is actually applied to the\n * route, so a dropped `@LoadTenant` turns into `undefined` flowing\n * silently into an auth check. `require()` fails loudly at that point\n * instead, naming the key.\n *\n * ```ts\n * // before — compiles even if @OperatorPerm was never applied\n * const perm = ctx.get('operatorPerm')!\n *\n * // after — throws MissingContextValueError naming the key + route\n * const perm = ctx.require('operatorPerm')\n * ```\n *\n * When `TKeys` is narrowed — which it is for a handler typed as\n * `Ctx<KickRoutes…>`, from typegen's per-route `contextKeys` — asking\n * for a key the route doesn't carry is a compile error, so a dropped\n * decorator fails the build. `TKeys` stays `string` (no narrowing) for\n * a plain `RequestContext` and for any route typegen couldn't prove\n * complete; there this is a runtime guarantee only.\n * See `architecture.md` §20.14.\n *\n * `null` counts as present; only `undefined` (or a missing entry)\n * throws. A contributor that deliberately resolves to `null` is\n * expressing \"looked, found nothing\", which is a real value — which\n * is why the return type is `Exclude<…, undefined>` and NOT\n * `NonNullable<…>`. The latter would strip `null` from the type while\n * the runtime still hands it back, so a caller would be told a value\n * is non-null and then get `null`.\n */\n require<K extends TKeys>(key: K): Exclude<MetaValue<K>, undefined> {\n const value = this.metadataReadOnly()?.get(key)\n if (value === undefined) {\n throw new MissingContextValueError(key, this.routeLabel())\n }\n return value as Exclude<MetaValue<K>, undefined>\n }\n\n /**\n * Best-effort `\"GET /path\"` label for error messages. Runtime-agnostic:\n * express/fastify requests and web-standard `Request` all expose\n * `method` + `url`, but none of them are guaranteed here, so every\n * access is defensive and the whole thing is optional.\n */\n private routeLabel(): string | undefined {\n const req = this.req as { method?: unknown; url?: unknown } | undefined\n const method = typeof req?.method === 'string' ? req.method : undefined\n const url = typeof req?.url === 'string' ? req.url : undefined\n if (method && url) return `${method} ${url}`\n return method ?? url\n }\n\n /**\n * Write a value to the per-request metadata store. Throws when no\n * AsyncLocalStorage frame is active — see {@link metadataForWrite}\n * for why a silent fallback is the wrong default.\n */\n set<K extends ContextMetaKey>(key: K, value: MetaValue<K>): void {\n this.metadataForWrite().set(key, value)\n }\n\n // ── Response Helpers ────────────────────────────────────────────────\n\n /**\n * Set a response header. Runtime-neutral — works on every engine and on\n * the web/edge entry. No-op guards are the runtime's job (headers set\n * after the response settles are ignored or throw per engine).\n */\n setHeader(name: string, value: string | number | readonly string[]): this {\n this._response.setHeader(name, value)\n return this\n }\n\n json(data: any, status = 200) {\n return this._response.status(status).json(data)\n }\n\n created(data: any) {\n return this._response.status(201).json(data)\n }\n\n noContent() {\n return this._response.status(204).end()\n }\n\n /**\n * @deprecated Prefer {@link ProblemSender.notFound | ctx.problem.notFound()},\n * which emits an RFC 9457 — Problem Details response. This helper stays\n * for backward compatibility and will not be removed.\n */\n notFound(message = 'Not Found') {\n return this._response.status(404).json({ message })\n }\n\n /**\n * @deprecated Prefer {@link ProblemSender.badRequest | ctx.problem.badRequest()},\n * which emits an RFC 9457 — Problem Details response. This helper stays\n * for backward compatibility and will not be removed.\n */\n badRequest(message: string) {\n return this._response.status(400).json({ message })\n }\n\n // ── RFC 9457 Problem Details ───────────────────────────────────────\n\n /**\n * RFC 9457 — Problem Details for HTTP APIs response helper.\n * https://datatracker.ietf.org/doc/html/rfc9457\n *\n * `ctx.problem(...)` sends a canonical problem-detail response with\n * `Content-Type: application/problem+json`. The convenience methods\n * (`ctx.problem.notFound`, `.badRequest`, etc.) pre-fill `status` +\n * `title` for the common codes.\n *\n * `type` defaults to `about:blank` (§3.1.1); `title` defaults to the\n * IANA reason phrase for `status` (§3.1.4). Extension members per §3.2\n * pass through unchanged.\n *\n * @example\n * ```ts\n * ctx.problem({\n * type: 'https://api.example.com/problems/out-of-credit',\n * status: 403,\n * detail: 'Your balance is 30, but that costs 50.',\n * instance: `/account/${ctx.user.id}/messages/${ctx.params.id}`,\n * balance: 30,\n * })\n *\n * ctx.problem.notFound({ detail: 'User abc not found' })\n * ctx.problem.validation(zodResult.error.issues)\n * ```\n *\n * @see {@link ProblemException} for the exception-throwing equivalent\n * (use it from services where `ctx` isn't in scope).\n */\n get problem(): ProblemSender {\n // Memoized — building the sender allocates ~10 closures, so pay that only\n // on the first access per context instead of per `ctx.problem.*` call.\n if (this._problem) return this._problem\n const send = (input: ProblemDetails): RuntimeResponse => this.sendProblem(input)\n send.badRequest = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 400, ...input })\n send.unauthorized = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 401, ...input })\n send.forbidden = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 403, ...input })\n send.notFound = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 404, ...input })\n send.conflict = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 409, ...input })\n send.unprocessable = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 422, ...input })\n send.tooManyRequests = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 429, ...input })\n send.internal = (input: ProblemInput = {}): RuntimeResponse =>\n this.sendProblem({ status: 500, ...input })\n send.validation = (issues: any[], input: ProblemInput = {}): RuntimeResponse => {\n const errors: ValidationError[] = (issues || []).map((issue: any) => ({\n field: issue.path?.join?.('.') ?? '',\n message: issue.message ?? '',\n code: issue.code,\n }))\n return this.sendProblem({\n status: 422,\n detail: issues?.[0]?.message,\n ...input,\n errors,\n })\n }\n this._problem = send as ProblemSender\n return this._problem\n }\n\n private sendProblem(input: ProblemDetails): RuntimeResponse {\n const body = normalizeProblem(input)\n this._response.setHeader('Content-Type', 'application/problem+json')\n return this._response.status(body.status).json(body)\n }\n\n /**\n * Send an HTML response.\n *\n * Passes the full MIME type rather than the `'html'` shorthand. The\n * shorthand is an Express affordance — `res.type()` runs it through a MIME\n * lookup — but the Fastify and h3 drivers forward the string to their own\n * `type()` / `setHeader()` verbatim, so `'html'` reached the wire as a\n * malformed `Content-Type: html` on those engines. Fastify 5.10 started\n * normalising that invalid value to `text/plain`, which is what surfaced it.\n *\n * Express produces `text/html; charset=utf-8` for `.type('html')`, so\n * spelling it out here is byte-identical there and correct everywhere else.\n */\n /**\n * The client's IP address, or `undefined` when it cannot be determined.\n *\n * Prefers the value the runtime computed (Express derives `req.ip` from the\n * app's `trust proxy` setting; Fastify from `trustProxy`), because raw\n * forwarded headers are client-SPOOFABLE on deployments that do not\n * normalize them. The header fallbacks exist for runtimes with no `ip`\n * field at all — notably the web/edge entry.\n *\n * `ctx.req.ip` is Express-only, so reaching for it directly broke on\n * Fastify and h3.\n */\n get ip(): string | undefined {\n return resolveClientIp(this.req as ClientRequestLike)\n }\n\n /**\n * Redirect the client. Works on every runtime.\n *\n * Every engine spells this differently — Express and Fastify have\n * `res.redirect()`, h3 has `sendRedirect(event, location, code)` — and none\n * of them is shared, so this writes the status and `Location` header through\n * the runtime response surface, which all four expose.\n *\n * The response carries no body. Express sends a short text line and h3 an\n * HTML meta-refresh fallback; a bare `Location` with a 3xx is sufficient per\n * RFC 9110, and interpolating the URL into an HTML body would add an\n * injection surface to a value this method already warns is dangerous.\n *\n * ::: warning\n * Never pass an unvalidated user-supplied `url`. An attacker-controlled\n * destination turns this into an open redirect, which is a common phishing\n * primitive. Allow-list the target or keep it relative.\n * :::\n */\n redirect(url: string, status = 302) {\n this._response.status(status).setHeader('location', url)\n return this._response.end()\n }\n\n html(content: string, status = 200) {\n return this._response.status(status).type('text/html; charset=utf-8').send(content)\n }\n\n download(buffer: Buffer, filename: string, contentType = 'application/octet-stream') {\n this._response.setHeader('Content-Disposition', `attachment; filename=\"${filename}\"`)\n this._response.setHeader('Content-Type', contentType)\n return this._response.send(buffer)\n }\n\n /**\n * Render a template using the registered view engine (EJS, Pug, Handlebars, etc.).\n * Requires a ViewAdapter to be configured in bootstrap().\n *\n * @param template - Template name (without extension, relative to viewsDir)\n * @param data - Data to pass to the template\n *\n * @example\n * ```ts\n * ctx.render('dashboard', { user, title: 'Dashboard' })\n * ctx.render('emails/welcome', { name: 'Alice' })\n * ```\n */\n render(template: string, data: Record<string, any> = {}) {\n return this._response.render(template, data)\n }\n\n /**\n * Parse query params and return a standardized paginated response.\n * Calls `ctx.qs()` internally, then wraps your data with pagination meta.\n *\n * @param fetcher - Async function that receives ParsedQuery and returns `{ data, total }`\n * @param fieldConfig - Optional whitelist for filterable, sortable, searchable fields\n *\n * @example\n * ```ts\n * @Get('/')\n * async list(ctx: RequestContext) {\n * return ctx.paginate(\n * async (parsed) => {\n * const data = await db.select().from(users)\n * .where(query.where).limit(parsed.pagination.limit)\n * .offset(parsed.pagination.offset).all()\n * const total = await db.select({ count: count() }).from(users).get()\n * return { data, total: total?.count ?? 0 }\n * },\n * { filterable: ['name', 'role'], sortable: ['createdAt'] },\n * )\n * }\n * ```\n */\n async paginate<T, TConfig extends QueryFieldConfig | undefined = undefined>(\n fetcher: (parsed: TypedParsedQuery<TConfig>) => Promise<{ data: T[]; total: number }>,\n fieldConfig?: TConfig,\n ): Promise<PaginatedResponse<T>> {\n const parsed = this.qs<TConfig>(fieldConfig)\n const { data, total } = await fetcher(parsed)\n const { page, limit } = parsed.pagination\n const totalPages = Math.ceil(total / limit) || 1\n\n const response: PaginatedResponse<T> = {\n data,\n meta: {\n page,\n limit,\n total,\n totalPages,\n hasNext: page < totalPages,\n hasPrev: page > 1,\n },\n }\n\n this.json(response)\n // Sends AND returns the payload. Returning `this.json(...)` handed back\n // the engine's `RuntimeResponse`, so the documented usage —\n // `return ctx.paginate(...)` — emitted `response: RuntimeResponse` into\n // `KickRoutes`, and the typed client offered `.status()` / `.setHeader()`\n // where the caller expected `data` and `meta`.\n //\n // Returning the payload rather than tagging the response reuses the\n // existing return-value inference (the same path `return user` and\n // `return reply(201, user)` take) instead of adding a second mechanism\n // beside `reply`. Sending here is kept for handlers that call `paginate`\n // without returning it; the runtimes only auto-send a returned value when\n // nothing was written (`if (!res.headersSent)`), so there is no double\n // send.\n return response\n }\n\n // ── Server-Sent Events ──────────────────────────────────────────────\n\n /**\n * Start an SSE (Server-Sent Events) stream.\n * Sets the correct headers and returns helpers to send events.\n *\n * Pass a payload type to get typed sends — and `return sse` from the\n * handler so `kick typegen` carries the event type into\n * `KickRoutes.Api`, where `@forinda/kickjs-client`'s `api.stream()`\n * picks it up (the returned handler is ignored at runtime — the\n * response is already streaming).\n *\n * @example\n * ```ts\n * interface Tick { time: string }\n *\n * @Get('/events')\n * async stream(ctx: RequestContext) {\n * const sse = ctx.sse<Tick>()\n *\n * const interval = setInterval(() => {\n * sse.send({ time: new Date().toISOString() }, 'tick')\n * }, 1000)\n *\n * sse.onClose(() => clearInterval(interval))\n * return sse // typed as SseHandler<Tick> → response inference\n * }\n * ```\n */\n /**\n * Send a web-standard `Response` — status, headers (every `Set-Cookie`\n * kept) and body, streamed chunk by chunk — on any runtime. For handing off\n * to libraries that speak `Request`/`Response`, such as the MCP SDK's\n * web-standard transport.\n *\n * Resolves once the body is fully written. Stops reading the body when the\n * client disconnects.\n *\n * @example\n * ```ts\n * @Post('/mcp')\n * async mcp(ctx: RequestContext) {\n * await ctx.sendResponse(await transport.handleRequest(webRequest))\n * }\n * ```\n */\n async sendResponse(response: Response): Promise<void> {\n const headers: Record<string, string | string[]> = {}\n response.headers.forEach((value, name) => {\n if (name !== 'set-cookie') headers[name] = value\n })\n const cookies = response.headers.getSetCookie()\n if (cookies.length > 0) headers['set-cookie'] = cookies\n\n if (!response.body) {\n // Not writeHead() + end(): on the web driver writeHead switches to a\n // streamed Response, which a 204/304 cannot be, and on Fastify end()\n // bypasses the headers set on the reply. send() is each runtime's own\n // buffered reply path.\n this._response.status(response.status)\n for (const [name, value] of Object.entries(headers)) this._response.setHeader(name, value)\n this._response.send('')\n return\n }\n this._response.writeHead(response.status, headers as Record<string, string>)\n // Send the headers now: a stream whose first chunk comes later (SSE)\n // would otherwise leave the client waiting to see the response open.\n this._response.flushHeaders()\n const reader = response.body.getReader()\n const stop = () => {\n reader.cancel().catch(() => {})\n }\n this.signal.addEventListener('abort', stop, { once: true })\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n // Honour backpressure: on a slow client, wait for the socket to drain\n // (or the client to go away) before reading more of the body.\n if (this._response.write(Buffer.from(value)) === false && !this.signal.aborted) {\n await new Promise<void>((resolve) => {\n const resume = () => {\n this.signal.removeEventListener('abort', resume)\n resolve()\n }\n this._response.once('drain', resume)\n this.signal.addEventListener('abort', resume, { once: true })\n })\n }\n }\n } finally {\n this.signal.removeEventListener('abort', stop)\n this._response.end()\n }\n }\n\n /**\n * Keep `promise` running after the response is sent — an audit log, an\n * email, an analytics call. The Node server's `shutdown()` waits for it;\n * `createHandler()` and the web entry hand it to the platform's\n * `waitUntil` when you pass the platform context in. A rejection is\n * logged, never unhandled. Same as the standalone `waitUntil()` export.\n */\n waitUntil(promise: Promise<unknown>): void {\n waitUntil(promise)\n }\n\n sse<T = unknown>(): SseHandler<T> {\n this._response.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n })\n this._response.flushHeaders()\n\n const closeCallbacks: Array<() => void> = []\n\n this.req.on('close', () => {\n for (const cb of closeCallbacks) cb()\n })\n\n return {\n /** Send an SSE event with optional event name and id */\n send: (data: T, event?: string, id?: string) => {\n if (id) this._response.write(`id: ${id}\\n`)\n if (event) this._response.write(`event: ${event}\\n`)\n this._response.write(`data: ${JSON.stringify(data)}\\n\\n`)\n },\n /** Send a comment (keeps connection alive) */\n comment: (text: string) => {\n this._response.write(`: ${text}\\n\\n`)\n },\n /** Register a callback when the client disconnects */\n onClose: (fn: () => void) => {\n closeCallbacks.push(fn)\n },\n /** End the SSE stream */\n close: () => {\n this._response.end()\n },\n }\n }\n}\n\n/**\n * The handle {@link RequestContext.sse} returns. The optional phantom\n * `__sse` property carries the event payload type STRUCTURALLY — never set\n * at runtime — so `kick typegen`'s response inference (handler `return sse`)\n * flows it into `KickRoutes.Api`, and `@forinda/kickjs-client` detects SSE\n * routes without importing any server types.\n */\nexport interface SseHandler<T = unknown> {\n /** Phantom event-payload type marker — never assigned at runtime. */\n readonly __sse?: T\n /** Send an SSE event with optional event name and id. */\n send(data: T, event?: string, id?: string): void\n /** Send a comment line (keeps the connection alive through proxies). */\n comment(text: string): void\n /** Register a callback for client disconnect. */\n onClose(fn: () => void): void\n /** End the SSE stream. */\n close(): void\n}\n\n/**\n * Short alias for `RequestContext` parameterised by a route shape.\n *\n * Use with `kick typegen`'s generated `KickRoutes` namespace for\n * fully-typed `ctx.params`, `ctx.body`, and `ctx.query`. Metadata\n * typing (`ctx.get()`, `ctx.set()`, `ctx.user`) comes from\n * `ContextMeta` augmentation and works on all `RequestContext` instances.\n *\n * @example\n * ```ts\n * @Get('/:id')\n * getUser(ctx: Ctx<KickRoutes.UserController['getUser']>) {\n * ctx.params.id // typed from RouteShape\n * ctx.body // typed from RouteShape\n * ctx.user // typed from ContextMeta\n * ctx.get('tenant') // typed from ContextMeta\n * }\n * ```\n */\nexport type Ctx<TRoute extends RouteShape = RouteShape> = RequestContext<\n TRoute extends { body: infer B } ? B : any,\n TRoute extends { params: infer P } ? P : any,\n TRoute extends { query: infer Q } ? Q : any,\n // Falls back to `string` (no narrowing) whenever typegen couldn't prove\n // the route's full contributor set — see `RouteShape.contextKeys`.\n TRoute extends { contextKeys: infer K }\n ? K extends ContextMetaKey\n ? K\n : ContextMetaKey\n : ContextMetaKey\n>\n\n/**\n * Global ambient registry of typed routes, populated by `kick typegen`.\n *\n * Interfaces inside `KickRoutes` correspond to controller classes (each\n * property one route method, conforming to `RouteShape`) — plus the\n * reserved `KickRoutes.Api`: a flat `'METHOD /mounted/path'` map over the\n * same shapes, consumed by `@forinda/kickjs-client`. Don't name a\n * controller class `Api` — it would declaration-merge into that map.\n *\n * Empty by default — declarations come from `.kickjs/types/kick__routes.ts`\n * generated alongside the rest of the typegen output.\n */\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace KickRoutes {}\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.5.0
2
+ * @forinda/kickjs v8.7.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -10,4 +10,4 @@
10
10
  */
11
11
  var MissingContributorError=class extends Error{key;dependent;route;dependentDefinedAt;constructor(key,dependent,route,dependentDefinedAt){let where=route?` on route ${route}`:``,declaredAt=formatDeclaredAt(dependentDefinedAt);super(`Missing context contributor '${key}' required by '${dependent}'${where}${declaredAt}`),this.name=`MissingContributorError`,this.key=key,this.dependent=dependent,this.route=route,this.dependentDefinedAt=dependentDefinedAt}},MissingContextValueError=class extends Error{key;route;constructor(key,route){let where=route?` on ${route}`:``;super(`No context value for '${key}'${where}\n Nothing wrote this key before the handler ran. Either the contributor\n that produces it isn't applied to this route, or it ran and resolved\n to undefined (check \`optional: true\` contributors).`),this.name=`MissingContextValueError`,this.key=key,this.route=route}};function formatDeclaredAt(stack){if(!stack)return``;let lines=stack.split(`
12
12
  `);for(let line of lines){let trimmed=line.trim();if(trimmed.startsWith(`at `)&&!trimmed.includes(`context-decorator.`)&&!trimmed.includes(`context-errors.`))return`\n declared at ${trimmed.replace(/^at /,``)}`}return``}var ContributorCycleError=class extends Error{cycle;route;constructor(cycle,route){let where=route?` on route ${route}`:``;super(`Cycle in context contributors${where}: ${cycle.join(` → `)}`),this.name=`ContributorCycleError`,this.cycle=Object.freeze([...cycle]),this.route=route}},DuplicateContributorError=class extends Error{key;sources;constructor(key,sources){super(`Duplicate context contributor for key '${key}' at the same precedence level. Sources: ${sources.join(`, `)}`),this.name=`DuplicateContributorError`,this.key=key,this.sources=Object.freeze([...sources])}};export{MissingContributorError as i,DuplicateContributorError as n,MissingContextValueError as r,ContributorCycleError as t};
13
- //# sourceMappingURL=context-errors-Cj2_aDaj.mjs.map
13
+ //# sourceMappingURL=context-errors-VduncZFQ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-errors-Cj2_aDaj.mjs","names":[],"sources":["../src/core/context-errors.ts"],"sourcesContent":["/**\n * Errors thrown by the Context Contributor pipeline.\n *\n * `MissingContributorError`, `ContributorCycleError`, and\n * `DuplicateContributorError` are startup-time errors raised during\n * pipeline build (topo-sort + validation), not per-request — a\n * misconfigured app fails to boot rather than fails a request. Per the\n * locked design in `architecture.md` §20.4, the pipeline is validated\n * once when each route is mounted and the resolved order is cached on\n * the route.\n *\n * `MissingContextValueError` is the one request-time error here: it is\n * raised by `ctx.require(key)` when a value a handler declared it needs\n * is absent. See its doc comment for why that case can't be caught at\n * startup today.\n */\n\n/**\n * A contributor declared `dependsOn: ['<key>']` but no other contributor\n * (at any precedence level) produces that key for the same route.\n *\n * @example\n * ```text\n * MissingContributorError: Missing context contributor 'tenant' required by 'project' on route GET /projects/:id\n * declared at src/contributors/load-project.ts:42:18\n * ```\n */\nexport class MissingContributorError extends Error {\n readonly key: string\n readonly dependent: string\n readonly route?: string\n readonly dependentDefinedAt?: string\n\n constructor(key: string, dependent: string, route?: string, dependentDefinedAt?: string) {\n const where = route ? ` on route ${route}` : ''\n const declaredAt = formatDeclaredAt(dependentDefinedAt)\n super(`Missing context contributor '${key}' required by '${dependent}'${where}${declaredAt}`)\n this.name = 'MissingContributorError'\n this.key = key\n this.dependent = dependent\n this.route = route\n this.dependentDefinedAt = dependentDefinedAt\n }\n}\n\n/**\n * `ctx.require(key)` found no value under `key` in the per-request\n * metadata store.\n *\n * Thrown at request time, by design. `ctx.get(key)` returns\n * `T | undefined` for every key, so the usual workaround is a\n * non-null assertion — `ctx.get('tenant')!` — which compiles whether or\n * not the route actually carries the contributor that produces it. On\n * an authorization value that is the worst possible thing to leave\n * untypeable: dropping the decorator produces no diagnostic anywhere,\n * and the handler reads `undefined` as though it were a real value.\n *\n * `ctx.require()` converts that silent hole into a loud, named failure\n * that points at the missing contributor.\n *\n * A handler typed with a generated route type (`Ctx<KickRoutes…>`) also\n * gets compile-time narrowing, so a dropped decorator fails `tsc` rather\n * than reaching this error at all. This error remains the backstop for\n * routes typegen couldn't prove (module/adapter/bootstrap registrations),\n * handlers typed as plain `RequestContext`, and contributors that ran but\n * resolved to `undefined`. See `architecture.md` §20.14.\n *\n * @example\n * ```text\n * MissingContextValueError: No context value for 'tenantPerm' on GET /projects/:id\n * Nothing wrote this key before the handler ran. Either the contributor\n * that produces it isn't applied to this route, or it ran and resolved\n * to undefined (check `optional: true` contributors).\n * ```\n */\nexport class MissingContextValueError extends Error {\n readonly key: string\n readonly route?: string\n\n constructor(key: string, route?: string) {\n const where = route ? ` on ${route}` : ''\n super(\n `No context value for '${key}'${where}\\n` +\n ` Nothing wrote this key before the handler ran. Either the contributor\\n` +\n ` that produces it isn't applied to this route, or it ran and resolved\\n` +\n ` to undefined (check \\`optional: true\\` contributors).`,\n )\n this.name = 'MissingContextValueError'\n this.key = key\n this.route = route\n }\n}\n\n/**\n * Slice the first non-framework frame off a captured `Error.stack`\n * and format it as `\\n declared at <frame>` for inclusion in\n * boot-time error messages. Returns `''` when no useful frame is\n * available (hand-rolled registrations, missing stacks).\n */\nfunction formatDeclaredAt(stack: string | undefined): string {\n if (!stack) return ''\n const lines = stack.split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('at ')) continue\n // Skip frames inside this file or the decorator factory — the\n // adopter's call site is the next frame after those.\n if (trimmed.includes('context-decorator.')) continue\n if (trimmed.includes('context-errors.')) continue\n return `\\n declared at ${trimmed.replace(/^at /, '')}`\n }\n return ''\n}\n\n/**\n * Topo-sort detected a cycle in `dependsOn` declarations. The `cycle` field\n * lists the keys in the order they form the loop, with the first key\n * repeated at the end so the path reads naturally.\n *\n * @example\n * ```text\n * ContributorCycleError: Cycle in context contributors on route GET /projects/:id: tenant → project → tenant\n * ```\n */\nexport class ContributorCycleError extends Error {\n readonly cycle: readonly string[]\n readonly route?: string\n\n constructor(cycle: readonly string[], route?: string) {\n const where = route ? ` on route ${route}` : ''\n super(`Cycle in context contributors${where}: ${cycle.join(' → ')}`)\n this.name = 'ContributorCycleError'\n this.cycle = Object.freeze([...cycle])\n this.route = route\n }\n}\n\n/**\n * Two contributors at the same precedence level both produce the same key\n * for the same route. Precedence resolution (method > class > module >\n * adapter > global, per architecture.md §20.4) silently drops\n * lower-precedence duplicates; this error fires only when ties occur\n * within a single level — e.g., two method-level decorators declaring\n * `key: 'tenant'`.\n *\n * `sources` is best-effort identifying labels for each conflicting\n * registration (typically the decorator's call site or registering\n * module/adapter name).\n *\n * @example\n * ```text\n * DuplicateContributorError: Duplicate context contributor for key 'tenant' at the same precedence level. Sources: LoadTenantFromHeader, LoadTenantFromSubdomain\n * ```\n */\nexport class DuplicateContributorError extends Error {\n readonly key: string\n readonly sources: readonly string[]\n\n constructor(key: string, sources: readonly string[]) {\n super(\n `Duplicate context contributor for key '${key}' at the same precedence level. ` +\n `Sources: ${sources.join(', ')}`,\n )\n this.name = 'DuplicateContributorError'\n this.key = key\n this.sources = Object.freeze([...sources])\n }\n}\n"],"mappings":";;;;;;;;;;AA2BA,IAAa,wBAAb,cAA6C,KAAM,CACjD,IACA,UACA,MACA,mBAEA,YAAY,IAAa,UAAmB,MAAgB,mBAA6B,CACvF,IAAM,MAAQ,MAAQ,aAAa,QAAU,GACvC,WAAa,iBAAiB,kBAAkB,EACtD,MAAM,gCAAgC,IAAI,iBAAiB,UAAU,GAAG,QAAQ,YAAY,EAC5F,KAAK,KAAO,0BACZ,KAAK,IAAM,IACX,KAAK,UAAY,UACjB,KAAK,MAAQ,MACb,KAAK,mBAAqB,kBAC5B,CACF,EAgCa,yBAAb,cAA8C,KAAM,CAClD,IACA,MAEA,YAAY,IAAa,MAAgB,CACvC,IAAM,MAAQ,MAAQ,OAAO,QAAU,GACvC,MACE,yBAAyB,IAAI,GAAG,MAAM,iNAIxC,EACA,KAAK,KAAO,2BACZ,KAAK,IAAM,IACX,KAAK,MAAQ,KACf,CACF,EAQA,SAAS,iBAAiB,MAAmC,CAC3D,GAAI,CAAC,MAAO,MAAO,GACnB,IAAM,MAAQ,MAAM,MAAM;CAAI,EAC9B,IAAK,IAAM,QAAQ,MAAO,CACxB,IAAM,QAAU,KAAK,KAAK,EACrB,WAAQ,WAAW,KAAK,GAGzB,SAAQ,SAAS,oBAAoB,GACrC,SAAQ,SAAS,iBAAiB,EACtC,MAAO,qBAAqB,QAAQ,QAAQ,OAAQ,EAAE,GACxD,CACA,MAAO,EACT,CAYA,IAAa,sBAAb,cAA2C,KAAM,CAC/C,MACA,MAEA,YAAY,MAA0B,MAAgB,CACpD,IAAM,MAAQ,MAAQ,aAAa,QAAU,GAC7C,MAAM,gCAAgC,MAAM,IAAI,MAAM,KAAK,KAAK,GAAG,EACnE,KAAK,KAAO,wBACZ,KAAK,MAAQ,OAAO,OAAO,CAAC,GAAG,KAAK,CAAC,EACrC,KAAK,MAAQ,KACf,CACF,EAmBa,0BAAb,cAA+C,KAAM,CACnD,IACA,QAEA,YAAY,IAAa,QAA4B,CACnD,MACE,0CAA0C,IAAI,2CAChC,QAAQ,KAAK,IAAI,GACjC,EACA,KAAK,KAAO,4BACZ,KAAK,IAAM,IACX,KAAK,QAAU,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC,CAC3C,CACF"}
1
+ {"version":3,"file":"context-errors-VduncZFQ.mjs","names":[],"sources":["../src/core/context-errors.ts"],"sourcesContent":["/**\n * Errors thrown by the Context Contributor pipeline.\n *\n * `MissingContributorError`, `ContributorCycleError`, and\n * `DuplicateContributorError` are startup-time errors raised during\n * pipeline build (topo-sort + validation), not per-request — a\n * misconfigured app fails to boot rather than fails a request. Per the\n * locked design in `architecture.md` §20.4, the pipeline is validated\n * once when each route is mounted and the resolved order is cached on\n * the route.\n *\n * `MissingContextValueError` is the one request-time error here: it is\n * raised by `ctx.require(key)` when a value a handler declared it needs\n * is absent. See its doc comment for why that case can't be caught at\n * startup today.\n */\n\n/**\n * A contributor declared `dependsOn: ['<key>']` but no other contributor\n * (at any precedence level) produces that key for the same route.\n *\n * @example\n * ```text\n * MissingContributorError: Missing context contributor 'tenant' required by 'project' on route GET /projects/:id\n * declared at src/contributors/load-project.ts:42:18\n * ```\n */\nexport class MissingContributorError extends Error {\n readonly key: string\n readonly dependent: string\n readonly route?: string\n readonly dependentDefinedAt?: string\n\n constructor(key: string, dependent: string, route?: string, dependentDefinedAt?: string) {\n const where = route ? ` on route ${route}` : ''\n const declaredAt = formatDeclaredAt(dependentDefinedAt)\n super(`Missing context contributor '${key}' required by '${dependent}'${where}${declaredAt}`)\n this.name = 'MissingContributorError'\n this.key = key\n this.dependent = dependent\n this.route = route\n this.dependentDefinedAt = dependentDefinedAt\n }\n}\n\n/**\n * `ctx.require(key)` found no value under `key` in the per-request\n * metadata store.\n *\n * Thrown at request time, by design. `ctx.get(key)` returns\n * `T | undefined` for every key, so the usual workaround is a\n * non-null assertion — `ctx.get('tenant')!` — which compiles whether or\n * not the route actually carries the contributor that produces it. On\n * an authorization value that is the worst possible thing to leave\n * untypeable: dropping the decorator produces no diagnostic anywhere,\n * and the handler reads `undefined` as though it were a real value.\n *\n * `ctx.require()` converts that silent hole into a loud, named failure\n * that points at the missing contributor.\n *\n * A handler typed with a generated route type (`Ctx<KickRoutes…>`) also\n * gets compile-time narrowing, so a dropped decorator fails `tsc` rather\n * than reaching this error at all. This error remains the backstop for\n * routes typegen couldn't prove (module/adapter/bootstrap registrations),\n * handlers typed as plain `RequestContext`, and contributors that ran but\n * resolved to `undefined`. See `architecture.md` §20.14.\n *\n * @example\n * ```text\n * MissingContextValueError: No context value for 'tenantPerm' on GET /projects/:id\n * Nothing wrote this key before the handler ran. Either the contributor\n * that produces it isn't applied to this route, or it ran and resolved\n * to undefined (check `optional: true` contributors).\n * ```\n */\nexport class MissingContextValueError extends Error {\n readonly key: string\n readonly route?: string\n\n constructor(key: string, route?: string) {\n const where = route ? ` on ${route}` : ''\n super(\n `No context value for '${key}'${where}\\n` +\n ` Nothing wrote this key before the handler ran. Either the contributor\\n` +\n ` that produces it isn't applied to this route, or it ran and resolved\\n` +\n ` to undefined (check \\`optional: true\\` contributors).`,\n )\n this.name = 'MissingContextValueError'\n this.key = key\n this.route = route\n }\n}\n\n/**\n * Slice the first non-framework frame off a captured `Error.stack`\n * and format it as `\\n declared at <frame>` for inclusion in\n * boot-time error messages. Returns `''` when no useful frame is\n * available (hand-rolled registrations, missing stacks).\n */\nfunction formatDeclaredAt(stack: string | undefined): string {\n if (!stack) return ''\n const lines = stack.split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('at ')) continue\n // Skip frames inside this file or the decorator factory — the\n // adopter's call site is the next frame after those.\n if (trimmed.includes('context-decorator.')) continue\n if (trimmed.includes('context-errors.')) continue\n return `\\n declared at ${trimmed.replace(/^at /, '')}`\n }\n return ''\n}\n\n/**\n * Topo-sort detected a cycle in `dependsOn` declarations. The `cycle` field\n * lists the keys in the order they form the loop, with the first key\n * repeated at the end so the path reads naturally.\n *\n * @example\n * ```text\n * ContributorCycleError: Cycle in context contributors on route GET /projects/:id: tenant → project → tenant\n * ```\n */\nexport class ContributorCycleError extends Error {\n readonly cycle: readonly string[]\n readonly route?: string\n\n constructor(cycle: readonly string[], route?: string) {\n const where = route ? ` on route ${route}` : ''\n super(`Cycle in context contributors${where}: ${cycle.join(' → ')}`)\n this.name = 'ContributorCycleError'\n this.cycle = Object.freeze([...cycle])\n this.route = route\n }\n}\n\n/**\n * Two contributors at the same precedence level both produce the same key\n * for the same route. Precedence resolution (method > class > module >\n * adapter > global, per architecture.md §20.4) silently drops\n * lower-precedence duplicates; this error fires only when ties occur\n * within a single level — e.g., two method-level decorators declaring\n * `key: 'tenant'`.\n *\n * `sources` is best-effort identifying labels for each conflicting\n * registration (typically the decorator's call site or registering\n * module/adapter name).\n *\n * @example\n * ```text\n * DuplicateContributorError: Duplicate context contributor for key 'tenant' at the same precedence level. Sources: LoadTenantFromHeader, LoadTenantFromSubdomain\n * ```\n */\nexport class DuplicateContributorError extends Error {\n readonly key: string\n readonly sources: readonly string[]\n\n constructor(key: string, sources: readonly string[]) {\n super(\n `Duplicate context contributor for key '${key}' at the same precedence level. ` +\n `Sources: ${sources.join(', ')}`,\n )\n this.name = 'DuplicateContributorError'\n this.key = key\n this.sources = Object.freeze([...sources])\n }\n}\n"],"mappings":";;;;;;;;;;AA2BA,IAAa,wBAAb,cAA6C,KAAM,CACjD,IACA,UACA,MACA,mBAEA,YAAY,IAAa,UAAmB,MAAgB,mBAA6B,CACvF,IAAM,MAAQ,MAAQ,aAAa,QAAU,GACvC,WAAa,iBAAiB,kBAAkB,EACtD,MAAM,gCAAgC,IAAI,iBAAiB,UAAU,GAAG,QAAQ,YAAY,EAC5F,KAAK,KAAO,0BACZ,KAAK,IAAM,IACX,KAAK,UAAY,UACjB,KAAK,MAAQ,MACb,KAAK,mBAAqB,kBAC5B,CACF,EAgCa,yBAAb,cAA8C,KAAM,CAClD,IACA,MAEA,YAAY,IAAa,MAAgB,CACvC,IAAM,MAAQ,MAAQ,OAAO,QAAU,GACvC,MACE,yBAAyB,IAAI,GAAG,MAAM,iNAIxC,EACA,KAAK,KAAO,2BACZ,KAAK,IAAM,IACX,KAAK,MAAQ,KACf,CACF,EAQA,SAAS,iBAAiB,MAAmC,CAC3D,GAAI,CAAC,MAAO,MAAO,GACnB,IAAM,MAAQ,MAAM,MAAM;CAAI,EAC9B,IAAK,IAAM,QAAQ,MAAO,CACxB,IAAM,QAAU,KAAK,KAAK,EACrB,WAAQ,WAAW,KAAK,GAGzB,SAAQ,SAAS,oBAAoB,GACrC,SAAQ,SAAS,iBAAiB,EACtC,MAAO,qBAAqB,QAAQ,QAAQ,OAAQ,EAAE,GACxD,CACA,MAAO,EACT,CAYA,IAAa,sBAAb,cAA2C,KAAM,CAC/C,MACA,MAEA,YAAY,MAA0B,MAAgB,CACpD,IAAM,MAAQ,MAAQ,aAAa,QAAU,GAC7C,MAAM,gCAAgC,MAAM,IAAI,MAAM,KAAK,KAAK,GAAG,EACnE,KAAK,KAAO,wBACZ,KAAK,MAAQ,OAAO,OAAO,CAAC,GAAG,KAAK,CAAC,EACrC,KAAK,MAAQ,KACf,CACF,EAmBa,0BAAb,cAA+C,KAAM,CACnD,IACA,QAEA,YAAY,IAAa,QAA4B,CACnD,MACE,0CAA0C,IAAI,2CAChC,QAAQ,KAAK,IAAI,GACjC,EACA,KAAK,KAAO,4BACZ,KAAK,IAAM,IACX,KAAK,QAAU,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC,CAC3C,CACF"}