@forinda/kickjs 5.17.0 → 5.18.0-alpha.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 (118) hide show
  1. package/dist/adapter-D_uzU-QA.d.mts +2121 -0
  2. package/dist/adapter-D_uzU-QA.d.mts.map +1 -0
  3. package/dist/{context-decorator-DDZ30BIW.d.mts → app-module-CuAS6hJC.d.mts} +184 -4
  4. package/dist/app-module-CuAS6hJC.d.mts.map +1 -0
  5. package/dist/application-BU5N6o_M.mjs +12 -0
  6. package/dist/application-BU5N6o_M.mjs.map +1 -0
  7. package/dist/{application-CAnpDQ8d.d.mts → application-pbx53JnX.d.mts} +31 -5
  8. package/dist/application-pbx53JnX.d.mts.map +1 -0
  9. package/dist/{bootstrap-CHmCh_cE.mjs → bootstrap-CFog4v_L.mjs} +3 -3
  10. package/dist/{bootstrap-CHmCh_cE.mjs.map → bootstrap-CFog4v_L.mjs.map} +1 -1
  11. package/dist/{bootstrap-8DDzBjhx.d.mts → bootstrap-DzfBj6Ja.d.mts} +2 -2
  12. package/dist/{bootstrap-8DDzBjhx.d.mts.map → bootstrap-DzfBj6Ja.d.mts.map} +1 -1
  13. package/dist/config/index.mjs +2 -2
  14. package/dist/{config-BvyPboRZ.mjs → config-Di3HlRTS.mjs} +3 -3
  15. package/dist/{config-BvyPboRZ.mjs.map → config-Di3HlRTS.mjs.map} +1 -1
  16. package/dist/{container-CPz0l8-b.mjs → container-BiGimG12.mjs} +3 -3
  17. package/dist/{container-CPz0l8-b.mjs.map → container-BiGimG12.mjs.map} +1 -1
  18. package/dist/context-leZajQAj.mjs +12 -0
  19. package/dist/context-leZajQAj.mjs.map +1 -0
  20. package/dist/core/adapter.d.mts +1 -1
  21. package/dist/core/adapter.mjs +1 -1
  22. package/dist/core/app-module.d.mts +1 -1
  23. package/dist/core/app-module.mjs +1 -1
  24. package/dist/core/cache.mjs +1 -1
  25. package/dist/core/container.mjs +2 -2
  26. package/dist/core/cron.mjs +2 -2
  27. package/dist/core/decorators.mjs +2 -2
  28. package/dist/core/errors.d.mts +1 -1
  29. package/dist/core/errors.mjs +2 -2
  30. package/dist/core/errors.mjs.map +1 -1
  31. package/dist/core/interfaces.mjs +1 -1
  32. package/dist/core/logger.mjs +1 -1
  33. package/dist/core/path.mjs +1 -1
  34. package/dist/core/plugin.d.mts +1 -1
  35. package/dist/core/plugin.mjs +1 -1
  36. package/dist/core/reactivity.mjs +1 -1
  37. package/dist/{core-C_gfh7JQ.mjs → core-Y_xSqZ7v.mjs} +3 -3
  38. package/dist/{core-C_gfh7JQ.mjs.map → core-Y_xSqZ7v.mjs.map} +1 -1
  39. package/dist/{env-LIrKUSAJ.mjs → env-BoMBrGWs.mjs} +3 -3
  40. package/dist/{env-LIrKUSAJ.mjs.map → env-BoMBrGWs.mjs.map} +1 -1
  41. package/dist/{errors-CRCTjEUK.d.mts → errors-n0H96eEz.d.mts} +3 -1
  42. package/dist/errors-n0H96eEz.d.mts.map +1 -0
  43. package/dist/http/application.d.mts +1 -1
  44. package/dist/http/application.mjs +2 -2
  45. package/dist/http/bootstrap.d.mts +1 -1
  46. package/dist/http/bootstrap.mjs +2 -2
  47. package/dist/http/context.d.mts +2 -2
  48. package/dist/http/context.mjs +2 -2
  49. package/dist/http/middleware/cors.mjs +1 -1
  50. package/dist/http/middleware/csrf.mjs +1 -1
  51. package/dist/http/middleware/error-handler.mjs +2 -2
  52. package/dist/http/middleware/helmet.mjs +1 -1
  53. package/dist/http/middleware/rate-limit.mjs +1 -1
  54. package/dist/http/middleware/request-id.mjs +1 -1
  55. package/dist/http/middleware/request-logger.mjs +2 -2
  56. package/dist/http/middleware/session.mjs +1 -1
  57. package/dist/http/middleware/spa.d.mts +1 -1
  58. package/dist/http/middleware/spa.mjs +2 -2
  59. package/dist/http/middleware/upload.d.mts +2 -2
  60. package/dist/http/middleware/upload.mjs +3 -3
  61. package/dist/http/middleware/upload.mjs.map +1 -1
  62. package/dist/http/middleware/validate.mjs +2 -2
  63. package/dist/http/middleware/views.d.mts +1 -1
  64. package/dist/http/middleware/views.mjs +2 -2
  65. package/dist/http/query/index.mjs +2 -2
  66. package/dist/http/router-builder.d.mts +2 -2
  67. package/dist/http/router-builder.mjs +2 -2
  68. package/dist/http/router-builder.mjs.map +1 -1
  69. package/dist/http/runtimes/fastify.d.mts +66 -0
  70. package/dist/http/runtimes/fastify.d.mts.map +1 -0
  71. package/dist/http/runtimes/fastify.mjs +13 -0
  72. package/dist/http/runtimes/fastify.mjs.map +1 -0
  73. package/dist/http/runtimes/h3.d.mts +29 -0
  74. package/dist/http/runtimes/h3.d.mts.map +1 -0
  75. package/dist/http/runtimes/h3.mjs +12 -0
  76. package/dist/http/runtimes/h3.mjs.map +1 -0
  77. package/dist/index.d.mts +11 -16
  78. package/dist/index.mjs +2 -2
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/{metadata-D8hSFaux.mjs → metadata-Pvgc-K8a.mjs} +2 -2
  81. package/dist/{metadata-D8hSFaux.mjs.map → metadata-Pvgc-K8a.mjs.map} +1 -1
  82. package/dist/{query-NhwucLHS.mjs → query-B3PhfU6M.mjs} +2 -2
  83. package/dist/{query-NhwucLHS.mjs.map → query-B3PhfU6M.mjs.map} +1 -1
  84. package/dist/request-scope-BaM8_e_8.mjs +12 -0
  85. package/dist/request-scope-BaM8_e_8.mjs.map +1 -0
  86. package/dist/router-builder-Bh3qSiDz.d.mts +39 -0
  87. package/dist/router-builder-Bh3qSiDz.d.mts.map +1 -0
  88. package/dist/{spa-IRFC2eMN.d.mts → spa-BdvZFXJz.d.mts} +3 -2
  89. package/dist/spa-BdvZFXJz.d.mts.map +1 -0
  90. package/dist/{upload-D6edAELH.d.mts → upload-Bc_rBRtE.d.mts} +37 -2
  91. package/dist/upload-Bc_rBRtE.d.mts.map +1 -0
  92. package/dist/{views-DkllbVB3.d.mts → views-DcTVDiOh.d.mts} +31 -7
  93. package/dist/views-DcTVDiOh.d.mts.map +1 -0
  94. package/package.json +29 -1
  95. package/dist/adapter-CkpcZwzZ.d.mts +0 -407
  96. package/dist/adapter-CkpcZwzZ.d.mts.map +0 -1
  97. package/dist/app-module-BTzb-Vd6.d.mts +0 -92
  98. package/dist/app-module-BTzb-Vd6.d.mts.map +0 -1
  99. package/dist/application-CAnpDQ8d.d.mts.map +0 -1
  100. package/dist/application-CRmMytW1.mjs +0 -12
  101. package/dist/application-CRmMytW1.mjs.map +0 -1
  102. package/dist/context-C6e-S0uc.mjs +0 -12
  103. package/dist/context-C6e-S0uc.mjs.map +0 -1
  104. package/dist/context-DV7RjM9F.d.mts +0 -445
  105. package/dist/context-DV7RjM9F.d.mts.map +0 -1
  106. package/dist/context-decorator-DDZ30BIW.d.mts.map +0 -1
  107. package/dist/errors-CRCTjEUK.d.mts.map +0 -1
  108. package/dist/execution-context-B4cfhivz.d.mts +0 -98
  109. package/dist/execution-context-B4cfhivz.d.mts.map +0 -1
  110. package/dist/index-frSEHiL_.d.mts +0 -796
  111. package/dist/index-frSEHiL_.d.mts.map +0 -1
  112. package/dist/plugin-BQCn6zyj.d.mts +0 -260
  113. package/dist/plugin-BQCn6zyj.d.mts.map +0 -1
  114. package/dist/router-builder-oQpcQMWl.d.mts +0 -31
  115. package/dist/router-builder-oQpcQMWl.d.mts.map +0 -1
  116. package/dist/spa-IRFC2eMN.d.mts.map +0 -1
  117. package/dist/upload-D6edAELH.d.mts.map +0 -1
  118. package/dist/views-DkllbVB3.d.mts.map +0 -1
@@ -0,0 +1,2121 @@
1
+
2
+ import { i as Constructor, o as MaybePromise } from "./interfaces-DbTyKGdS.mjs";
3
+ import { s as InjectionToken, t as Container } from "./container-TWOUyR_0.mjs";
4
+ import { O as RouteDefinition, g as FileUploadConfig, x as MiddlewareHandler } from "./decorators-ejABLo9H.mjs";
5
+ import { _ as ExecutionContext, a as AnyContributorRegistration, d as ContributorRegistrations, r as AppModuleEntry, t as AppModule, v as MetaValue } from "./app-module-CuAS6hJC.mjs";
6
+ import { i as ProblemDetails } from "./errors-n0H96eEz.mjs";
7
+ import { S as QueryFieldConfig, T as TypedParsedQuery, t as ParseQueryOptions } from "./index-BtWJ9SLP.mjs";
8
+ import http, { IncomingMessage, ServerResponse } from "node:http";
9
+ import { ErrorRequestHandler, Express as Express$1, NextFunction, Request, RequestHandler, Response } from "express";
10
+
11
+ //#region src/core/introspect.d.ts
12
+ /**
13
+ * Per-primitive introspection snapshot — the JSON shape an adapter or
14
+ * plugin's optional `introspect()` hook returns for DevTools.
15
+ *
16
+ * Lives in `@forinda/kickjs` (not `@forinda/kickjs-devtools-kit`) so
17
+ * adopters can type their `introspect()` return without taking on a
18
+ * dep on the kit just to satisfy the contract. The kit's own
19
+ * `IntrospectionSnapshot` type is structurally identical and continues
20
+ * to be the canonical doc location for DevTools-side consumers; the
21
+ * two stay in lockstep — if a field is added on one side it MUST be
22
+ * added on the other (covered by the type-equivalence test in the
23
+ * devtools-kit package).
24
+ *
25
+ * Cheap-by-default principle: `state` and `metrics` should be O(1) to
26
+ * compute (counters, flags, recent samples). Anything that requires a
27
+ * DB round trip or a full DI graph walk belongs behind a separate
28
+ * explicit RPC — DevTools polls topology aggressively.
29
+ */
30
+ interface IntrospectionSnapshot {
31
+ /**
32
+ * Wire-protocol version this snapshot conforms to. The kit ships a
33
+ * `PROTOCOL_VERSION` constant typed as the literal `1`; we mirror
34
+ * that literal here so structural parity is enforced both ways —
35
+ * an adopter who writes `protocolVersion: 1` satisfies both this
36
+ * type and the kit's `IntrospectionSnapshot`. Bumping is a
37
+ * coordinated kit + framework release; widen this literal in
38
+ * lockstep.
39
+ */
40
+ protocolVersion: 1;
41
+ /** Stable identity matching the `name` from `definePlugin` / `defineAdapter`. */
42
+ name: string;
43
+ /** Discriminator for what kind of primitive this is. */
44
+ kind: IntrospectionKind;
45
+ /** Optional version string from the plugin / adapter metadata. */
46
+ version?: string;
47
+ /**
48
+ * Reactive state surface — JSON-serialisable snapshot. Keep small
49
+ * (under a few KB) so polling stays cheap.
50
+ */
51
+ state?: Record<string, unknown>;
52
+ /** DI tokens this primitive registers (`provides`) or consumes (`requires`). */
53
+ tokens?: {
54
+ provides: readonly string[];
55
+ requires: readonly string[];
56
+ };
57
+ /**
58
+ * Per-instance counters — active connections, in-flight jobs, cached
59
+ * items, etc. Numbers only so the DevTools panel can chart trends.
60
+ */
61
+ metrics?: Record<string, number>;
62
+ /** Self-reported memory footprint estimate, in bytes. Optional. */
63
+ memoryBytes?: number;
64
+ }
65
+ /**
66
+ * What kind of primitive an introspection snapshot describes. Matches
67
+ * the kit's `IntrospectionKind` and the routing the topology aggregator
68
+ * does when bucketing adapters vs plugins vs middleware vs contributor
69
+ * primitives.
70
+ */
71
+ type IntrospectionKind = 'plugin' | 'adapter' | 'middleware' | 'contributor';
72
+ /**
73
+ * Convenience function signature for adopter-side helpers that return
74
+ * a complete snapshot. Used by the optional `introspect?()` hook on
75
+ * `AppAdapter` and `KickPlugin`.
76
+ */
77
+ type IntrospectFn = () => IntrospectionSnapshot | Promise<IntrospectionSnapshot>;
78
+ //#endregion
79
+ //#region src/core/augmentation.d.ts
80
+ /**
81
+ * Augmentation registry — runtime no-op + the canonical
82
+ * `KickJsPluginRegistry` interface that `kick typegen` augments.
83
+ *
84
+ * See `architecture.md` §21.2.1 (typegen for `dependsOn`) and §21.3.3
85
+ * (standardized augmentation registry) for the design rationale.
86
+ *
87
+ * @module @forinda/kickjs/core/augmentation
88
+ */
89
+ /**
90
+ * Marker interface that lists every plugin/adapter in the project. The
91
+ * keys are the literal `name` field passed to `defineAdapter` /
92
+ * `definePlugin` (or declared on a `class implements AppAdapter`); the
93
+ * values are the kind tag (`'plugin' | 'adapter'`).
94
+ *
95
+ * The framework ships this empty. `kick typegen` augments it from
96
+ * source so `keyof KickJsPluginRegistry` resolves to a string-literal
97
+ * union of every name in scope — and therefore so do `dependsOn`
98
+ * declarations on plugins/adapters.
99
+ */
100
+ interface KickJsPluginRegistry {}
101
+ /**
102
+ * Resolves to a string-literal union of every registered plugin/adapter
103
+ * name when the project's typegen has populated `KickJsPluginRegistry`,
104
+ * or to `string` when the registry is empty (no typegen pass yet).
105
+ *
106
+ * Falling back to `string` keeps fresh projects compiling — the typegen
107
+ * narrowing is a progressive enhancement, not a hard prerequisite.
108
+ */
109
+ type KickJsPluginName = keyof KickJsPluginRegistry extends never ? string : keyof KickJsPluginRegistry & string;
110
+ /**
111
+ * Metadata attached to a `defineAugmentation` call. The fields are
112
+ * documentation-only — `kick typegen` surfaces them in the generated
113
+ * `.kickjs/types/augmentations.d.ts` so adopters can browse every
114
+ * augmentable interface from one place.
115
+ *
116
+ * `description` and `example` may both be multi-line — typegen preserves
117
+ * line breaks when rendering them as JSDoc. There is no upper limit on
118
+ * the example size; entire shape definitions or worked snippets are fine.
119
+ */
120
+ interface AugmentationMeta {
121
+ /**
122
+ * Free-form description of what the augmentation customises. May be
123
+ * multi-line — newlines are preserved in the generated JSDoc.
124
+ */
125
+ description?: string;
126
+ /**
127
+ * TS snippet showing a typical shape. May be multi-line; rendered
128
+ * inside a fenced code block in the generated catalogue. Use this
129
+ * for anything from `'{ foo: string }'` up to a full interface body
130
+ * — typegen no longer flattens newlines.
131
+ */
132
+ example?: string;
133
+ }
134
+ /**
135
+ * Advertise an augmentable interface so `kick typegen` can list it in
136
+ * `.kickjs/types/augmentations.d.ts` for project-wide discovery.
137
+ *
138
+ * **This function is a runtime AND type-level no-op.** It does NOT
139
+ * augment the interface for TypeScript. The actual augmentation is
140
+ * the `declare module` block — `defineAugmentation` only adds an
141
+ * entry to the typegen catalogue so other contributors / adopters
142
+ * can find the interface and know its expected shape.
143
+ *
144
+ * Both calls are needed:
145
+ *
146
+ * ```ts
147
+ * import { defineAugmentation } from '@forinda/kickjs'
148
+ *
149
+ * // (1) The actual TS augmentation — what `ctx.get('tenant')` reads.
150
+ * declare module '@forinda/kickjs' {
151
+ * interface ContextMeta {
152
+ * tenant: { id: string; name: string }
153
+ * }
154
+ * }
155
+ *
156
+ * // (2) Catalogue entry — what `kick typegen` lists in
157
+ * // `.kickjs/types/augmentations.d.ts` for browseability.
158
+ * defineAugmentation('ContextMeta', {
159
+ * description: 'Tenant resolved from x-tenant-id by TenantAdapter',
160
+ * example: `{
161
+ * tenant: {
162
+ * id: string
163
+ * name: string
164
+ * plan: 'free' | 'pro' | 'enterprise'
165
+ * featureFlags: Record<string, boolean>
166
+ * }
167
+ * }`,
168
+ * })
169
+ * ```
170
+ *
171
+ * Skip `defineAugmentation` if you don't care about the catalogue — the
172
+ * `declare module` block alone is enough for runtime + type behaviour.
173
+ *
174
+ * @deprecated Low value in practice: it does nothing at runtime or at
175
+ * the type level — the `declare module` block alone gives you the
176
+ * augmentation, and the generated `.kickjs/types/kick__augmentations.d.ts`
177
+ * catalogue it feeds is documentation-only. Prefer a plain
178
+ * `declare module '@forinda/kickjs' { ... }` block and (optionally) a
179
+ * JSDoc comment on your own interface. `defineAugmentation` and the
180
+ * `kick/augmentations` typegen plugin will be removed in a future major.
181
+ */
182
+ declare function defineAugmentation(name: string, meta?: AugmentationMeta): void;
183
+ //#endregion
184
+ //#region src/core/module-registry.d.ts
185
+ /**
186
+ * Imperative receiver passed to {@link KickPlugin.setup} and the
187
+ * top-level `bootstrap({ setup })` callback so adopters can register
188
+ * modules conditionally — based on env flags, runtime config,
189
+ * tenant lists, etc. — instead of being limited to the static
190
+ * `modules: [...]` array.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * bootstrap({
195
+ * modules: [HelloModule()], // static — always mounted
196
+ * setup(registry) {
197
+ * if (env.ENABLE_ADMIN) registry.mount(AdminModule())
198
+ * for (const tenant of env.TENANTS) {
199
+ * registry.mount(TenantModule.scoped(tenant.id, { id: tenant.id }))
200
+ * }
201
+ * },
202
+ * })
203
+ * ```
204
+ *
205
+ * **Currently exposes only `.mount(module)` — the HTTP-feature path.**
206
+ * A future `.use(module)` for non-HTTP modules (queues, cron, workers,
207
+ * DI-only seeds) is planned but not yet implemented; existing non-HTTP
208
+ * modules continue returning `null` from `routes()` and registering
209
+ * via `.mount()`.
210
+ *
211
+ * Same dispatch rules as the static `modules: [...]` array — class
212
+ * entries get `new`-ed at boot, `defineModule` factory output is used
213
+ * directly. The registry doesn't validate the entry shape itself; the
214
+ * Application loader does the typeof discrimination at mount time.
215
+ */
216
+ interface ModuleRegistry {
217
+ /**
218
+ * Register a module for the full HTTP lifecycle: `register()` runs,
219
+ * `import.meta.glob` decorator side-effects fire, `routes()` is
220
+ * called and its result mounted, `contributors()` are merged at
221
+ * `'module'` precedence and applied to the mounted routes.
222
+ *
223
+ * Modules are mounted in registration order (first call → first
224
+ * mount). Order across sources is: plugin static modules → plugin
225
+ * `setup()` calls → user static modules → user `setup()` callback.
226
+ */
227
+ mount(module: AppModuleEntry): void;
228
+ }
229
+ /**
230
+ * Internal collector implementing {@link ModuleRegistry}. Application
231
+ * passes one of these to every `setup()` callback during bootstrap;
232
+ * the collected `entries` array is then run through the same
233
+ * class-vs-instance dispatch as the static `modules: [...]` array.
234
+ *
235
+ * Not exported from the public surface — adopters interact only
236
+ * through the {@link ModuleRegistry} interface.
237
+ */
238
+ declare class MutableModuleRegistry implements ModuleRegistry {
239
+ readonly entries: AppModuleEntry[];
240
+ mount(module: AppModuleEntry): void;
241
+ }
242
+ //#endregion
243
+ //#region src/core/plugin.d.ts
244
+ /**
245
+ * Plugin interface for extending KickJS applications.
246
+ *
247
+ * Plugins bundle modules, adapters, middleware, and DI bindings into a single
248
+ * reusable unit. They run before the application bootstraps, so they can
249
+ * register services and configure the app before any module loads.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * import type { KickPlugin } from '@forinda/kickjs'
254
+ *
255
+ * export class CorsPlugin implements KickPlugin {
256
+ * name = 'CorsPlugin'
257
+ *
258
+ * middleware() {
259
+ * return [cors({ origin: '*' })]
260
+ * }
261
+ * }
262
+ *
263
+ * // Usage:
264
+ * bootstrap({
265
+ * modules,
266
+ * plugins: [new CorsPlugin()],
267
+ * })
268
+ * ```
269
+ *
270
+ * @example
271
+ * ```ts
272
+ * // A more complex plugin that bundles modules and adapters
273
+ * export class AuthPlugin implements KickPlugin {
274
+ * name = 'AuthPlugin'
275
+ *
276
+ * constructor(private config: AuthConfig) {}
277
+ *
278
+ * register(container: Container) {
279
+ * container.registerFactory(AUTH_SERVICE, () => new JwtAuthService(this.config))
280
+ * }
281
+ *
282
+ * modules() {
283
+ * return [AuthModule]
284
+ * }
285
+ *
286
+ * adapters() {
287
+ * return [new AuthGuardAdapter(this.config)]
288
+ * }
289
+ *
290
+ * middleware() {
291
+ * return [passport.initialize()]
292
+ * }
293
+ * }
294
+ * ```
295
+ */
296
+ interface KickPlugin {
297
+ /** Human-readable name for logging, debugging, and `dependsOn` resolution. */
298
+ name: string;
299
+ /**
300
+ * Other plugin names that must mount before this one. The framework
301
+ * topologically sorts plugins at boot — cycles or unknown names throw
302
+ * via {@link MountCycleError} / {@link MissingMountDepError} so bad
303
+ * configurations fail boot rather than corrupt live traffic.
304
+ *
305
+ * Plugins with no `dependsOn` retain their declaration order, so
306
+ * existing apps that don't use this field see no behaviour change.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * class MonitoringPlugin implements KickPlugin {
311
+ * name = 'MonitoringPlugin'
312
+ * // OTel must initialize traces before request logger reads them
313
+ * dependsOn = ['OtelPlugin']
314
+ * }
315
+ * ```
316
+ */
317
+ dependsOn?: readonly KickJsPluginName[];
318
+ /**
319
+ * Register DI bindings before modules load.
320
+ * Use this to provide services that modules depend on.
321
+ */
322
+ register?(container: Container): void;
323
+ /**
324
+ * Return modules to be loaded alongside user modules. Plugin
325
+ * modules are loaded before user modules.
326
+ *
327
+ * Accepts both class form (`SomeModule extends AppModule`) and
328
+ * instance form (output of {@link defineModule}'s factory call,
329
+ * e.g. `TasksModule({ scope: 'admin' })`) — the bootstrap loader
330
+ * discriminates and either `new`-s the class or uses the instance
331
+ * directly.
332
+ *
333
+ * Static path — for conditional / dynamic registration, use
334
+ * {@link KickPlugin.setup} instead.
335
+ */
336
+ modules?(): AppModuleEntry[];
337
+ /**
338
+ * Imperative module registration hook. Receives a {@link ModuleRegistry}
339
+ * the plugin can call `.mount(module)` on to register modules
340
+ * conditionally — e.g. based on injected config, env flags, or a
341
+ * tenant list resolved at boot.
342
+ *
343
+ * Runs after the static {@link KickPlugin.modules} array is collected
344
+ * but before user modules and the user-level `setup()` callback. The
345
+ * plugin sees the same dispatch rules as everything else: class
346
+ * entries get `new`-ed, factory output is used directly.
347
+ *
348
+ * @example
349
+ * ```ts
350
+ * definePlugin<{ tenants: string[] }>({
351
+ * name: 'MultiTenantPlugin',
352
+ * build: (config) => ({
353
+ * setup(registry) {
354
+ * for (const tenant of config.tenants) {
355
+ * registry.mount(TenantModule.scoped(tenant, { id: tenant }))
356
+ * }
357
+ * },
358
+ * }),
359
+ * })
360
+ * ```
361
+ */
362
+ setup?(registry: ModuleRegistry): void;
363
+ /**
364
+ * Return adapter instances to be added to the application.
365
+ * Plugin adapters are added before user adapters.
366
+ */
367
+ adapters?(): AppAdapter[];
368
+ /**
369
+ * Return Express middleware to be added to the global pipeline.
370
+ * Plugin middleware runs before user-defined middleware.
371
+ */
372
+ middleware?(): any[];
373
+ /**
374
+ * Return Context Contributors (#107) the plugin ships. Contributors
375
+ * returned here merge into the per-route pipeline at the **`'adapter'`
376
+ * precedence level** — same as those returned by adapters the plugin
377
+ * ships via {@link KickPlugin.adapters}, and for the same reason: a
378
+ * plugin is a cross-cutting bundle, narrower than the global default
379
+ * but broader than a per-module hook.
380
+ *
381
+ * Use when a plugin wants to ship a typed contributor without standing
382
+ * up an accompanying adapter. For full coverage of the precedence
383
+ * matrix (method > class > module > adapter > global) see
384
+ * `docs/guide/context-decorators.md`.
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * import { defineContextDecorator, type KickPlugin } from '@forinda/kickjs'
389
+ *
390
+ * const LoadFlags = defineContextDecorator({
391
+ * key: 'flags',
392
+ * resolve: (ctx) => fetchFlags(ctx.requestId!),
393
+ * })
394
+ *
395
+ * export class FlagsPlugin implements KickPlugin {
396
+ * name = 'FlagsPlugin'
397
+ * contributors() {
398
+ * return [LoadFlags.registration]
399
+ * }
400
+ * }
401
+ * ```
402
+ */
403
+ contributors?(): ContributorRegistrations;
404
+ /**
405
+ * Called after the application has fully bootstrapped.
406
+ * Use for post-startup logic like logging, health registration, etc.
407
+ */
408
+ onReady?(container: Container): void | Promise<void>;
409
+ /**
410
+ * Called during application shutdown.
411
+ * Clean up plugin resources (connections, intervals, etc.).
412
+ */
413
+ shutdown?(): void | Promise<void>;
414
+ /**
415
+ * Optional DevTools introspection hook (architecture.md §23). Returns
416
+ * a snapshot describing this plugin's current state, metrics, and DI
417
+ * tokens. DevTools awaits the result, so async work is fine — but keep
418
+ * the implementation cheap (counters + flags, no DB round trips) since
419
+ * the topology endpoint polls on a short interval.
420
+ *
421
+ * The return type lives in `@forinda/kickjs` itself
422
+ * ({@link IntrospectionSnapshot}) so adopters can author `introspect()`
423
+ * without importing the kit just to satisfy the contract. The kit's
424
+ * own `IntrospectionSnapshot` type stays structurally identical;
425
+ * either import works.
426
+ */
427
+ introspect?(): IntrospectionSnapshot | Promise<IntrospectionSnapshot>;
428
+ /**
429
+ * Optional DevTools tabs this plugin contributes (architecture.md §23).
430
+ * Same shape as {@link AppAdapter.devtoolsTabs}; the kit's
431
+ * `DevtoolsTabDescriptor` types it for plugin authors.
432
+ */
433
+ devtoolsTabs?(): readonly unknown[];
434
+ }
435
+ //#endregion
436
+ //#region src/core/define-plugin.d.ts
437
+ /**
438
+ * Options passed to {@link definePlugin}. Mirrors the spec in
439
+ * `architecture.md` §21.3.1 + §21.2.2 (Phase A "lite").
440
+ */
441
+ interface DefinePluginOptions<TConfig> {
442
+ /** Stable identity used for logging, `dependsOn` lookups, and `.scoped()` namespacing. */
443
+ name: string;
444
+ /** Optional plugin version — surfaced to DevTools and `kick add` compatibility checks (Phase B). */
445
+ version?: string;
446
+ /**
447
+ * Peer-version ranges this plugin needs from the framework or other
448
+ * plugins. Recorded as metadata; runtime enforcement lands in §21.4.4.
449
+ */
450
+ requires?: {
451
+ kickjs?: string;
452
+ };
453
+ /** Default config merged under any caller overrides. */
454
+ defaults?: Partial<TConfig>;
455
+ /** Builds the underlying {@link KickPlugin} from the resolved config. */
456
+ build(config: TConfig, ctx: BuildContext): Omit<KickPlugin, 'name'>;
457
+ }
458
+ /**
459
+ * Hook context handed to a plugin/adapter `build` function. `name` is the
460
+ * resolved instance name (the bare definition name for the bare call,
461
+ * or `${defName}:${scope}` for `.scoped()`). `scoped` is the discriminator
462
+ * authors use to namespace DI tokens or resource keys.
463
+ */
464
+ interface BuildContext {
465
+ name: string;
466
+ scoped: boolean;
467
+ }
468
+ /**
469
+ * Async-config form of a plugin or adapter — `inject` lists DI tokens to
470
+ * resolve from the container, then `useFactory` produces the config.
471
+ *
472
+ * Plugins resolve this lazily inside `onReady`, so any module/adapter/
473
+ * middleware/contributor a plugin contributes via `build` is **not
474
+ * available** when `.async()` is used. Use the bare/`.scoped()` form for
475
+ * plugins that need to register modules or middleware.
476
+ */
477
+ interface PluginAsyncOptions<TConfig> {
478
+ inject?: ReadonlyArray<unknown>;
479
+ useFactory(...deps: any[]): TConfig | Promise<TConfig>;
480
+ }
481
+ /**
482
+ * Factory returned by {@link definePlugin}. Callable form is the singleton
483
+ * (`AuthPlugin(config)`); `.scoped()` and `.async()` are the multi-instance
484
+ * and deferred-config forms.
485
+ */
486
+ interface PluginFactory<TConfig> {
487
+ (config?: Partial<TConfig>): KickPlugin;
488
+ scoped(scopeName: string, config?: Partial<TConfig>): KickPlugin;
489
+ async(opts: PluginAsyncOptions<TConfig>): KickPlugin;
490
+ /** Read-only access to the original definition — useful for DevTools introspection. */
491
+ readonly definition: Readonly<DefinePluginOptions<TConfig>>;
492
+ }
493
+ /**
494
+ * Build a {@link KickPlugin} factory from a typed definition. See
495
+ * `architecture.md` §21.2.2 for the design rationale.
496
+ *
497
+ * @example
498
+ * ```ts
499
+ * const FlagsPlugin = definePlugin<FlagsConfig>({
500
+ * name: 'FlagsPlugin',
501
+ * defaults: { defaultTtl: 60_000 },
502
+ * build(config) {
503
+ * return {
504
+ * register(container) {
505
+ * container.registerInstance(FLAGS, makeProvider(config))
506
+ * },
507
+ * }
508
+ * },
509
+ * })
510
+ *
511
+ * bootstrap({ plugins: [FlagsPlugin({ provider: launchDarkly })] })
512
+ * ```
513
+ */
514
+ declare function definePlugin<TConfig = Record<string, unknown>>(options: DefinePluginOptions<TConfig>): PluginFactory<TConfig>;
515
+ //#endregion
516
+ //#region src/core/define-module.d.ts
517
+ /**
518
+ * Options passed to {@link defineModule}. Mirrors {@link DefineAdapterOptions}
519
+ * and {@link DefinePluginOptions} so adopters learn one mental model
520
+ * across all three primitives.
521
+ *
522
+ * The optional `TExtra` generic lets a module ship public methods
523
+ * beyond the standard {@link AppModule} contract — they're preserved
524
+ * on the returned module instance so external callers (tests, sibling
525
+ * modules) can invoke them directly.
526
+ */
527
+ interface DefineModuleOptions<TConfig, TExtra = unknown> {
528
+ /**
529
+ * Stable identity surfaced in diagnostics, route logs, and
530
+ * `.scoped()` namespacing. Required because boot-time error
531
+ * messages reference modules by name — anonymous modules make
532
+ * cycles + missing-dep errors hard to localize.
533
+ */
534
+ name: string;
535
+ /** Optional version string surfaced in diagnostics. */
536
+ version?: string;
537
+ /**
538
+ * Optional kickjs version constraint. Enforced by the framework
539
+ * when implemented; today it lives on the definition for
540
+ * documentation and forward compatibility.
541
+ */
542
+ requires?: {
543
+ kickjs?: string;
544
+ };
545
+ /**
546
+ * Default config merged with overrides at instantiation time
547
+ * (`MyModule({ field: x })`). Call-site overrides win.
548
+ */
549
+ defaults?: Partial<TConfig>;
550
+ /**
551
+ * Build the AppModule shape from the resolved config and context.
552
+ * `ctx.name` is the resolved instance name (the bare definition
553
+ * name for the default call, or `${name}:${scope}` for
554
+ * `.scoped()`); `ctx.scoped` discriminates so the build function
555
+ * can branch on namespacing if needed.
556
+ *
557
+ * Return any extension methods alongside the standard
558
+ * {@link AppModule} surface — they're typed via `TExtra` and stay
559
+ * accessible on the resulting instance.
560
+ */
561
+ build(config: TConfig, ctx: BuildContext): AppModule & TExtra;
562
+ }
563
+ /**
564
+ * Factory returned by {@link defineModule}. Mirrors {@link AdapterFactory}
565
+ * — call (`MyModule({...})`), scope (`MyModule.scoped('public', {...})`),
566
+ * inspect (`MyModule.definition`).
567
+ *
568
+ * `.async()` is intentionally **not** part of the module factory
569
+ * surface. Module config has no async-resolution window: `register()`
570
+ * and `routes()` both run synchronously during bootstrap, before any
571
+ * adapter `beforeStart` hook fires. Adopters that need async-resolved
572
+ * config push it into an adapter (which has `beforeStart`), then
573
+ * inject the resolved value into the module via DI tokens.
574
+ */
575
+ interface ModuleFactory<TConfig, TExtra = unknown> {
576
+ (config?: Partial<TConfig>): AppModule & TExtra;
577
+ scoped(scopeName: string, config?: Partial<TConfig>): AppModule & TExtra;
578
+ readonly definition: Readonly<DefineModuleOptions<TConfig, TExtra>>;
579
+ }
580
+ /**
581
+ * Build an {@link AppModule} factory from a typed definition. Pairs
582
+ * with {@link defineAdapter}, {@link definePlugin}, and
583
+ * {@link defineContextDecorator} as the fourth `define*` primitive
584
+ * — adopters write modules the same way they write adapters and
585
+ * plugins.
586
+ *
587
+ * The returned factory produces real {@link AppModule} instances
588
+ * that drop into `bootstrap({ modules })` directly. The legacy
589
+ * class form (`class TasksModule implements AppModule { ... }`) keeps
590
+ * working — `bootstrap` accepts either shape.
591
+ *
592
+ * @example
593
+ * ```ts
594
+ * interface TasksConfig {
595
+ * scope: 'public' | 'admin'
596
+ * }
597
+ *
598
+ * const TasksModule = defineModule<TasksConfig>({
599
+ * name: 'TasksModule',
600
+ * defaults: { scope: 'public' },
601
+ * build: (config, { name }) => ({
602
+ * register(container) {
603
+ * container.registerInstance(`tasks:scope:${name}`, config.scope)
604
+ * },
605
+ * routes() {
606
+ * return { path: `/${config.scope}/tasks`, router: buildTasksRouter() }
607
+ * },
608
+ * contributors() {
609
+ * return [LoadTenant.registration]
610
+ * },
611
+ * }),
612
+ * })
613
+ *
614
+ * bootstrap({
615
+ * modules: [
616
+ * TasksModule(), // public scope (defaults)
617
+ * TasksModule.scoped('admin', { scope: 'admin' }),
618
+ * ],
619
+ * })
620
+ * ```
621
+ */
622
+ declare function defineModule<TConfig = Record<string, unknown>, TExtra = unknown>(options: DefineModuleOptions<TConfig, TExtra>): ModuleFactory<TConfig, TExtra>;
623
+ //#endregion
624
+ //#region src/core/define-modules.d.ts
625
+ /**
626
+ * Chainable registry returned by {@link defineModules}. Extends
627
+ * `Array<AppModuleEntry>` so it drops into `bootstrap({ modules })`
628
+ * unchanged — the framework loader already iterates the array, so a
629
+ * `ModuleList` IS a valid `modules` value with no special handling.
630
+ *
631
+ * The added affordance is `.mount(module)` returning `this` so
632
+ * adopters can build the list fluently:
633
+ *
634
+ * ```ts
635
+ * const modules = defineModules()
636
+ * .mount(HelloModule())
637
+ * .mount(AdminModule())
638
+ *
639
+ * bootstrap({ modules })
640
+ * ```
641
+ *
642
+ * **Why not just use a plain array?** Two reasons. First, the
643
+ * fluent shape reads more naturally for module-list construction —
644
+ * adopters used to `definePlugin().scoped(...)` / `defineAdapter(...)`
645
+ * see the same call-then-call pattern. Second, the registry is the
646
+ * stable extension point: a future `.use(module)` for non-HTTP
647
+ * modules (queues, cron, workers) lands as a method on `ModuleList`
648
+ * without breaking the `Array<AppModuleEntry>` superclass contract.
649
+ */
650
+ declare class ModuleList extends Array<AppModuleEntry> {
651
+ /**
652
+ * Append a module to the list. Returns `this` for chaining.
653
+ *
654
+ * Accepts both class form (`SomeModule extends AppModule`) and
655
+ * `defineModule` factory output — same as the underlying
656
+ * `bootstrap({ modules })` array. The framework loader
657
+ * discriminates `typeof entry === 'function'` at boot.
658
+ */
659
+ mount(module: AppModuleEntry): this;
660
+ }
661
+ /**
662
+ * Build a {@link ModuleList} for the application. Optional vararg
663
+ * lets adopters seed the list inline; subsequent `.mount()` calls
664
+ * append.
665
+ *
666
+ * @example
667
+ * ```ts
668
+ * import { bootstrap, defineModules } from '@forinda/kickjs'
669
+ * import { HelloModule } from './modules/hello/hello.module'
670
+ * import { AdminModule } from './modules/admin/admin.module'
671
+ *
672
+ * // Fluent — most common
673
+ * const modules = defineModules()
674
+ * .mount(HelloModule())
675
+ * .mount(AdminModule())
676
+ *
677
+ * // Or seeded inline + chained
678
+ * const modules2 = defineModules(HelloModule()).mount(AdminModule())
679
+ *
680
+ * await bootstrap({ modules })
681
+ * ```
682
+ *
683
+ * The returned value is an `AppModuleEntry[]` subclass — `bootstrap`
684
+ * accepts it directly without any special-casing. For conditional /
685
+ * dynamic registration at bootstrap time (env flags, runtime config,
686
+ * tenant lists), use the `setup(registry)` callback on `bootstrap`
687
+ * instead — both surfaces compose.
688
+ */
689
+ declare function defineModules(...initial: AppModuleEntry[]): ModuleList;
690
+ //#endregion
691
+ //#region src/core/kick-error.d.ts
692
+ /**
693
+ * Structured framework error with code, cause, and actionable fix hint.
694
+ *
695
+ * The goal is "the developer who hits this knows what to do next." A
696
+ * cryptic `Error('No binding found for: UserService')` becomes a
697
+ * multi-line explanation pointing at the likely cause, the exact code
698
+ * change to apply, and a docs link.
699
+ *
700
+ * `.message` carries the full multi-line plain-text body so Node's
701
+ * default `Error.toString()` and unhandled-exception printing still
702
+ * surface the helpful version. Call {@link formatKickError} to render a
703
+ * colorized version for terminals.
704
+ *
705
+ * Per-error factories live in `kick-errors.ts` — those are the public
706
+ * entry points framework code uses. `KickError` is exported so adopters
707
+ * can build their own structured errors using the same shape.
708
+ */
709
+ interface KickErrorInit {
710
+ /** Short stable identifier (e.g. `KICK001`, `NO_PROVIDER`). */
711
+ code: string;
712
+ /** One-line headline. */
713
+ summary: string;
714
+ /** Multi-line explanation of why this happened. */
715
+ cause?: string;
716
+ /** Multi-line actionable fix — show exact code if possible. */
717
+ fix?: string;
718
+ /** Absolute URL to the docs page that covers this error. */
719
+ docsUrl?: string;
720
+ /** Arbitrary structured fields for log consumers / debug tooling. */
721
+ context?: Record<string, unknown>;
722
+ }
723
+ declare class KickError extends Error {
724
+ readonly code: string;
725
+ readonly summary: string;
726
+ readonly cause?: string;
727
+ readonly fix?: string;
728
+ readonly docsUrl?: string;
729
+ readonly context?: Record<string, unknown>;
730
+ constructor(init: KickErrorInit);
731
+ }
732
+ interface FormatKickErrorOptions {
733
+ /**
734
+ * Render ANSI colour codes. Defaults to TTY detection (`NO_COLOR` /
735
+ * `FORCE_COLOR` env vars honoured). Pass `false` for log files,
736
+ * structured loggers, or HTTP response bodies.
737
+ */
738
+ color?: boolean;
739
+ }
740
+ /**
741
+ * Render a {@link KickErrorInit} (or {@link KickError} instance) into a
742
+ * multi-line human-readable string. Identical layout regardless of
743
+ * colour — the colour flag only toggles ANSI codes.
744
+ */
745
+ declare function formatKickError(err: KickErrorInit | KickError, options?: FormatKickErrorOptions): string;
746
+ //#endregion
747
+ //#region src/core/metadata.d.ts
748
+ /**
749
+ * Typed utilities for reflect-metadata operations.
750
+ *
751
+ * Replaces scattered Reflect.defineMetadata/getMetadata calls with a single
752
+ * source of truth. If we later move to Stage 3 decorators or a WeakMap-based
753
+ * store, only this file changes — all 24+ consumer files stay untouched.
754
+ *
755
+ * Keys may be symbols, strings, or `InjectionToken<T>` objects produced by
756
+ * `createToken()`. Using `createToken` is the preferred pattern because the
757
+ * token's phantom type flows through the helpers, enabling type-safe reads.
758
+ */
759
+ type MetaKey = symbol | string | InjectionToken<unknown>;
760
+ /** Set metadata on a class (or prototype for property decorators) */
761
+ declare function setClassMeta<T>(key: MetaKey, value: T, target: object): void;
762
+ /** Set metadata on a specific method */
763
+ declare function setMethodMeta<T>(key: MetaKey, value: T, target: object, method: string): void;
764
+ /** Get metadata from a class, returning `fallback` if not set */
765
+ declare function getClassMeta<T>(key: MetaKey, target: object, fallback: T): T;
766
+ /** Get metadata from a specific method, returning `fallback` if not set */
767
+ declare function getMethodMeta<T>(key: MetaKey, target: object, method: string, fallback: T): T;
768
+ /** Check if metadata exists on a class */
769
+ declare function hasClassMeta(key: MetaKey, target: object): boolean;
770
+ /** Get metadata from a class with no default (returns undefined if absent) */
771
+ declare function getClassMetaOrUndefined<T>(key: MetaKey, target: object): T | undefined;
772
+ /** Get metadata from a method with no default (returns undefined if absent) */
773
+ declare function getMethodMetaOrUndefined<T>(key: MetaKey, target: object, method: string): T | undefined;
774
+ /** Append items to an array stored in class metadata */
775
+ declare function pushClassMeta<T>(key: MetaKey, target: object, ...items: T[]): void;
776
+ /** Append items to an array stored in method metadata */
777
+ declare function pushMethodMeta<T>(key: MetaKey, target: object, method: string, ...items: T[]): void;
778
+ /** Set a key/value in a Map stored in class metadata */
779
+ declare function setInMetaMap<K, V>(key: MetaKey, target: object, mapKey: K, mapValue: V): void;
780
+ /** Get a Map from class metadata (returns empty Map if absent) */
781
+ declare function getMetaMap<K, V>(key: MetaKey, target: object): Map<K, V>;
782
+ /** Set a key/value in a Record stored in class metadata */
783
+ declare function setInMetaRecord<V>(key: MetaKey, target: object, recKey: string | number, recValue: V): void;
784
+ /** Get a Record from class metadata (returns empty object if absent) */
785
+ declare function getMetaRecord<V>(key: MetaKey, target: object): Record<string | number, V>;
786
+ //#endregion
787
+ //#region src/core/circuit-breaker.d.ts
788
+ /**
789
+ * Circuit Breaker pattern for external service calls.
790
+ *
791
+ * Protects your application from cascading failures when downstream
792
+ * services are unhealthy by short-circuiting requests after a
793
+ * configurable failure threshold.
794
+ *
795
+ * @example
796
+ * ```ts
797
+ * const breaker = new CircuitBreaker('payment-api', {
798
+ * failureThreshold: 5,
799
+ * resetTimeout: 30_000,
800
+ * })
801
+ *
802
+ * const result = await breaker.execute(() => fetch('https://payment.example.com/charge'))
803
+ * ```
804
+ */
805
+ type CircuitBreakerState = 'closed' | 'open' | 'half_open';
806
+ interface CircuitBreakerOptions {
807
+ /** Number of consecutive failures before the circuit opens. */
808
+ failureThreshold: number;
809
+ /** Milliseconds to wait before transitioning from OPEN to HALF_OPEN. */
810
+ resetTimeout: number;
811
+ /** Max requests allowed in HALF_OPEN state before deciding (default 1). */
812
+ halfOpenMax?: number;
813
+ }
814
+ interface CircuitBreakerStats {
815
+ failures: number;
816
+ successes: number;
817
+ state: CircuitBreakerState;
818
+ lastFailure?: Date;
819
+ }
820
+ /**
821
+ * Error thrown when the circuit is open and calls are being rejected.
822
+ */
823
+ declare class CircuitOpenError extends Error {
824
+ readonly breakerName: string;
825
+ constructor(breakerName: string);
826
+ }
827
+ declare class CircuitBreaker {
828
+ readonly name: string;
829
+ private state;
830
+ private failures;
831
+ private successes;
832
+ private halfOpenAttempts;
833
+ private lastFailure?;
834
+ private openedAt?;
835
+ private readonly failureThreshold;
836
+ private readonly resetTimeout;
837
+ private readonly halfOpenMax;
838
+ constructor(name: string, options: CircuitBreakerOptions);
839
+ /**
840
+ * Execute an async function through the circuit breaker.
841
+ * Throws `CircuitOpenError` if the circuit is open.
842
+ */
843
+ execute<T>(fn: () => Promise<T>): Promise<T>;
844
+ /** Returns the current circuit state. */
845
+ getState(): CircuitBreakerState;
846
+ /** Returns current statistics. */
847
+ getStats(): CircuitBreakerStats;
848
+ /** Manually reset the circuit breaker to CLOSED state. */
849
+ reset(): void;
850
+ private onSuccess;
851
+ private onFailure;
852
+ private maybeTransitionToHalfOpen;
853
+ }
854
+ //#endregion
855
+ //#region src/core/context-errors.d.ts
856
+ /**
857
+ * Errors thrown by the Context Contributor pipeline.
858
+ *
859
+ * All three are startup-time errors raised during pipeline build (topo-sort
860
+ * + validation), not per-request — a misconfigured app fails to boot rather
861
+ * than fails a request. Per the locked design in `architecture.md` §20.4,
862
+ * the pipeline is validated once when each route is mounted and the
863
+ * resolved order is cached on the route.
864
+ */
865
+ /**
866
+ * A contributor declared `dependsOn: ['<key>']` but no other contributor
867
+ * (at any precedence level) produces that key for the same route.
868
+ *
869
+ * @example
870
+ * ```text
871
+ * MissingContributorError: Missing context contributor 'tenant' required by 'project' on route GET /projects/:id
872
+ * declared at src/contributors/load-project.ts:42:18
873
+ * ```
874
+ */
875
+ declare class MissingContributorError extends Error {
876
+ readonly key: string;
877
+ readonly dependent: string;
878
+ readonly route?: string;
879
+ readonly dependentDefinedAt?: string;
880
+ constructor(key: string, dependent: string, route?: string, dependentDefinedAt?: string);
881
+ }
882
+ /**
883
+ * Topo-sort detected a cycle in `dependsOn` declarations. The `cycle` field
884
+ * lists the keys in the order they form the loop, with the first key
885
+ * repeated at the end so the path reads naturally.
886
+ *
887
+ * @example
888
+ * ```text
889
+ * ContributorCycleError: Cycle in context contributors on route GET /projects/:id: tenant → project → tenant
890
+ * ```
891
+ */
892
+ declare class ContributorCycleError extends Error {
893
+ readonly cycle: readonly string[];
894
+ readonly route?: string;
895
+ constructor(cycle: readonly string[], route?: string);
896
+ }
897
+ /**
898
+ * Two contributors at the same precedence level both produce the same key
899
+ * for the same route. Precedence resolution (method > class > module >
900
+ * adapter > global, per architecture.md §20.4) silently drops
901
+ * lower-precedence duplicates; this error fires only when ties occur
902
+ * within a single level — e.g., two method-level decorators declaring
903
+ * `key: 'tenant'`.
904
+ *
905
+ * `sources` is best-effort identifying labels for each conflicting
906
+ * registration (typically the decorator's call site or registering
907
+ * module/adapter name).
908
+ *
909
+ * @example
910
+ * ```text
911
+ * DuplicateContributorError: Duplicate context contributor for key 'tenant' at the same precedence level. Sources: LoadTenantFromHeader, LoadTenantFromSubdomain
912
+ * ```
913
+ */
914
+ declare class DuplicateContributorError extends Error {
915
+ readonly key: string;
916
+ readonly sources: readonly string[];
917
+ constructor(key: string, sources: readonly string[]);
918
+ }
919
+ //#endregion
920
+ //#region src/core/contributor-pipeline.d.ts
921
+ /**
922
+ * Where a contributor was registered, ordered by precedence
923
+ * (lower index wins). Mirrors `architecture.md` §20.4.
924
+ *
925
+ * Levels (highest → lowest precedence):
926
+ * - `'method'` — `@LoadX` on a controller method.
927
+ * - `'class'` — `@LoadX` on a controller class.
928
+ * - `'module'` — returned by `AppModule.contributors?()`. Per-module scope.
929
+ * - `'adapter'` — returned by `AppAdapter.contributors?()`. Cross-cutting, applies
930
+ * to every module's routes.
931
+ * - `'global'` — `ApplicationOptions.contributors`. App-wide default.
932
+ */
933
+ type ContributorSource = 'method' | 'class' | 'module' | 'adapter' | 'global';
934
+ /**
935
+ * Input row to {@link buildPipeline}. Pairs a registration with its origin
936
+ * so dedup can apply the precedence rule and diagnostic errors can name
937
+ * the offending site.
938
+ *
939
+ * `registration` is the type-erased {@link AnyContributorRegistration}
940
+ * — the runner doesn't care about each registration's narrow `D` /
941
+ * `Ctx` shape, and adopters frequently mix HTTP / transport-agnostic
942
+ * contributors in the same `externalSources` / `contributors()`
943
+ * array. The narrow generics from `defineContextDecorator` survive
944
+ * at the *definition* site (where `resolve` is typed); they're erased
945
+ * here so collections compose without `as any` casts.
946
+ */
947
+ interface SourcedRegistration {
948
+ registration: AnyContributorRegistration;
949
+ source: ContributorSource;
950
+ /** Human label surfaced in DuplicateContributorError messages. */
951
+ label?: string;
952
+ }
953
+ /**
954
+ * Resolved, validated, topo-sorted pipeline ready for execution.
955
+ *
956
+ * `contributors` is in execution order — every entry's `dependsOn`
957
+ * keys appear earlier. `keys` is the set of all keys this pipeline
958
+ * produces; the runner uses it for fast existence checks and the
959
+ * builder uses it for missing-dep validation.
960
+ */
961
+ interface ContributorPipeline {
962
+ readonly contributors: readonly AnyContributorRegistration[];
963
+ readonly keys: ReadonlySet<string>;
964
+ }
965
+ interface BuildPipelineOptions {
966
+ /** Optional route identifier surfaced in diagnostic errors (e.g., 'GET /projects/:id'). */
967
+ route?: string;
968
+ }
969
+ /**
970
+ * Build, validate, and topo-sort a contributor pipeline.
971
+ *
972
+ * Pipeline:
973
+ *
974
+ * 1. Group sources by precedence (method > class > module > adapter > global).
975
+ * 2. Within each precedence level, two contributors with the same `key`
976
+ * raise {@link DuplicateContributorError}.
977
+ * 3. Across levels, the highest-precedence contributor wins each key
978
+ * (lower-precedence duplicates are silently dropped — that is the
979
+ * documented override mechanism, not an error).
980
+ * 4. Every key referenced by `dependsOn` must be produced by the
981
+ * surviving set, otherwise {@link MissingContributorError}.
982
+ * 5. Kahn's algorithm topo-sorts the survivors. Any nodes left after
983
+ * the queue drains form a cycle → {@link ContributorCycleError}
984
+ * with a reconstructed path.
985
+ *
986
+ * Pure function — no DI, no I/O. Safe to call once at startup per
987
+ * route and cache the result.
988
+ */
989
+ declare function buildPipeline(sources: readonly SourcedRegistration[], options?: BuildPipelineOptions): ContributorPipeline;
990
+ //#endregion
991
+ //#region src/core/contributor-runner.d.ts
992
+ interface RunContributorsOptions {
993
+ /** Pre-built, validated, topo-sorted pipeline. */
994
+ pipeline: ContributorPipeline;
995
+ /** The execution context contributors read from and write into. */
996
+ ctx: ExecutionContext;
997
+ /** DI container used to resolve declared `deps`. */
998
+ container: Container;
999
+ }
1000
+ /**
1001
+ * Execute a built {@link ContributorPipeline} against an {@link ExecutionContext}.
1002
+ *
1003
+ * Sequential — one contributor at a time, in the topo order chosen at
1004
+ * pipeline-build time. Per `architecture.md` §20.10, V1 does not run
1005
+ * contributors in parallel even within the same topo level; that is a
1006
+ * V2 follow-up gated on profiling.
1007
+ *
1008
+ * ## Error matrix (architecture.md §20.9)
1009
+ *
1010
+ * | resolve outcome | optional | onError defined | runner behaviour |
1011
+ * |-----------------|----------|-----------------|--------------------------------------------------|
1012
+ * | throws | true | — | skip; `ctx.get(key)` remains undefined |
1013
+ * | throws | false | yes | call hook; returned value (if any) stored |
1014
+ * | throws | false | no | propagate the original error |
1015
+ * | hook throws | — | yes | propagate the new error |
1016
+ * | hook returns | — | yes | undefined/void → skip; value → `ctx.set(key, v)` |
1017
+ * | resolves | — | — | `ctx.set(key, value)` |
1018
+ *
1019
+ * Container.resolve() throwing while building `deps` is treated as a
1020
+ * resolve-side throw and flows through the same matrix — adopters who
1021
+ * need different behaviour for missing-DI vs. data-lookup failures can
1022
+ * wrap the container access inside their own `resolve()`.
1023
+ */
1024
+ declare function runContributors(options: RunContributorsOptions): Promise<void>;
1025
+ //#endregion
1026
+ //#region src/core/mount-sort.d.ts
1027
+ /**
1028
+ * Topological sort for plugins and adapters with `dependsOn` declarations.
1029
+ *
1030
+ * Mirrors the algorithm used by the Context Contributor pipeline
1031
+ * (`contributor-pipeline.ts:topoSort`) but operates on a name-keyed
1032
+ * `MountSortItem` shape rather than a `ContributorRegistration`. Kept
1033
+ * standalone instead of factored generic so that:
1034
+ *
1035
+ * 1. The contributor pipeline doesn't need refactoring (risk reduction).
1036
+ * 2. The error classes can carry plugin/adapter-specific diagnostic
1037
+ * fields (`kind`, `dependent`) without bending the contributor types.
1038
+ */
1039
+ /**
1040
+ * A plugin or adapter that participates in dependency-ordered mounting.
1041
+ * Both `KickPlugin` and `AppAdapter` satisfy this shape via their `name`
1042
+ * and `dependsOn` fields.
1043
+ */
1044
+ interface MountSortItem {
1045
+ /** Unique mount name within the plugin/adapter set being sorted. */
1046
+ name: string;
1047
+ /**
1048
+ * Other plugin/adapter names that must mount before this one. Cycles
1049
+ * and missing names fail at boot — same fail-fast contract as the
1050
+ * Context Contributor pipeline (architecture.md §20.4).
1051
+ */
1052
+ dependsOn?: readonly string[];
1053
+ }
1054
+ /** What kind of mount set the sort is operating on — surfaces in error messages. */
1055
+ type MountKind = 'plugin' | 'adapter';
1056
+ /**
1057
+ * Two items in the same mount set declared the same `name`. Plugin/adapter
1058
+ * names must be unique so `dependsOn` references resolve unambiguously.
1059
+ */
1060
+ declare class DuplicateMountNameError extends Error {
1061
+ readonly kind: MountKind;
1062
+ readonly mountName: string;
1063
+ constructor(kind: MountKind, mountName: string);
1064
+ }
1065
+ /**
1066
+ * A `dependsOn` entry references a plugin/adapter that isn't in the
1067
+ * current set. The dependency might not be installed, might be misspelled,
1068
+ * or might live in a different mount set (plugin referencing an adapter,
1069
+ * or vice versa — they sort independently).
1070
+ */
1071
+ declare class MissingMountDepError extends Error {
1072
+ readonly kind: MountKind;
1073
+ readonly missing: string;
1074
+ readonly dependent: string;
1075
+ constructor(kind: MountKind, missing: string, dependent: string);
1076
+ }
1077
+ /**
1078
+ * The `dependsOn` declarations form a cycle. Boot fails so the cycle
1079
+ * can't poison live traffic. The `cycle` field reports one path through
1080
+ * the offending nodes for diagnostics — formatted `A → B → C → A`.
1081
+ */
1082
+ declare class MountCycleError extends Error {
1083
+ readonly kind: MountKind;
1084
+ readonly cycle: readonly string[];
1085
+ constructor(kind: MountKind, cycle: readonly string[]);
1086
+ }
1087
+ /**
1088
+ * Topologically sort `items` so every item with `dependsOn: ['X']` mounts
1089
+ * after `X`. Items without `dependsOn` keep their input order via Kahn's
1090
+ * algorithm with a FIFO queue seeded in input order — preserves the
1091
+ * historical "plugins/adapters mount in declaration order" behaviour for
1092
+ * any set that doesn't use `dependsOn`.
1093
+ *
1094
+ * @param items - mountable items, e.g. plugins or adapters
1095
+ * @param kind - 'plugin' | 'adapter', used in error messages
1096
+ * @returns a new array in valid mount order
1097
+ * @throws {DuplicateMountNameError} two items share a name
1098
+ * @throws {MissingMountDepError} a `dependsOn` references an unknown name
1099
+ * @throws {MountCycleError} the dep graph contains a cycle
1100
+ */
1101
+ declare function mountSort<T extends MountSortItem>(items: readonly T[], kind: MountKind): T[];
1102
+ //#endregion
1103
+ //#region src/core/define-adapter.d.ts
1104
+ /**
1105
+ * Options passed to {@link defineAdapter}. Mirrors {@link DefinePluginOptions}
1106
+ * exactly — see `architecture.md` §21.3.4 for the definePlugin/defineAdapter
1107
+ * symmetry rationale.
1108
+ *
1109
+ * The optional `TExtra` generic lets an adapter ship public methods
1110
+ * beyond the standard {@link AppAdapter} contract — they're preserved
1111
+ * on the returned adapter instance so external callers (tests, peer
1112
+ * adapters) can invoke them directly. See `OtelAdapter.applyRedaction`
1113
+ * for the canonical use case.
1114
+ */
1115
+ interface DefineAdapterOptions<TConfig, TExtra = unknown> {
1116
+ /**
1117
+ * Stable identity used for logging, `dependsOn` lookups, and `.scoped()`
1118
+ * namespacing. Required for adapters that participate in `dependsOn`
1119
+ * sort — error messages reference adapters by name.
1120
+ */
1121
+ name: string;
1122
+ version?: string;
1123
+ requires?: {
1124
+ kickjs?: string;
1125
+ };
1126
+ defaults?: Partial<TConfig>;
1127
+ build(config: TConfig, ctx: BuildContext): Omit<AppAdapter, 'name'> & TExtra;
1128
+ }
1129
+ /**
1130
+ * Async-config form of an adapter. Identical shape to `PluginAsyncOptions`
1131
+ * but resolved during `beforeStart` (the earliest async hook in
1132
+ * `AppAdapter`) — meaning `middleware()` and `contributors()` from the inner
1133
+ * adapter are **not** picked up. Use the bare/`.scoped()` form when you need
1134
+ * those.
1135
+ */
1136
+ interface AdapterAsyncOptions<TConfig> {
1137
+ inject?: ReadonlyArray<unknown>;
1138
+ useFactory(...deps: any[]): TConfig | Promise<TConfig>;
1139
+ }
1140
+ /**
1141
+ * Factory returned by {@link defineAdapter}. Same surface as
1142
+ * {@link PluginFactory} (call / `.scoped()` / `.async()`) so adopters learn
1143
+ * one mental model for both primitives. Returned instances carry both
1144
+ * the standard {@link AppAdapter} contract and any `TExtra` extension
1145
+ * methods the `build` function exposed.
1146
+ */
1147
+ interface AdapterFactory<TConfig, TExtra = unknown> {
1148
+ (config?: Partial<TConfig>): AppAdapter & TExtra;
1149
+ scoped(scopeName: string, config?: Partial<TConfig>): AppAdapter & TExtra;
1150
+ async(opts: AdapterAsyncOptions<TConfig>): AppAdapter;
1151
+ readonly definition: Readonly<DefineAdapterOptions<TConfig, TExtra>>;
1152
+ }
1153
+ /**
1154
+ * Build an {@link AppAdapter} factory from a typed definition. See
1155
+ * `architecture.md` §21.3.4 for migration guidance — most first-party
1156
+ * adapter packages collapse to a single `defineAdapter()` call after this
1157
+ * lands.
1158
+ *
1159
+ * @example
1160
+ * ```ts
1161
+ * const TenantAdapter = defineAdapter<TenantConfig>({
1162
+ * name: 'TenantAdapter',
1163
+ * defaults: { strategy: 'header', required: true },
1164
+ * build: (config) => ({
1165
+ * middleware: () => [tenantResolverMiddleware(config)],
1166
+ * beforeStart: ({ container }) => registerTenantFactory(container, config),
1167
+ * }),
1168
+ * })
1169
+ *
1170
+ * bootstrap({
1171
+ * adapters: [
1172
+ * TenantAdapter({ strategy: 'subdomain' }),
1173
+ * TenantAdapter.scoped('shard-eu', { headerName: 'x-eu-tenant' }),
1174
+ * ],
1175
+ * })
1176
+ * ```
1177
+ */
1178
+ declare function defineAdapter<TConfig = Record<string, unknown>, TExtra = unknown>(options: DefineAdapterOptions<TConfig, TExtra>): AdapterFactory<TConfig, TExtra>;
1179
+ //#endregion
1180
+ //#region src/core/asset-keys.d.ts
1181
+ type AssetKeyStrategy = 'auto' | 'strip' | 'with-extension';
1182
+ interface AssetKeyPair {
1183
+ /** Source-relative path the file was discovered at. */
1184
+ rel: string;
1185
+ /** Final logical key for the manifest, including the namespace prefix. */
1186
+ key: string;
1187
+ }
1188
+ interface GroupAssetKeysOptions {
1189
+ /**
1190
+ * Strategy applied to this namespace. Default `'auto'`.
1191
+ *
1192
+ * Adopters override per-namespace via `assetMap.<ns>.keys` in
1193
+ * kick.config.ts when they want uniform behaviour (e.g. always
1194
+ * include the extension regardless of collisions).
1195
+ */
1196
+ strategy?: AssetKeyStrategy;
1197
+ }
1198
+ interface GroupAssetKeysResult {
1199
+ pairs: AssetKeyPair[];
1200
+ /**
1201
+ * Number of collision groups resolved by keeping extensions.
1202
+ * Surfaces as an informational log ("auto-resolved N collisions")
1203
+ * so adopters know whether they're relying on auto-mode or not.
1204
+ * Always 0 for `'strip'` (overwrites) and `'with-extension'`
1205
+ * (extensions always preserved, no collision possible).
1206
+ */
1207
+ collisionGroupsResolved: number;
1208
+ }
1209
+ /**
1210
+ * Compute final manifest keys for a namespace's discovered paths,
1211
+ * given the chosen strategy. Pure function — no fs / config / globals
1212
+ * involved.
1213
+ *
1214
+ * @param namespace assetMap key — e.g. `'mails'`, `'pages'`.
1215
+ * @param paths source-relative file paths (POSIX separators) discovered
1216
+ * under the namespace's `src` directory.
1217
+ * @param opts grouping options.
1218
+ */
1219
+ declare function groupAssetKeys(namespace: string, paths: readonly string[], opts?: GroupAssetKeysOptions): GroupAssetKeysResult;
1220
+ /** Strip the final extension from a path. `'mails/welcome.ejs'` → `'mails/welcome'`. */
1221
+ declare function stripExt(path: string): string;
1222
+ //#endregion
1223
+ //#region src/http/context.d.ts
1224
+ /**
1225
+ * Input shape for the typed problem-detail convenience methods on
1226
+ * {@link ProblemSender} (`ctx.problem.notFound`, `.badRequest`, etc.) —
1227
+ * RFC 9457 details minus the `status`, which each method pre-fills.
1228
+ */
1229
+ type ProblemInput = Omit<ProblemDetails, 'status'>;
1230
+ /**
1231
+ * Callable response helper for RFC 9457 — Problem Details responses.
1232
+ * Returned by `ctx.problem`. Invoke directly for a fully-specified
1233
+ * payload, or use the named convenience methods to pre-fill `status` +
1234
+ * `title` for the common codes.
1235
+ *
1236
+ * Each call sets `Content-Type: application/problem+json` and serializes
1237
+ * the canonical shape (defaulting `type` to `about:blank` and `title` to
1238
+ * the IANA reason phrase per RFC 9457 §3.1.1 / §3.1.4).
1239
+ *
1240
+ * @example
1241
+ * ```ts
1242
+ * ctx.problem({
1243
+ * type: 'https://api.example.com/problems/out-of-credit',
1244
+ * status: 403,
1245
+ * detail: 'Your balance is 30, but that costs 50.',
1246
+ * balance: 30, // extension per §3.2
1247
+ * })
1248
+ *
1249
+ * ctx.problem.notFound({ detail: 'User abc not found' })
1250
+ * ctx.problem.validation(zodResult.error.issues)
1251
+ * ```
1252
+ */
1253
+ interface ProblemSender {
1254
+ (input: ProblemDetails): RuntimeResponse;
1255
+ /** 400 Bad Request — RFC 9457. */
1256
+ badRequest(input?: ProblemInput): RuntimeResponse;
1257
+ /** 401 Unauthorized — RFC 9457. */
1258
+ unauthorized(input?: ProblemInput): RuntimeResponse;
1259
+ /** 403 Forbidden — RFC 9457. */
1260
+ forbidden(input?: ProblemInput): RuntimeResponse;
1261
+ /** 404 Not Found — RFC 9457. */
1262
+ notFound(input?: ProblemInput): RuntimeResponse;
1263
+ /** 409 Conflict — RFC 9457. */
1264
+ conflict(input?: ProblemInput): RuntimeResponse;
1265
+ /** 422 Unprocessable Entity — RFC 9457. */
1266
+ unprocessable(input?: ProblemInput): RuntimeResponse;
1267
+ /** 429 Too Many Requests — RFC 9457. */
1268
+ tooManyRequests(input?: ProblemInput): RuntimeResponse;
1269
+ /** 500 Internal Server Error — RFC 9457. */
1270
+ internal(input?: ProblemInput): RuntimeResponse;
1271
+ /**
1272
+ * 422 with Zod issues serialized into the `errors` extension per
1273
+ * RFC 9457 §3.2's array convention. Pass `error.issues` from a Zod
1274
+ * `safeParse` failure or `error.issues` from a thrown ZodError.
1275
+ */
1276
+ validation(issues: any[], input?: ProblemInput): RuntimeResponse;
1277
+ }
1278
+ /**
1279
+ * Recursively marks every property of `T` as `readonly`. Preserves
1280
+ * `Function`, `Date`, and `RegExp` unchanged, and converts `Map` / `Set`
1281
+ * to `ReadonlyMap` / `ReadonlySet` so their mutating methods (`set`,
1282
+ * `delete`, `add`, `clear`) are removed from the public surface.
1283
+ * Tuples keep their positional element types — only their per-element
1284
+ * mutability is locked.
1285
+ *
1286
+ * Exposed as a utility for adopters who want to opt into compile-time
1287
+ * sealing of their own shapes. **No longer applied to
1288
+ * `RequestContext.{body,params,query,headers,file,files}`** — that
1289
+ * broke TS narrowing / discriminated-union drilldowns on typed request
1290
+ * payloads. Runtime read-only enforcement now lives in
1291
+ * {@link makeReadOnlyProxy} (dev-only Proxy that warns on writes,
1292
+ * no-op in production).
1293
+ *
1294
+ * Type-only. No runtime cost. `DeepReadonly<any>` is `any` (TS
1295
+ * distributes conditional types over `any`).
1296
+ */
1297
+ type DeepReadonly<T> = T extends ((...args: never[]) => unknown) ? T : T extends Date | RegExp ? T : T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> : T extends Set<infer V> ? ReadonlySet<DeepReadonly<V>> : T extends readonly (infer U)[] ? number extends T['length'] ? ReadonlyArray<DeepReadonly<U>> : { readonly [K in keyof T]: DeepReadonly<T[K]> } : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
1298
+ /**
1299
+ * The shape of a single route entry in `KickRoutes`. Generated by
1300
+ * `kick typegen` and consumed via the `Ctx<T>` helper. Each field is
1301
+ * `unknown` by default and is filled in as the typegen learns more
1302
+ * about the route (URL params from the path, body/query from validation
1303
+ * schemas, etc.).
1304
+ */
1305
+ interface RouteShape {
1306
+ params?: unknown;
1307
+ body?: unknown;
1308
+ query?: unknown;
1309
+ response?: unknown;
1310
+ }
1311
+ /**
1312
+ * Unified request/response abstraction passed to every controller method.
1313
+ * Shields handlers from raw Express objects and provides convenience methods.
1314
+ *
1315
+ * Implements {@link ExecutionContext} so the transport-agnostic Context
1316
+ * Contributor pipeline (#107) can run against this concrete HTTP context
1317
+ * the same way it will run against future WS / queue / cron contexts.
1318
+ */
1319
+ declare class RequestContext<TBody = any, TParams = any, TQuery = any> implements ExecutionContext {
1320
+ readonly req: ActiveRuntime['request'];
1321
+ readonly res: ActiveRuntime['response'];
1322
+ readonly next: NextFunction;
1323
+ /**
1324
+ * Engine-agnostic response driver the response helpers write through. Under
1325
+ * the Express runtime it IS `res` (Express's Response satisfies
1326
+ * {@link RuntimeResponse} structurally); other runtimes pass a thin wrapper
1327
+ * over their native reply as the optional fourth argument.
1328
+ */
1329
+ private readonly _response;
1330
+ constructor(req: ActiveRuntime['request'], res: ActiveRuntime['response'], next: NextFunction, responseDriver?: RuntimeResponse);
1331
+ /**
1332
+ * Memo of the most recent {@link qs} call this request. The query
1333
+ * object is immutable for a request's lifetime, so a repeat call with
1334
+ * the same `(fieldConfig, options)` references returns the cached
1335
+ * parse instead of re-walking the filter/sort strings.
1336
+ */
1337
+ private _qsMemo?;
1338
+ /**
1339
+ * Read-side accessor for the per-request metadata map.
1340
+ *
1341
+ * Returns the AsyncLocalStorage map (`requestStore.getStore().values`)
1342
+ * shared with the Context Contributor pipeline when an ALS frame is
1343
+ * active. Returns `null` outside any frame — read-side callers
1344
+ * (`ctx.get`, `ctx.user`, `ctx.tenantId`, `ctx.roles`) treat that as
1345
+ * "no metadata" and fall through to their `req.*` defaults. Reads
1346
+ * never throw because outside an ALS frame nobody could have written
1347
+ * via `ctx.set` anyway, so an empty answer is the truthful one.
1348
+ */
1349
+ private metadataReadOnly;
1350
+ /**
1351
+ * Write-side accessor for the per-request metadata map.
1352
+ *
1353
+ * Returns the same map as {@link metadataReadOnly} when an ALS frame
1354
+ * is active. Throws otherwise — writing to a fallback map outside
1355
+ * the ALS frame would silently lose data because other call sites
1356
+ * (downstream contributors, request-logger, etc.) read from the
1357
+ * canonical store and would never see it. Mount
1358
+ * `requestScopeMiddleware()` (Application does this for you in
1359
+ * `'auto'` mode) or wrap your test setup in
1360
+ * `requestStore.run({...}, () => …)` to fix.
1361
+ */
1362
+ private metadataForWrite;
1363
+ /**
1364
+ * Parsed request body. Wrapped in a dev-only Proxy that warns when
1365
+ * adopters try to mutate Zod-validated payloads in place — compute
1366
+ * new values and stash them via `ctx.set('key', value)` or a Context
1367
+ * Contributor instead. Reach for `ctx.req.body` if you genuinely
1368
+ * need the raw, mutable Express handle (rare).
1369
+ *
1370
+ * In production the Proxy is bypassed and `this.req.body` is
1371
+ * returned as-is — no runtime cost on the hot path.
1372
+ */
1373
+ get body(): TBody;
1374
+ /** Path parameters parsed from the route — read-only, see {@link body}. */
1375
+ get params(): TParams;
1376
+ /** Parsed query string — read-only, see {@link body}. */
1377
+ get query(): TQuery;
1378
+ /** Inbound HTTP headers — read-only, see {@link body}. */
1379
+ get headers(): Request['headers'];
1380
+ get requestId(): string | undefined;
1381
+ /**
1382
+ * `AbortSignal` that fires when the underlying HTTP request is
1383
+ * closed — either because the client disconnected before the
1384
+ * response was sent, or because Node's socket-close event ran
1385
+ * after a normal response. Multiple `RequestContext` wrappers for
1386
+ * the same request (router-builder constructs one per
1387
+ * middleware/contributor pipeline / main handler) share a single
1388
+ * `AbortController` cached on the underlying `req` object, so the
1389
+ * signal is stable across the request lifecycle.
1390
+ *
1391
+ * Thread it through anything that takes an `AbortSignal` so the
1392
+ * work cancels as soon as the client gives up — e.g. a kickjs-db
1393
+ * relational query:
1394
+ *
1395
+ * ```ts
1396
+ * @Get('/:id/full')
1397
+ * async show(ctx: RequestContext) {
1398
+ * const row = await this.repo.findFullById(ctx.params.id, ctx.signal)
1399
+ * if (!row) return ctx.notFound()
1400
+ * ctx.json(row)
1401
+ * }
1402
+ * ```
1403
+ *
1404
+ * The repo passes `signal` to `db.query.<table>.findUnique({ signal })`;
1405
+ * if the client disconnects mid-flight, the in-flight query rejects
1406
+ * with `RelationalQueryCancelledError` instead of consuming the
1407
+ * connection until completion.
1408
+ *
1409
+ * Implementation note — both `req.on('close')` and `res.on('close')`
1410
+ * are wired so the signal aborts regardless of whether the close
1411
+ * event lands on the request stream or the response stream first.
1412
+ * Subsequent fires are no-ops (`AbortController.abort()` is
1413
+ * idempotent).
1414
+ */
1415
+ get signal(): AbortSignal;
1416
+ /** Session data (requires session middleware) */
1417
+ get session(): any;
1418
+ /**
1419
+ * The authenticated user set by AuthAdapter.
1420
+ * Reads from the per-request metadata store first (`ctx.set('user', ...)`),
1421
+ * then falls back to `req.user` (set directly by AuthAdapter middleware).
1422
+ *
1423
+ * Extend the `AuthUser` interface via module augmentation in `@forinda/kickjs-auth`
1424
+ * to get full typing.
1425
+ *
1426
+ * @example
1427
+ * ```ts
1428
+ * @Get('/me')
1429
+ * @Authenticated()
1430
+ * getProfile(ctx: RequestContext) {
1431
+ * const user = ctx.user
1432
+ * if (!user) return ctx.notFound()
1433
+ * return ctx.json({ user })
1434
+ * }
1435
+ * ```
1436
+ */
1437
+ get user(): MetaValue<'user', Record<string, any>> | undefined;
1438
+ /**
1439
+ * Tenant identifier for multi-tenant apps. Reads `user.tenantId` —
1440
+ * populated either by `AuthAdapter.testMode({ tenantId })`, a tenant
1441
+ * resolver middleware, or manually on the strategy's `mapPayload`.
1442
+ *
1443
+ * Augment `AuthUser` (or `ContextMeta['user']`) to widen the return
1444
+ * type if your user shape guarantees a non-`string | undefined` id.
1445
+ */
1446
+ get tenantId(): string | undefined;
1447
+ /**
1448
+ * Effective role list for the current user. Prefers `user.tenantRoles`
1449
+ * (populated by `AuthAdapterOptions.roleResolver` under a tenant) and
1450
+ * falls back to `user.roles`. Returns an empty array when the user is
1451
+ * absent or the shape has neither field.
1452
+ */
1453
+ get roles(): string[];
1454
+ /**
1455
+ * Parse the request query string into structured filters, sort, pagination, and search.
1456
+ * Pass the result to an ORM query builder adapter (Drizzle, Prisma, Sequelize, etc.).
1457
+ *
1458
+ * Generic over the field config: when passed an inline literal (or
1459
+ * `as const`), `filters[].field` and `sort[].field` are narrowed to
1460
+ * the exact whitelisted field names. Pass nothing or a non-literal
1461
+ * config and you get the loose `string` fallback.
1462
+ *
1463
+ * @param fieldConfig - Optional whitelist for filterable, sortable, and searchable fields
1464
+ * @param options - Limits (`maxLimit`, `defaultLimit`, `maxSearchLength`) and an
1465
+ * `onReject` hook. By default a rejected/over-broad field is logged via
1466
+ * `console.warn` with the request id; pass an explicit `onReject` to override
1467
+ * (e.g. throw to return a 400), or `onReject: () => {}` to silence.
1468
+ *
1469
+ * @example
1470
+ * ```ts
1471
+ * @Get('/')
1472
+ * async list(ctx: RequestContext) {
1473
+ * const parsed = ctx.qs({
1474
+ * filterable: ['status', 'priority'],
1475
+ * sortable: ['createdAt', 'title'],
1476
+ * } as const)
1477
+ * parsed.filters[0].field // typed as 'status' | 'priority'
1478
+ * parsed.sort[0].field // typed as 'createdAt' | 'title'
1479
+ * }
1480
+ * ```
1481
+ */
1482
+ qs<TConfig extends QueryFieldConfig | undefined = undefined>(fieldConfig?: TConfig, options?: ParseQueryOptions): TypedParsedQuery<TConfig>;
1483
+ /**
1484
+ * Single uploaded file (requires `@FileUpload({ mode: 'single' })`).
1485
+ * Wrapped in a dev-only Proxy that warns on mutation — see {@link body}.
1486
+ * File metadata is set by the upload middleware and shouldn't be
1487
+ * mutated downstream.
1488
+ */
1489
+ get file(): Express.Multer.File | undefined;
1490
+ /**
1491
+ * Array of uploaded files (requires `@FileUpload({ mode: 'array' })`).
1492
+ * The array (and each entry in dev) is wrapped in a read-only Proxy
1493
+ * that warns on `push`/`pop` and per-file mutation. Use
1494
+ * `ctx.req.files` if you genuinely need the mutable Express handle.
1495
+ */
1496
+ get files(): Express.Multer.File[] | undefined;
1497
+ /**
1498
+ * Read a value from the per-request metadata store.
1499
+ *
1500
+ * When `ContextMeta` has been augmented with a matching key, the return
1501
+ * type is inferred automatically. For ad-hoc keys, pass a generic:
1502
+ * `ctx.get<MyType>('custom')`.
1503
+ */
1504
+ get<K extends string>(key: K): MetaValue<K> | undefined;
1505
+ /**
1506
+ * Write a value to the per-request metadata store. Throws when no
1507
+ * AsyncLocalStorage frame is active — see {@link metadataForWrite}
1508
+ * for why a silent fallback is the wrong default.
1509
+ */
1510
+ set<K extends string>(key: K, value: MetaValue<K>): void;
1511
+ json(data: any, status?: number): RuntimeResponse;
1512
+ created(data: any): RuntimeResponse;
1513
+ noContent(): unknown;
1514
+ /**
1515
+ * @deprecated Prefer {@link ProblemSender.notFound | ctx.problem.notFound()},
1516
+ * which emits an RFC 9457 — Problem Details response. This helper stays
1517
+ * for backward compatibility and will not be removed.
1518
+ */
1519
+ notFound(message?: string): RuntimeResponse;
1520
+ /**
1521
+ * @deprecated Prefer {@link ProblemSender.badRequest | ctx.problem.badRequest()},
1522
+ * which emits an RFC 9457 — Problem Details response. This helper stays
1523
+ * for backward compatibility and will not be removed.
1524
+ */
1525
+ badRequest(message: string): RuntimeResponse;
1526
+ /**
1527
+ * RFC 9457 — Problem Details for HTTP APIs response helper.
1528
+ * https://datatracker.ietf.org/doc/html/rfc9457
1529
+ *
1530
+ * `ctx.problem(...)` sends a canonical problem-detail response with
1531
+ * `Content-Type: application/problem+json`. The convenience methods
1532
+ * (`ctx.problem.notFound`, `.badRequest`, etc.) pre-fill `status` +
1533
+ * `title` for the common codes.
1534
+ *
1535
+ * `type` defaults to `about:blank` (§3.1.1); `title` defaults to the
1536
+ * IANA reason phrase for `status` (§3.1.4). Extension members per §3.2
1537
+ * pass through unchanged.
1538
+ *
1539
+ * @example
1540
+ * ```ts
1541
+ * ctx.problem({
1542
+ * type: 'https://api.example.com/problems/out-of-credit',
1543
+ * status: 403,
1544
+ * detail: 'Your balance is 30, but that costs 50.',
1545
+ * instance: `/account/${ctx.user.id}/messages/${ctx.params.id}`,
1546
+ * balance: 30,
1547
+ * })
1548
+ *
1549
+ * ctx.problem.notFound({ detail: 'User abc not found' })
1550
+ * ctx.problem.validation(zodResult.error.issues)
1551
+ * ```
1552
+ *
1553
+ * @see {@link ProblemException} for the exception-throwing equivalent
1554
+ * (use it from services where `ctx` isn't in scope).
1555
+ */
1556
+ get problem(): ProblemSender;
1557
+ private sendProblem;
1558
+ html(content: string, status?: number): RuntimeResponse;
1559
+ download(buffer: Buffer, filename: string, contentType?: string): RuntimeResponse;
1560
+ /**
1561
+ * Render a template using the registered view engine (EJS, Pug, Handlebars, etc.).
1562
+ * Requires a ViewAdapter to be configured in bootstrap().
1563
+ *
1564
+ * @param template - Template name (without extension, relative to viewsDir)
1565
+ * @param data - Data to pass to the template
1566
+ *
1567
+ * @example
1568
+ * ```ts
1569
+ * ctx.render('dashboard', { user, title: 'Dashboard' })
1570
+ * ctx.render('emails/welcome', { name: 'Alice' })
1571
+ * ```
1572
+ */
1573
+ render(template: string, data?: Record<string, any>): unknown;
1574
+ /**
1575
+ * Parse query params and return a standardized paginated response.
1576
+ * Calls `ctx.qs()` internally, then wraps your data with pagination meta.
1577
+ *
1578
+ * @param fetcher - Async function that receives ParsedQuery and returns `{ data, total }`
1579
+ * @param fieldConfig - Optional whitelist for filterable, sortable, searchable fields
1580
+ *
1581
+ * @example
1582
+ * ```ts
1583
+ * @Get('/')
1584
+ * async list(ctx: RequestContext) {
1585
+ * return ctx.paginate(
1586
+ * async (parsed) => {
1587
+ * const data = await db.select().from(users)
1588
+ * .where(query.where).limit(parsed.pagination.limit)
1589
+ * .offset(parsed.pagination.offset).all()
1590
+ * const total = await db.select({ count: count() }).from(users).get()
1591
+ * return { data, total: total?.count ?? 0 }
1592
+ * },
1593
+ * { filterable: ['name', 'role'], sortable: ['createdAt'] },
1594
+ * )
1595
+ * }
1596
+ * ```
1597
+ */
1598
+ paginate<T, TConfig extends QueryFieldConfig | undefined = undefined>(fetcher: (parsed: TypedParsedQuery<TConfig>) => Promise<{
1599
+ data: T[];
1600
+ total: number;
1601
+ }>, fieldConfig?: TConfig): Promise<RuntimeResponse>;
1602
+ /**
1603
+ * Start an SSE (Server-Sent Events) stream.
1604
+ * Sets the correct headers and returns helpers to send events.
1605
+ *
1606
+ * @example
1607
+ * ```ts
1608
+ * @Get('/events')
1609
+ * async stream(ctx: RequestContext) {
1610
+ * const sse = ctx.sse()
1611
+ *
1612
+ * const interval = setInterval(() => {
1613
+ * sse.send({ time: new Date().toISOString() }, 'tick')
1614
+ * }, 1000)
1615
+ *
1616
+ * sse.onClose(() => clearInterval(interval))
1617
+ * }
1618
+ * ```
1619
+ */
1620
+ sse(): {
1621
+ /** Send an SSE event with optional event name and id */send: (data: any, event?: string, id?: string) => void; /** Send a comment (keeps connection alive) */
1622
+ comment: (text: string) => void; /** Register a callback when the client disconnects */
1623
+ onClose: (fn: () => void) => void; /** End the SSE stream */
1624
+ close: () => void;
1625
+ };
1626
+ }
1627
+ /**
1628
+ * Short alias for `RequestContext` parameterised by a route shape.
1629
+ *
1630
+ * Use with `kick typegen`'s generated `KickRoutes` namespace for
1631
+ * fully-typed `ctx.params`, `ctx.body`, and `ctx.query`. Metadata
1632
+ * typing (`ctx.get()`, `ctx.set()`, `ctx.user`) comes from
1633
+ * `ContextMeta` augmentation and works on all `RequestContext` instances.
1634
+ *
1635
+ * @example
1636
+ * ```ts
1637
+ * @Get('/:id')
1638
+ * getUser(ctx: Ctx<KickRoutes.UserController['getUser']>) {
1639
+ * ctx.params.id // typed from RouteShape
1640
+ * ctx.body // typed from RouteShape
1641
+ * ctx.user // typed from ContextMeta
1642
+ * ctx.get('tenant') // typed from ContextMeta
1643
+ * }
1644
+ * ```
1645
+ */
1646
+ type Ctx<TRoute extends RouteShape = RouteShape> = RequestContext<TRoute extends {
1647
+ body: infer B;
1648
+ } ? B : any, TRoute extends {
1649
+ params: infer P;
1650
+ } ? P : any, TRoute extends {
1651
+ query: infer Q;
1652
+ } ? Q : any>;
1653
+ /**
1654
+ * Global ambient registry of typed routes, populated by `kick typegen`.
1655
+ *
1656
+ * Each interface inside `KickRoutes` corresponds to a controller class;
1657
+ * each property is one route method on that controller. The values
1658
+ * conform to `RouteShape`.
1659
+ *
1660
+ * Empty by default — declarations come from `.kickjs/types/routes.d.ts`
1661
+ * generated alongside the rest of the typegen output.
1662
+ */
1663
+ declare global {
1664
+ namespace KickRoutes {}
1665
+ } //# sourceMappingURL=context.d.ts.map
1666
+ //#endregion
1667
+ //#region src/http/runtime.d.ts
1668
+ /** HTTP verbs the router emits. Mirrors the decorator surface (@Get/@Post/...). */
1669
+ type RouteMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
1670
+ /**
1671
+ * A context-based handler: the runtime-neutral unit of the request pipeline.
1672
+ * Contributors, route middleware (via {@link MiddlewareHandler}), and the
1673
+ * terminal controller call all reduce to this shape. The runtime owns how a
1674
+ * `CtxHandler` is wrapped onto its engine (Express wraps it in `(req, res,
1675
+ * next)`; Fastify in its own handler).
1676
+ */
1677
+ type CtxHandler = (ctx: RequestContext) => unknown | Promise<unknown>;
1678
+ /**
1679
+ * Connect-style middleware — the portable middleware format. Built-in
1680
+ * middleware and adopter-supplied Express middleware are this shape. The
1681
+ * Express runtime hands them straight to `app.use`; later runtimes bridge
1682
+ * them (Fastify via `@fastify/middie`, h3 via its node adapter).
1683
+ *
1684
+ * Typed as the Express handler union for M1 (Express is the only runtime);
1685
+ * a future milestone narrows this to bare node `(req, res, next)` primitives
1686
+ * once the driver layer lands.
1687
+ */
1688
+ type ConnectMiddleware = RequestHandler | ErrorRequestHandler;
1689
+ /**
1690
+ * One materialized route, as plain data. Produced by {@link buildRouteTable}
1691
+ * from decorator metadata; consumed by {@link HttpRuntime.mountRoutes}.
1692
+ *
1693
+ * The shape is deliberately richer than a flat `CtxHandler[]`: `middlewares`
1694
+ * keep the `(ctx, next)` signature (they may short-circuit or call `next(err)`),
1695
+ * `contributorRunner` is a closure over the pre-built pipeline + container (the
1696
+ * pipeline is built — and any cycle / missing-dep error thrown — at table-build
1697
+ * time, i.e. boot), and `validation` / `upload` stay as metadata the runtime
1698
+ * materializes its own way. Faithfully reproducing the legacy Express chain —
1699
+ * not collapsing it — is what keeps M1 behavior-neutral.
1700
+ */
1701
+ interface RouteEntry {
1702
+ method: RouteMethod;
1703
+ /** Path with `:param` segments — the portable subset all engines accept. */
1704
+ path: string;
1705
+ /** Class + method middleware, in execution order; each receives `(ctx, next)`. */
1706
+ middlewares: MiddlewareHandler[];
1707
+ /**
1708
+ * Runs the contributor pipeline against `ctx` (populates `ctx.set(...)`),
1709
+ * or `null` when no contributors apply. Closes over the built pipeline and
1710
+ * the DI container so it stays engine-neutral; the runtime advances the
1711
+ * chain (Express: call `next()`) once it resolves.
1712
+ */
1713
+ contributorRunner: CtxHandler | null;
1714
+ /** Terminal handler: resolves the controller per-request and invokes it. */
1715
+ handler: CtxHandler;
1716
+ /** Introspection + runtime-materialized concerns (swagger/typegen read this). */
1717
+ meta: RouteMeta;
1718
+ }
1719
+ /** Per-route metadata: introspection refs plus runtime-materialized concerns. */
1720
+ interface RouteMeta {
1721
+ /**
1722
+ * Owning controller class — for DI resolution and adapter introspection.
1723
+ * Absent for ad-hoc routes registered through {@link AdapterHttp.route},
1724
+ * which have no controller class behind them.
1725
+ */
1726
+ controller?: Constructor;
1727
+ /** Handler method name on the controller, when there is one. */
1728
+ handlerName?: string;
1729
+ /** Zod/JSON-schema validation config from the route decorator, if any. */
1730
+ validation?: RouteDefinition['validation'];
1731
+ /** `@FileUpload` config, if present — the runtime supplies the backend. */
1732
+ upload?: FileUploadConfig;
1733
+ }
1734
+ /** A controller's routes grouped under the module mount prefix. */
1735
+ type RouteTable = {
1736
+ mountPath: string;
1737
+ routes: RouteEntry[];
1738
+ }[];
1739
+ /**
1740
+ * The response surface {@link RequestContext}'s helpers (`json` / `html` /
1741
+ * `sse` / `download` / `render` / `problem`) drive, instead of calling Express
1742
+ * `res` methods directly (spec §4.3). Sized so `express.Response` satisfies it
1743
+ * structurally — under the Express runtime the driver IS the Express response,
1744
+ * so there is no wrapping and no behavior change. Other runtimes (Fastify / h3)
1745
+ * provide a thin object implementing this over their native reply.
1746
+ *
1747
+ * The terminal methods return `RuntimeResponse` for fluent chaining (Express's
1748
+ * `Response` is assignable, since it is a superset).
1749
+ */
1750
+ interface RuntimeResponse {
1751
+ status(code: number): RuntimeResponse;
1752
+ json(data: unknown): RuntimeResponse;
1753
+ send(data: unknown): RuntimeResponse;
1754
+ type(contentType: string): RuntimeResponse;
1755
+ setHeader(name: string, value: string | number | readonly string[]): unknown;
1756
+ render(view: string, data?: Record<string, unknown>): unknown;
1757
+ writeHead(statusCode: number, headers?: Record<string, string>): unknown;
1758
+ flushHeaders(): unknown;
1759
+ write(chunk: string | Buffer): boolean;
1760
+ end(data?: unknown): unknown;
1761
+ once(event: 'close', listener: () => void): unknown;
1762
+ readonly headersSent: boolean;
1763
+ }
1764
+ /**
1765
+ * Per-runtime type map: the engine-native request / response / app types an
1766
+ * `HttpRuntime` exposes through `ctx.req.raw` / `ctx.res.raw` / `AdapterContext.app`.
1767
+ */
1768
+ interface RuntimeTypeMap {
1769
+ request: unknown;
1770
+ response: unknown;
1771
+ app: unknown;
1772
+ }
1773
+ /** The Express runtime's native types — the default when nothing is augmented. */
1774
+ interface ExpressRuntimeTypes {
1775
+ request: Request;
1776
+ response: Response;
1777
+ app: Express$1;
1778
+ }
1779
+ /**
1780
+ * Augmentable runtime registry. Empty by default; the `kick/runtime` typegen
1781
+ * plugin emits a `{ runtime: <RuntimeTypeMap> }` augmentation from the configured
1782
+ * runtime, which flips the engine-native escape-hatch types — `AdapterContext.app`,
1783
+ * `getRuntimeApp()`, and (once the driver layer lands) `ctx.req.raw` / `ctx.res.raw`
1784
+ * — to that runtime. Mirrors `KickDbRegister` / `KickEnv`, so the type story is
1785
+ * uniform with `kick/db` and env typing.
1786
+ *
1787
+ * @example
1788
+ * ```ts
1789
+ * // .kickjs/types/kick__runtime.d.ts — generated by typegen
1790
+ * declare module '@forinda/kickjs' {
1791
+ * interface KickRuntimeRegister {
1792
+ * runtime: import('@forinda/kickjs/fastify').FastifyRuntimeTypes
1793
+ * }
1794
+ * }
1795
+ * ```
1796
+ */
1797
+ interface KickRuntimeRegister {}
1798
+ /** The active runtime's type map — the augmented one, or Express by default. */
1799
+ type ActiveRuntime = KickRuntimeRegister extends {
1800
+ runtime: infer R extends RuntimeTypeMap;
1801
+ } ? R : ExpressRuntimeTypes;
1802
+ /** Options passed to {@link HttpRuntime.createApp}. */
1803
+ interface RuntimeAppOptions {
1804
+ /**
1805
+ * Express `trust proxy` setting (and the equivalent on other engines). The
1806
+ * function form is Express-specific; other runtimes interpret the simpler
1807
+ * shapes (or normalize X-Forwarded-For themselves — see spec §10 Q1).
1808
+ */
1809
+ trustProxy?: boolean | string | number | string[] | ((ip: string, hopIndex: number) => boolean);
1810
+ }
1811
+ /** Where a connect middleware sits and what it scopes to. */
1812
+ interface UseConnectOptions {
1813
+ path?: string | RegExp | ReadonlyArray<string | RegExp>;
1814
+ }
1815
+ /**
1816
+ * Optional engine capabilities. Absence means the corresponding `ctx` feature
1817
+ * raises a clear error on that runtime rather than failing obscurely.
1818
+ */
1819
+ interface RuntimeCapabilities {
1820
+ /** View-engine rendering (`ctx.render`). Express: true; Fastify/h3: false initially. */
1821
+ render: boolean;
1822
+ /** File uploads (`ctx.file` / `@FileUpload`). All engines, different backends. */
1823
+ uploads: boolean;
1824
+ /** Connect-style middleware. Express/Fastify(middie): true; h3: best-effort. */
1825
+ connectMiddleware: boolean;
1826
+ /**
1827
+ * The engine parses request bodies itself (Fastify, h3). When true, the
1828
+ * Application skips its default `express.json()` so the body stream isn't
1829
+ * consumed twice (which would deadlock the engine's own parser). Express:
1830
+ * false — it relies on the `express.json()` connect middleware.
1831
+ */
1832
+ nativeBodyParsing: boolean;
1833
+ }
1834
+ /**
1835
+ * The engine driver. KickJS owns decorators → {@link RouteTable}, the
1836
+ * contributor pipeline, the `RequestContext` surface, and error mapping. The
1837
+ * runtime owns app/server creation, route materialization, connect-middleware
1838
+ * registration, static serving, and the terminal not-found / error handlers.
1839
+ *
1840
+ * `expressRuntime()` is the default and the M1 reference implementation.
1841
+ */
1842
+ interface HttpRuntime<TApp = unknown> {
1843
+ readonly name: 'express' | 'fastify' | 'h3' | (string & {});
1844
+ /** Create the engine app. Called once per Application (and per HMR rebuild). */
1845
+ createApp(options?: RuntimeAppOptions): TApp;
1846
+ /**
1847
+ * Node-compatible request listener — the transport contract.
1848
+ * `http.createServer(handler)` in prod, Vite post-middleware in dev. MUST
1849
+ * invoke `next` (when given) instead of 404-ing if no route matched — the
1850
+ * Vite dev chain depends on fall-through.
1851
+ */
1852
+ nodeHandler(app: TApp): (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void;
1853
+ /** Materialize the framework-built route table onto the engine. */
1854
+ mountRoutes(app: TApp, table: RouteTable): void;
1855
+ /** Register a connect-style middleware (built-ins + adopter middleware). */
1856
+ useConnect(app: TApp, mw: ConnectMiddleware, opts?: UseConnectOptions): void;
1857
+ /** Serve a static directory (swagger-ui assets, devtools SPA). */
1858
+ serveStatic(app: TApp, prefix: string, dir: string): void;
1859
+ /** Terminal not-found handler. */
1860
+ setNotFound(app: TApp, mw: ConnectMiddleware): void;
1861
+ /** Terminal error handler. */
1862
+ setErrorHandler(app: TApp, mw: ConnectMiddleware): void;
1863
+ readonly capabilities: RuntimeCapabilities;
1864
+ }
1865
+ /**
1866
+ * The supported surface adapters use to add HTTP routes, mounts, static dirs,
1867
+ * and middleware — engine-agnostic, in place of reaching for the raw Express
1868
+ * `app` (spec §4.4). Exposed as {@link AdapterContext.http}. Built by the
1869
+ * Application over the active {@link HttpRuntime}, so an adapter written against
1870
+ * this works under any runtime; the raw `app` escape hatch stays available for
1871
+ * the rare engine-specific need.
1872
+ */
1873
+ interface AdapterHttp {
1874
+ /** Register a single context-handler route (e.g. a docs or transport endpoint). */
1875
+ route(method: RouteMethod, path: string, handler: CtxHandler): void;
1876
+ /** Mount a pre-built route table under a path prefix. */
1877
+ mount(prefix: string, routes: RouteEntry[]): void;
1878
+ /** Serve a static directory under a path prefix. */
1879
+ serveStatic(prefix: string, dir: string): void;
1880
+ /** Register a connect-style middleware (optionally path-scoped). */
1881
+ use(mw: ConnectMiddleware, opts?: UseConnectOptions): void;
1882
+ }
1883
+ //#endregion
1884
+ //#region src/core/adapter.d.ts
1885
+ /**
1886
+ * Where in the middleware pipeline an adapter's middleware should be inserted.
1887
+ *
1888
+ * - `beforeGlobal` — runs **before** any user-defined global middleware.
1889
+ * Suitable for transport-level concerns (request tagging, low-level
1890
+ * request rewriting). Fires on every request.
1891
+ * - `afterGlobal` — runs **after** global middleware, **before** module
1892
+ * routes are matched. Default for most adapter middleware (auth pre-checks,
1893
+ * tenant resolution, body shaping). Fires on every request.
1894
+ * - `beforeRoutes` — runs just before module routes are mounted. Functionally
1895
+ * equivalent to `afterGlobal` for most cases; the explicit phase exists so
1896
+ * adapters that *must* sit at the very end of the pre-route stack can
1897
+ * declare intent. Fires on every request.
1898
+ * - `afterRoutes` — sits **after** module routes but **before** error
1899
+ * handlers. **Fires only on fall-through** — when no route matched (404
1900
+ * path) or a route handler called `next()` without ending the response.
1901
+ * Controllers that respond with `ctx.json(...)` end the chain and skip
1902
+ * this phase, so it is *not* a "post-response" hook. For per-response
1903
+ * work (logging, metrics) use `res.on('finish', ...)` from inside an
1904
+ * earlier-phase middleware instead.
1905
+ */
1906
+ type MiddlewarePhase = 'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes';
1907
+ /**
1908
+ * Path scope for an {@link AdapterMiddleware} entry. Mirrors what
1909
+ * Express's `app.use(path, handler)` accepts so adapters get the full
1910
+ * range without learning a new mini-language:
1911
+ *
1912
+ * - A `string` prefix — `'/api'` matches `/api`, `/api/v1`, `/api/v1/x`.
1913
+ * - A `RegExp` — `/^\/api\/v\d+/` matches `/api/v1`, `/api/v42`.
1914
+ * - An array of either — `['/api', '/admin']` matches either prefix;
1915
+ * `['/api', /^\/internal\//]` mixes shapes.
1916
+ *
1917
+ * Omit to apply the middleware unconditionally (Express `app.use(handler)`).
1918
+ */
1919
+ type MiddlewarePath = string | RegExp | ReadonlyArray<string | RegExp>;
1920
+ /** A middleware entry contributed by an adapter */
1921
+ interface AdapterMiddleware {
1922
+ /**
1923
+ * Express handler: either a `RequestHandler` (`(req, res, next) => …`)
1924
+ * or an `ErrorRequestHandler` (`(err, req, res, next) => …`).
1925
+ * Express dispatches based on arity, so the same field accepts both
1926
+ * without a discriminant.
1927
+ */
1928
+ handler: RequestHandler | ErrorRequestHandler;
1929
+ /** Which phase to insert into (default: 'afterGlobal') */
1930
+ phase?: MiddlewarePhase;
1931
+ /**
1932
+ * Optional path scope. See {@link MiddlewarePath} for the accepted
1933
+ * shapes (string prefix, RegExp, or an array mixing both). Omit to
1934
+ * apply the middleware to every request that reaches this phase.
1935
+ */
1936
+ path?: MiddlewarePath;
1937
+ }
1938
+ /**
1939
+ * Context passed to adapter lifecycle hooks.
1940
+ * Populated by the Application — provides access to the Express app,
1941
+ * DI container, http.Server, and environment info without requiring
1942
+ * external type imports. Cast `app` or `server` if you need specific types.
1943
+ *
1944
+ * @example
1945
+ * ```ts
1946
+ * beforeMount({ app, container }: AdapterContext) {
1947
+ * app.use(myMiddleware())
1948
+ * }
1949
+ * ```
1950
+ */
1951
+ interface AdapterContext {
1952
+ /**
1953
+ * Engine-agnostic HTTP surface — the supported way to add routes, mounts,
1954
+ * static dirs, and middleware (spec §4.4). Prefer this over {@link app}:
1955
+ * an adapter written against `http` works under any runtime.
1956
+ */
1957
+ http: AdapterHttp;
1958
+ /**
1959
+ * Engine-native application instance — Express under the default runtime,
1960
+ * typed from the active runtime via {@link ActiveRuntime} (spec §4.3b).
1961
+ * Escape hatch for genuinely engine-specific needs; for everything else use
1962
+ * {@link http}.
1963
+ */
1964
+ app: ActiveRuntime['app'];
1965
+ /** DI container */
1966
+ container: Container;
1967
+ /** Node.js http.Server — only available in afterStart */
1968
+ server?: http.Server;
1969
+ /** Current NODE_ENV value (default: 'development') */
1970
+ env: string;
1971
+ /** true when NODE_ENV === 'production' */
1972
+ isProduction: boolean;
1973
+ }
1974
+ /**
1975
+ * Adapters plug into the Application lifecycle.
1976
+ * Implement this to add WebSocket, database, rate limiting, docs, etc.
1977
+ *
1978
+ * All hooks receive an `AdapterContext` populated by the framework.
1979
+ * No external type imports needed — the context is fully typed from KickJS internals.
1980
+ *
1981
+ * @example
1982
+ * ```ts
1983
+ * import type { AppAdapter, AdapterContext } from '@forinda/kickjs/adapter'
1984
+ *
1985
+ * class SentryAdapter implements AppAdapter {
1986
+ * name = 'SentryAdapter'
1987
+ *
1988
+ * beforeMount({ app, container }: AdapterContext) {
1989
+ * app.use(Sentry.expressRequestHandler())
1990
+ * }
1991
+ *
1992
+ * afterStart({ server }: AdapterContext) {
1993
+ * // server is the http.Server — available after listen()
1994
+ * }
1995
+ *
1996
+ * async shutdown() {
1997
+ * await Sentry.close(2000)
1998
+ * }
1999
+ * }
2000
+ * ```
2001
+ */
2002
+ interface AppAdapter {
2003
+ /**
2004
+ * Human-readable name for logging and `dependsOn` resolution.
2005
+ * Required when the adapter (or any other in the same set) declares
2006
+ * `dependsOn` — sort errors will reference adapters by name.
2007
+ */
2008
+ name?: string;
2009
+ /**
2010
+ * Other adapter names that must mount before this one. The framework
2011
+ * topologically sorts adapters at boot — cycles or unknown names throw
2012
+ * via `MountCycleError` / `MissingMountDepError` so bad configurations
2013
+ * fail boot rather than corrupt live traffic.
2014
+ *
2015
+ * Common pattern: `TenantAdapter` populates `req.tenant` and
2016
+ * `AuthAdapter` reads it for tenant-scoped RBAC, so AuthAdapter
2017
+ * declares `dependsOn = ['TenantAdapter']`. Adapters without
2018
+ * `dependsOn` retain their declaration order.
2019
+ *
2020
+ * @example
2021
+ * ```ts
2022
+ * class AuthAdapter implements AppAdapter {
2023
+ * name = 'AuthAdapter'
2024
+ * dependsOn = ['TenantAdapter']
2025
+ * }
2026
+ * ```
2027
+ */
2028
+ dependsOn?: readonly KickJsPluginName[];
2029
+ /**
2030
+ * Return middleware entries to be inserted into the pipeline.
2031
+ * The `phase` controls ordering relative to global middleware and routes.
2032
+ */
2033
+ middleware?(): AdapterMiddleware[];
2034
+ /**
2035
+ * Return Context Contributors (#107) that apply to every route in the
2036
+ * application. Adapter-level contributors merge into the per-route
2037
+ * pipeline at the `'adapter'` precedence level — they win over global
2038
+ * contributors but lose to module, class, and method decorators.
2039
+ *
2040
+ * Useful for cross-cutting concerns: an auth adapter contributing
2041
+ * `user`, a multi-tenant adapter contributing `tenant`, etc. Optional.
2042
+ */
2043
+ contributors?(): ContributorRegistrations;
2044
+ /**
2045
+ * Called before global middleware — register early routes (docs UI, health).
2046
+ * May return a Promise — async rejections are caught and logged.
2047
+ */
2048
+ beforeMount?(ctx: AdapterContext): void | Promise<void>;
2049
+ /**
2050
+ * Called for each module route that gets mounted.
2051
+ * Use this to collect route metadata (e.g. for OpenAPI spec generation).
2052
+ */
2053
+ onRouteMount?(controllerClass: Constructor, mountPath: string): void;
2054
+ /**
2055
+ * Called after modules are registered and the DI container is bootstrapped,
2056
+ * before the HTTP server starts listening. **This is the correct hook for
2057
+ * adapters that register DI bindings, resolve services, or otherwise prepare
2058
+ * container-side state** (e.g. `container.registerInstance(MAILER, ...)`).
2059
+ *
2060
+ * `beforeStart` runs inside `app.setup()`, which means it also fires under
2061
+ * `createTestApp` — adapters using this hook work in unit tests without
2062
+ * needing a live HTTP server. See {@link afterStart} for the contrast.
2063
+ *
2064
+ * May return a Promise — async rejections are caught and logged.
2065
+ */
2066
+ beforeStart?(ctx: AdapterContext): void | Promise<void>;
2067
+ /**
2068
+ * Called after the HTTP server is listening — `ctx.server` is the live
2069
+ * `http.Server`. **Use this hook only for work that genuinely needs a
2070
+ * listening server**: attaching Socket.IO / WS upgrades, reading the bound
2071
+ * port via `server.address()`, dispatching to the local server over HTTP,
2072
+ * etc.
2073
+ *
2074
+ * `afterStart` does NOT fire under `createTestApp`, since tests call
2075
+ * `app.setup()` and never `app.start()`. If your adapter only registers DI
2076
+ * bindings or resolves services, use {@link beforeStart} — that hook runs
2077
+ * in both production (`start()`) and tests (`setup()`).
2078
+ *
2079
+ * May return a Promise — async rejections are caught and logged.
2080
+ */
2081
+ afterStart?(ctx: AdapterContext): void | Promise<void>;
2082
+ /**
2083
+ * Called by the /health/ready endpoint. Return the health status of your adapter's
2084
+ * backing service (database, Redis, queue, etc.).
2085
+ */
2086
+ onHealthCheck?(): Promise<{
2087
+ name: string;
2088
+ status: 'up' | 'down';
2089
+ }>;
2090
+ /** Called on shutdown — clean up connections */
2091
+ shutdown?(): MaybePromise;
2092
+ /**
2093
+ * Optional DevTools introspection hook (architecture.md §23). Returns
2094
+ * a snapshot describing this adapter's current state, metrics, and DI
2095
+ * tokens. DevTools awaits the result, so async work is fine — but keep
2096
+ * the implementation cheap (counters + flags, no DB round trips) since
2097
+ * the topology endpoint polls on a short interval.
2098
+ *
2099
+ * The return type lives in `@forinda/kickjs` itself ({@link IntrospectionSnapshot})
2100
+ * so adopters can author `introspect()` without importing the kit just
2101
+ * to satisfy the contract. The kit's own `IntrospectionSnapshot` type
2102
+ * stays structurally identical; either import works.
2103
+ */
2104
+ introspect?(): IntrospectionSnapshot | Promise<IntrospectionSnapshot>;
2105
+ /**
2106
+ * Optional DevTools tabs this adapter contributes (architecture.md §23).
2107
+ * Returns an array of `DevtoolsTabDescriptor` shapes — typically one
2108
+ * entry per adapter-owned panel (iframe / launch / html view).
2109
+ *
2110
+ * Same untyped-at-this-layer convention as {@link introspect}: adapter
2111
+ * authors import `defineDevtoolsTab` (or the bare type) from
2112
+ * `@forinda/kickjs-devtools-kit` so `@forinda/kickjs` doesn't take on
2113
+ * a runtime dep on the kit.
2114
+ */
2115
+ devtoolsTabs?(): readonly unknown[];
2116
+ }
2117
+ /** Constructor type for AppAdapter classes */
2118
+ type AppAdapterClass = new () => AppAdapter;
2119
+ //#endregion
2120
+ export { CircuitBreaker as $, GroupAssetKeysResult as A, definePlugin as At, MountKind as B, IntrospectionSnapshot as Bt, Ctx as C, DefineModuleOptions as Ct, AssetKeyPair as D, DefinePluginOptions as Dt, RouteShape as E, BuildContext as Et, DefineAdapterOptions as F, KickJsPluginName as Ft, BuildPipelineOptions as G, mountSort as H, defineAdapter as I, KickJsPluginRegistry as It, SourcedRegistration as J, ContributorPipeline as K, DuplicateMountNameError as L, defineAugmentation as Lt, stripExt as M, ModuleRegistry as Mt, AdapterAsyncOptions as N, MutableModuleRegistry as Nt, AssetKeyStrategy as O, PluginAsyncOptions as Ot, AdapterFactory as P, AugmentationMeta as Pt, MissingContributorError as Q, MissingMountDepError as R, IntrospectFn as Rt, UseConnectOptions as S, defineModules as St, RequestContext as T, defineModule as Tt, RunContributorsOptions as U, MountSortItem as V, runContributors as W, ContributorCycleError as X, buildPipeline as Y, DuplicateContributorError as Z, RouteTable as _, FormatKickErrorOptions as _t, MiddlewarePath as a, getClassMetaOrUndefined as at, RuntimeResponse as b, formatKickError as bt, AdapterHttp as c, getMethodMeta as ct, ExpressRuntimeTypes as d, pushClassMeta as dt, CircuitBreakerOptions as et, HttpRuntime as f, pushMethodMeta as ft, RouteMethod as g, setMethodMeta as gt, RouteMeta as h, setInMetaRecord as ht, AppAdapterClass as i, getClassMeta as it, groupAssetKeys as j, KickPlugin as jt, GroupAssetKeysOptions as k, PluginFactory as kt, ConnectMiddleware as l, getMethodMetaOrUndefined as lt, RouteEntry as m, setInMetaMap as mt, AdapterMiddleware as n, CircuitBreakerStats as nt, MiddlewarePhase as o, getMetaMap as ot, KickRuntimeRegister as p, setClassMeta as pt, ContributorSource as q, AppAdapter as r, CircuitOpenError as rt, ActiveRuntime as s, getMetaRecord as st, AdapterContext as t, CircuitBreakerState as tt, CtxHandler as u, hasClassMeta as ut, RuntimeAppOptions as v, KickError as vt, DeepReadonly as w, ModuleFactory as wt, RuntimeTypeMap as x, ModuleList as xt, RuntimeCapabilities as y, KickErrorInit as yt, MountCycleError as z, IntrospectionKind as zt };
2121
+ //# sourceMappingURL=adapter-D_uzU-QA.d.mts.map