@forinda/kickjs 8.0.0 → 8.1.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 (132) hide show
  1. package/dist/{adapter-BuOTeSQy.d.mts → adapter-OjEKLePS.d.mts} +4 -4
  2. package/dist/{adapter-BuOTeSQy.d.mts.map → adapter-OjEKLePS.d.mts.map} +1 -1
  3. package/dist/{app-module-DZW0uYNA.d.mts → app-module-CmIHP7uW.d.mts} +2 -2
  4. package/dist/{app-module-DZW0uYNA.d.mts.map → app-module-CmIHP7uW.d.mts.map} +1 -1
  5. package/dist/{application-D1koPFmH.d.mts → application-BmJaP130.d.mts} +4 -4
  6. package/dist/{application-D1koPFmH.d.mts.map → application-BmJaP130.d.mts.map} +1 -1
  7. package/dist/{application-IWSzCdB8.mjs → application-DrOxJWUm.mjs} +3 -3
  8. package/dist/{application-IWSzCdB8.mjs.map → application-DrOxJWUm.mjs.map} +1 -1
  9. package/dist/{bootstrap-BIjHgG4v.d.mts → bootstrap-2QoNFOmO.d.mts} +2 -2
  10. package/dist/bootstrap-2QoNFOmO.d.mts.map +1 -0
  11. package/dist/{bootstrap-BG86IC_L.mjs → bootstrap-CJkXHDeQ.mjs} +3 -3
  12. package/dist/{bootstrap-BG86IC_L.mjs.map → bootstrap-CJkXHDeQ.mjs.map} +1 -1
  13. package/dist/{client-ip-C909xeVL.mjs → client-ip-D_XUCo45.mjs} +2 -2
  14. package/dist/{client-ip-C909xeVL.mjs.map → client-ip-D_XUCo45.mjs.map} +1 -1
  15. package/dist/config/index.d.mts +1 -1
  16. package/dist/config/index.mjs +2 -2
  17. package/dist/{config-ClZsO9eK.mjs → config-Bi6kDjo9.mjs} +3 -3
  18. package/dist/{config-ClZsO9eK.mjs.map → config-Bi6kDjo9.mjs.map} +1 -1
  19. package/dist/{container-BW_MlgLj.d.mts → container-BA-O7uBF.d.mts} +18 -1
  20. package/dist/container-BA-O7uBF.d.mts.map +1 -0
  21. package/dist/container-jtn5BmL5.mjs +117 -0
  22. package/dist/container-jtn5BmL5.mjs.map +1 -0
  23. package/dist/{context-BnIvtMUl.mjs → context-caMuzOpA.mjs} +3 -3
  24. package/dist/{context-BnIvtMUl.mjs.map → context-caMuzOpA.mjs.map} +1 -1
  25. package/dist/{context-errors-vB3ZRXSu.mjs → context-errors-D0EbFrTN.mjs} +2 -2
  26. package/dist/{context-errors-vB3ZRXSu.mjs.map → context-errors-D0EbFrTN.mjs.map} +1 -1
  27. package/dist/core/adapter.d.mts +1 -1
  28. package/dist/core/adapter.mjs +1 -1
  29. package/dist/core/app-module.d.mts +1 -1
  30. package/dist/core/app-module.mjs +1 -1
  31. package/dist/core/cache.mjs +1 -1
  32. package/dist/core/container.d.mts +1 -1
  33. package/dist/core/container.mjs +2 -2
  34. package/dist/core/cron.mjs +2 -2
  35. package/dist/core/decorators.d.mts +1 -1
  36. package/dist/core/decorators.mjs +2 -2
  37. package/dist/core/decorators.mjs.map +1 -1
  38. package/dist/core/errors.mjs +1 -1
  39. package/dist/core/interfaces.mjs +1 -1
  40. package/dist/core/logger.mjs +1 -1
  41. package/dist/core/path.mjs +1 -1
  42. package/dist/core/plugin.d.mts +1 -1
  43. package/dist/core/plugin.mjs +1 -1
  44. package/dist/core/reactivity.mjs +1 -1
  45. package/dist/{core-DVbWxSyr.mjs → core-B1PK7S5p.mjs} +3 -3
  46. package/dist/{core-DVbWxSyr.mjs.map → core-B1PK7S5p.mjs.map} +1 -1
  47. package/dist/{csrf-DGZqpNSq.mjs → csrf-Cab0-YPs.mjs} +3 -3
  48. package/dist/{csrf-DGZqpNSq.mjs.map → csrf-Cab0-YPs.mjs.map} +1 -1
  49. package/dist/{decorate-DDLimr-C.mjs → decorate-C0Rr6ywx.mjs} +1 -1
  50. package/dist/{decorators-CbTBUVOu.d.mts → decorators-BbtQ3Vf6.d.mts} +2 -2
  51. package/dist/{decorators-CbTBUVOu.d.mts.map → decorators-BbtQ3Vf6.d.mts.map} +1 -1
  52. package/dist/{define-adapter-BIwBwOK_.mjs → define-adapter-DUBru2GV.mjs} +2 -2
  53. package/dist/{define-adapter-BIwBwOK_.mjs.map → define-adapter-DUBru2GV.mjs.map} +1 -1
  54. package/dist/{disposables-Ht0M5KKc.mjs → disposables-jJAxz_e1.mjs} +2 -2
  55. package/dist/{disposables-Ht0M5KKc.mjs.map → disposables-jJAxz_e1.mjs.map} +1 -1
  56. package/dist/{env-CabVU6cy.mjs → env-C4hwX5of.mjs} +3 -3
  57. package/dist/{env-CabVU6cy.mjs.map → env-C4hwX5of.mjs.map} +1 -1
  58. package/dist/{error-handler-C-xGxZKP.d.mts → error-handler-ade-elv6.d.mts} +7 -6
  59. package/dist/{error-handler-C-xGxZKP.d.mts.map → error-handler-ade-elv6.d.mts.map} +1 -1
  60. package/dist/{handler-9qFN7nRw.mjs → handler-BuzdkiTz.mjs} +3 -3
  61. package/dist/{handler-9qFN7nRw.mjs.map → handler-BuzdkiTz.mjs.map} +1 -1
  62. package/dist/http/application.d.mts +1 -1
  63. package/dist/http/application.mjs +2 -2
  64. package/dist/http/bootstrap.d.mts +1 -1
  65. package/dist/http/bootstrap.mjs +2 -2
  66. package/dist/http/context.d.mts +2 -2
  67. package/dist/http/context.mjs +2 -2
  68. package/dist/http/middleware/cors.mjs +1 -1
  69. package/dist/http/middleware/csrf.mjs +2 -2
  70. package/dist/http/middleware/error-handler.d.mts +1 -1
  71. package/dist/http/middleware/error-handler.mjs +2 -2
  72. package/dist/http/middleware/error-handler.mjs.map +1 -1
  73. package/dist/http/middleware/helmet.mjs +1 -1
  74. package/dist/http/middleware/rate-limit.mjs +2 -2
  75. package/dist/http/middleware/request-id.mjs +1 -1
  76. package/dist/http/middleware/request-logger.mjs +2 -2
  77. package/dist/http/middleware/session.mjs +2 -2
  78. package/dist/http/middleware/spa.d.mts +1 -1
  79. package/dist/http/middleware/spa.mjs +2 -2
  80. package/dist/http/middleware/upload.d.mts +1 -1
  81. package/dist/http/middleware/upload.mjs +2 -2
  82. package/dist/http/middleware/validate.mjs +1 -1
  83. package/dist/http/middleware/views.d.mts +1 -1
  84. package/dist/http/middleware/views.mjs +2 -2
  85. package/dist/http/query/index.mjs +2 -2
  86. package/dist/http/router-builder.d.mts +1 -1
  87. package/dist/http/router-builder.mjs +2 -2
  88. package/dist/http/runtimes/fastify.d.mts +1 -1
  89. package/dist/http/runtimes/fastify.mjs +2 -2
  90. package/dist/http/runtimes/h3-web.d.mts +1 -1
  91. package/dist/http/runtimes/h3-web.mjs +2 -2
  92. package/dist/http/runtimes/h3.d.mts +1 -1
  93. package/dist/http/runtimes/h3.d.mts.map +1 -1
  94. package/dist/http/runtimes/h3.mjs +2 -2
  95. package/dist/http/runtimes/h3.mjs.map +1 -1
  96. package/dist/{index-Di64XOKG.d.mts → index-BaMA8_NF.d.mts} +2 -2
  97. package/dist/{index-Di64XOKG.d.mts.map → index-BaMA8_NF.d.mts.map} +1 -1
  98. package/dist/index.d.mts +12 -12
  99. package/dist/index.mjs +2 -2
  100. package/dist/{kv-stores-CbsEHE5J.d.mts → kv-stores-BPxJjf56.d.mts} +23 -6
  101. package/dist/kv-stores-BPxJjf56.d.mts.map +1 -0
  102. package/dist/{kv-stores-pjWD0-ri.mjs → kv-stores-CosFzIYm.mjs} +2 -2
  103. package/dist/kv-stores-CosFzIYm.mjs.map +1 -0
  104. package/dist/{metadata-CeUKYNRZ.mjs → metadata-DUJjjW0o.mjs} +2 -2
  105. package/dist/{metadata-CeUKYNRZ.mjs.map → metadata-DUJjjW0o.mjs.map} +1 -1
  106. package/dist/{module-registry-DIngF0Ne.mjs → module-registry-CpXYcf4e.mjs} +2 -2
  107. package/dist/{module-registry-DIngF0Ne.mjs.map → module-registry-CpXYcf4e.mjs.map} +1 -1
  108. package/dist/{query-BzyWJrd6.mjs → query-u5LBf5yW.mjs} +2 -2
  109. package/dist/{query-BzyWJrd6.mjs.map → query-u5LBf5yW.mjs.map} +1 -1
  110. package/dist/{reply-CdVb6ipw.mjs → reply-Ca-H1Ozr.mjs} +3 -3
  111. package/dist/{reply-CdVb6ipw.mjs.map → reply-Ca-H1Ozr.mjs.map} +1 -1
  112. package/dist/{respond-ERWwiws9.mjs → respond-C6fe709p.mjs} +2 -2
  113. package/dist/{respond-ERWwiws9.mjs.map → respond-C6fe709p.mjs.map} +1 -1
  114. package/dist/{router-builder-DptndWhd.d.mts → router-builder-DUi2RHBv.d.mts} +2 -2
  115. package/dist/{router-builder-DptndWhd.d.mts.map → router-builder-DUi2RHBv.d.mts.map} +1 -1
  116. package/dist/{router-builder-FukfDDLt.mjs → router-builder-Dqs8ITU7.mjs} +3 -3
  117. package/dist/{router-builder-FukfDDLt.mjs.map → router-builder-Dqs8ITU7.mjs.map} +1 -1
  118. package/dist/{spa-DhGwM3_A.d.mts → spa-DAoE9C2S.d.mts} +11 -11
  119. package/dist/{spa-DhGwM3_A.d.mts.map → spa-DAoE9C2S.d.mts.map} +1 -1
  120. package/dist/{upload-config-DtnX4uVR.mjs → upload-config-Da_g7Rig.mjs} +2 -2
  121. package/dist/{upload-config-DtnX4uVR.mjs.map → upload-config-Da_g7Rig.mjs.map} +1 -1
  122. package/dist/{upload-CbJOK-j9.d.mts → upload-dSsVE0BW.d.mts} +3 -3
  123. package/dist/{upload-CbJOK-j9.d.mts.map → upload-dSsVE0BW.d.mts.map} +1 -1
  124. package/dist/web.d.mts +4 -4
  125. package/dist/web.mjs +2 -2
  126. package/package.json +1 -1
  127. package/dist/bootstrap-BIjHgG4v.d.mts.map +0 -1
  128. package/dist/container-BW_MlgLj.d.mts.map +0 -1
  129. package/dist/container-GbeejFhH.mjs +0 -117
  130. package/dist/container-GbeejFhH.mjs.map +0 -1
  131. package/dist/kv-stores-CbsEHE5J.d.mts.map +0 -1
  132. package/dist/kv-stores-pjWD0-ri.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"router-builder-FukfDDLt.mjs","names":[],"sources":["../src/core/contributor-pipeline.ts","../src/core/contributor-runner.ts","../src/http/router-builder.ts"],"sourcesContent":["import type { AnyContributorRegistration } from './context-decorator'\nimport {\n ContributorCycleError,\n DuplicateContributorError,\n MissingContributorError,\n} from './context-errors'\n\n/**\n * Where a contributor was registered, ordered by precedence\n * (lower index wins). Mirrors `architecture.md` §20.4.\n *\n * Levels (highest → lowest precedence):\n * - `'method'` — `@LoadX` on a controller method.\n * - `'class'` — `@LoadX` on a controller class.\n * - `'module'` — returned by `AppModule.contributors?()`. Per-module scope.\n * - `'adapter'` — returned by `AppAdapter.contributors?()`. Cross-cutting, applies\n * to every module's routes.\n * - `'global'` — `ApplicationOptions.contributors`. App-wide default.\n */\nexport type ContributorSource = 'method' | 'class' | 'module' | 'adapter' | 'global'\n\n/** Precedence-ranked source list. Index = precedence; method (0) wins ties. */\nconst PRECEDENCE_ORDER: readonly ContributorSource[] = [\n 'method',\n 'class',\n 'module',\n 'adapter',\n 'global',\n]\n\n/**\n * Input row to {@link buildPipeline}. Pairs a registration with its origin\n * so dedup can apply the precedence rule and diagnostic errors can name\n * the offending site.\n *\n * `registration` is the type-erased {@link AnyContributorRegistration}\n * — the runner doesn't care about each registration's narrow `D` /\n * `Ctx` shape, and adopters frequently mix HTTP / transport-agnostic\n * contributors in the same `externalSources` / `contributors()`\n * array. The narrow generics from `defineContextDecorator` survive\n * at the *definition* site (where `resolve` is typed); they're erased\n * here so collections compose without `as any` casts.\n */\nexport interface SourcedRegistration {\n registration: AnyContributorRegistration\n source: ContributorSource\n /** Human label surfaced in DuplicateContributorError messages. */\n label?: string\n}\n\n/**\n * Resolved, validated, topo-sorted pipeline ready for execution.\n *\n * `contributors` is in execution order — every entry's `dependsOn`\n * keys appear earlier. `keys` is the set of all keys this pipeline\n * produces; the runner uses it for fast existence checks and the\n * builder uses it for missing-dep validation.\n */\nexport interface ContributorPipeline {\n readonly contributors: readonly AnyContributorRegistration[]\n readonly keys: ReadonlySet<string>\n}\n\nexport interface BuildPipelineOptions {\n /** Optional route identifier surfaced in diagnostic errors (e.g., 'GET /projects/:id'). */\n route?: string\n}\n\n/**\n * Build, validate, and topo-sort a contributor pipeline.\n *\n * Pipeline:\n *\n * 1. Group sources by precedence (method > class > module > adapter > global).\n * 2. Within each precedence level, two contributors with the same `key`\n * raise {@link DuplicateContributorError}.\n * 3. Across levels, the highest-precedence contributor wins each key\n * (lower-precedence duplicates are silently dropped — that is the\n * documented override mechanism, not an error).\n * 4. Every key referenced by `dependsOn` must be produced by the\n * surviving set, otherwise {@link MissingContributorError}.\n * 5. Kahn's algorithm topo-sorts the survivors. Any nodes left after\n * the queue drains form a cycle → {@link ContributorCycleError}\n * with a reconstructed path.\n *\n * Pure function — no DI, no I/O. Safe to call once at startup per\n * route and cache the result.\n */\nexport function buildPipeline(\n sources: readonly SourcedRegistration[],\n options: BuildPipelineOptions = {},\n): ContributorPipeline {\n const route = options.route\n\n // Step 1: group by source.\n const bySource = new Map<ContributorSource, SourcedRegistration[]>()\n for (const src of PRECEDENCE_ORDER) bySource.set(src, [])\n for (const entry of sources) bySource.get(entry.source)!.push(entry)\n\n // Step 2 + 3: dedup with precedence. We walk highest precedence first,\n // claim its keys, and drop lower-precedence rows that conflict.\n const winners = new Map<string, SourcedRegistration>()\n for (const src of PRECEDENCE_ORDER) {\n const rows = bySource.get(src)!\n const keysAtThisLevel = new Map<string, SourcedRegistration[]>()\n for (const row of rows) {\n const key = row.registration.key\n const existing = keysAtThisLevel.get(key) ?? []\n existing.push(row)\n keysAtThisLevel.set(key, existing)\n }\n for (const [key, rowsForKey] of keysAtThisLevel) {\n if (rowsForKey.length > 1) {\n const labels = rowsForKey.map((r, i) => r.label ?? `${src}#${i}`)\n throw new DuplicateContributorError(key, labels)\n }\n if (!winners.has(key)) winners.set(key, rowsForKey[0])\n }\n }\n\n const survivors = [...winners.values()].map((r) => r.registration)\n const allKeys = new Set(survivors.map((r) => r.key))\n\n // Step 4: dependsOn validation.\n for (const reg of survivors) {\n for (const dep of reg.dependsOn) {\n if (!allKeys.has(dep)) {\n throw new MissingContributorError(dep, reg.key, route, reg.definedAt)\n }\n }\n }\n\n // Step 5: Kahn topo-sort.\n const sorted = topoSort(survivors, route)\n\n return Object.freeze({\n contributors: Object.freeze(sorted),\n keys: allKeys,\n })\n}\n\n/**\n * Kahn's algorithm with deterministic ordering on ties.\n *\n * Nodes with no remaining incoming edges enter the queue in input\n * order. Cycles surface as nodes still present after the queue\n * drains; we reconstruct one cycle path for diagnostics.\n */\nfunction topoSort(\n registrations: readonly AnyContributorRegistration[],\n route: string | undefined,\n): AnyContributorRegistration[] {\n // indegree[key] = number of unresolved dependsOn entries for key\n const indegree = new Map<string, number>()\n // dependents[depKey] = list of registrations that depend on depKey\n const dependents = new Map<string, AnyContributorRegistration[]>()\n const byKey = new Map<string, AnyContributorRegistration>()\n\n for (const reg of registrations) {\n indegree.set(reg.key, reg.dependsOn.length)\n byKey.set(reg.key, reg)\n }\n for (const reg of registrations) {\n for (const dep of reg.dependsOn) {\n const list = dependents.get(dep) ?? []\n list.push(reg)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: registrations with zero indegree, preserving input order.\n const queue: AnyContributorRegistration[] = []\n for (const reg of registrations) {\n if (indegree.get(reg.key) === 0) queue.push(reg)\n }\n\n const sorted: AnyContributorRegistration[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.key) ?? []) {\n const next = (indegree.get(dependent.key) ?? 0) - 1\n indegree.set(dependent.key, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== registrations.length) {\n const remaining = registrations.filter((r) => (indegree.get(r.key) ?? 0) > 0)\n throw new ContributorCycleError(reconstructCycle(remaining, byKey), route)\n }\n\n return sorted\n}\n\n/**\n * Reconstruct one cycle path from the residual graph.\n *\n * Walks from any remaining node, following its first dependency that's\n * still in the residual set, until we revisit a node. The path returned\n * starts and ends with the same key so the message reads naturally:\n * `tenant → project → tenant`.\n */\nfunction reconstructCycle(\n residual: readonly AnyContributorRegistration[],\n byKey: ReadonlyMap<string, AnyContributorRegistration>,\n): string[] {\n const residualKeys = new Set(residual.map((r) => r.key))\n const start = residual[0].key\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor: string = start\n\n while (true) {\n const reg = byKey.get(cursor)!\n const nextDep = reg.dependsOn.find((d) => residualKeys.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n // Trim the prefix that isn't part of the cycle.\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n // Fallback — couldn't reconstruct a strict cycle, return what we walked.\n return path\n}\n","import type { Container } from './container'\nimport type { ContributorRegistration } from './context-decorator'\nimport type { ExecutionContext } from './execution-context'\nimport type { ContributorPipeline } from './contributor-pipeline'\n\nexport interface RunContributorsOptions {\n /** Pre-built, validated, topo-sorted pipeline. */\n pipeline: ContributorPipeline\n /** The execution context contributors read from and write into. */\n ctx: ExecutionContext\n /** DI container used to resolve declared `deps`. */\n container: Container\n}\n\n/**\n * Execute a built {@link ContributorPipeline} against an {@link ExecutionContext}.\n *\n * Sequential — one contributor at a time, in the topo order chosen at\n * pipeline-build time. Per `architecture.md` §20.10, V1 does not run\n * contributors in parallel even within the same topo level; that is a\n * V2 follow-up gated on profiling.\n *\n * ## Error matrix (architecture.md §20.9)\n *\n * | resolve outcome | optional | onError defined | runner behaviour |\n * |-----------------|----------|-----------------|--------------------------------------------------|\n * | throws | true | — | skip; `ctx.get(key)` remains undefined |\n * | throws | false | yes | call hook; returned value (if any) stored |\n * | throws | false | no | propagate the original error |\n * | hook throws | — | yes | propagate the new error |\n * | hook returns | — | yes | undefined/void → skip; value → `ctx.set(key, v)` |\n * | resolves | — | — | `ctx.set(key, value)` |\n *\n * Container.resolve() throwing while building `deps` is treated as a\n * resolve-side throw and flows through the same matrix — adopters who\n * need different behaviour for missing-DI vs. data-lookup failures can\n * wrap the container access inside their own `resolve()`.\n */\nexport async function runContributors(options: RunContributorsOptions): Promise<void> {\n const { pipeline, ctx, container } = options\n\n for (const reg of pipeline.contributors) {\n await runOne(reg, ctx, container)\n }\n}\n\nasync function runOne(\n reg: ContributorRegistration,\n ctx: ExecutionContext,\n container: Container,\n): Promise<void> {\n let value: unknown\n try {\n const deps = resolveDeps(reg, container)\n value = await reg.resolve(ctx, deps)\n } catch (err) {\n if (reg.optional) return\n if (reg.onError) {\n // Hook throws are unconditionally propagated — there is no\n // second-chance hook. Returning undefined/void from the hook\n // means \"skip\"; returning a value means \"use this instead\".\n const replacement = await reg.onError(err, ctx)\n if (replacement === undefined) return\n ctx.set(reg.key, replacement as never)\n return\n }\n throw err\n }\n ctx.set(reg.key, value as never)\n}\n\n/**\n * Resolve a contributor's declared dependencies through the container,\n * preserving the property names from the spec so `resolve(ctx, deps)`\n * receives exactly the shape the user wrote.\n *\n * Empty `deps` produces an empty object — `resolve()` always sees an\n * object, never `undefined`.\n */\nfunction resolveDeps(reg: ContributorRegistration, container: Container): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, token] of Object.entries(reg.deps)) {\n out[key] = container.resolve(token as never)\n }\n return out\n}\n","// Concrete-module imports (NOT the `../core` barrel): the barrel re-exports\n// the asset manager, whose eager `node:fs` import would poison the edge-safe\n// `@forinda/kickjs/web` entry graph that flows through this file.\nimport { buildPipeline, type SourcedRegistration } from '../core/contributor-pipeline'\nimport { runContributors } from '../core/contributor-runner'\nimport { Container } from '../core/container'\nimport { METADATA } from '../core/interfaces'\nimport type { ContributorRegistration } from '../core/context-decorator'\nimport type { FileUploadConfig, MiddlewareHandler, RouteDefinition } from '../core/decorators'\nimport { getClassMeta, getMethodMeta, getMethodMetaOrUndefined } from '../core/metadata'\nimport { duplicateRouteError } from '../core/kick-errors'\nimport type { CtxHandler, RouteEntry, RouteMethod } from './runtime'\n\n/**\n * Per-module SourcedRegistration[] threaded through the route-mount loop by\n * Application.setup(). Carries module + adapter + global contributors so\n * buildRouteTable() can merge them with class + method ones into a single pipeline.\n *\n * Module setup is sequential, so this slot is race-free. Cleared in a finally\n * block after each module mounts. Outside of Application setup the slot is\n * empty — direct buildRouteTable()/buildRoutes() callers (mostly tests) see only\n * class + method contributors unless they pass `externalSources` explicitly.\n *\n * Same idiom as `Container._requestStoreProvider`: an internal escape hatch for\n * cross-module wiring without inversion-of-control gymnastics.\n *\n * @internal\n */\nlet _externalContributorSources: readonly SourcedRegistration[] = []\n\n/** @internal — set by Application.setup() before each `mod.routes()` call. */\nexport function _setExternalContributorSources(sources: readonly SourcedRegistration[]): void {\n _externalContributorSources = sources\n}\n\n/**\n * Boot-time duplicate-route guard shared by the node and web mount loops.\n *\n * Registers `METHOD /mounted/path` into `registry` and throws\n * {@link duplicateRouteError} (KICK006) if another handler already claimed\n * it. Param *names* are ignored — `/tasks/:id` and `/tasks/:taskId` are the\n * same route at dispatch time, so they collide here too.\n *\n * @internal\n */\nexport function assertRouteUnique(\n registry: Map<string, string>,\n method: string,\n fullPath: string,\n owner: string,\n): void {\n const key = `${method.toUpperCase()} ${fullPath.replace(/:[^/]+/g, ':*')}`\n const prior = registry.get(key)\n if (prior !== undefined) {\n throw duplicateRouteError(method.toUpperCase(), fullPath, owner, prior)\n }\n registry.set(key, owner)\n}\n\nexport interface BuildRoutesOptions {\n /**\n * Extra contributors to merge into the per-route pipeline at their declared\n * precedence levels. Pass explicitly when calling buildRouteTable/buildRoutes\n * outside the Application route-mount loop (typically in tests). When omitted,\n * falls back to the slot set by Application.setup().\n */\n externalSources?: readonly SourcedRegistration[]\n}\n\n/**\n * Turn a controller class decorated with @Get, @Post, etc. into a plain-data\n * {@link RouteEntry}[] — the engine-neutral route table an {@link HttpRuntime}\n * materializes (see `docs/http/spec-http-runtimes.md`, Avenue B).\n *\n * What used to be per-handler Express `(req, res, next)` closures is now\n * captured as data: `middlewares` keep their `(ctx, next)` shape, the contributor\n * pipeline is pre-built into a `contributorRunner` closure, validation / upload\n * stay as metadata, and the terminal `handler` resolves the controller per-request\n * (to respect DI scoping) and invokes it. The runtime owns how these get wrapped\n * onto its engine.\n *\n * Routes use only the method-level decorator paths (e.g. @Get('/me') → '/me').\n * The @Controller path is NOT baked in — it is metadata for Swagger/introspection.\n * The module's routes().path is the single source of truth for the mount prefix,\n * which avoids path doubling when both the module and controller specify the path.\n *\n * The contributor pipeline is built here, so a cycle or missing `dependsOn`\n * throws at table-build time (boot) rather than on first request.\n */\nexport function buildRouteTable(\n controllerClass: any,\n options: BuildRoutesOptions = {},\n): RouteEntry[] {\n const container = Container.getInstance()\n const externalSources = options.externalSources ?? _externalContributorSources\n const routes: RouteDefinition[] = getClassMeta<RouteDefinition[]>(\n METADATA.ROUTES,\n controllerClass,\n [],\n )\n\n // Class-level middleware\n const classMiddlewares: MiddlewareHandler[] = getClassMeta<MiddlewareHandler[]>(\n METADATA.CLASS_MIDDLEWARES,\n controllerClass,\n [],\n )\n\n // Class-level Context Contributors (#107) — applied to every method on the\n // controller. Method-level contributors are added per-route below.\n const classContributors: ContributorRegistration[] = getClassMeta<ContributorRegistration[]>(\n METADATA.CLASS_CONTRIBUTORS,\n controllerClass,\n [],\n )\n\n const entries: RouteEntry[] = []\n\n for (const route of routes) {\n const method = route.method.toUpperCase() as RouteMethod\n const fullPath = route.path || '/'\n\n // Method-level middleware\n const methodMiddlewares: MiddlewareHandler[] = getMethodMeta<MiddlewareHandler[]>(\n METADATA.METHOD_MIDDLEWARES,\n controllerClass,\n route.handlerName,\n [],\n )\n\n // @FileUpload decorator — runtime supplies the upload backend.\n const fileUploadConfig = getMethodMetaOrUndefined<FileUploadConfig>(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n\n // Context Contributor pipeline (#107) — class + method contributors,\n // built once at mount time so cycle/missing-dep failures abort boot.\n const methodContributors: ContributorRegistration[] = getMethodMeta<ContributorRegistration[]>(\n METADATA.METHOD_CONTRIBUTORS,\n controllerClass,\n route.handlerName,\n [],\n )\n\n let contributorRunner: CtxHandler | null = null\n if (\n classContributors.length > 0 ||\n methodContributors.length > 0 ||\n externalSources.length > 0\n ) {\n // Labels are surfaced in DuplicateContributorError messages — include\n // the registration's key + the original index so a same-key collision\n // at one precedence level points at the conflicting decorator slot,\n // not just the host method/class.\n const sources: SourcedRegistration[] = [\n ...methodContributors.map((registration, i): SourcedRegistration => ({\n source: 'method',\n registration,\n label: `${controllerClass.name}.${String(route.handlerName)}#${i}(${registration.key})`,\n })),\n ...classContributors.map((registration, i): SourcedRegistration => ({\n source: 'class',\n registration,\n label: `${controllerClass.name}.@class#${i}(${registration.key})`,\n })),\n ...externalSources,\n ]\n const pipeline = buildPipeline(sources, {\n route: `${method} ${fullPath}`,\n })\n contributorRunner = (ctx) => runContributors({ pipeline, ctx, container })\n }\n\n // Terminal handler — resolve controller per-request to respect DI scoping.\n const handler: CtxHandler = (ctx) => container.resolve(controllerClass)[route.handlerName](ctx)\n\n entries.push({\n method,\n path: fullPath,\n middlewares: [...classMiddlewares, ...methodMiddlewares],\n contributorRunner,\n handler,\n meta: {\n controller: controllerClass,\n handlerName: route.handlerName,\n validation: route.validation,\n upload: fileUploadConfig,\n },\n })\n }\n\n return entries\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"router-builder-Dqs8ITU7.mjs","names":[],"sources":["../src/core/contributor-pipeline.ts","../src/core/contributor-runner.ts","../src/http/router-builder.ts"],"sourcesContent":["import type { AnyContributorRegistration } from './context-decorator'\nimport {\n ContributorCycleError,\n DuplicateContributorError,\n MissingContributorError,\n} from './context-errors'\n\n/**\n * Where a contributor was registered, ordered by precedence\n * (lower index wins). Mirrors `architecture.md` §20.4.\n *\n * Levels (highest → lowest precedence):\n * - `'method'` — `@LoadX` on a controller method.\n * - `'class'` — `@LoadX` on a controller class.\n * - `'module'` — returned by `AppModule.contributors?()`. Per-module scope.\n * - `'adapter'` — returned by `AppAdapter.contributors?()`. Cross-cutting, applies\n * to every module's routes.\n * - `'global'` — `ApplicationOptions.contributors`. App-wide default.\n */\nexport type ContributorSource = 'method' | 'class' | 'module' | 'adapter' | 'global'\n\n/** Precedence-ranked source list. Index = precedence; method (0) wins ties. */\nconst PRECEDENCE_ORDER: readonly ContributorSource[] = [\n 'method',\n 'class',\n 'module',\n 'adapter',\n 'global',\n]\n\n/**\n * Input row to {@link buildPipeline}. Pairs a registration with its origin\n * so dedup can apply the precedence rule and diagnostic errors can name\n * the offending site.\n *\n * `registration` is the type-erased {@link AnyContributorRegistration}\n * — the runner doesn't care about each registration's narrow `D` /\n * `Ctx` shape, and adopters frequently mix HTTP / transport-agnostic\n * contributors in the same `externalSources` / `contributors()`\n * array. The narrow generics from `defineContextDecorator` survive\n * at the *definition* site (where `resolve` is typed); they're erased\n * here so collections compose without `as any` casts.\n */\nexport interface SourcedRegistration {\n registration: AnyContributorRegistration\n source: ContributorSource\n /** Human label surfaced in DuplicateContributorError messages. */\n label?: string\n}\n\n/**\n * Resolved, validated, topo-sorted pipeline ready for execution.\n *\n * `contributors` is in execution order — every entry's `dependsOn`\n * keys appear earlier. `keys` is the set of all keys this pipeline\n * produces; the runner uses it for fast existence checks and the\n * builder uses it for missing-dep validation.\n */\nexport interface ContributorPipeline {\n readonly contributors: readonly AnyContributorRegistration[]\n readonly keys: ReadonlySet<string>\n}\n\nexport interface BuildPipelineOptions {\n /** Optional route identifier surfaced in diagnostic errors (e.g., 'GET /projects/:id'). */\n route?: string\n}\n\n/**\n * Build, validate, and topo-sort a contributor pipeline.\n *\n * Pipeline:\n *\n * 1. Group sources by precedence (method > class > module > adapter > global).\n * 2. Within each precedence level, two contributors with the same `key`\n * raise {@link DuplicateContributorError}.\n * 3. Across levels, the highest-precedence contributor wins each key\n * (lower-precedence duplicates are silently dropped — that is the\n * documented override mechanism, not an error).\n * 4. Every key referenced by `dependsOn` must be produced by the\n * surviving set, otherwise {@link MissingContributorError}.\n * 5. Kahn's algorithm topo-sorts the survivors. Any nodes left after\n * the queue drains form a cycle → {@link ContributorCycleError}\n * with a reconstructed path.\n *\n * Pure function — no DI, no I/O. Safe to call once at startup per\n * route and cache the result.\n */\nexport function buildPipeline(\n sources: readonly SourcedRegistration[],\n options: BuildPipelineOptions = {},\n): ContributorPipeline {\n const route = options.route\n\n // Step 1: group by source.\n const bySource = new Map<ContributorSource, SourcedRegistration[]>()\n for (const src of PRECEDENCE_ORDER) bySource.set(src, [])\n for (const entry of sources) bySource.get(entry.source)!.push(entry)\n\n // Step 2 + 3: dedup with precedence. We walk highest precedence first,\n // claim its keys, and drop lower-precedence rows that conflict.\n const winners = new Map<string, SourcedRegistration>()\n for (const src of PRECEDENCE_ORDER) {\n const rows = bySource.get(src)!\n const keysAtThisLevel = new Map<string, SourcedRegistration[]>()\n for (const row of rows) {\n const key = row.registration.key\n const existing = keysAtThisLevel.get(key) ?? []\n existing.push(row)\n keysAtThisLevel.set(key, existing)\n }\n for (const [key, rowsForKey] of keysAtThisLevel) {\n if (rowsForKey.length > 1) {\n const labels = rowsForKey.map((r, i) => r.label ?? `${src}#${i}`)\n throw new DuplicateContributorError(key, labels)\n }\n if (!winners.has(key)) winners.set(key, rowsForKey[0])\n }\n }\n\n const survivors = [...winners.values()].map((r) => r.registration)\n const allKeys = new Set(survivors.map((r) => r.key))\n\n // Step 4: dependsOn validation.\n for (const reg of survivors) {\n for (const dep of reg.dependsOn) {\n if (!allKeys.has(dep)) {\n throw new MissingContributorError(dep, reg.key, route, reg.definedAt)\n }\n }\n }\n\n // Step 5: Kahn topo-sort.\n const sorted = topoSort(survivors, route)\n\n return Object.freeze({\n contributors: Object.freeze(sorted),\n keys: allKeys,\n })\n}\n\n/**\n * Kahn's algorithm with deterministic ordering on ties.\n *\n * Nodes with no remaining incoming edges enter the queue in input\n * order. Cycles surface as nodes still present after the queue\n * drains; we reconstruct one cycle path for diagnostics.\n */\nfunction topoSort(\n registrations: readonly AnyContributorRegistration[],\n route: string | undefined,\n): AnyContributorRegistration[] {\n // indegree[key] = number of unresolved dependsOn entries for key\n const indegree = new Map<string, number>()\n // dependents[depKey] = list of registrations that depend on depKey\n const dependents = new Map<string, AnyContributorRegistration[]>()\n const byKey = new Map<string, AnyContributorRegistration>()\n\n for (const reg of registrations) {\n indegree.set(reg.key, reg.dependsOn.length)\n byKey.set(reg.key, reg)\n }\n for (const reg of registrations) {\n for (const dep of reg.dependsOn) {\n const list = dependents.get(dep) ?? []\n list.push(reg)\n dependents.set(dep, list)\n }\n }\n\n // Initial queue: registrations with zero indegree, preserving input order.\n const queue: AnyContributorRegistration[] = []\n for (const reg of registrations) {\n if (indegree.get(reg.key) === 0) queue.push(reg)\n }\n\n const sorted: AnyContributorRegistration[] = []\n while (queue.length > 0) {\n const node = queue.shift()!\n sorted.push(node)\n for (const dependent of dependents.get(node.key) ?? []) {\n const next = (indegree.get(dependent.key) ?? 0) - 1\n indegree.set(dependent.key, next)\n if (next === 0) queue.push(dependent)\n }\n }\n\n if (sorted.length !== registrations.length) {\n const remaining = registrations.filter((r) => (indegree.get(r.key) ?? 0) > 0)\n throw new ContributorCycleError(reconstructCycle(remaining, byKey), route)\n }\n\n return sorted\n}\n\n/**\n * Reconstruct one cycle path from the residual graph.\n *\n * Walks from any remaining node, following its first dependency that's\n * still in the residual set, until we revisit a node. The path returned\n * starts and ends with the same key so the message reads naturally:\n * `tenant → project → tenant`.\n */\nfunction reconstructCycle(\n residual: readonly AnyContributorRegistration[],\n byKey: ReadonlyMap<string, AnyContributorRegistration>,\n): string[] {\n const residualKeys = new Set(residual.map((r) => r.key))\n const start = residual[0].key\n const path: string[] = [start]\n const seen = new Set<string>([start])\n let cursor: string = start\n\n while (true) {\n const reg = byKey.get(cursor)!\n const nextDep = reg.dependsOn.find((d) => residualKeys.has(d))\n if (nextDep === undefined) break\n if (seen.has(nextDep)) {\n // Trim the prefix that isn't part of the cycle.\n const cycleStart = path.indexOf(nextDep)\n return [...path.slice(cycleStart), nextDep]\n }\n path.push(nextDep)\n seen.add(nextDep)\n cursor = nextDep\n }\n\n // Fallback — couldn't reconstruct a strict cycle, return what we walked.\n return path\n}\n","import type { Container } from './container'\nimport type { ContributorRegistration } from './context-decorator'\nimport type { ExecutionContext } from './execution-context'\nimport type { ContributorPipeline } from './contributor-pipeline'\n\nexport interface RunContributorsOptions {\n /** Pre-built, validated, topo-sorted pipeline. */\n pipeline: ContributorPipeline\n /** The execution context contributors read from and write into. */\n ctx: ExecutionContext\n /** DI container used to resolve declared `deps`. */\n container: Container\n}\n\n/**\n * Execute a built {@link ContributorPipeline} against an {@link ExecutionContext}.\n *\n * Sequential — one contributor at a time, in the topo order chosen at\n * pipeline-build time. Per `architecture.md` §20.10, V1 does not run\n * contributors in parallel even within the same topo level; that is a\n * V2 follow-up gated on profiling.\n *\n * ## Error matrix (architecture.md §20.9)\n *\n * | resolve outcome | optional | onError defined | runner behaviour |\n * |-----------------|----------|-----------------|--------------------------------------------------|\n * | throws | true | — | skip; `ctx.get(key)` remains undefined |\n * | throws | false | yes | call hook; returned value (if any) stored |\n * | throws | false | no | propagate the original error |\n * | hook throws | — | yes | propagate the new error |\n * | hook returns | — | yes | undefined/void → skip; value → `ctx.set(key, v)` |\n * | resolves | — | — | `ctx.set(key, value)` |\n *\n * Container.resolve() throwing while building `deps` is treated as a\n * resolve-side throw and flows through the same matrix — adopters who\n * need different behaviour for missing-DI vs. data-lookup failures can\n * wrap the container access inside their own `resolve()`.\n */\nexport async function runContributors(options: RunContributorsOptions): Promise<void> {\n const { pipeline, ctx, container } = options\n\n for (const reg of pipeline.contributors) {\n await runOne(reg, ctx, container)\n }\n}\n\nasync function runOne(\n reg: ContributorRegistration,\n ctx: ExecutionContext,\n container: Container,\n): Promise<void> {\n let value: unknown\n try {\n const deps = resolveDeps(reg, container)\n value = await reg.resolve(ctx, deps)\n } catch (err) {\n if (reg.optional) return\n if (reg.onError) {\n // Hook throws are unconditionally propagated — there is no\n // second-chance hook. Returning undefined/void from the hook\n // means \"skip\"; returning a value means \"use this instead\".\n const replacement = await reg.onError(err, ctx)\n if (replacement === undefined) return\n ctx.set(reg.key, replacement as never)\n return\n }\n throw err\n }\n ctx.set(reg.key, value as never)\n}\n\n/**\n * Resolve a contributor's declared dependencies through the container,\n * preserving the property names from the spec so `resolve(ctx, deps)`\n * receives exactly the shape the user wrote.\n *\n * Empty `deps` produces an empty object — `resolve()` always sees an\n * object, never `undefined`.\n */\nfunction resolveDeps(reg: ContributorRegistration, container: Container): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [key, token] of Object.entries(reg.deps)) {\n out[key] = container.resolve(token as never)\n }\n return out\n}\n","// Concrete-module imports (NOT the `../core` barrel): the barrel re-exports\n// the asset manager, whose eager `node:fs` import would poison the edge-safe\n// `@forinda/kickjs/web` entry graph that flows through this file.\nimport { buildPipeline, type SourcedRegistration } from '../core/contributor-pipeline'\nimport { runContributors } from '../core/contributor-runner'\nimport { Container } from '../core/container'\nimport { METADATA } from '../core/interfaces'\nimport type { ContributorRegistration } from '../core/context-decorator'\nimport type { FileUploadConfig, MiddlewareHandler, RouteDefinition } from '../core/decorators'\nimport { getClassMeta, getMethodMeta, getMethodMetaOrUndefined } from '../core/metadata'\nimport { duplicateRouteError } from '../core/kick-errors'\nimport type { CtxHandler, RouteEntry, RouteMethod } from './runtime'\n\n/**\n * Per-module SourcedRegistration[] threaded through the route-mount loop by\n * Application.setup(). Carries module + adapter + global contributors so\n * buildRouteTable() can merge them with class + method ones into a single pipeline.\n *\n * Module setup is sequential, so this slot is race-free. Cleared in a finally\n * block after each module mounts. Outside of Application setup the slot is\n * empty — direct buildRouteTable()/buildRoutes() callers (mostly tests) see only\n * class + method contributors unless they pass `externalSources` explicitly.\n *\n * Same idiom as `Container._requestStoreProvider`: an internal escape hatch for\n * cross-module wiring without inversion-of-control gymnastics.\n *\n * @internal\n */\nlet _externalContributorSources: readonly SourcedRegistration[] = []\n\n/** @internal — set by Application.setup() before each `mod.routes()` call. */\nexport function _setExternalContributorSources(sources: readonly SourcedRegistration[]): void {\n _externalContributorSources = sources\n}\n\n/**\n * Boot-time duplicate-route guard shared by the node and web mount loops.\n *\n * Registers `METHOD /mounted/path` into `registry` and throws\n * {@link duplicateRouteError} (KICK006) if another handler already claimed\n * it. Param *names* are ignored — `/tasks/:id` and `/tasks/:taskId` are the\n * same route at dispatch time, so they collide here too.\n *\n * @internal\n */\nexport function assertRouteUnique(\n registry: Map<string, string>,\n method: string,\n fullPath: string,\n owner: string,\n): void {\n const key = `${method.toUpperCase()} ${fullPath.replace(/:[^/]+/g, ':*')}`\n const prior = registry.get(key)\n if (prior !== undefined) {\n throw duplicateRouteError(method.toUpperCase(), fullPath, owner, prior)\n }\n registry.set(key, owner)\n}\n\nexport interface BuildRoutesOptions {\n /**\n * Extra contributors to merge into the per-route pipeline at their declared\n * precedence levels. Pass explicitly when calling buildRouteTable/buildRoutes\n * outside the Application route-mount loop (typically in tests). When omitted,\n * falls back to the slot set by Application.setup().\n */\n externalSources?: readonly SourcedRegistration[]\n}\n\n/**\n * Turn a controller class decorated with @Get, @Post, etc. into a plain-data\n * {@link RouteEntry}[] — the engine-neutral route table an {@link HttpRuntime}\n * materializes (see `docs/http/spec-http-runtimes.md`, Avenue B).\n *\n * What used to be per-handler Express `(req, res, next)` closures is now\n * captured as data: `middlewares` keep their `(ctx, next)` shape, the contributor\n * pipeline is pre-built into a `contributorRunner` closure, validation / upload\n * stay as metadata, and the terminal `handler` resolves the controller per-request\n * (to respect DI scoping) and invokes it. The runtime owns how these get wrapped\n * onto its engine.\n *\n * Routes use only the method-level decorator paths (e.g. @Get('/me') → '/me').\n * The @Controller path is NOT baked in — it is metadata for Swagger/introspection.\n * The module's routes().path is the single source of truth for the mount prefix,\n * which avoids path doubling when both the module and controller specify the path.\n *\n * The contributor pipeline is built here, so a cycle or missing `dependsOn`\n * throws at table-build time (boot) rather than on first request.\n */\nexport function buildRouteTable(\n controllerClass: any,\n options: BuildRoutesOptions = {},\n): RouteEntry[] {\n const container = Container.getInstance()\n const externalSources = options.externalSources ?? _externalContributorSources\n const routes: RouteDefinition[] = getClassMeta<RouteDefinition[]>(\n METADATA.ROUTES,\n controllerClass,\n [],\n )\n\n // Class-level middleware\n const classMiddlewares: MiddlewareHandler[] = getClassMeta<MiddlewareHandler[]>(\n METADATA.CLASS_MIDDLEWARES,\n controllerClass,\n [],\n )\n\n // Class-level Context Contributors (#107) — applied to every method on the\n // controller. Method-level contributors are added per-route below.\n const classContributors: ContributorRegistration[] = getClassMeta<ContributorRegistration[]>(\n METADATA.CLASS_CONTRIBUTORS,\n controllerClass,\n [],\n )\n\n const entries: RouteEntry[] = []\n\n for (const route of routes) {\n const method = route.method.toUpperCase() as RouteMethod\n const fullPath = route.path || '/'\n\n // Method-level middleware\n const methodMiddlewares: MiddlewareHandler[] = getMethodMeta<MiddlewareHandler[]>(\n METADATA.METHOD_MIDDLEWARES,\n controllerClass,\n route.handlerName,\n [],\n )\n\n // @FileUpload decorator — runtime supplies the upload backend.\n const fileUploadConfig = getMethodMetaOrUndefined<FileUploadConfig>(\n METADATA.FILE_UPLOAD,\n controllerClass,\n route.handlerName,\n )\n\n // Context Contributor pipeline (#107) — class + method contributors,\n // built once at mount time so cycle/missing-dep failures abort boot.\n const methodContributors: ContributorRegistration[] = getMethodMeta<ContributorRegistration[]>(\n METADATA.METHOD_CONTRIBUTORS,\n controllerClass,\n route.handlerName,\n [],\n )\n\n let contributorRunner: CtxHandler | null = null\n if (\n classContributors.length > 0 ||\n methodContributors.length > 0 ||\n externalSources.length > 0\n ) {\n // Labels are surfaced in DuplicateContributorError messages — include\n // the registration's key + the original index so a same-key collision\n // at one precedence level points at the conflicting decorator slot,\n // not just the host method/class.\n const sources: SourcedRegistration[] = [\n ...methodContributors.map((registration, i): SourcedRegistration => ({\n source: 'method',\n registration,\n label: `${controllerClass.name}.${String(route.handlerName)}#${i}(${registration.key})`,\n })),\n ...classContributors.map((registration, i): SourcedRegistration => ({\n source: 'class',\n registration,\n label: `${controllerClass.name}.@class#${i}(${registration.key})`,\n })),\n ...externalSources,\n ]\n const pipeline = buildPipeline(sources, {\n route: `${method} ${fullPath}`,\n })\n contributorRunner = (ctx) => runContributors({ pipeline, ctx, container })\n }\n\n // Terminal handler — resolve controller per-request to respect DI scoping.\n const handler: CtxHandler = (ctx) => container.resolve(controllerClass)[route.handlerName](ctx)\n\n entries.push({\n method,\n path: fullPath,\n middlewares: [...classMiddlewares, ...methodMiddlewares],\n contributorRunner,\n handler,\n meta: {\n controller: controllerClass,\n handlerName: route.handlerName,\n validation: route.validation,\n upload: fileUploadConfig,\n },\n })\n }\n\n return entries\n}\n"],"mappings":"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"}
@@ -1,21 +1,21 @@
1
1
 
2
- import { s as InjectionToken, t as Container } from "./container-BW_MlgLj.mjs";
3
- import "./index-Di64XOKG.mjs";
4
- import { C as MetaValue, c as ContextDecorator, h as DepValue, u as ContextDecoratorSpec, x as ContextMetaKey } from "./app-module-DZW0uYNA.mjs";
5
- import { T as RequestContext, f as HttpRuntime, k as AdapterFactory, m as RouteEntry, tt as ModuleFactory } from "./adapter-BuOTeSQy.mjs";
6
- import "./application-D1koPFmH.mjs";
7
- import "./bootstrap-BIjHgG4v.mjs";
2
+ import { s as InjectionToken, t as Container } from "./container-BA-O7uBF.mjs";
3
+ import "./index-BaMA8_NF.mjs";
4
+ import { C as MetaValue, c as ContextDecorator, h as DepValue, u as ContextDecoratorSpec, x as ContextMetaKey } from "./app-module-CmIHP7uW.mjs";
5
+ import { T as RequestContext, f as HttpRuntime, k as AdapterFactory, m as RouteEntry, tt as ModuleFactory } from "./adapter-OjEKLePS.mjs";
6
+ import "./application-BmJaP130.mjs";
7
+ import "./bootstrap-2QoNFOmO.mjs";
8
8
  import "./cors-CsibNTQ6.mjs";
9
9
  import "./csrf-DwOhkOC6.mjs";
10
- import "./error-handler-C-xGxZKP.mjs";
10
+ import "./error-handler-ade-elv6.mjs";
11
11
  import "./helmet-C2QEU6S-.mjs";
12
12
  import "./rate-limit-D_cKx6Uw.mjs";
13
13
  import "./request-id-CVzJ1uHD.mjs";
14
14
  import "./session-BHgRyfqZ.mjs";
15
- import { l as Reply } from "./kv-stores-CbsEHE5J.mjs";
16
- import { t as BuildRoutesOptions } from "./router-builder-DptndWhd.mjs";
15
+ import { l as Reply } from "./kv-stores-BPxJjf56.mjs";
16
+ import { t as BuildRoutesOptions } from "./router-builder-DUi2RHBv.mjs";
17
17
  import "./validate-DMYpUnrf.mjs";
18
- import "./upload-CbJOK-j9.mjs";
18
+ import "./upload-dSsVE0BW.mjs";
19
19
  import { Express, NextFunction, Request, Response, Router } from "express";
20
20
  import { AsyncLocalStorage } from "node:async_hooks";
21
21
  //#region src/core/disposables.d.ts
@@ -839,4 +839,4 @@ declare function resolveClientDir(clientDir: string, cwd?: string, entry?: strin
839
839
  declare const SpaAdapter: AdapterFactory<SpaAdapterOptions, unknown>;
840
840
  //#endregion
841
841
  export { disposeAll as $, GroupAssetKeysResult as A, DuplicateContributorError as B, HealthCheckResult as C, AssetKeyPair as D, healthModule as E, MountCycleError as F, CircuitBreakerState as G, MissingContributorError as H, MountKind as I, FormatKickErrorOptions as J, CircuitBreakerStats as K, MountSortItem as L, stripExt as M, DuplicateMountNameError as N, AssetKeyStrategy as O, MissingMountDepError as P, Disposable as Q, mountSort as R, HEALTH_PROBE as S, HealthProbe as T, CircuitBreaker as U, MissingContextValueError as V, CircuitBreakerOptions as W, KickErrorInit as X, KickError as Y, formatKickError as Z, expressRuntime as _, ViewAdapterOptions as a, ClusterOptions as b, TraceContextOptions as c, requestScopeMiddleware as d, registerDisposable as et, RequestStore as f, buildRoutes as g, requestStore as h, ViewAdapter as i, groupAssetKeys as j, GroupAssetKeysOptions as k, parseTraceparent as l, getRequestValue as m, SpaAdapterOptions as n, TRACEPARENT_HEADER as o, getRequestStore as p, CircuitOpenError as q, resolveClientDir as r, TraceContext as s, SpaAdapter as t, traceContext as u, materializeRouter as v, HealthController as w, isClusterPrimary as x, defineHttpContextDecorator as y, ContributorCycleError as z };
842
- //# sourceMappingURL=spa-DhGwM3_A.d.mts.map
842
+ //# sourceMappingURL=spa-DAoE9C2S.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spa-DhGwM3_A.d.mts","names":[],"sources":["../src/core/disposables.ts","../src/core/kick-error.ts","../src/core/circuit-breaker.ts","../src/core/context-errors.ts","../src/core/mount-sort.ts","../src/core/asset-keys.ts","../src/http/health-module.ts","../src/http/cluster.ts","../src/http/define-http-context-decorator.ts","../src/http/runtimes/express.ts","../src/http/request-store.ts","../src/http/middleware/request-scope.ts","../src/http/middleware/trace-context.ts","../src/http/middleware/views.ts","../src/http/middleware/spa.ts"],"mappings":"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"}
1
+ {"version":3,"file":"spa-DAoE9C2S.d.mts","names":[],"sources":["../src/core/disposables.ts","../src/core/kick-error.ts","../src/core/circuit-breaker.ts","../src/core/context-errors.ts","../src/core/mount-sort.ts","../src/core/asset-keys.ts","../src/http/health-module.ts","../src/http/cluster.ts","../src/http/define-http-context-decorator.ts","../src/http/runtimes/express.ts","../src/http/request-store.ts","../src/http/middleware/request-scope.ts","../src/http/middleware/trace-context.ts","../src/http/middleware/views.ts","../src/http/middleware/spa.ts"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.0.0
2
+ * @forinda/kickjs v8.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -9,4 +9,4 @@
9
9
  * @license MIT
10
10
  */
11
11
  import{HttpException,HttpStatus}from"./core/errors.mjs";const MIME_MAP={jpg:`image/jpeg`,jpeg:`image/jpeg`,png:`image/png`,gif:`image/gif`,webp:`image/webp`,svg:`image/svg+xml`,bmp:`image/bmp`,ico:`image/x-icon`,tiff:`image/tiff`,tif:`image/tiff`,avif:`image/avif`,pdf:`application/pdf`,doc:`application/msword`,docx:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`,xls:`application/vnd.ms-excel`,xlsx:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,ppt:`application/vnd.ms-powerpoint`,pptx:`application/vnd.openxmlformats-officedocument.presentationml.presentation`,odt:`application/vnd.oasis.opendocument.text`,ods:`application/vnd.oasis.opendocument.spreadsheet`,odp:`application/vnd.oasis.opendocument.presentation`,rtf:`application/rtf`,txt:`text/plain`,csv:`text/csv`,zip:`application/zip`,gz:`application/gzip`,tar:`application/x-tar`,rar:`application/vnd.rar`,"7z":`application/x-7z-compressed`,mp3:`audio/mpeg`,wav:`audio/wav`,ogg:`audio/ogg`,flac:`audio/flac`,aac:`audio/aac`,mp4:`video/mp4`,webm:`video/webm`,avi:`video/x-msvideo`,mov:`video/quicktime`,mkv:`video/x-matroska`,json:`application/json`,xml:`application/xml`,html:`text/html`};function resolveMimeTypes(types){return types.map(t=>{let lower=t.toLowerCase().replace(/^\./,``);return MIME_MAP[lower]??t})}function buildFileTypeFilter(allowedTypes,customMimeMap){if(!allowedTypes)return()=>!0;if(typeof allowedTypes==`function`)return allowedTypes;let mimeMap=customMimeMap?{...MIME_MAP,...customMimeMap}:MIME_MAP,resolved=allowedTypes.map(t=>mimeMap[t.toLowerCase().replace(/^\./,``)]??t);return mimetype=>resolved.some(type=>type.endsWith(`/*`)?mimetype.startsWith(type.replace(`/*`,`/`)):mimetype===type)}function applyUploadConfig(parts,config){if(config.mode===`none`)return{};let field=config.fieldName??`file`,maxSize=config.maxSize??5242880,typeAllowed=buildFileTypeFilter(config.allowedTypes,config.customMimeMap),files=parts.filter(p=>p.fieldname===field).map(p=>{if(!typeAllowed(p.mimetype,p.filename))throw new HttpException(HttpStatus.UNSUPPORTED_MEDIA_TYPE,`File type ${p.mimetype} is not allowed`);if(p.buffer.length>maxSize)throw new HttpException(HttpStatus.PAYLOAD_TOO_LARGE,`File ${p.filename} exceeds the ${maxSize}-byte limit`);return{fieldname:p.fieldname,originalname:p.filename,encoding:`7bit`,mimetype:p.mimetype,size:p.buffer.length,buffer:p.buffer}});if(config.mode===`single`)return{file:files[0]};let maxCount=config.maxCount??10;return{files:files.slice(0,maxCount)}}export{buildFileTypeFilter as n,resolveMimeTypes as r,applyUploadConfig as t};
12
- //# sourceMappingURL=upload-config-DtnX4uVR.mjs.map
12
+ //# sourceMappingURL=upload-config-Da_g7Rig.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-config-DtnX4uVR.mjs","names":[],"sources":["../src/http/middleware/upload-config.ts"],"sourcesContent":["// Pure (edge-safe) upload core — the engine-neutral half of the upload\n// middleware. Extracted from `upload.ts` so the `@forinda/kickjs/web` entry\n// graph can validate/shape buffered multipart parts WITHOUT dragging in\n// upload.ts's node-only surface (multer peer loading via `node:module`,\n// disk cleanup via `node:fs/promises`). `upload.ts` re-exports everything\n// here, so existing import sites are unchanged.\n\nimport { HttpException, HttpStatus } from '../../core/errors'\nimport type { BaseUploadOptions, FileUploadConfig } from '../../core/decorators'\n\nexport const MIME_MAP: Record<string, string> = {\n // Images\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n ico: 'image/x-icon',\n tiff: 'image/tiff',\n tif: 'image/tiff',\n avif: 'image/avif',\n // Documents\n pdf: 'application/pdf',\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n ppt: 'application/vnd.ms-powerpoint',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n odt: 'application/vnd.oasis.opendocument.text',\n ods: 'application/vnd.oasis.opendocument.spreadsheet',\n odp: 'application/vnd.oasis.opendocument.presentation',\n rtf: 'application/rtf',\n txt: 'text/plain',\n csv: 'text/csv',\n // Archives\n zip: 'application/zip',\n gz: 'application/gzip',\n tar: 'application/x-tar',\n rar: 'application/vnd.rar',\n '7z': 'application/x-7z-compressed',\n // Audio\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n flac: 'audio/flac',\n aac: 'audio/aac',\n // Video\n mp4: 'video/mp4',\n webm: 'video/webm',\n avi: 'video/x-msvideo',\n mov: 'video/quicktime',\n mkv: 'video/x-matroska',\n // Other\n json: 'application/json',\n xml: 'application/xml',\n html: 'text/html',\n}\n\n/**\n * Resolves a list of file type identifiers to MIME type strings.\n * Accepts short extensions (`'jpg'`, `'pdf'`) or full MIME types (`'image/jpeg'`).\n *\n * @example\n * ```ts\n * resolveMimeTypes(['jpg', 'png', 'application/pdf'])\n * // → ['image/jpeg', 'image/png', 'application/pdf']\n * ```\n */\nexport function resolveMimeTypes(types: string[]): string[] {\n return types.map((t) => {\n const lower = t.toLowerCase().replace(/^\\./, '')\n return MIME_MAP[lower] ?? t\n })\n}\n\n/**\n * Build a runtime-agnostic `(mimetype, filename) => boolean` file-type filter\n * from `allowedTypes` (a predicate, or a list of extensions / MIME types /\n * wildcards). Returned by both the multer middleware (Express) and the\n * Fastify / h3 / web multipart paths so the accept/reject rule stays\n * identical across engines. No `allowedTypes` → accept everything.\n */\nexport function buildFileTypeFilter(\n allowedTypes: BaseUploadOptions['allowedTypes'],\n customMimeMap?: Record<string, string>,\n): (mimetype: string, filename: string) => boolean {\n if (!allowedTypes) return () => true\n if (typeof allowedTypes === 'function') return allowedTypes\n const mimeMap = customMimeMap ? { ...MIME_MAP, ...customMimeMap } : MIME_MAP\n const resolved = allowedTypes.map((t) => mimeMap[t.toLowerCase().replace(/^\\./, '')] ?? t)\n return (mimetype: string) =>\n resolved.some((type) =>\n type.endsWith('/*') ? mimetype.startsWith(type.replace('/*', '/')) : mimetype === type,\n )\n}\n\n/** A multer-shaped uploaded file — what `ctx.file` / `ctx.files` expose. */\nexport interface UploadedFileLike {\n fieldname: string\n originalname: string\n encoding: string\n mimetype: string\n size: number\n buffer: Buffer\n}\n\n/** A buffered multipart file part, as the runtime hands it to {@link applyUploadConfig}. */\nexport interface RawUploadPart {\n fieldname: string\n filename: string\n mimetype: string\n buffer: Buffer\n}\n\nconst DEFAULT_MAX_SIZE = 5 * 1024 * 1024\n\n/**\n * Apply a `@FileUpload` config to already-buffered multipart parts and return\n * the multer-shaped `file` / `files` for `ctx`. Enforces the field name, type\n * filter, per-file size limit, and (array mode) max count — throwing\n * `HttpException` on a violation, mirroring the Express/multer error path.\n * Used by the Fastify, h3 and web runtimes; Express keeps using multer directly.\n */\nexport function applyUploadConfig(\n parts: RawUploadPart[],\n config: FileUploadConfig,\n): { file?: UploadedFileLike; files?: UploadedFileLike[] } {\n if (config.mode === 'none') return {}\n\n const field = config.fieldName ?? 'file'\n const maxSize = config.maxSize ?? DEFAULT_MAX_SIZE\n const typeAllowed = buildFileTypeFilter(config.allowedTypes, config.customMimeMap)\n\n const matched = parts.filter((p) => p.fieldname === field)\n const files: UploadedFileLike[] = matched.map((p) => {\n if (!typeAllowed(p.mimetype, p.filename)) {\n throw new HttpException(\n HttpStatus.UNSUPPORTED_MEDIA_TYPE,\n `File type ${p.mimetype} is not allowed`,\n )\n }\n if (p.buffer.length > maxSize) {\n throw new HttpException(\n HttpStatus.PAYLOAD_TOO_LARGE,\n `File ${p.filename} exceeds the ${maxSize}-byte limit`,\n )\n }\n return {\n fieldname: p.fieldname,\n originalname: p.filename,\n encoding: '7bit',\n mimetype: p.mimetype,\n size: p.buffer.length,\n buffer: p.buffer,\n }\n })\n\n if (config.mode === 'single') return { file: files[0] }\n const maxCount = config.maxCount ?? 10\n return { files: files.slice(0, maxCount) }\n}\n"],"mappings":";;;;;;;;;;wDAUA,MAAa,SAAmC,CAE9C,IAAK,aACL,KAAM,aACN,IAAK,YACL,IAAK,YACL,KAAM,aACN,IAAK,gBACL,IAAK,YACL,IAAK,eACL,KAAM,aACN,IAAK,aACL,KAAM,aAEN,IAAK,kBACL,IAAK,qBACL,KAAM,0EACN,IAAK,2BACL,KAAM,oEACN,IAAK,gCACL,KAAM,4EACN,IAAK,0CACL,IAAK,iDACL,IAAK,kDACL,IAAK,kBACL,IAAK,aACL,IAAK,WAEL,IAAK,kBACL,GAAI,mBACJ,IAAK,oBACL,IAAK,sBACL,KAAM,8BAEN,IAAK,aACL,IAAK,YACL,IAAK,YACL,KAAM,aACN,IAAK,YAEL,IAAK,YACL,KAAM,aACN,IAAK,kBACL,IAAK,kBACL,IAAK,mBAEL,KAAM,mBACN,IAAK,kBACL,KAAM,WACR,EAYA,SAAgB,iBAAiB,MAA2B,CAC1D,OAAO,MAAM,IAAK,GAAM,CACtB,IAAM,MAAQ,EAAE,YAAY,CAAC,CAAC,QAAQ,MAAO,EAAE,EAC/C,OAAO,SAAS,QAAU,CAC5B,CAAC,CACH,CASA,SAAgB,oBACd,aACA,cACiD,CACjD,GAAI,CAAC,aAAc,UAAa,GAChC,GAAI,OAAO,cAAiB,WAAY,OAAO,aAC/C,IAAM,QAAU,cAAgB,CAAE,GAAG,SAAU,GAAG,aAAc,EAAI,SAC9D,SAAW,aAAa,IAAK,GAAM,QAAQ,EAAE,YAAY,CAAC,CAAC,QAAQ,MAAO,EAAE,IAAM,CAAC,EACzF,MAAQ,WACN,SAAS,KAAM,MACb,KAAK,SAAS,IAAI,EAAI,SAAS,WAAW,KAAK,QAAQ,KAAM,GAAG,CAAC,EAAI,WAAa,IACpF,CACJ,CA6BA,SAAgB,kBACd,MACA,OACyD,CACzD,GAAI,OAAO,OAAS,OAAQ,MAAO,CAAC,EAEpC,IAAM,MAAQ,OAAO,WAAa,OAC5B,QAAU,OAAO,SAAW,QAC5B,YAAc,oBAAoB,OAAO,aAAc,OAAO,aAAa,EAG3E,MADU,MAAM,OAAQ,GAAM,EAAE,YAAc,KACZ,CAAC,CAAC,IAAK,GAAM,CACnD,GAAI,CAAC,YAAY,EAAE,SAAU,EAAE,QAAQ,EACrC,MAAM,IAAI,cACR,WAAW,uBACX,aAAa,EAAE,SAAS,gBAC1B,EAEF,GAAI,EAAE,OAAO,OAAS,QACpB,MAAM,IAAI,cACR,WAAW,kBACX,QAAQ,EAAE,SAAS,eAAe,QAAQ,YAC5C,EAEF,MAAO,CACL,UAAW,EAAE,UACb,aAAc,EAAE,SAChB,SAAU,OACV,SAAU,EAAE,SACZ,KAAM,EAAE,OAAO,OACf,OAAQ,EAAE,MACZ,CACF,CAAC,EAED,GAAI,OAAO,OAAS,SAAU,MAAO,CAAE,KAAM,MAAM,EAAG,EACtD,IAAM,SAAW,OAAO,UAAY,GACpC,MAAO,CAAE,MAAO,MAAM,MAAM,EAAG,QAAQ,CAAE,CAC3C"}
1
+ {"version":3,"file":"upload-config-Da_g7Rig.mjs","names":[],"sources":["../src/http/middleware/upload-config.ts"],"sourcesContent":["// Pure (edge-safe) upload core — the engine-neutral half of the upload\n// middleware. Extracted from `upload.ts` so the `@forinda/kickjs/web` entry\n// graph can validate/shape buffered multipart parts WITHOUT dragging in\n// upload.ts's node-only surface (multer peer loading via `node:module`,\n// disk cleanup via `node:fs/promises`). `upload.ts` re-exports everything\n// here, so existing import sites are unchanged.\n\nimport { HttpException, HttpStatus } from '../../core/errors'\nimport type { BaseUploadOptions, FileUploadConfig } from '../../core/decorators'\n\nexport const MIME_MAP: Record<string, string> = {\n // Images\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n ico: 'image/x-icon',\n tiff: 'image/tiff',\n tif: 'image/tiff',\n avif: 'image/avif',\n // Documents\n pdf: 'application/pdf',\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n ppt: 'application/vnd.ms-powerpoint',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n odt: 'application/vnd.oasis.opendocument.text',\n ods: 'application/vnd.oasis.opendocument.spreadsheet',\n odp: 'application/vnd.oasis.opendocument.presentation',\n rtf: 'application/rtf',\n txt: 'text/plain',\n csv: 'text/csv',\n // Archives\n zip: 'application/zip',\n gz: 'application/gzip',\n tar: 'application/x-tar',\n rar: 'application/vnd.rar',\n '7z': 'application/x-7z-compressed',\n // Audio\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n flac: 'audio/flac',\n aac: 'audio/aac',\n // Video\n mp4: 'video/mp4',\n webm: 'video/webm',\n avi: 'video/x-msvideo',\n mov: 'video/quicktime',\n mkv: 'video/x-matroska',\n // Other\n json: 'application/json',\n xml: 'application/xml',\n html: 'text/html',\n}\n\n/**\n * Resolves a list of file type identifiers to MIME type strings.\n * Accepts short extensions (`'jpg'`, `'pdf'`) or full MIME types (`'image/jpeg'`).\n *\n * @example\n * ```ts\n * resolveMimeTypes(['jpg', 'png', 'application/pdf'])\n * // → ['image/jpeg', 'image/png', 'application/pdf']\n * ```\n */\nexport function resolveMimeTypes(types: string[]): string[] {\n return types.map((t) => {\n const lower = t.toLowerCase().replace(/^\\./, '')\n return MIME_MAP[lower] ?? t\n })\n}\n\n/**\n * Build a runtime-agnostic `(mimetype, filename) => boolean` file-type filter\n * from `allowedTypes` (a predicate, or a list of extensions / MIME types /\n * wildcards). Returned by both the multer middleware (Express) and the\n * Fastify / h3 / web multipart paths so the accept/reject rule stays\n * identical across engines. No `allowedTypes` → accept everything.\n */\nexport function buildFileTypeFilter(\n allowedTypes: BaseUploadOptions['allowedTypes'],\n customMimeMap?: Record<string, string>,\n): (mimetype: string, filename: string) => boolean {\n if (!allowedTypes) return () => true\n if (typeof allowedTypes === 'function') return allowedTypes\n const mimeMap = customMimeMap ? { ...MIME_MAP, ...customMimeMap } : MIME_MAP\n const resolved = allowedTypes.map((t) => mimeMap[t.toLowerCase().replace(/^\\./, '')] ?? t)\n return (mimetype: string) =>\n resolved.some((type) =>\n type.endsWith('/*') ? mimetype.startsWith(type.replace('/*', '/')) : mimetype === type,\n )\n}\n\n/** A multer-shaped uploaded file — what `ctx.file` / `ctx.files` expose. */\nexport interface UploadedFileLike {\n fieldname: string\n originalname: string\n encoding: string\n mimetype: string\n size: number\n buffer: Buffer\n}\n\n/** A buffered multipart file part, as the runtime hands it to {@link applyUploadConfig}. */\nexport interface RawUploadPart {\n fieldname: string\n filename: string\n mimetype: string\n buffer: Buffer\n}\n\nconst DEFAULT_MAX_SIZE = 5 * 1024 * 1024\n\n/**\n * Apply a `@FileUpload` config to already-buffered multipart parts and return\n * the multer-shaped `file` / `files` for `ctx`. Enforces the field name, type\n * filter, per-file size limit, and (array mode) max count — throwing\n * `HttpException` on a violation, mirroring the Express/multer error path.\n * Used by the Fastify, h3 and web runtimes; Express keeps using multer directly.\n */\nexport function applyUploadConfig(\n parts: RawUploadPart[],\n config: FileUploadConfig,\n): { file?: UploadedFileLike; files?: UploadedFileLike[] } {\n if (config.mode === 'none') return {}\n\n const field = config.fieldName ?? 'file'\n const maxSize = config.maxSize ?? DEFAULT_MAX_SIZE\n const typeAllowed = buildFileTypeFilter(config.allowedTypes, config.customMimeMap)\n\n const matched = parts.filter((p) => p.fieldname === field)\n const files: UploadedFileLike[] = matched.map((p) => {\n if (!typeAllowed(p.mimetype, p.filename)) {\n throw new HttpException(\n HttpStatus.UNSUPPORTED_MEDIA_TYPE,\n `File type ${p.mimetype} is not allowed`,\n )\n }\n if (p.buffer.length > maxSize) {\n throw new HttpException(\n HttpStatus.PAYLOAD_TOO_LARGE,\n `File ${p.filename} exceeds the ${maxSize}-byte limit`,\n )\n }\n return {\n fieldname: p.fieldname,\n originalname: p.filename,\n encoding: '7bit',\n mimetype: p.mimetype,\n size: p.buffer.length,\n buffer: p.buffer,\n }\n })\n\n if (config.mode === 'single') return { file: files[0] }\n const maxCount = config.maxCount ?? 10\n return { files: files.slice(0, maxCount) }\n}\n"],"mappings":";;;;;;;;;;wDAUA,MAAa,SAAmC,CAE9C,IAAK,aACL,KAAM,aACN,IAAK,YACL,IAAK,YACL,KAAM,aACN,IAAK,gBACL,IAAK,YACL,IAAK,eACL,KAAM,aACN,IAAK,aACL,KAAM,aAEN,IAAK,kBACL,IAAK,qBACL,KAAM,0EACN,IAAK,2BACL,KAAM,oEACN,IAAK,gCACL,KAAM,4EACN,IAAK,0CACL,IAAK,iDACL,IAAK,kDACL,IAAK,kBACL,IAAK,aACL,IAAK,WAEL,IAAK,kBACL,GAAI,mBACJ,IAAK,oBACL,IAAK,sBACL,KAAM,8BAEN,IAAK,aACL,IAAK,YACL,IAAK,YACL,KAAM,aACN,IAAK,YAEL,IAAK,YACL,KAAM,aACN,IAAK,kBACL,IAAK,kBACL,IAAK,mBAEL,KAAM,mBACN,IAAK,kBACL,KAAM,WACR,EAYA,SAAgB,iBAAiB,MAA2B,CAC1D,OAAO,MAAM,IAAK,GAAM,CACtB,IAAM,MAAQ,EAAE,YAAY,CAAC,CAAC,QAAQ,MAAO,EAAE,EAC/C,OAAO,SAAS,QAAU,CAC5B,CAAC,CACH,CASA,SAAgB,oBACd,aACA,cACiD,CACjD,GAAI,CAAC,aAAc,UAAa,GAChC,GAAI,OAAO,cAAiB,WAAY,OAAO,aAC/C,IAAM,QAAU,cAAgB,CAAE,GAAG,SAAU,GAAG,aAAc,EAAI,SAC9D,SAAW,aAAa,IAAK,GAAM,QAAQ,EAAE,YAAY,CAAC,CAAC,QAAQ,MAAO,EAAE,IAAM,CAAC,EACzF,MAAQ,WACN,SAAS,KAAM,MACb,KAAK,SAAS,IAAI,EAAI,SAAS,WAAW,KAAK,QAAQ,KAAM,GAAG,CAAC,EAAI,WAAa,IACpF,CACJ,CA6BA,SAAgB,kBACd,MACA,OACyD,CACzD,GAAI,OAAO,OAAS,OAAQ,MAAO,CAAC,EAEpC,IAAM,MAAQ,OAAO,WAAa,OAC5B,QAAU,OAAO,SAAW,QAC5B,YAAc,oBAAoB,OAAO,aAAc,OAAO,aAAa,EAG3E,MADU,MAAM,OAAQ,GAAM,EAAE,YAAc,KACZ,CAAC,CAAC,IAAK,GAAM,CACnD,GAAI,CAAC,YAAY,EAAE,SAAU,EAAE,QAAQ,EACrC,MAAM,IAAI,cACR,WAAW,uBACX,aAAa,EAAE,SAAS,gBAC1B,EAEF,GAAI,EAAE,OAAO,OAAS,QACpB,MAAM,IAAI,cACR,WAAW,kBACX,QAAQ,EAAE,SAAS,eAAe,QAAQ,YAC5C,EAEF,MAAO,CACL,UAAW,EAAE,UACb,aAAc,EAAE,SAChB,SAAU,OACV,SAAU,EAAE,SACZ,KAAM,EAAE,OAAO,OACf,OAAQ,EAAE,MACZ,CACF,CAAC,EAED,GAAI,OAAO,OAAS,SAAU,MAAO,CAAE,KAAM,MAAM,EAAG,EACtD,IAAM,SAAW,OAAO,UAAY,GACpC,MAAO,CAAE,MAAO,MAAM,MAAM,EAAG,QAAQ,CAAE,CAC3C"}
@@ -1,6 +1,6 @@
1
1
 
2
- import { g as FileUploadConfig, o as BaseUploadOptions } from "./decorators-CbTBUVOu.mjs";
3
- import "./adapter-BuOTeSQy.mjs";
2
+ import { g as FileUploadConfig, o as BaseUploadOptions } from "./decorators-BbtQ3Vf6.mjs";
3
+ import "./adapter-OjEKLePS.mjs";
4
4
  import { NextFunction, Request, RequestHandler, Response } from "express";
5
5
  import { Options } from "multer";
6
6
  //#region src/http/middleware/upload-config.d.ts
@@ -113,4 +113,4 @@ declare const upload: {
113
113
  };
114
114
  //#endregion
115
115
  export { RawUploadPart as a, buildFileTypeFilter as c, upload as i, resolveMimeTypes as l, buildUploadMiddleware as n, UploadedFileLike as o, cleanupFiles as r, applyUploadConfig as s, UploadOptions as t };
116
- //# sourceMappingURL=upload-CbJOK-j9.d.mts.map
116
+ //# sourceMappingURL=upload-dSsVE0BW.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-CbJOK-j9.d.mts","names":[],"sources":["../src/http/middleware/upload-config.ts","../src/http/middleware/upload.ts"],"mappings":";;;;;;;;;;;;;;;;iBAuEgB,iBAAiB;;;;;;;;iBAcjB,oBACd,cAAc,mCACd,gBAAgB,0BACd,kBAAkB;;UAYL;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ;;;UAIO;EACf;EACA;EACA;EACA,QAAQ;;;;;;;;;iBAYM,kBACd,OAAO,iBACP,QAAQ;EACL,OAAO;EAAkB,QAAQ;;;;;;;;;UC/ErB,sBAAsB;;EAErC,UAAU;;EAEV;;;;;;;;;;;;;;iBA6EO,OAAO,mBAAmB,UAAS,gBAAqB;;;;iBAQxD,MAAM,mBAAmB,mBAAe,UAAS,gBAAqB;;;;iBAQtE,KAAK,UAAS,gBAAqB;;;;;;;;;;;;;;;iBAmB5B,iBAAY,KACb,SAAO,KAAO,UAAQ,MAAQ;;;;;;iBA+B7B,sBAAsB,QAAQ,mBAAmB;;cAmBpD"}
1
+ {"version":3,"file":"upload-dSsVE0BW.d.mts","names":[],"sources":["../src/http/middleware/upload-config.ts","../src/http/middleware/upload.ts"],"mappings":";;;;;;;;;;;;;;;;iBAuEgB,iBAAiB;;;;;;;;iBAcjB,oBACd,cAAc,mCACd,gBAAgB,0BACd,kBAAkB;;UAYL;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ;;;UAIO;EACf;EACA;EACA;EACA,QAAQ;;;;;;;;;iBAYM,kBACd,OAAO,iBACP,QAAQ;EACL,OAAO;EAAkB,QAAQ;;;;;;;;;UC/ErB,sBAAsB;;EAErC,UAAU;;EAEV;;;;;;;;;;;;;;iBA6EO,OAAO,mBAAmB,UAAS,gBAAqB;;;;iBAQxD,MAAM,mBAAmB,mBAAe,UAAS,gBAAqB;;;;iBAQtE,KAAK,UAAS,gBAAqB;;;;;;;;;;;;;;;iBAmB5B,iBAAY,KACb,SAAO,KAAO,UAAQ,MAAQ;;;;;;iBA+B7B,sBAAsB,QAAQ,mBAAmB;;cAmBpD"}
package/dist/web.d.mts CHANGED
@@ -1,8 +1,8 @@
1
1
 
2
- import { x as MiddlewareHandler } from "./decorators-CbTBUVOu.mjs";
3
- import { m as ContributorRegistrations, r as AppModuleEntry } from "./app-module-DZW0uYNA.mjs";
4
- import { D as SseHandler, b as RuntimeResponse, m as RouteEntry } from "./adapter-BuOTeSQy.mjs";
5
- import { a as KvSessionStoreOptions, c as InferHandlerResponse, d as isReply, f as reply, i as KvSessionStore, l as Reply, n as KvRateLimitStore, o as RateLimitGuardOptions, r as KvRateLimitStoreOptions, s as rateLimitGuard, t as KvLike } from "./kv-stores-CbsEHE5J.mjs";
2
+ import { x as MiddlewareHandler } from "./decorators-BbtQ3Vf6.mjs";
3
+ import { m as ContributorRegistrations, r as AppModuleEntry } from "./app-module-CmIHP7uW.mjs";
4
+ import { D as SseHandler, b as RuntimeResponse, m as RouteEntry } from "./adapter-OjEKLePS.mjs";
5
+ import { a as KvSessionStoreOptions, c as InferHandlerResponse, d as isReply, f as reply, i as KvSessionStore, l as Reply, n as KvRateLimitStore, o as RateLimitGuardOptions, r as KvRateLimitStoreOptions, s as rateLimitGuard, t as KvLike } from "./kv-stores-BPxJjf56.mjs";
6
6
  //#region src/http/web/driver.d.ts
7
7
  /**
8
8
  * Presents a web `Request` as the express-shaped `req` object that
package/dist/web.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.0.0
2
+ * @forinda/kickjs v8.1.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as Container}from"./container-GbeejFhH.mjs";import{t as MutableModuleRegistry}from"./module-registry-DIngF0Ne.mjs";import{buildMountPath}from"./core/path.mjs";import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"./router-builder-FukfDDLt.mjs";import{n as isReply,r as reply}from"./reply-CdVb6ipw.mjs";import{i as requestStore}from"./context-BnIvtMUl.mjs";import{n as KvSessionStore,r as rateLimitGuard,t as KvRateLimitStore}from"./kv-stores-pjWD0-ri.mjs";import{n as WebRequestShim,r as WebResponseDriver,t as compileWebRoute}from"./handler-9qFN7nRw.mjs";function createWebApp(options){let h3mod=options.h3;if(typeof h3mod?.H3!=`function`)throw Error(`@forinda/kickjs/web: pass the h3 v2 module — \`createWebApp({ h3: await import("h3"), ... })\`. The installed h3 must be v2 (npm dist-tag 'latest'); v1 has no H3 class.`);let container=Container.getInstance();if(options.env){let env=options.env;Container._envResolver=key=>env[key]}Container._requestStoreProvider=()=>requestStore.getStore()??null;let registry=new MutableModuleRegistry;for(let m of options.modules)registry.mount(m);let modules=registry.entries.map(entry=>{let mod=typeof entry==`function`?new entry:entry;return mod.register?.(container),mod});container.bootstrap();let app=new h3mod.H3,apiPrefix=options.apiPrefix??`/api`,defaultVersion=options.defaultVersion??1,globalMiddleware=options.middlewares??[],globalSources=(options.contributors??[]).map(registration=>({source:`global`,registration,label:`createWebApp`})),seenRoutes=new Map;for(let mod of modules){let declaredName=mod.name,moduleLabel=typeof declaredName==`string`&&declaredName.length>0?declaredName:mod.constructor?.name??`module`;_setExternalContributorSources([...(mod.contributors?.()??[]).map(registration=>({source:`module`,registration,label:moduleLabel})),...globalSources]);try{let result=mod.routes();if(!result)continue;let routeSets=Array.isArray(result)?result:[result];for(let route of routeSets){if(!route.controller)throw Error(`@forinda/kickjs/web: module route at '${route.path}' has no controller. Express-style \`router\` mounts are not supported on the web entry.`);let mountPath=buildMountPath(apiPrefix,route.version??defaultVersion,route.path);for(let entry of buildRouteTable(route.controller)){let url=joinPath(mountPath,entry.path),owner=`${route.controller.name??`controller`}.${String(entry.meta.handlerName??`?`)}`;assertRouteUnique(seenRoutes,entry.method,url,owner),globalMiddleware.length>0&&(entry.middlewares=[...globalMiddleware,...entry.middlewares]);let run=compileWebRoute(entry);app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{}}))}}}finally{_setExternalContributorSources([])}}return{fetch:request=>app.fetch(request),h3:app}}function createFetchHandler(build){let app;return{fetch:(request,env={})=>(app??=createWebApp(build(env)),app.fetch(request))}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{KvRateLimitStore,KvSessionStore,WebRequestShim,WebResponseDriver,compileWebRoute,createFetchHandler,createWebApp,isReply,rateLimitGuard,reply};
11
+ import{t as Container}from"./container-jtn5BmL5.mjs";import{t as MutableModuleRegistry}from"./module-registry-CpXYcf4e.mjs";import{buildMountPath}from"./core/path.mjs";import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"./router-builder-Dqs8ITU7.mjs";import{n as isReply,r as reply}from"./reply-Ca-H1Ozr.mjs";import{i as requestStore}from"./context-caMuzOpA.mjs";import{n as KvSessionStore,r as rateLimitGuard,t as KvRateLimitStore}from"./kv-stores-CosFzIYm.mjs";import{n as WebRequestShim,r as WebResponseDriver,t as compileWebRoute}from"./handler-BuzdkiTz.mjs";function createWebApp(options){let h3mod=options.h3;if(typeof h3mod?.H3!=`function`)throw Error(`@forinda/kickjs/web: pass the h3 v2 module — \`createWebApp({ h3: await import("h3"), ... })\`. The installed h3 must be v2 (npm dist-tag 'latest'); v1 has no H3 class.`);let container=Container.getInstance();if(options.env){let env=options.env;Container._envResolver=key=>env[key]}Container._requestStoreProvider=()=>requestStore.getStore()??null;let registry=new MutableModuleRegistry;for(let m of options.modules)registry.mount(m);let modules=registry.entries.map(entry=>{let mod=typeof entry==`function`?new entry:entry;return mod.register?.(container),mod});container.bootstrap();let app=new h3mod.H3,apiPrefix=options.apiPrefix??`/api`,defaultVersion=options.defaultVersion??1,globalMiddleware=options.middlewares??[],globalSources=(options.contributors??[]).map(registration=>({source:`global`,registration,label:`createWebApp`})),seenRoutes=new Map;for(let mod of modules){let declaredName=mod.name,moduleLabel=typeof declaredName==`string`&&declaredName.length>0?declaredName:mod.constructor?.name??`module`;_setExternalContributorSources([...(mod.contributors?.()??[]).map(registration=>({source:`module`,registration,label:moduleLabel})),...globalSources]);try{let result=mod.routes();if(!result)continue;let routeSets=Array.isArray(result)?result:[result];for(let route of routeSets){if(!route.controller)throw Error(`@forinda/kickjs/web: module route at '${route.path}' has no controller. Express-style \`router\` mounts are not supported on the web entry.`);let mountPath=buildMountPath(apiPrefix,route.version??defaultVersion,route.path);for(let entry of buildRouteTable(route.controller)){let url=joinPath(mountPath,entry.path),owner=`${route.controller.name??`controller`}.${String(entry.meta.handlerName??`?`)}`;assertRouteUnique(seenRoutes,entry.method,url,owner),globalMiddleware.length>0&&(entry.middlewares=[...globalMiddleware,...entry.middlewares]);let run=compileWebRoute(entry);app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{}}))}}}finally{_setExternalContributorSources([])}}return{fetch:request=>app.fetch(request),h3:app}}function createFetchHandler(build){let app;return{fetch:(request,env={})=>(app??=createWebApp(build(env)),app.fetch(request))}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{KvRateLimitStore,KvSessionStore,WebRequestShim,WebResponseDriver,compileWebRoute,createFetchHandler,createWebApp,isReply,rateLimitGuard,reply};
12
12
  //# sourceMappingURL=web.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs",
3
- "version": "8.0.0",
3
+ "version": "8.1.0",
4
4
  "description": "A production-grade, decorator-driven Node.js framework for TypeScript — runs on Express, Fastify, or h3 (swap the engine in one line).",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -1 +0,0 @@
1
- {"version":3,"file":"bootstrap-BIjHgG4v.d.mts","names":[],"sources":["../src/http/bootstrap.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;iBAqCsB,UAAU,SAAS,qBAAqB,QAAQ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"container-BW_MlgLj.d.mts","names":[],"sources":["../src/core/token.ts","../src/core/container.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAmDa;;;;;;;;;;UAWI,eAAe;YACpB;;WAED;;WAEA,QAAQ;;;;;;;;;iBAUH,YAAY,GAAG,eAAe,eAAe;;iBAQ7C,iBAAiB,iBAAiB,SAAS;;;;iBCL3C,UAAU;;UAqBT;EACf;EACA;EACA,OAAO;EACP;;;KAIU,2BAA2B,SAAS;;;;;;;;;;;;;;;;;;UAmB/B;;;;;;;;;;;;cA+BJ;iBACI;UACP;UACA;UAGA;UACA;UACA;0BACgB;;SAGjB,YAAY,WAAW;;SAEvB,YAAY,WAAW;;;;;;SAMvB,gBAAgB;;;;;;;SAOhB;IACM,WAAW;IAAe,QAAQ;;SAGxC,eAAe;;;;;;SAiBf;;;;;;;;;;;;SAqDA,UAAU;;EAKjB,SAAS,YAAY,QAAQ,aAAa,QAAO;;EAcjD,gBAAgB,YAAY,oBAAoB,QAAO;;EAuCvD,iBAAiB,YAAY;;EA6B7B,IAAI;;EAKJ,oBAAoB;IAClB;IACA;IACA,MAAM;IACN;IACA;IACA;IACA;IACA;IACA,qBAAqB;IACrB;;;;;;;;;;;;;;;EAmCF,QAAQ,GAAG,OAAO,eAAe,KAAK;EACtC,QAAQ,GAAG,OAAO,YAAY,KAAK;EACnC,QAAQ,gBAAgB,yBAAyB,OAAO,IAAI,eAAe;EAC3E,QAAQ,SAAS,aAAa;;EAgH9B;;;;;;;;;;;;;;;;;;;;;;;;;EA+BA,SAAS,UAAU;;;;;;;;;EAanB,WAAW;;;;;;;UA8BH;;EA6CR;;;;;;;;;;;UA0BQ;UAgFA;;;;;;;;;;;UAwDA;UAuBA"}
@@ -1,117 +0,0 @@
1
- /**
2
- * @forinda/kickjs v8.0.0
3
- *
4
- * Copyright (c) Felix Orinda
5
- *
6
- * This source code is licensed under the MIT license found in the
7
- * LICENSE file in the root directory of this source tree.
8
- *
9
- * @license MIT
10
- */
11
- import{METADATA}from"./core/interfaces.mjs";import{createLogger}from"./core/logger.mjs";import{i as getMetaRecord,n as getClassMetaOrUndefined,o as getMethodMetaOrUndefined,r as getMetaMap,t as getClassMeta}from"./metadata-CeUKYNRZ.mjs";import"reflect-metadata";let impl=null;function _setAssetResolver(fn){impl=fn}function resolveAssetViaSlot(namespace,key){if(!impl)throw Error(`@Asset('${namespace}/${key}'): the asset manager is not loaded. Import it (any import from '@forinda/kickjs' loads it) — note @Asset reads from the filesystem and is not supported on edge runtimes.`);return impl(namespace,key)}var KickError=class extends Error{code;summary;cause;fix;docsUrl;context;constructor(init){super(formatKickError(init,{color:!1})),this.name=`KickError`,this.code=init.code,this.summary=init.summary,init.cause!==void 0&&(this.cause=init.cause),init.fix!==void 0&&(this.fix=init.fix),init.docsUrl!==void 0&&(this.docsUrl=init.docsUrl),init.context!==void 0&&(this.context=init.context)}};function ansiEnabled(){return process.env.NO_COLOR?!1:process.env.FORCE_COLOR?!0:process.stderr?.isTTY===!0}function paint(text,code,enabled){return enabled?`[${code}m${text}`:text}const c={red:(s,enabled)=>paint(s,`31`,enabled),bold:(s,enabled)=>paint(s,`1`,enabled),dim:(s,enabled)=>paint(s,`2`,enabled),cyan:(s,enabled)=>paint(s,`36`,enabled),blue:(s,enabled)=>paint(s,`34`,enabled),underline:(s,enabled)=>paint(s,`4`,enabled)};function formatKickError(err,options={}){let color=options.color??ansiEnabled(),lines=[],code=c.bold(c.red(err.code,color),color);if(lines.push(`${code}: ${err.summary}`),err.cause){lines.push(``,` ${c.bold(c.dim(`Cause:`,color),color)}`);for(let line of err.cause.split(`
12
- `))lines.push(` ${highlightInline(line,color)}`)}if(err.fix){lines.push(``,` ${c.bold(c.dim(`Fix:`,color),color)}`);for(let line of err.fix.split(`
13
- `))lines.push(` ${highlightInline(line,color)}`)}return err.docsUrl&&(lines.push(``,` ${c.bold(c.dim(`Docs:`,color),color)}`),lines.push(` ${c.underline(c.blue(err.docsUrl,color),color)}`)),lines.join(`
14
- `)}function highlightInline(line,color){return color?line.replace(/`([^`]+)`/g,(_,inner)=>`\`${c.cyan(inner,!0)}\``):line}const DOCS_BASE=`https://kickjs.app`;function noProviderError(tokenName){return new KickError({code:`KICK001`,summary:`No provider for ${tokenName}`,cause:`\`${tokenName}\` was requested from the DI container but no binding is registered.
15
- This usually means one of:
16
- • The class is decorated with @Service() / @Repository() / @Controller(),
17
- but its enclosing module isn't passed to bootstrap({ modules: [...] }).
18
- • The class isn't decorated at all (decorators register the binding).
19
- • You're injecting a token (created with createToken()) that nothing
20
- provides — add a Container.register(TOKEN, ...) call or a module that
21
- binds it.`,fix:`If \`${tokenName}\` lives in a module, add the module to bootstrap:
22
-
23
- bootstrap({
24
- modules: [
25
- UsersModule, // add this
26
- OtherModule,
27
- ],
28
- })
29
-
30
- If it's a custom token, register it explicitly:
31
-
32
- const TENANT_REPO = createToken<TenantRepo>('TENANT_REPO')
33
- Container.getInstance().register(TENANT_REPO, { useClass: PrismaTenantRepo })`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#registering-services`,context:{token:tokenName}})}function requestScopeMiddlewareMissingError(tokenName){return new KickError({code:`KICK002`,summary:`Cannot resolve REQUEST-scoped "${tokenName}" — request scope middleware not mounted`,cause:`\`${tokenName}\` has \`scope: Scope.REQUEST\` but no AsyncLocalStorage frame is wired.
34
- The framework's request-scope middleware sets up that frame; without it,
35
- REQUEST-scoped resolutions have nowhere to cache the per-request instance.`,fix:`Add \`requestScopeMiddleware()\` to your global middleware pipeline,
36
- or let the framework auto-mount it by using \`contextStore: 'auto'\`
37
- (the default — only check this if you set it to 'manual' explicitly):
38
-
39
- bootstrap({
40
- modules,
41
- // contextStore: 'auto', // default; no need to set
42
- })
43
-
44
- If you set \`contextStore: 'manual'\` deliberately (rare), you own the
45
- ALS wrapping — call \`requestStore.run({...}, () => ...)\` around the
46
- code that resolves the binding.`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#scopes`,context:{token:tokenName}})}function requestScopeOutsideRequestError(tokenName){return new KickError({code:`KICK003`,summary:`Cannot resolve REQUEST-scoped "${tokenName}" outside an HTTP request`,cause:`\`${tokenName}\` has \`scope: Scope.REQUEST\` and was resolved outside
47
- any HTTP request context. The request-scope middleware is wired, but
48
- the call happened before a request started (e.g. in a top-level
49
- import, a constructor, a cron job, a test without setup).`,fix:`If the caller is request-scoped (controller, request-scoped service),
50
- make sure the call happens inside the request lifecycle, not at module
51
- load time.
52
-
53
- In tests, wrap setup in an ALS frame manually:
54
-
55
- import { requestStore } from '@forinda/kickjs'
56
-
57
- requestStore.run(
58
- { requestId: 'test', instances: new Map(), values: new Map() },
59
- () => {
60
- const svc = container.resolve(MyRequestScopedService)
61
- // ...
62
- },
63
- )
64
-
65
- If the caller is a SINGLETON, change \`${tokenName}\` to TRANSIENT or
66
- SINGLETON scope, or inject a factory instead of the instance directly.`,docsUrl:`${DOCS_BASE}/guide/dependency-injection#scopes`,context:{token:tokenName}})}function envValueMissingError(envKey){return new KickError({code:`KICK004`,summary:`@Value('${envKey}'): environment variable not set and no default provided`,cause:`\`@Value('${envKey}')\` resolved but the env var isn't present and the
67
- decorator didn't declare a default. Possible reasons:
68
- • The variable is genuinely missing from your environment / .env file.
69
- • Your \`src/env.ts\` calls \`loadEnv(envSchema)\` but \`src/index.ts\`
70
- doesn't import \`./env\` before \`bootstrap()\`. Without that import
71
- the schema never runs and ConfigService stays empty — \`@Value\`
72
- only finds the var via the \`process.env\` fallback.`,fix:`Either set the variable in your shell / .env file:
73
-
74
- ${envKey}=...
75
-
76
- Or add a default at the decorator:
77
-
78
- @Value('${envKey}', { default: 'fallback' })
79
- private value!: string
80
-
81
- If you're using a schema, make sure \`src/env.ts\` is imported from
82
- \`src/index.ts\` BEFORE bootstrap runs:
83
-
84
- // src/index.ts
85
- import 'reflect-metadata'
86
- import './env' // ← this line is required
87
- import { bootstrap } from '@forinda/kickjs'
88
- // ...`,docsUrl:`${DOCS_BASE}/guide/configuration#wiring-the-schema-at-startup`,context:{envKey}})}function moduleRouteMissingControllerError(mountPath){return new KickError({code:`KICK005`,summary:`Module route at ${mountPath} requires either 'controller' or 'router'`,cause:`A module's \`routes()\` returned an entry for \`${mountPath}\` but didn't
89
- specify how to build the router. The framework needs either a decorated
90
- controller class (it calls \`buildRoutes()\` internally) or a hand-built
91
- Express Router.`,fix:`Pass \`controller:\` for the common case:
92
-
93
- routes() {
94
- return {
95
- path: '${mountPath}',
96
- controller: UsersController,
97
- }
98
- }
99
-
100
- Or pass \`router:\` for full control:
101
-
102
- routes() {
103
- return {
104
- path: '${mountPath}',
105
- router: myExpressRouter,
106
- }
107
- }`,docsUrl:`${DOCS_BASE}/guide/modules`,context:{mountPath}})}function duplicateRouteError(method,path,owner,prior){return new KickError({code:`KICK006`,summary:`Duplicate route: ${method} ${path} is registered twice`,cause:`\`${owner}\` registers \`${method} ${path}\`, but that verb + path is
108
- already claimed by \`${prior}\`. Only one handler can win at dispatch
109
- time, and which one depends on the engine — while \`kick typegen\` and
110
- the typed client may describe the other. Param names are ignored when
111
- comparing (\`/:id\` and \`/:taskId\` are the same route).`,fix:`Change one of the two paths, remove the duplicate handler, or mount
112
- the second controller under a different module path / version:
113
-
114
- routes() {
115
- return { path: '/tasks', controller: TasksController, version: 2 }
116
- }`,docsUrl:`${DOCS_BASE}/guide/modules`,context:{method,path,owner,prior}})}const INJECTION_TOKEN=Symbol.for(`@kickjs/InjectionToken`);function createToken(name){return Object.freeze({[INJECTION_TOKEN]:!0,name})}function isInjectionToken(value){return typeof value==`object`&&!!value&&value[INJECTION_TOKEN]===!0}const log=createLogger(`Container`);function createReg(fields){return{kind:`unknown`,resolveCount:0,postConstructStatus:`skipped`,dependencies:[],...fields}}function tokenName(token){return typeof token==`symbol`?token.toString():isInjectionToken(token)?token.name:token?.name||String(token)}function getPersistentStore(){return globalThis.__kickjs_persistent||(globalThis.__kickjs_persistent={factories:[],instances:[],resolvedInstances:new Map}),globalThis.__kickjs_persistent}var Container=class Container{static instance;registrations=new Map;resolving=new Set;changeListeners=new Set;pendingChanges=[];notifyTimer=null;static DEBOUNCE_MS=50;static _onReady=null;static _onReset=null;static _envResolver=null;static _requestStoreProvider=null;static getInstance(){return Container.instance||=new Container,Container._onReady&&=(Container._onReady(Container.instance),null),Container.instance}static reset(){Container.instance=new Container,Container._onReset?.(Container.instance);let store=getPersistentStore();for(let{token,factory,scope}of store.factories)if(!Container.instance.has(token)){let name=tokenName(token),existing=store.resolvedInstances.get(name);Container.instance.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existing,resolveCount:+!!existing,postConstructStatus:existing?`completed`:`skipped`}))}for(let{token,instance}of store.instances)Container.instance.has(token)||Container.instance.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1}))}static create(){return new Container}register(token,target,scope=`singleton`){let kind=getClassMeta(METADATA.CLASS_KIND,target,`unknown`),reg=createReg({target,scope,kind,dependencies:this.extractDependencies(target)});this.registrations.set(token,reg),typeof token==`function`&&token.name&&this.registrations.set(`__hmr__${token.name}`,reg),this.emit(token,`registered`,kind)}registerFactory(token,factory,scope=`singleton`){let store=getPersistentStore(),name=tokenName(token),factoryUnchanged=store.factories.find(r=>tokenName(r.token)===name)?.factory===factory,existingInstance=factoryUnchanged?store.resolvedInstances.get(name):void 0;factoryUnchanged||store.resolvedInstances.delete(name),this.registrations.set(token,createReg({target:Object,scope,factory,kind:`factory`,instance:existingInstance,resolveCount:+!!existingInstance,postConstructStatus:existingInstance?`completed`:`skipped`}));let idx=store.factories.findIndex(r=>tokenName(r.token)===name);idx>=0?store.factories[idx]={token,factory,scope}:store.factories.push({token,factory,scope}),this.emit(name,`registered`,`factory`)}registerInstance(token,instance){let store=getPersistentStore(),name=tokenName(token);this.registrations.set(token,createReg({target:instance.constructor,scope:`singleton`,instance,kind:`instance`,postConstructStatus:`completed`,resolveCount:1,firstResolvedAt:Date.now(),lastResolvedAt:Date.now()})),store.resolvedInstances.set(name,instance);let idx=store.instances.findIndex(r=>tokenName(r.token)===name);idx>=0?store.instances[idx]={token,instance}:store.instances.push({token,instance}),this.emit(name,`registered`,`instance`)}has(token){return this.registrations.has(token)}getRegistrations(){let entries=[];for(let[token,reg]of this.registrations)entries.push({token:tokenName(token),scope:reg.scope,kind:reg.kind,instantiated:reg.scope===`singleton`?reg.instance!==void 0:reg.resolveCount>0,resolveCount:reg.resolveCount,lastResolvedAt:reg.lastResolvedAt,firstResolvedAt:reg.firstResolvedAt,resolveDurationMs:reg.resolveDurationMs,postConstructStatus:reg.postConstructStatus,dependencies:reg.dependencies});return entries}resolve(token){let reg=this.registrations.get(token);if(!reg&&typeof token==`function`&&token.name&&(reg=this.registrations.get(`__hmr__${token.name}`)),!reg)throw noProviderError(tokenName(token));if(reg.scope===`singleton`&&reg.instance!==void 0)return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),reg.instance;if(reg.scope===`request`){if(!Container._requestStoreProvider)throw requestScopeMiddlewareMissingError(tokenName(token));let store=Container._requestStoreProvider();if(!store)throw requestScopeOutsideRequestError(tokenName(token));if(store.values.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),store.values.get(token);if(store.instances.has(token))return reg.resolveCount++,reg.lastResolvedAt=Date.now(),this.emit(token,`resolved`,reg.kind),store.instances.get(token);if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(token,`resolved`,reg.kind),instance}let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),store.instances.set(token,instance),this.emit(token,`resolved`,reg.kind),instance}if(reg.factory){let start=performance.now(),instance=reg.factory();return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance,getPersistentStore().resolvedInstances.set(tokenName(token),instance)),this.emit(token,`resolved`,reg.kind),instance}if(this.resolving.has(token)){let chain=[...this.resolving].map(tokenName);throw chain.push(tokenName(token)),Error(`Circular dependency detected: ${chain.join(` -> `)}`)}this.resolving.add(token);try{let start=performance.now(),instance=this.createInstance(reg);return reg.resolveDurationMs=performance.now()-start,reg.resolveCount++,reg.lastResolvedAt=Date.now(),reg.firstResolvedAt||=Date.now(),reg.scope===`singleton`&&(reg.instance=instance),this.emit(token,`resolved`,reg.kind),instance}finally{this.resolving.delete(token)}}bootstrap(){}onChange(callback){return this.changeListeners.add(callback),()=>this.changeListeners.delete(callback)}invalidate(token){let name=tokenName(token),reg=this.registrations.get(token)??this.registrations.get(`__hmr__${name}`);if(reg&&!getPersistentStore().resolvedInstances.has(name)){reg.instance=void 0,reg.resolveCount=0,reg.postConstructStatus=`skipped`,this.emit(name,`invalidated`,reg.kind);for(let[depToken,depReg]of this.registrations)depReg.dependencies.includes(name)&&this.invalidate(depToken)}}emit(rawToken,event,kind){if(this.changeListeners.size===0)return;let token=tokenName(rawToken),existing=this.pendingChanges.find(p=>p.token===token&&p.event===event);existing?existing.timestamp=Date.now():this.pendingChanges.push({token,event,kind,timestamp:Date.now()}),this.notifyTimer&&clearTimeout(this.notifyTimer),this.notifyTimer=setTimeout(()=>{let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}},Container.DEBOUNCE_MS),this.notifyTimer.unref?.()}flushChanges(){this.notifyTimer&&=(clearTimeout(this.notifyTimer),null);let batch=this.pendingChanges.splice(0);if(batch.length!==0)for(let listener of this.changeListeners)try{listener(batch)}catch{}}buildPlan(reg){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,reg.target,[]),injectTokens=getMetaRecord(METADATA.INJECT,reg.target),maxInjectIndex=Object.keys(injectTokens).length>0?Math.max(...Object.keys(injectTokens).map(Number))+1:0,paramCount=Math.max(paramTypes.length,maxInjectIndex),ctorTokens=[];for(let index=0;index<paramCount;index++){let token=injectTokens[index]||paramTypes[index];if(!token)throw Error(`Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). Add @Inject(Token) to every constructor parameter.`);if(reg.scope===`singleton`){let depReg=this.registrations.get(token);if(depReg&&depReg.scope===`request`)throw Error(`Cannot inject REQUEST-scoped "${tokenName(token)}" into SINGLETON "${tokenName(reg.target)}". Singletons outlive requests. Use TRANSIENT or REQUEST scope for the parent.`)}ctorTokens.push(token)}let autowiredProps=getMetaMap(METADATA.AUTOWIRED,reg.target.prototype),autowired=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,reg.target.prototype,prop);resolvedToken&&autowired.push([prop,resolvedToken])}let values=[...getMetaMap(METADATA.VALUE,reg.target.prototype).entries()],assetProps=getMetaMap(METADATA.ASSET,reg.target.prototype),assets=[];for(let[prop,config]of assetProps){let slashIdx=config.assetKey.indexOf(`/`);if(slashIdx===-1)throw Error(`@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`);assets.push([prop,{namespace:config.assetKey.slice(0,slashIdx),key:config.assetKey.slice(slashIdx+1)}])}return{ctorTokens,autowired,values,assets,postConstruct:getClassMetaOrUndefined(METADATA.POST_CONSTRUCT,reg.target.prototype)}}createInstance(reg){let plan=reg.plan??=this.buildPlan(reg),args=[];for(let token of plan.ctorTokens)args.push(this.resolve(token));let instance=new reg.target(...args);this.injectProperties(instance,plan);let postConstruct=plan.postConstruct;if(postConstruct&&typeof instance[postConstruct]==`function`)try{let result=instance[postConstruct]();result&&typeof result.then==`function`?(reg.postConstructStatus=`pending`,result.then(()=>{reg.postConstructStatus=`completed`}).catch(()=>{reg.postConstructStatus=`failed`}),log.warn(`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise but the container is synchronous. The async operation will not be awaited.`)):reg.postConstructStatus=`completed`}catch(err){reg.postConstructStatus=`failed`,log.error(err,`@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`)}return instance}extractDependencies(target){let paramTypes=getClassMeta(METADATA.PARAM_TYPES,target,[]),injectTokens=getMetaRecord(METADATA.INJECT,target),ctorDeps=paramTypes.map((type,index)=>tokenName(injectTokens[index]||type)),autowiredProps=getMetaMap(METADATA.AUTOWIRED,target.prototype),propDeps=[];for(let[prop,token]of autowiredProps){let resolvedToken=token||getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE,target.prototype,prop);resolvedToken&&propDeps.push(tokenName(resolvedToken))}return[...new Set([...ctorDeps,...propDeps])]}injectProperties(instance,plan){for(let[prop,resolvedToken]of plan.autowired)Object.defineProperty(instance,prop,{get:()=>{let value=this.resolve(resolvedToken);return this.registrations.get(resolvedToken)?.scope===`singleton`&&Object.defineProperty(instance,prop,{value,enumerable:!0,configurable:!0,writable:!1}),value},enumerable:!0,configurable:!0});for(let[prop,config]of plan.values)Object.defineProperty(instance,prop,{get(){if(Container._envResolver){let val=Container._envResolver(config.envKey);if(val!==void 0)return val}let val=process.env[config.envKey];if(val!==void 0)return val;if(config.defaultValue!==void 0)return config.defaultValue;throw envValueMissingError(config.envKey)},enumerable:!0,configurable:!0});for(let[prop,{namespace,key}]of plan.assets)Object.defineProperty(instance,prop,{get(){return resolveAssetViaSlot(namespace,key)},enumerable:!0,configurable:!0})}};export{isInjectionToken as a,KickError as c,createToken as i,formatKickError as l,tokenName as n,duplicateRouteError as o,INJECTION_TOKEN as r,moduleRouteMissingControllerError as s,Container as t,_setAssetResolver as u};
117
- //# sourceMappingURL=container-GbeejFhH.mjs.map