@forinda/kickjs 9.0.3 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-DhjoIYVI.d.mts → adapter-DjfRMob8.d.mts} +3 -13
- package/dist/{adapter-DhjoIYVI.d.mts.map → adapter-DjfRMob8.d.mts.map} +1 -1
- package/dist/application-BSJpaiSY.mjs +12 -0
- package/dist/{application-DPLzamTb.mjs.map → application-BSJpaiSY.mjs.map} +1 -1
- package/dist/{application-DqD58rhF.d.mts → application-CGxZCYl1.d.mts} +8 -8
- package/dist/{application-DqD58rhF.d.mts.map → application-CGxZCYl1.d.mts.map} +1 -1
- package/dist/{bootstrap-DUNvDJ1a.mjs → bootstrap-CbTF6xvv.mjs} +3 -3
- package/dist/{bootstrap-DUNvDJ1a.mjs.map → bootstrap-CbTF6xvv.mjs.map} +1 -1
- package/dist/{client-ip-CTr33Gtu.mjs → client-ip-pcINkkPr.mjs} +2 -2
- package/dist/{client-ip-CTr33Gtu.mjs.map → client-ip-pcINkkPr.mjs.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-C-sAHakM.mjs → config-DmGiAIuH.mjs} +3 -3
- package/dist/{config-C-sAHakM.mjs.map → config-DmGiAIuH.mjs.map} +1 -1
- package/dist/{container-ByH0uCpG.mjs → container-CAPN7pBG.mjs} +3 -3
- package/dist/{container-ByH0uCpG.mjs.map → container-CAPN7pBG.mjs.map} +1 -1
- package/dist/{context-errors-VjQBtxEc.mjs → context-errors-DYJjhalQ.mjs} +2 -2
- package/dist/{context-errors-VjQBtxEc.mjs.map → context-errors-DYJjhalQ.mjs.map} +1 -1
- package/dist/context-gpvcMlG5.mjs +12 -0
- package/dist/context-gpvcMlG5.mjs.map +1 -0
- package/dist/{contributor-runner-BEjgGaoG.mjs → contributor-runner-vk9XlOED.mjs} +3 -3
- package/dist/{contributor-runner-BEjgGaoG.mjs.map → contributor-runner-vk9XlOED.mjs.map} +1 -1
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/decorators.d.mts +1 -1
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/decorators.mjs.map +1 -1
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.d.mts +2 -2
- package/dist/core/logger.mjs +2 -2
- package/dist/core/logger.mjs.map +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-DrjVUTxG.mjs → core-DKrKVLfj.mjs} +3 -3
- package/dist/{core-DrjVUTxG.mjs.map → core-DKrKVLfj.mjs.map} +1 -1
- package/dist/{csrf-COFjRrUz.d.mts → csrf-C73UkRgc.d.mts} +2 -2
- package/dist/{csrf-COFjRrUz.d.mts.map → csrf-C73UkRgc.d.mts.map} +1 -1
- package/dist/{csrf-CbFJiyKW.mjs → csrf-CxJItZVa.mjs} +3 -3
- package/dist/{csrf-CbFJiyKW.mjs.map → csrf-CxJItZVa.mjs.map} +1 -1
- package/dist/{decorate-hW_esogS.mjs → decorate-DfH1NIqV.mjs} +1 -1
- package/dist/{decorators-DOcWsXci.d.mts → decorators-BxNSz9Qt.d.mts} +4 -4
- package/dist/{decorators-DOcWsXci.d.mts.map → decorators-BxNSz9Qt.d.mts.map} +1 -1
- package/dist/{define-adapter-B4Iu_TcU.mjs → define-adapter-C6lFhCL-.mjs} +2 -2
- package/dist/{define-adapter-B4Iu_TcU.mjs.map → define-adapter-C6lFhCL-.mjs.map} +1 -1
- package/dist/{disposables-CgCLyCoP.mjs → disposables-NlW6Mibk.mjs} +2 -2
- package/dist/{disposables-CgCLyCoP.mjs.map → disposables-NlW6Mibk.mjs.map} +1 -1
- package/dist/{env-w27RDkCQ.mjs → env-__fzPsHD.mjs} +3 -3
- package/dist/{env-w27RDkCQ.mjs.map → env-__fzPsHD.mjs.map} +1 -1
- package/dist/{error-handler-DiueQHes.d.mts → error-handler-itG65lj0.d.mts} +2 -2
- package/dist/{error-handler-DiueQHes.d.mts.map → error-handler-itG65lj0.d.mts.map} +1 -1
- package/dist/handler-DnZbcleS.mjs +13 -0
- package/dist/handler-DnZbcleS.mjs.map +1 -0
- package/dist/http/application.d.mts +1 -1
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.d.mts +1 -1
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.d.mts +1 -1
- package/dist/http/middleware/csrf.mjs +2 -2
- package/dist/http/middleware/error-handler.d.mts +1 -1
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/helmet.mjs +1 -1
- package/dist/http/middleware/rate-limit.mjs +2 -2
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/request-logger.mjs.map +1 -1
- package/dist/http/middleware/session.mjs +2 -2
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +2 -2
- package/dist/http/middleware/validate.mjs +1 -1
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/runtimes/fastify.d.mts +3 -1
- package/dist/http/runtimes/fastify.d.mts.map +1 -1
- package/dist/http/runtimes/fastify.mjs +2 -2
- package/dist/http/runtimes/fastify.mjs.map +1 -1
- package/dist/http/runtimes/h3-web.d.mts +1 -1
- package/dist/http/runtimes/h3-web.mjs +2 -2
- package/dist/http/runtimes/h3.d.mts +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/http/runtimes/h3.mjs.map +1 -1
- package/dist/{index-CDn-p3tq.d.mts → index-CNHSbZTE.d.mts} +4 -4
- package/dist/{index-CDn-p3tq.d.mts.map → index-CNHSbZTE.d.mts.map} +1 -1
- package/dist/index.d.mts +10 -10
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{jobs-CeP4aLYR.mjs → jobs-BmpeFvi0.mjs} +3 -3
- package/dist/{jobs-CeP4aLYR.mjs.map → jobs-BmpeFvi0.mjs.map} +1 -1
- package/dist/{kv-stores-CetFF9N_.d.mts → kv-stores-BZTe23Tw.d.mts} +3 -3
- package/dist/{kv-stores-CetFF9N_.d.mts.map → kv-stores-BZTe23Tw.d.mts.map} +1 -1
- package/dist/{kv-stores-5ocdxfdh.mjs → kv-stores-DJwxntyw.mjs} +3 -3
- package/dist/{kv-stores-5ocdxfdh.mjs.map → kv-stores-DJwxntyw.mjs.map} +1 -1
- package/dist/{logger-DA43DvNY.d.mts → logger-xE9qI_FS.d.mts} +45 -5
- package/dist/logger-xE9qI_FS.d.mts.map +1 -0
- package/dist/{metadata-CIXErBTj.mjs → metadata-CzzRUnAx.mjs} +2 -2
- package/dist/{metadata-CIXErBTj.mjs.map → metadata-CzzRUnAx.mjs.map} +1 -1
- package/dist/{module-registry-IO8RfVjX.mjs → module-registry-BnmRNy-3.mjs} +2 -2
- package/dist/{module-registry-IO8RfVjX.mjs.map → module-registry-BnmRNy-3.mjs.map} +1 -1
- package/dist/{observers-BtXkP5km.mjs → observers-COlQfinD.mjs} +2 -2
- package/dist/{observers-BtXkP5km.mjs.map → observers-COlQfinD.mjs.map} +1 -1
- package/dist/{query-CNa0mxSK.mjs → query-B5DTq_4W.mjs} +2 -2
- package/dist/{query-CNa0mxSK.mjs.map → query-B5DTq_4W.mjs.map} +1 -1
- package/dist/reply-CbTYIj_e.mjs +12 -0
- package/dist/reply-CbTYIj_e.mjs.map +1 -0
- package/dist/{respond-C4Vt5r46.mjs → respond-wbMSqHYo.mjs} +2 -2
- package/dist/{respond-C4Vt5r46.mjs.map → respond-wbMSqHYo.mjs.map} +1 -1
- package/dist/{route-flag-CpTa8y1n.mjs → route-flag-Bb7n9Mnp.mjs} +3 -3
- package/dist/{route-flag-CpTa8y1n.mjs.map → route-flag-Bb7n9Mnp.mjs.map} +1 -1
- package/dist/{route-policy-DPTLfer-.mjs → route-policy-C_Wsc-b1.mjs} +2 -2
- package/dist/{route-policy-DPTLfer-.mjs.map → route-policy-C_Wsc-b1.mjs.map} +1 -1
- package/dist/{router-builder-BSBSYgYW.d.mts → router-builder-CDkW88KQ.d.mts} +2 -2
- package/dist/{router-builder-BSBSYgYW.d.mts.map → router-builder-CDkW88KQ.d.mts.map} +1 -1
- package/dist/{router-builder-BxXKP8Wy.mjs → router-builder-DawnRzlp.mjs} +3 -3
- package/dist/{router-builder-BxXKP8Wy.mjs.map → router-builder-DawnRzlp.mjs.map} +1 -1
- package/dist/{upload-DjhnyHHm.d.mts → upload-Cy7qQy7M.d.mts} +3 -3
- package/dist/{upload-DjhnyHHm.d.mts.map → upload-Cy7qQy7M.d.mts.map} +1 -1
- package/dist/{upload-config-BmpmZZVK.mjs → upload-config-D01Ty40f.mjs} +2 -2
- package/dist/{upload-config-BmpmZZVK.mjs.map → upload-config-D01Ty40f.mjs.map} +1 -1
- package/dist/web.d.mts +3 -3
- package/dist/web.mjs +2 -2
- package/dist/web.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/application-DPLzamTb.mjs +0 -12
- package/dist/context-fth87C3G.mjs +0 -12
- package/dist/context-fth87C3G.mjs.map +0 -1
- package/dist/handler-eguNTfCv.mjs +0 -13
- package/dist/handler-eguNTfCv.mjs.map +0 -1
- package/dist/logger-DA43DvNY.d.mts.map +0 -1
- package/dist/runtime-B4NQGN66.mjs +0 -12
- package/dist/runtime-B4NQGN66.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container-ByH0uCpG.mjs","names":["resolveAsset"],"sources":["../src/core/asset-resolver-slot.ts","../src/core/kick-error.ts","../src/core/kick-errors.ts","../src/core/token.ts","../src/core/container.ts"],"sourcesContent":["/**\n * Indirection slot between the DI container's `@Asset` property injection\n * and the asset manager. The container must NOT import `./assets` directly —\n * its eager `node:fs`/`node:path` imports would poison the edge-safe\n * `@forinda/kickjs/web` entry graph (every graph includes the container).\n *\n * `./assets` registers the real resolver at module scope, so any node app\n * that imports the asset manager (the core barrel does) gets full `@Asset`\n * behavior; an edge bundle that never imports it gets a clear error on\n * first `@Asset` property access instead of a broken bundle.\n */\n\ntype AssetResolver = (namespace: string, key: string) => unknown\n\nlet impl: AssetResolver | null = null\n\n/** Called by `./assets` at module scope to install the real resolver. */\nexport function _setAssetResolver(fn: AssetResolver): void {\n impl = fn\n}\n\n/** Resolve an asset through the slot — throws when no asset manager is loaded. */\nexport function resolveAssetViaSlot(namespace: string, key: string): unknown {\n if (!impl) {\n throw new Error(\n `@Asset('${namespace}/${key}'): the asset manager is not loaded. ` +\n \"Import it (any import from '@forinda/kickjs' loads it) — note @Asset \" +\n 'reads from the filesystem and is not supported on edge runtimes.',\n )\n }\n return impl(namespace, key)\n}\n","/**\n * Structured framework error with code, cause, and actionable fix hint.\n *\n * The goal is \"the developer who hits this knows what to do next.\" A\n * cryptic `Error('No binding found for: UserService')` becomes a\n * multi-line explanation pointing at the likely cause, the exact code\n * change to apply, and a docs link.\n *\n * `.message` carries the full multi-line plain-text body so Node's\n * default `Error.toString()` and unhandled-exception printing still\n * surface the helpful version. Call {@link formatKickError} to render a\n * colorized version for terminals.\n *\n * Per-error factories live in `kick-errors.ts` — those are the public\n * entry points framework code uses. `KickError` is exported so adopters\n * can build their own structured errors using the same shape.\n */\nexport interface KickErrorInit {\n /** Short stable identifier (e.g. `KICK001`, `NO_PROVIDER`). */\n code: string\n /** One-line headline. */\n summary: string\n /** Multi-line explanation of why this happened. */\n cause?: string\n /** Multi-line actionable fix — show exact code if possible. */\n fix?: string\n /** Absolute URL to the docs page that covers this error. */\n docsUrl?: string\n /** Arbitrary structured fields for log consumers / debug tooling. */\n context?: Record<string, unknown>\n}\n\nexport class KickError extends Error {\n public readonly code: string\n public readonly summary: string\n public readonly cause?: string\n public readonly fix?: string\n public readonly docsUrl?: string\n public readonly context?: Record<string, unknown>\n\n constructor(init: KickErrorInit) {\n super(formatKickError(init, { color: false }))\n this.name = 'KickError'\n this.code = init.code\n this.summary = init.summary\n if (init.cause !== undefined) this.cause = init.cause\n if (init.fix !== undefined) this.fix = init.fix\n if (init.docsUrl !== undefined) this.docsUrl = init.docsUrl\n if (init.context !== undefined) this.context = init.context\n }\n}\n\n// ── ANSI colour helpers ────────────────────────────────────────────────\n\n/**\n * `true` when the runtime can render ANSI escape codes on stderr.\n * Honors `NO_COLOR` (https://no-color.org) and `FORCE_COLOR`.\n */\nfunction ansiEnabled(): boolean {\n if (process.env.NO_COLOR) return false\n if (process.env.FORCE_COLOR) return true\n // Node sets `isTTY` on the writable streams when stdout/stderr is a\n // terminal. KickError output goes to stderr conceptually (it's an\n // error), so we follow that stream's affordances.\n return process.stderr?.isTTY === true\n}\n\n// Use the Unicode-escape form (`\u001b`) rather than the hex-escape (`\\x1b`)\n// for the ANSI ESC byte — both produce the same character, but oxlint's\n// `no-control-character` rule flags the hex form as \"unexpected control\n// character in string literal\". Functionally identical, lint-clean.\nconst ESC = '\u001b['\nconst RESET = `${ESC}0m`\n\nfunction paint(text: string, code: string, enabled: boolean): string {\n return enabled ? `${ESC}${code}m${text}${RESET}` : text\n}\n\nconst c = {\n red: (s: string, enabled: boolean) => paint(s, '31', enabled),\n bold: (s: string, enabled: boolean) => paint(s, '1', enabled),\n dim: (s: string, enabled: boolean) => paint(s, '2', enabled),\n cyan: (s: string, enabled: boolean) => paint(s, '36', enabled),\n blue: (s: string, enabled: boolean) => paint(s, '34', enabled),\n underline: (s: string, enabled: boolean) => paint(s, '4', enabled),\n}\n\n// ── Formatter ─────────────────────────────────────────────────────────\n\nexport interface FormatKickErrorOptions {\n /**\n * Render ANSI colour codes. Defaults to TTY detection (`NO_COLOR` /\n * `FORCE_COLOR` env vars honoured). Pass `false` for log files,\n * structured loggers, or HTTP response bodies.\n */\n color?: boolean\n}\n\n/**\n * Render a {@link KickErrorInit} (or {@link KickError} instance) into a\n * multi-line human-readable string. Identical layout regardless of\n * colour — the colour flag only toggles ANSI codes.\n */\nexport function formatKickError(\n err: KickErrorInit | KickError,\n options: FormatKickErrorOptions = {},\n): string {\n const color = options.color ?? ansiEnabled()\n const lines: string[] = []\n\n // Headline: `KICK001: No provider for UserService`\n const code = c.bold(c.red(err.code, color), color)\n lines.push(`${code}: ${err.summary}`)\n\n if (err.cause) {\n lines.push('', ` ${c.bold(c.dim('Cause:', color), color)}`)\n for (const line of err.cause.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.fix) {\n lines.push('', ` ${c.bold(c.dim('Fix:', color), color)}`)\n for (const line of err.fix.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.docsUrl) {\n lines.push('', ` ${c.bold(c.dim('Docs:', color), color)}`)\n lines.push(` ${c.underline(c.blue(err.docsUrl, color), color)}`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Highlight inline `code spans` in a line of cause/fix text. Anything\n * between backticks gets cyan when colour is enabled; backticks stay\n * in the output so the plain-text rendering still distinguishes\n * literals.\n */\nfunction highlightInline(line: string, color: boolean): string {\n if (!color) return line\n return line.replace(/`([^`]+)`/g, (_, inner: string) => `\\`${c.cyan(inner, true)}\\``)\n}\n","/**\n * Catalog of framework errors with structured cause + fix hints.\n *\n * Each factory function returns a {@link KickError} with a stable\n * `code` (KICK001, KICK002, …), a human-readable summary, an\n * explanation of the likely cause, an actionable fix, and a docs URL.\n *\n * Adding a new error: pick the next free code, add the factory below,\n * call it from the framework site that used to throw a bare `Error`.\n * Keep the public function name descriptive — `noProviderError`,\n * `envValueMissingError` — so call sites grep cleanly.\n *\n * Codes are stable. Reordering or renumbering breaks adopters whose\n * tools key on `err.code`.\n */\nimport { KickError } from './kick-error'\n\nconst DOCS_BASE = 'https://kickjs.app'\n\n// ── DI container ──────────────────────────────────────────────────────\n\n/**\n * KICK001 — No provider registered for a token.\n *\n * Thrown by `Container.resolve()` when no binding can be found for the\n * requested token. The most common cause is forgetting to add the\n * enclosing module to `bootstrap({ modules })`.\n */\nexport function noProviderError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK001',\n summary: `No provider for ${tokenName}`,\n cause: `\\`${tokenName}\\` was requested from the DI container but no binding is registered.\nThis usually means one of:\n • The class is decorated with @Service() / @Repository() / @Controller(),\n but its enclosing module isn't passed to bootstrap({ modules: [...] }).\n • The class isn't decorated at all (decorators register the binding).\n • You're injecting a token (created with createToken()) that nothing\n provides — add a Container.register(TOKEN, ...) call or a module that\n binds it.`,\n fix: `If \\`${tokenName}\\` lives in a module, add the module to bootstrap:\n\n bootstrap({\n modules: [\n UsersModule, // add this\n OtherModule,\n ],\n })\n\nIf it's a custom token, register it explicitly:\n\n const TENANT_REPO = createToken<TenantRepo>('TENANT_REPO')\n Container.getInstance().register(TENANT_REPO, { useClass: PrismaTenantRepo })`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#registering-services`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK002 — REQUEST-scoped binding resolved without a request store\n * provider configured.\n */\nexport function requestScopeMiddlewareMissingError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK002',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" — request scope middleware not mounted`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` but no AsyncLocalStorage frame is wired.\nThe framework's request-scope middleware sets up that frame; without it,\nREQUEST-scoped resolutions have nowhere to cache the per-request instance.`,\n fix: `Add \\`requestScopeMiddleware()\\` to your global middleware pipeline,\nor let the framework auto-mount it by using \\`contextStore: 'auto'\\`\n(the default — only check this if you set it to 'manual' explicitly):\n\n bootstrap({\n modules,\n // contextStore: 'auto', // default; no need to set\n })\n\nIf you set \\`contextStore: 'manual'\\` deliberately (rare), you own the\nALS wrapping — call \\`requestStore.run({...}, () => ...)\\` around the\ncode that resolves the binding.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK003 — REQUEST-scoped binding resolved outside any HTTP request\n * context (no ALS store active).\n */\nexport function requestScopeOutsideRequestError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK003',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" outside an HTTP request`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` and was resolved outside\nany HTTP request context. The request-scope middleware is wired, but\nthe call happened before a request started (e.g. in a top-level\nimport, a constructor, a cron job, a test without setup).`,\n fix: `If the caller is request-scoped (controller, request-scoped service),\nmake sure the call happens inside the request lifecycle, not at module\nload time.\n\nIn tests, wrap setup in an ALS frame manually:\n\n import { requestStore } from '@forinda/kickjs'\n\n requestStore.run(\n { requestId: 'test', instances: new Map(), values: new Map() },\n () => {\n const svc = container.resolve(MyRequestScopedService)\n // ...\n },\n )\n\nIf the caller is a SINGLETON, change \\`${tokenName}\\` to TRANSIENT or\nSINGLETON scope, or inject a factory instead of the instance directly.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n// ── Config / env wiring ───────────────────────────────────────────────\n\n/**\n * KICK004 — `@Value('X')` resolved but the env var isn't set and no\n * default was provided.\n *\n * This is the canonical \"you forgot to wire env\" symptom — surfaces\n * when ConfigService can't find the key. The fix usually involves\n * either setting the env var or wiring `loadEnv(envSchema)` at startup.\n */\nexport function envValueMissingError(envKey: string): KickError {\n return new KickError({\n code: 'KICK004',\n summary: `@Value('${envKey}'): environment variable not set and no default provided`,\n cause: `\\`@Value('${envKey}')\\` resolved but the env var isn't present and the\ndecorator didn't declare a default. Possible reasons:\n • The variable is genuinely missing from your environment / .env file.\n • Your \\`src/env.ts\\` calls \\`loadEnv(envSchema)\\` but \\`src/index.ts\\`\n doesn't import \\`./env\\` before \\`bootstrap()\\`. Without that import\n the schema never runs and ConfigService stays empty — \\`@Value\\`\n only finds the var via the \\`process.env\\` fallback.`,\n fix: `Either set the variable in your shell / .env file:\n\n ${envKey}=...\n\nOr add a default at the decorator:\n\n @Value('${envKey}', { default: 'fallback' })\n private value!: string\n\nIf you're using a schema, make sure \\`src/env.ts\\` is imported from\n\\`src/index.ts\\` BEFORE bootstrap runs:\n\n // src/index.ts\n import 'reflect-metadata'\n import './env' // ← this line is required\n import { bootstrap } from '@forinda/kickjs'\n // ...`,\n docsUrl: `${DOCS_BASE}/guide/configuration#wiring-the-schema-at-startup`,\n context: { envKey },\n })\n}\n\n// ── Module setup ──────────────────────────────────────────────────────\n\n/**\n * KICK005 — A module's `routes()` return value declares a mount path\n * but provides neither a `controller` class nor a pre-built `router`.\n */\nexport function moduleRouteMissingControllerError(mountPath: string): KickError {\n return new KickError({\n code: 'KICK005',\n summary: `Module route at ${mountPath} requires either 'controller' or 'router'`,\n cause: `A module's \\`routes()\\` returned an entry for \\`${mountPath}\\` but didn't\nspecify how to build the router. The framework needs either a decorated\ncontroller class (it calls \\`buildRoutes()\\` internally) or a hand-built\nExpress Router.`,\n fix: `Pass \\`controller:\\` for the common case:\n\n routes() {\n return {\n path: '${mountPath}',\n controller: UsersController,\n }\n }\n\nOr pass \\`router:\\` for full control:\n\n routes() {\n return {\n path: '${mountPath}',\n router: myExpressRouter,\n }\n }`,\n docsUrl: `${DOCS_BASE}/guide/modules`,\n context: { mountPath },\n })\n}\n\n/**\n * KICK006 — Two handlers claim the same HTTP verb + mounted path. The\n * engine would silently dispatch one of them (Express/h3: first wins,\n * Fastify: its own throw), while typegen and the typed client may\n * describe the other — so this fails at boot instead.\n */\nexport function duplicateRouteError(\n method: string,\n path: string,\n owner: string,\n prior: string,\n): KickError {\n return new KickError({\n code: 'KICK006',\n summary: `Duplicate route: ${method} ${path} is registered twice`,\n cause: `\\`${owner}\\` registers \\`${method} ${path}\\`, but that verb + path is\nalready claimed by \\`${prior}\\`. Only one handler can win at dispatch\ntime, and which one depends on the engine — while \\`kick typegen\\` and\nthe typed client may describe the other. Param names are ignored when\ncomparing (\\`/:id\\` and \\`/:taskId\\` are the same route).`,\n fix: `Change one of the two paths, remove the duplicate handler, or mount\nthe second controller under a different module path / version:\n\n routes() {\n return { path: '/tasks', controller: TasksController, version: 2 }\n }`,\n docsUrl: `${DOCS_BASE}/guide/modules`,\n context: { method, path, owner, prior },\n })\n}\n\n// ── Server ────────────────────────────────────────────────────────────\n\n/**\n * KICK007 — `server.http2` on a runtime that cannot serve HTTP/2. Express 5\n * does not run on Node's HTTP/2 compatibility layer (requests hang and it\n * throws internally), so this fails at boot rather than on the first request.\n */\nexport function http2UnsupportedRuntimeError(runtime: string): KickError {\n return new KickError({\n code: 'KICK007',\n summary: `The ${runtime} runtime cannot serve HTTP/2`,\n cause: `\\`bootstrap({ server: { http2: true } })\\` asks for an HTTP/2 server, but the\n\\`${runtime}\\` runtime does not support Node's HTTP/2 compatibility layer —\nrequests would hang instead of being answered.`,\n fix: `Pick one:\n • Switch to a runtime that serves HTTP/2:\n bootstrap({ runtime: fastifyRuntime(), server: { tls, http2: true } })\n (or h3Runtime()).\n • Drop \\`http2\\` and keep HTTPS over HTTP/1.1: server: { tls }.\n • Terminate HTTP/2 at your proxy / load balancer and serve HTTP/1.1 to it.`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n context: { runtime },\n })\n}\n\n/**\n * KICK008 — `server.http2` without `server.tls`. Browsers only speak HTTP/2\n * over TLS; cleartext HTTP/2 (h2c) is not offered.\n */\nexport function http2RequiresTlsError(): KickError {\n return new KickError({\n code: 'KICK008',\n summary: 'server.http2 requires server.tls',\n cause: `\\`bootstrap({ server: { http2: true } })\\` was set without \\`tls\\`. Browsers only\nnegotiate HTTP/2 over TLS, and KickJS does not serve cleartext HTTP/2 (h2c).`,\n fix: `Pass the certificate and key alongside http2:\n\n server: {\n tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },\n http2: true,\n }\n\nOr remove \\`http2\\` if TLS is terminated in front of the app.`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n })\n}\n\n/**\n * KICK009 — `server.tls` cannot identify the server. Node starts an HTTPS /\n * HTTP/2 server without a certificate (or with a key that does not match it)\n * and then fails every TLS handshake, so this is checked at boot instead.\n */\nexport function tlsCredentialsError(problem: string): KickError {\n return new KickError({\n code: 'KICK009',\n summary: `server.tls is not usable: ${problem}`,\n cause: `\\`bootstrap({ server: { tls } })\\` was given TLS options the server cannot use:\n${problem}. Node would start anyway and fail every TLS handshake.`,\n fix: `Give the server an identity:\n • \\`key\\` and \\`cert\\` together (PEM, matching each other), or\n • \\`pfx\\` (with \\`passphrase\\` if it is encrypted), or\n • \\`SNICallback\\` / \\`pskCallback\\` for per-host or PSK setups.\n\n server: { tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') } }`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n context: { problem },\n })\n}\n","/**\n * Collision-safe injection tokens with phantom type parameters.\n *\n * `createToken<T>(name)` is the recommended way to declare any DI token\n * that isn't a class. Each call returns a unique frozen object — even if\n * two files write `createToken<X>('Logger')`, they produce two distinct\n * references that the container treats as different tokens. Collisions\n * become impossible by construction, not by convention.\n *\n * The phantom type parameter `T` lets `container.resolve(token)` infer\n * the correct return type without any codegen.\n *\n * ## Hardening layers\n *\n * KickJS has four DI token kinds, listed from safest to riskiest:\n *\n * 1. **Class identity** — `container.resolve(UserService)`. Reference\n * equality, never collides. Type-safe via `Constructor<T>` overload.\n * 2. **`createToken<T>(name)`** — frozen object identity, never collides.\n * Type-safe via the `InjectionToken<T>` overload.\n * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`\n * is interned and CAN collide. Discouraged.\n * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.\n * Reserved for legacy code and runtime-computed token names.\n *\n * Prefer (1) and (2). Use (4) only when you have to.\n *\n * @example\n * ```ts\n * // src/tokens.ts\n * export const DATABASE_URL = createToken<string>('config.database.url')\n * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')\n *\n * // bootstrap\n * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)\n *\n * // anywhere else\n * const url = container.resolve(DATABASE_URL) // typed as `string`\n * ```\n *\n * @module @forinda/kickjs/core/token\n */\n\n/**\n * Symbol marker stamped on every InjectionToken so the container can\n * detect them at runtime regardless of which package created them.\n *\n * Uses `Symbol.for()` so cross-package isolation (different copies of\n * `@forinda/kickjs` loaded under different module identities) still\n * shares the same marker.\n */\nexport const INJECTION_TOKEN: unique symbol = Symbol.for('@kickjs/InjectionToken') as never\n\n/**\n * A type-safe DI token. Each `createToken()` call returns a unique frozen\n * object identified by reference, not by the `name` string. Two\n * `createToken<X>('foo')` calls in different files produce two distinct\n * tokens that the container treats independently.\n *\n * The phantom `_type` parameter is never assigned at runtime — it exists\n * solely to thread the type through `container.resolve(token)`.\n */\nexport interface InjectionToken<T> {\n readonly [INJECTION_TOKEN]: true\n /** Descriptive name used in error messages, logs, and devtools */\n readonly name: string\n /** Phantom — never assigned at runtime */\n readonly _type?: T\n}\n\n/**\n * Create a collision-safe injection token.\n *\n * The returned token is a frozen object identified by reference, not by\n * the `name` string. The `name` is used only for error messages, logs,\n * and debugging.\n */\nexport function createToken<T>(name: string): InjectionToken<T> {\n return Object.freeze({\n [INJECTION_TOKEN]: true as const,\n name,\n })\n}\n\n/** Type guard — returns `true` if `value` is an InjectionToken */\nexport function isInjectionToken(value: unknown): value is InjectionToken<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[INJECTION_TOKEN] === true\n )\n}\n","import 'reflect-metadata'\nimport {\n Constructor,\n Scope,\n METADATA,\n type ClassKind,\n type PostConstructStatus,\n} from './interfaces'\n// Slot indirection, NOT `./assets` — that module's eager node:fs import\n// would poison the edge-safe web entry graph (see asset-resolver-slot.ts).\nimport { resolveAssetViaSlot as resolveAsset } from './asset-resolver-slot'\nimport {\n envValueMissingError,\n noProviderError,\n requestScopeMiddlewareMissingError,\n requestScopeOutsideRequestError,\n} from './kick-errors'\nimport { createLogger } from './logger'\nimport {\n getClassMeta,\n getClassMetaOrUndefined,\n getMetaMap,\n getMetaRecord,\n getMethodMetaOrUndefined,\n} from './metadata'\nimport { isInjectionToken, type InjectionToken } from './token'\n\nconst log = createLogger('Container')\n\n/** Internal registration entry that tracks how a dependency should be resolved */\ninterface Registration {\n target: Constructor\n scope: Scope\n instance?: any\n factory?: () => any\n // Observability fields\n kind: ClassKind\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n /** Lazily built instantiation plan — see {@link Container.buildPlan}. */\n plan?: InstantiationPlan\n}\n\n/**\n * Precomputed instantiation recipe for a class. Class metadata is immutable\n * after definition, so the Reflect reads (5 per create) and the derived\n * structures only need computing ONCE — previously they re-ran on every\n * REQUEST-scoped resolve (i.e. every request) and every TRANSIENT resolve.\n */\ninterface InstantiationPlan {\n /** Ordered, validated constructor parameter tokens. */\n ctorTokens: any[]\n /** `@Autowired` properties: [property, resolved token]. */\n autowired: Array<[string, any]>\n /** `@Value` properties: [property, env config]. */\n values: Array<[string, { envKey: string; defaultValue?: any }]>\n /** `@Asset` properties: [property, pre-split namespace/key]. */\n assets: Array<[string, { namespace: string; key: string }]>\n /** `@PostConstruct` method name, if any. */\n postConstruct?: string | symbol\n}\n\n/** Create a Registration with observability defaults */\nfunction createReg(\n fields: Partial<Registration> & Pick<Registration, 'target' | 'scope'>,\n): Registration {\n return {\n kind: 'unknown',\n resolveCount: 0,\n postConstructStatus: 'skipped',\n dependencies: [],\n ...fields,\n }\n}\n\n/** Format a token for display in error messages */\nexport function tokenName(token: any): string {\n if (typeof token === 'symbol') return token.toString()\n if (isInjectionToken(token)) return token.name\n return token?.name || String(token)\n}\n\n// ── Persistent Store ────────────────────────────────────────────────────\n// globalThis-based storage that survives BOTH Container.reset() AND Vite's\n// ssrLoadModule() re-evaluation. When Vite re-evaluates a module, the\n// Container class gets a new identity (new statics). But globalThis persists.\n//\n// This is how DB connections, Redis clients, WsAdapter instances, and any\n// other stateful bindings survive HMR without being dropped.\n//\n// Pattern from: React Router (globalThis for state), Vinxi (globalThis.app)\n\n// ── Change Event System ─────────────────────────────────────────────────\n// Batched (50ms debounce) so `kick g module` creating 10+ files emits ONE\n// notification, not 10. Any adapter can subscribe: Swagger, DevTools, WS, etc.\n\n/** A single container change event */\nexport interface ContainerChangeEvent {\n token: string\n event: 'registered' | 'resolved' | 'invalidated'\n kind?: ClassKind\n timestamp: number\n}\n\n/** Listener that receives batched change events */\nexport type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void\n\n/**\n * Type-safe registry of string DI tokens. Augmented by `kick typegen` —\n * the generated `.kickjs/types/registry.d.ts` adds entries like:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface KickJsRegistry {\n * UserService: import('../../src/modules/users/user.service').UserService\n * }\n * }\n * ```\n *\n * Once typegen has run, `container.resolve('UserService')` returns the\n * correct type instead of `any`. Class-token resolution\n * (`container.resolve(UserService)`) is already type-safe via the\n * `Constructor<T>` overload and does not need typegen.\n */\nexport interface KickJsRegistry {}\n\ninterface PersistentStore {\n factories: Array<{ token: any; factory: () => any; scope: Scope }>\n instances: Array<{ token: any; instance: any }>\n /** Resolved instances keyed by string name — survives class identity changes */\n resolvedInstances: Map<string, any>\n}\n\nfunction getPersistentStore(): PersistentStore {\n if (!(globalThis as any).__kickjs_persistent) {\n ;(globalThis as any).__kickjs_persistent = {\n factories: [],\n instances: [],\n resolvedInstances: new Map(),\n } satisfies PersistentStore\n }\n return (globalThis as any).__kickjs_persistent\n}\n\n/**\n * Inversion-of-Control (IoC) container that manages dependency registration,\n * resolution, and lifecycle. Implements the Singleton pattern so all parts of\n * the application share a single container instance.\n *\n * Supports constructor injection, property injection (@Autowired),\n * factory registrations, and lifecycle hooks (@PostConstruct).\n *\n * Persistent state (DB connections, Redis, etc.) is stored on globalThis so\n * it survives both Container.reset() and Vite module re-evaluation.\n */\nexport class Container {\n private static instance: Container\n private registrations = new Map<any, Registration>()\n private resolving = new Set<any>()\n\n // ── Reactive change tracking ──────────────────────────────────────\n private changeListeners = new Set<ContainerChangeListener>()\n private pendingChanges: ContainerChangeEvent[] = []\n private notifyTimer: ReturnType<typeof setTimeout> | null = null\n private static readonly DEBOUNCE_MS = 50\n\n /** Callback set by the decorators module to flush pending registrations */\n static _onReady: ((container: Container) => void) | null = null\n /** Callback invoked on reset so decorators can update their container reference */\n static _onReset: ((container: Container) => void) | null = null\n\n /**\n * Seeds a NEWLY CREATED container with the decorator registrations, without\n * adopting it as the global one. `_onReset` does both; an isolated container\n * must only have the first half, or it would capture every subsequent\n * decorator registration in the process.\n */\n static _onCreate: ((container: Container) => void) | null = null\n /**\n * Environment resolver for @Value decorator. Set by the unified\n * `@forinda/kickjs` config layer to return Zod-validated, type-coerced\n * env values instead of raw process.env strings.\n */\n static _envResolver: ((key: string) => any) | null = null\n /**\n * Request store provider for REQUEST-scoped DI. Set by the HTTP layer\n * inside `@forinda/kickjs` to return the current request's\n * AsyncLocalStorage store. Returns { instances: Map, values: Map } or\n * null if outside a request.\n */\n /**\n * Whether this container is the global singleton.\n *\n * The persistent store exists to carry the GLOBAL container's registrations\n * across HMR and module re-evaluation. An isolated container is per-test and\n * must neither write to it — a `registerInstance` override was replayed into\n * every later `Container.reset()` — nor read from it, which would pull global\n * state into a container created to be free of it.\n */\n private isGlobal = false\n\n static _requestStoreProvider:\n | (() => { instances: Map<any, any>; values: Map<any, any> } | null)\n | null = null\n\n static getInstance(): Container {\n if (!Container.instance) {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n }\n // Flush any decorator registrations that queued before the container existed\n if (Container._onReady) {\n Container._onReady(Container.instance)\n Container._onReady = null\n }\n return Container.instance\n }\n\n /**\n * Resets the container by replacing the singleton with a fresh instance.\n * Persistent registrations (DB, Redis, etc.) are replayed from globalThis\n * so they survive both reset() and Vite module re-evaluation.\n */\n static reset(): void {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n // Notify decorators so they update their container reference\n Container._onReset?.(Container.instance)\n // Replay persistent registrations from globalThis store\n const store = getPersistentStore()\n for (const { token, factory, scope } of store.factories) {\n if (!Container.instance.has(token)) {\n // Check if we have a previously resolved instance for this token\n const name = tokenName(token)\n const existing = store.resolvedInstances.get(name)\n Container.instance.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existing, // Reuse resolved instance if available\n resolveCount: existing ? 1 : 0,\n postConstructStatus: existing ? 'completed' : 'skipped',\n }),\n )\n }\n }\n for (const { token, instance } of store.instances) {\n if (!Container.instance.has(token)) {\n Container.instance.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n }),\n )\n }\n }\n }\n\n /**\n * Create an isolated container instance (not the global singleton).\n * Useful for concurrent tests that must not share DI state.\n *\n * @example\n * ```ts\n * const container = Container.create()\n * container.register(UserService, UserService)\n * const svc = container.resolve(UserService)\n * ```\n */\n static create(): Container {\n const container = new Container()\n // Decorators register at class-definition time, into whichever container\n // was global then. Without this replay an isolated container is missing\n // every @Service / @Repository / @Controller in the process — and\n // `isolated: true` is what the generated docs recommend for test\n // isolation, so the failure reached adopters as `No provider for X`.\n Container._onCreate?.(container)\n return container\n }\n\n /** Register a class constructor under the given token */\n register(token: any, target: Constructor, scope: Scope = Scope.SINGLETON): void {\n const kind = getClassMeta<ClassKind>(METADATA.CLASS_KIND, target, 'unknown')\n const dependencies = this.extractDependencies(target)\n const reg = createReg({ target, scope, kind, dependencies })\n this.registrations.set(token, reg)\n // Store a name-based fallback so HMR class re-creation (new identity)\n // can still resolve by the original class name.\n if (typeof token === 'function' && token.name) {\n this.registrations.set(`__hmr__${token.name}`, reg)\n }\n this.emit(token, 'registered', kind)\n }\n\n /** Register a factory function under the given token */\n registerFactory(token: any, factory: () => any, scope: Scope = Scope.SINGLETON): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n // Check if we already have a resolved instance from a previous HMR cycle.\n // Only reuse if the factory hasn't changed (same reference). When a new\n // factory is provided (e.g. swapping implementations), clear the stale\n // instance so the new factory runs on next resolve.\n // Reads are global-only too. An isolated container adopting a cached\n // instance from the global store would be contaminated on creation — the\n // same leak, in the other direction.\n const existingEntry = this.isGlobal\n ? store.factories.find((r) => tokenName(r.token) === name)\n : undefined\n const factoryUnchanged = existingEntry?.factory === factory\n const existingInstance =\n this.isGlobal && factoryUnchanged ? store.resolvedInstances.get(name) : undefined\n\n // Clear stale resolved instance when factory changes\n if (this.isGlobal && !factoryUnchanged) {\n store.resolvedInstances.delete(name)\n }\n\n this.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existingInstance,\n resolveCount: existingInstance ? 1 : 0,\n postConstructStatus: existingInstance ? 'completed' : 'skipped',\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only, or an isolated container's factories are replayed\n // into the next Container.reset().\n if (this.isGlobal) {\n const idx = store.factories.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.factories[idx] = { token, factory, scope }\n } else {\n store.factories.push({ token, factory, scope })\n }\n }\n this.emit(name, 'registered', 'factory')\n }\n\n /** Register a pre-constructed singleton instance */\n registerInstance(token: any, instance: any): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n\n this.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n firstResolvedAt: Date.now(),\n lastResolvedAt: Date.now(),\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only. An isolated one that wrote here had its overrides\n // replayed into the next Container.reset().\n if (this.isGlobal) {\n store.resolvedInstances.set(name, instance)\n const idx = store.instances.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.instances[idx] = { token, instance }\n } else {\n store.instances.push({ token, instance })\n }\n }\n this.emit(name, 'registered', 'instance')\n }\n\n /** Check if a binding exists for the given token */\n has(token: any): boolean {\n return this.registrations.has(token)\n }\n\n /** Return a snapshot of all registered tokens with their scope and instantiation status */\n getRegistrations(): Array<{\n token: string\n scope: string\n kind: ClassKind\n instantiated: boolean\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n }> {\n const entries: Array<ReturnType<Container['getRegistrations']>[number]> = []\n for (const [token, reg] of this.registrations) {\n entries.push({\n token: tokenName(token),\n scope: reg.scope,\n kind: reg.kind,\n // Singletons check cached instance; transients check resolveCount\n instantiated:\n reg.scope === Scope.SINGLETON ? reg.instance !== undefined : reg.resolveCount > 0,\n resolveCount: reg.resolveCount,\n lastResolvedAt: reg.lastResolvedAt,\n firstResolvedAt: reg.firstResolvedAt,\n resolveDurationMs: reg.resolveDurationMs,\n postConstructStatus: reg.postConstructStatus,\n dependencies: reg.dependencies,\n })\n }\n return entries\n }\n\n /**\n * Resolve a dependency by its token.\n *\n * Four overloads, in order of preference:\n * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.\n * Collision-safe by construction; no codegen required.\n * - **Class constructor** → returns an instance of that class. Always\n * type-safe; no codegen required.\n * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →\n * returns the augmented type.\n * - **Anything else** → returns `any`. Use this only for legacy code or\n * string tokens that haven't been registered with typegen yet.\n */\n resolve<T>(token: InjectionToken<T>): T\n resolve<T>(token: Constructor<T>): T\n resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K]\n resolve<T = any>(token: any): T\n resolve<T = any>(token: any): T {\n let reg = this.registrations.get(token)\n // HMR fallback: when Vite re-evaluates a module, decorated classes get new\n // identity. Try resolving by class name if the primary token lookup fails.\n if (!reg && typeof token === 'function' && token.name) {\n reg = this.registrations.get(`__hmr__${token.name}`)\n }\n if (!reg) {\n throw noProviderError(tokenName(token))\n }\n\n if (reg.scope === Scope.SINGLETON && reg.instance !== undefined) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return reg.instance\n }\n\n // REQUEST scope: one instance per HTTP request, cached in AsyncLocalStorage\n if (reg.scope === Scope.REQUEST) {\n if (!Container._requestStoreProvider) {\n throw requestScopeMiddlewareMissingError(tokenName(token))\n }\n const store = Container._requestStoreProvider()\n if (!store) {\n throw requestScopeOutsideRequestError(tokenName(token))\n }\n // Check for pre-registered request values (user, tenant, etc.)\n if (store.values.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.values.get(token) as T\n }\n // Check per-request instance cache\n if (store.instances.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.instances.get(token) as T\n }\n // Factory-registered binding: invoke the factory once per request\n // and cache the result alongside class-instantiated REQUEST tokens.\n // Without this branch, registerFactory(token, fn, Scope.REQUEST)\n // silently falls through to createInstance(reg) which `new`s the\n // factory's placeholder `target: Object` and returns an empty {}\n // — the factory function never runs.\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n // Create instance and cache for this request only\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n // Persist resolved factory instances so they survive module\n // re-evaluation — global container only, for the same reason.\n if (this.isGlobal) {\n getPersistentStore().resolvedInstances.set(tokenName(token), instance)\n }\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n }\n\n if (this.resolving.has(token)) {\n const chain = [...this.resolving].map(tokenName)\n chain.push(tokenName(token))\n throw new Error(`Circular dependency detected: ${chain.join(' -> ')}`)\n }\n this.resolving.add(token)\n\n try {\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n } finally {\n this.resolving.delete(token)\n }\n }\n\n /** Lifecycle hook called during Application.setup() after module registration */\n bootstrap(): void {\n // Reserved for future use — adapters and modules register via\n // container.register(), registerFactory(), and registerInstance().\n }\n\n // ── Reactive Change API ─────────────────────────────────────────────\n\n /**\n * Subscribe to container changes. Returns an unsubscribe function.\n *\n * Changes are batched (50ms debounce) so bulk operations like\n * `kick g module` creating 10+ files emit ONE batch, not 10 events.\n *\n * Any adapter can subscribe:\n * - SwaggerAdapter: re-build spec when controllers change\n * - DevToolsAdapter: push SSE events to dashboard\n * - WsAdapter: re-discover namespaces when controllers change\n * - OtelAdapter: emit tracing spans for DI resolution\n * - Custom user adapters\n *\n * @example\n * ```ts\n * const unsub = container.onChange((changes) => {\n * for (const c of changes) {\n * console.log(`${c.event}: ${c.token} (${c.kind})`)\n * }\n * })\n * // later:\n * unsub()\n * ```\n */\n onChange(callback: ContainerChangeListener): () => void {\n this.changeListeners.add(callback)\n return () => this.changeListeners.delete(callback)\n }\n\n /**\n * Invalidate a specific registration. Clears the cached instance so the\n * next resolve() creates a fresh one. Also invalidates dependents\n * (anything that injected this token) by walking the dependency graph.\n *\n * Called by the Vite HMR plugin when a module file changes.\n * Skips persistent registrations (DB connections, etc.).\n */\n invalidate(token: any): void {\n const name = tokenName(token)\n // Try both the token itself and the HMR fallback name\n const reg = this.registrations.get(token) ?? this.registrations.get(`__hmr__${name}`)\n if (!reg) return\n\n // Don't invalidate persistent registrations (DB, Redis, etc.)\n const store = getPersistentStore()\n if (store.resolvedInstances.has(name)) return\n\n // Clear cached instance — next resolve() will re-create\n reg.instance = undefined\n reg.resolveCount = 0\n reg.postConstructStatus = 'skipped'\n this.emit(name, 'invalidated', reg.kind)\n\n // Walk dependency graph — invalidate anything that injected this token\n for (const [depToken, depReg] of this.registrations) {\n if (depReg.dependencies.includes(name)) {\n this.invalidate(depToken)\n }\n }\n }\n\n /**\n * Batch-emit a change event (debounced, flushed after 50ms of quiet).\n * Takes the RAW token — `tokenName()` is only computed after the\n * zero-listener guard, so the hot cached-singleton resolve path never pays\n * the name derivation when nobody is watching (the production default).\n */\n private emit(rawToken: any, event: ContainerChangeEvent['event'], kind?: ClassKind): void {\n // Zero-listener fast path. Nobody subscribes to container changes unless\n // DevTools (or a test) calls `onChange` — the production default. Without\n // this guard, every `resolve()` (including the hot cached-singleton path)\n // would scan `pendingChanges`, push an entry, and reschedule a `setTimeout`\n // only for the eventual flush to iterate an empty listener set and discard\n // the batch. Short-circuiting here keeps `resolve()` allocation- and\n // timer-free when no one is watching. When a listener attaches later,\n // subsequent emits work exactly as before — past resolves had no observer\n // to notify anyway.\n if (this.changeListeners.size === 0) return\n const token = tokenName(rawToken)\n\n // Dedupe within the pending batch: a high-throughput TRANSIENT\n // token that resolves 1000× per request would otherwise queue\n // 1000 identical 'resolved' events. The downstream listener only\n // cares that the token's state changed, so collapse to one entry\n // (the earliest timestamp wins; the latest update is reflected\n // in resolveCount/lastResolvedAt on the registration itself,\n // which the listener reads at notify time).\n const existing = this.pendingChanges.find((p) => p.token === token && p.event === event)\n if (existing) {\n existing.timestamp = Date.now()\n } else {\n this.pendingChanges.push({ token, event, kind, timestamp: Date.now() })\n }\n\n if (this.notifyTimer) clearTimeout(this.notifyTimer)\n this.notifyTimer = setTimeout(() => {\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }, Container.DEBOUNCE_MS)\n // Don't let a pending debounce window hold the event loop open — flush is\n // best-effort telemetry, not critical work.\n this.notifyTimer.unref?.()\n }\n\n /** Flush pending change events immediately (useful in tests) */\n flushChanges(): void {\n if (this.notifyTimer) {\n clearTimeout(this.notifyTimer)\n this.notifyTimer = null\n }\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }\n\n /**\n * Build the one-time instantiation plan for a registration. Runs on the\n * FIRST createInstance for the class — the metadata it reads is immutable\n * after class definition, so every later create (REQUEST scope: every\n * request) skips all Reflect reads and re-derivation.\n *\n * Scope validation (SINGLETON must not ctor-inject REQUEST) lives here\n * too: it's a static property of the (parent, dep) registration pair, so\n * checking it once at plan build is equivalent to the old per-create loop.\n */\n private buildPlan(reg: Registration): InstantiationPlan {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, reg.target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, reg.target)\n\n // When emitDecoratorMetadata is unavailable (tsx, ts-node without\n // SWC), paramTypes is empty even though @Inject set tokens on\n // specific constructor indices. Derive the constructor arity from\n // the @Inject metadata so constructor injection still works.\n const maxInjectIndex =\n Object.keys(injectTokens).length > 0\n ? Math.max(...Object.keys(injectTokens).map(Number)) + 1\n : 0\n const paramCount = Math.max(paramTypes.length, maxInjectIndex)\n\n const ctorTokens: any[] = []\n for (let index = 0; index < paramCount; index++) {\n const token = injectTokens[index] || paramTypes[index]\n if (!token) {\n throw new Error(\n `Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. ` +\n `No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). ` +\n `Add @Inject(Token) to every constructor parameter.`,\n )\n }\n // Scope validation: SINGLETON cannot inject REQUEST-scoped dependencies\n if (reg.scope === Scope.SINGLETON) {\n const depReg = this.registrations.get(token)\n if (depReg && depReg.scope === Scope.REQUEST) {\n // The fix depends on the parent: @Controller() takes no options and\n // is always SINGLETON, so \"change the parent's scope\" is advice a\n // controller cannot follow. @Autowired works for every parent — its\n // getter re-resolves REQUEST deps per access (injectProperties).\n const parent = tokenName(reg.target)\n const dep = tokenName(token)\n const kind = getClassMetaOrUndefined<ClassKind>(METADATA.CLASS_KIND, reg.target)\n const fix =\n kind === 'controller'\n ? `@Controller() is always SINGLETON, so inject \"${dep}\" with @Autowired() instead of the constructor.`\n : `Give \"${parent}\" TRANSIENT or REQUEST scope (e.g. @Service({ scope: Scope.REQUEST })), ` +\n `or inject \"${dep}\" with @Autowired() instead of the constructor.`\n throw new Error(\n `Cannot inject REQUEST-scoped \"${dep}\" into SINGLETON \"${parent}\". ` +\n `Singletons outlive requests. ${fix}`,\n )\n }\n }\n ctorTokens.push(token)\n }\n\n // @Autowired properties — resolve each property's token once.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, reg.target.prototype)\n const autowired: InstantiationPlan['autowired'] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, reg.target.prototype, prop)\n if (resolvedToken) autowired.push([prop, resolvedToken])\n }\n\n // @Value properties.\n const valueProps = getMetaMap<string, { envKey: string; defaultValue?: any }>(\n METADATA.VALUE,\n reg.target.prototype,\n )\n const values: InstantiationPlan['values'] = [...valueProps.entries()]\n\n // @Asset properties — pre-split namespace/key (and fail fast on bad keys).\n const assetProps = getMetaMap<string, { assetKey: string }>(\n METADATA.ASSET,\n reg.target.prototype,\n )\n const assets: InstantiationPlan['assets'] = []\n for (const [prop, config] of assetProps) {\n const slashIdx = config.assetKey.indexOf('/')\n if (slashIdx === -1) {\n throw new Error(\n `@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`,\n )\n }\n assets.push([\n prop,\n { namespace: config.assetKey.slice(0, slashIdx), key: config.assetKey.slice(slashIdx + 1) },\n ])\n }\n\n const postConstruct = getClassMetaOrUndefined<string | symbol>(\n METADATA.POST_CONSTRUCT,\n reg.target.prototype,\n )\n\n return { ctorTokens, autowired, values, assets, postConstruct }\n }\n\n private createInstance(reg: Registration): any {\n const plan = (reg.plan ??= this.buildPlan(reg))\n\n const args: any[] = []\n for (const token of plan.ctorTokens) {\n args.push(this.resolve(token))\n }\n\n const instance = new reg.target(...args)\n\n // Property injection via @Autowired / @Value / @Asset\n this.injectProperties(instance, plan)\n\n // @PostConstruct lifecycle hook\n const postConstruct = plan.postConstruct\n if (postConstruct && typeof instance[postConstruct] === 'function') {\n try {\n const result = instance[postConstruct]()\n if (result && typeof result.then === 'function') {\n reg.postConstructStatus = 'pending'\n ;(result as Promise<void>)\n .then(() => {\n reg.postConstructStatus = 'completed'\n })\n .catch(() => {\n reg.postConstructStatus = 'failed'\n })\n log.warn(\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise ` +\n `but the container is synchronous. The async operation will not be awaited.`,\n )\n } else {\n reg.postConstructStatus = 'completed'\n }\n } catch (err) {\n reg.postConstructStatus = 'failed'\n log.error(\n err,\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`,\n )\n }\n }\n\n return instance\n }\n\n /**\n * Extract every dependency token name a class declares — both\n * constructor params (`@Inject` + emitted `design:paramtypes`) and\n * `@Autowired` properties (read lazily at injection time, but\n * captured here so devtools surfaces a complete dependency graph).\n *\n * Without the property branch, classes that rely entirely on\n * `@Autowired` show `dependencies: []` in the introspection panel\n * even though they have real DI deps — see /_debug topology.\n */\n private extractDependencies(target: Constructor): string[] {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, target)\n const ctorDeps = paramTypes.map((type, index) => {\n const token = injectTokens[index] || type\n return tokenName(token)\n })\n\n // @Autowired properties live on `target.prototype`. Token may be\n // explicit (`@Autowired('SOME_TOKEN')`) or fall back to the\n // emitted `design:type` metadata of the property.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n const propDeps: string[] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) propDeps.push(tokenName(resolvedToken))\n }\n\n // De-dupe — a class can declare the same token in both surfaces.\n return [...new Set([...ctorDeps, ...propDeps])]\n }\n\n private injectProperties(instance: any, plan: InstantiationPlan): void {\n // @Autowired — lazy DI property injection. The getter re-resolves per\n // access by design (a SINGLETON may lazily read a REQUEST-scoped dep at\n // call time). For SINGLETON deps that laziness buys nothing after the\n // first read — the container returns the same cached instance forever —\n // so the getter memoizes itself into a plain data property then. REQUEST\n // and TRANSIENT deps keep the live getter (memoizing would pin the first\n // request's instance across requests).\n for (const [prop, resolvedToken] of plan.autowired) {\n Object.defineProperty(instance, prop, {\n get: () => {\n const value = this.resolve(resolvedToken)\n const depReg = this.registrations.get(resolvedToken)\n if (depReg?.scope === Scope.SINGLETON) {\n Object.defineProperty(instance, prop, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return value\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Value — lazy environment variable injection\n for (const [prop, config] of plan.values) {\n Object.defineProperty(instance, prop, {\n get() {\n // Use the registered env resolver if available (wired by the\n // config layer in @forinda/kickjs). Returns Zod-validated,\n // type-coerced values (e.g. PORT as number).\n if (Container._envResolver) {\n const val = Container._envResolver(config.envKey)\n if (val !== undefined) return val\n }\n\n // Fallback to raw process.env for apps that haven't called loadEnv()\n const val = process.env[config.envKey]\n if (val !== undefined) return val\n if (config.defaultValue !== undefined) return config.defaultValue\n throw envValueMissingError(config.envKey)\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Asset — lazy asset path injection. Same lazy-\n // getter pattern as @Value: resolve on every property access via\n // the asset manager's cached resolver, not at class instantiation.\n // namespace/key were pre-split at plan build.\n for (const [prop, { namespace, key }] of plan.assets) {\n Object.defineProperty(instance, prop, {\n get() {\n return resolveAsset(namespace, key)\n },\n enumerable: true,\n configurable: true,\n })\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"container-CAPN7pBG.mjs","names":["resolveAsset"],"sources":["../src/core/asset-resolver-slot.ts","../src/core/kick-error.ts","../src/core/kick-errors.ts","../src/core/token.ts","../src/core/container.ts"],"sourcesContent":["/**\n * Indirection slot between the DI container's `@Asset` property injection\n * and the asset manager. The container must NOT import `./assets` directly —\n * its eager `node:fs`/`node:path` imports would poison the edge-safe\n * `@forinda/kickjs/web` entry graph (every graph includes the container).\n *\n * `./assets` registers the real resolver at module scope, so any node app\n * that imports the asset manager (the core barrel does) gets full `@Asset`\n * behavior; an edge bundle that never imports it gets a clear error on\n * first `@Asset` property access instead of a broken bundle.\n */\n\ntype AssetResolver = (namespace: string, key: string) => unknown\n\nlet impl: AssetResolver | null = null\n\n/** Called by `./assets` at module scope to install the real resolver. */\nexport function _setAssetResolver(fn: AssetResolver): void {\n impl = fn\n}\n\n/** Resolve an asset through the slot — throws when no asset manager is loaded. */\nexport function resolveAssetViaSlot(namespace: string, key: string): unknown {\n if (!impl) {\n throw new Error(\n `@Asset('${namespace}/${key}'): the asset manager is not loaded. ` +\n \"Import it (any import from '@forinda/kickjs' loads it) — note @Asset \" +\n 'reads from the filesystem and is not supported on edge runtimes.',\n )\n }\n return impl(namespace, key)\n}\n","/**\n * Structured framework error with code, cause, and actionable fix hint.\n *\n * The goal is \"the developer who hits this knows what to do next.\" A\n * cryptic `Error('No binding found for: UserService')` becomes a\n * multi-line explanation pointing at the likely cause, the exact code\n * change to apply, and a docs link.\n *\n * `.message` carries the full multi-line plain-text body so Node's\n * default `Error.toString()` and unhandled-exception printing still\n * surface the helpful version. Call {@link formatKickError} to render a\n * colorized version for terminals.\n *\n * Per-error factories live in `kick-errors.ts` — those are the public\n * entry points framework code uses. `KickError` is exported so adopters\n * can build their own structured errors using the same shape.\n */\nexport interface KickErrorInit {\n /** Short stable identifier (e.g. `KICK001`, `NO_PROVIDER`). */\n code: string\n /** One-line headline. */\n summary: string\n /** Multi-line explanation of why this happened. */\n cause?: string\n /** Multi-line actionable fix — show exact code if possible. */\n fix?: string\n /** Absolute URL to the docs page that covers this error. */\n docsUrl?: string\n /** Arbitrary structured fields for log consumers / debug tooling. */\n context?: Record<string, unknown>\n}\n\nexport class KickError extends Error {\n public readonly code: string\n public readonly summary: string\n public readonly cause?: string\n public readonly fix?: string\n public readonly docsUrl?: string\n public readonly context?: Record<string, unknown>\n\n constructor(init: KickErrorInit) {\n super(formatKickError(init, { color: false }))\n this.name = 'KickError'\n this.code = init.code\n this.summary = init.summary\n if (init.cause !== undefined) this.cause = init.cause\n if (init.fix !== undefined) this.fix = init.fix\n if (init.docsUrl !== undefined) this.docsUrl = init.docsUrl\n if (init.context !== undefined) this.context = init.context\n }\n}\n\n// ── ANSI colour helpers ────────────────────────────────────────────────\n\n/**\n * `true` when the runtime can render ANSI escape codes on stderr.\n * Honors `NO_COLOR` (https://no-color.org) and `FORCE_COLOR`.\n */\nfunction ansiEnabled(): boolean {\n if (process.env.NO_COLOR) return false\n if (process.env.FORCE_COLOR) return true\n // Node sets `isTTY` on the writable streams when stdout/stderr is a\n // terminal. KickError output goes to stderr conceptually (it's an\n // error), so we follow that stream's affordances.\n return process.stderr?.isTTY === true\n}\n\n// Use the Unicode-escape form (`\u001b`) rather than the hex-escape (`\\x1b`)\n// for the ANSI ESC byte — both produce the same character, but oxlint's\n// `no-control-character` rule flags the hex form as \"unexpected control\n// character in string literal\". Functionally identical, lint-clean.\nconst ESC = '\u001b['\nconst RESET = `${ESC}0m`\n\nfunction paint(text: string, code: string, enabled: boolean): string {\n return enabled ? `${ESC}${code}m${text}${RESET}` : text\n}\n\nconst c = {\n red: (s: string, enabled: boolean) => paint(s, '31', enabled),\n bold: (s: string, enabled: boolean) => paint(s, '1', enabled),\n dim: (s: string, enabled: boolean) => paint(s, '2', enabled),\n cyan: (s: string, enabled: boolean) => paint(s, '36', enabled),\n blue: (s: string, enabled: boolean) => paint(s, '34', enabled),\n underline: (s: string, enabled: boolean) => paint(s, '4', enabled),\n}\n\n// ── Formatter ─────────────────────────────────────────────────────────\n\nexport interface FormatKickErrorOptions {\n /**\n * Render ANSI colour codes. Defaults to TTY detection (`NO_COLOR` /\n * `FORCE_COLOR` env vars honoured). Pass `false` for log files,\n * structured loggers, or HTTP response bodies.\n */\n color?: boolean\n}\n\n/**\n * Render a {@link KickErrorInit} (or {@link KickError} instance) into a\n * multi-line human-readable string. Identical layout regardless of\n * colour — the colour flag only toggles ANSI codes.\n */\nexport function formatKickError(\n err: KickErrorInit | KickError,\n options: FormatKickErrorOptions = {},\n): string {\n const color = options.color ?? ansiEnabled()\n const lines: string[] = []\n\n // Headline: `KICK001: No provider for UserService`\n const code = c.bold(c.red(err.code, color), color)\n lines.push(`${code}: ${err.summary}`)\n\n if (err.cause) {\n lines.push('', ` ${c.bold(c.dim('Cause:', color), color)}`)\n for (const line of err.cause.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.fix) {\n lines.push('', ` ${c.bold(c.dim('Fix:', color), color)}`)\n for (const line of err.fix.split('\\n')) {\n lines.push(` ${highlightInline(line, color)}`)\n }\n }\n\n if (err.docsUrl) {\n lines.push('', ` ${c.bold(c.dim('Docs:', color), color)}`)\n lines.push(` ${c.underline(c.blue(err.docsUrl, color), color)}`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Highlight inline `code spans` in a line of cause/fix text. Anything\n * between backticks gets cyan when colour is enabled; backticks stay\n * in the output so the plain-text rendering still distinguishes\n * literals.\n */\nfunction highlightInline(line: string, color: boolean): string {\n if (!color) return line\n return line.replace(/`([^`]+)`/g, (_, inner: string) => `\\`${c.cyan(inner, true)}\\``)\n}\n","/**\n * Catalog of framework errors with structured cause + fix hints.\n *\n * Each factory function returns a {@link KickError} with a stable\n * `code` (KICK001, KICK002, …), a human-readable summary, an\n * explanation of the likely cause, an actionable fix, and a docs URL.\n *\n * Adding a new error: pick the next free code, add the factory below,\n * call it from the framework site that used to throw a bare `Error`.\n * Keep the public function name descriptive — `noProviderError`,\n * `envValueMissingError` — so call sites grep cleanly.\n *\n * Codes are stable. Reordering or renumbering breaks adopters whose\n * tools key on `err.code`.\n */\nimport { KickError } from './kick-error'\n\nconst DOCS_BASE = 'https://kickjs.app'\n\n// ── DI container ──────────────────────────────────────────────────────\n\n/**\n * KICK001 — No provider registered for a token.\n *\n * Thrown by `Container.resolve()` when no binding can be found for the\n * requested token. The most common cause is forgetting to add the\n * enclosing module to `bootstrap({ modules })`.\n */\nexport function noProviderError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK001',\n summary: `No provider for ${tokenName}`,\n cause: `\\`${tokenName}\\` was requested from the DI container but no binding is registered.\nThis usually means one of:\n • The class is decorated with @Service() / @Repository() / @Controller(),\n but its enclosing module isn't passed to bootstrap({ modules: [...] }).\n • The class isn't decorated at all (decorators register the binding).\n • You're injecting a token (created with createToken()) that nothing\n provides — add a Container.register(TOKEN, ...) call or a module that\n binds it.`,\n fix: `If \\`${tokenName}\\` lives in a module, add the module to bootstrap:\n\n bootstrap({\n modules: [\n UsersModule, // add this\n OtherModule,\n ],\n })\n\nIf it's a custom token, register it explicitly:\n\n const TENANT_REPO = createToken<TenantRepo>('TENANT_REPO')\n Container.getInstance().register(TENANT_REPO, { useClass: PrismaTenantRepo })`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#registering-services`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK002 — REQUEST-scoped binding resolved without a request store\n * provider configured.\n */\nexport function requestScopeMiddlewareMissingError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK002',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" — request scope middleware not mounted`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` but no AsyncLocalStorage frame is wired.\nThe framework's request-scope middleware sets up that frame; without it,\nREQUEST-scoped resolutions have nowhere to cache the per-request instance.`,\n fix: `Add \\`requestScopeMiddleware()\\` to your global middleware pipeline,\nor let the framework auto-mount it by using \\`contextStore: 'auto'\\`\n(the default — only check this if you set it to 'manual' explicitly):\n\n bootstrap({\n modules,\n // contextStore: 'auto', // default; no need to set\n })\n\nIf you set \\`contextStore: 'manual'\\` deliberately (rare), you own the\nALS wrapping — call \\`requestStore.run({...}, () => ...)\\` around the\ncode that resolves the binding.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n/**\n * KICK003 — REQUEST-scoped binding resolved outside any HTTP request\n * context (no ALS store active).\n */\nexport function requestScopeOutsideRequestError(tokenName: string): KickError {\n return new KickError({\n code: 'KICK003',\n summary: `Cannot resolve REQUEST-scoped \"${tokenName}\" outside an HTTP request`,\n cause: `\\`${tokenName}\\` has \\`scope: Scope.REQUEST\\` and was resolved outside\nany HTTP request context. The request-scope middleware is wired, but\nthe call happened before a request started (e.g. in a top-level\nimport, a constructor, a cron job, a test without setup).`,\n fix: `If the caller is request-scoped (controller, request-scoped service),\nmake sure the call happens inside the request lifecycle, not at module\nload time.\n\nIn tests, wrap setup in an ALS frame manually:\n\n import { requestStore } from '@forinda/kickjs'\n\n requestStore.run(\n { requestId: 'test', instances: new Map(), values: new Map() },\n () => {\n const svc = container.resolve(MyRequestScopedService)\n // ...\n },\n )\n\nIf the caller is a SINGLETON, change \\`${tokenName}\\` to TRANSIENT or\nSINGLETON scope, or inject a factory instead of the instance directly.`,\n docsUrl: `${DOCS_BASE}/guide/dependency-injection#scopes`,\n context: { token: tokenName },\n })\n}\n\n// ── Config / env wiring ───────────────────────────────────────────────\n\n/**\n * KICK004 — `@Value('X')` resolved but the env var isn't set and no\n * default was provided.\n *\n * This is the canonical \"you forgot to wire env\" symptom — surfaces\n * when ConfigService can't find the key. The fix usually involves\n * either setting the env var or wiring `loadEnv(envSchema)` at startup.\n */\nexport function envValueMissingError(envKey: string): KickError {\n return new KickError({\n code: 'KICK004',\n summary: `@Value('${envKey}'): environment variable not set and no default provided`,\n cause: `\\`@Value('${envKey}')\\` resolved but the env var isn't present and the\ndecorator didn't declare a default. Possible reasons:\n • The variable is genuinely missing from your environment / .env file.\n • Your \\`src/env.ts\\` calls \\`loadEnv(envSchema)\\` but \\`src/index.ts\\`\n doesn't import \\`./env\\` before \\`bootstrap()\\`. Without that import\n the schema never runs and ConfigService stays empty — \\`@Value\\`\n only finds the var via the \\`process.env\\` fallback.`,\n fix: `Either set the variable in your shell / .env file:\n\n ${envKey}=...\n\nOr add a default at the decorator:\n\n @Value('${envKey}', { default: 'fallback' })\n private value!: string\n\nIf you're using a schema, make sure \\`src/env.ts\\` is imported from\n\\`src/index.ts\\` BEFORE bootstrap runs:\n\n // src/index.ts\n import 'reflect-metadata'\n import './env' // ← this line is required\n import { bootstrap } from '@forinda/kickjs'\n // ...`,\n docsUrl: `${DOCS_BASE}/guide/configuration#wiring-the-schema-at-startup`,\n context: { envKey },\n })\n}\n\n// ── Module setup ──────────────────────────────────────────────────────\n\n/**\n * KICK005 — A module's `routes()` return value declares a mount path\n * but provides neither a `controller` class nor a pre-built `router`.\n */\nexport function moduleRouteMissingControllerError(mountPath: string): KickError {\n return new KickError({\n code: 'KICK005',\n summary: `Module route at ${mountPath} requires either 'controller' or 'router'`,\n cause: `A module's \\`routes()\\` returned an entry for \\`${mountPath}\\` but didn't\nspecify how to build the router. The framework needs either a decorated\ncontroller class (it calls \\`buildRoutes()\\` internally) or a hand-built\nExpress Router.`,\n fix: `Pass \\`controller:\\` for the common case:\n\n routes() {\n return {\n path: '${mountPath}',\n controller: UsersController,\n }\n }\n\nOr pass \\`router:\\` for full control:\n\n routes() {\n return {\n path: '${mountPath}',\n router: myExpressRouter,\n }\n }`,\n docsUrl: `${DOCS_BASE}/guide/modules`,\n context: { mountPath },\n })\n}\n\n/**\n * KICK006 — Two handlers claim the same HTTP verb + mounted path. The\n * engine would silently dispatch one of them (Express/h3: first wins,\n * Fastify: its own throw), while typegen and the typed client may\n * describe the other — so this fails at boot instead.\n */\nexport function duplicateRouteError(\n method: string,\n path: string,\n owner: string,\n prior: string,\n): KickError {\n return new KickError({\n code: 'KICK006',\n summary: `Duplicate route: ${method} ${path} is registered twice`,\n cause: `\\`${owner}\\` registers \\`${method} ${path}\\`, but that verb + path is\nalready claimed by \\`${prior}\\`. Only one handler can win at dispatch\ntime, and which one depends on the engine — while \\`kick typegen\\` and\nthe typed client may describe the other. Param names are ignored when\ncomparing (\\`/:id\\` and \\`/:taskId\\` are the same route).`,\n fix: `Change one of the two paths, remove the duplicate handler, or mount\nthe second controller under a different module path / version:\n\n routes() {\n return { path: '/tasks', controller: TasksController, version: 2 }\n }`,\n docsUrl: `${DOCS_BASE}/guide/modules`,\n context: { method, path, owner, prior },\n })\n}\n\n// ── Server ────────────────────────────────────────────────────────────\n\n/**\n * KICK007 — `server.http2` on a runtime that cannot serve HTTP/2. Express 5\n * does not run on Node's HTTP/2 compatibility layer (requests hang and it\n * throws internally), so this fails at boot rather than on the first request.\n */\nexport function http2UnsupportedRuntimeError(runtime: string): KickError {\n return new KickError({\n code: 'KICK007',\n summary: `The ${runtime} runtime cannot serve HTTP/2`,\n cause: `\\`bootstrap({ server: { http2: true } })\\` asks for an HTTP/2 server, but the\n\\`${runtime}\\` runtime does not support Node's HTTP/2 compatibility layer —\nrequests would hang instead of being answered.`,\n fix: `Pick one:\n • Switch to a runtime that serves HTTP/2:\n bootstrap({ runtime: fastifyRuntime(), server: { tls, http2: true } })\n (or h3Runtime()).\n • Drop \\`http2\\` and keep HTTPS over HTTP/1.1: server: { tls }.\n • Terminate HTTP/2 at your proxy / load balancer and serve HTTP/1.1 to it.`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n context: { runtime },\n })\n}\n\n/**\n * KICK008 — `server.http2` without `server.tls`. Browsers only speak HTTP/2\n * over TLS; cleartext HTTP/2 (h2c) is not offered.\n */\nexport function http2RequiresTlsError(): KickError {\n return new KickError({\n code: 'KICK008',\n summary: 'server.http2 requires server.tls',\n cause: `\\`bootstrap({ server: { http2: true } })\\` was set without \\`tls\\`. Browsers only\nnegotiate HTTP/2 over TLS, and KickJS does not serve cleartext HTTP/2 (h2c).`,\n fix: `Pass the certificate and key alongside http2:\n\n server: {\n tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },\n http2: true,\n }\n\nOr remove \\`http2\\` if TLS is terminated in front of the app.`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n })\n}\n\n/**\n * KICK009 — `server.tls` cannot identify the server. Node starts an HTTPS /\n * HTTP/2 server without a certificate (or with a key that does not match it)\n * and then fails every TLS handshake, so this is checked at boot instead.\n */\nexport function tlsCredentialsError(problem: string): KickError {\n return new KickError({\n code: 'KICK009',\n summary: `server.tls is not usable: ${problem}`,\n cause: `\\`bootstrap({ server: { tls } })\\` was given TLS options the server cannot use:\n${problem}. Node would start anyway and fail every TLS handshake.`,\n fix: `Give the server an identity:\n • \\`key\\` and \\`cert\\` together (PEM, matching each other), or\n • \\`pfx\\` (with \\`passphrase\\` if it is encrypted), or\n • \\`SNICallback\\` / \\`pskCallback\\` for per-host or PSK setups.\n\n server: { tls: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') } }`,\n docsUrl: `${DOCS_BASE}/guide/http-runtimes#https-and-http-2`,\n context: { problem },\n })\n}\n","/**\n * Collision-safe injection tokens with phantom type parameters.\n *\n * `createToken<T>(name)` is the recommended way to declare any DI token\n * that isn't a class. Each call returns a unique frozen object — even if\n * two files write `createToken<X>('Logger')`, they produce two distinct\n * references that the container treats as different tokens. Collisions\n * become impossible by construction, not by convention.\n *\n * The phantom type parameter `T` lets `container.resolve(token)` infer\n * the correct return type without any codegen.\n *\n * ## Hardening layers\n *\n * KickJS has four DI token kinds, listed from safest to riskiest:\n *\n * 1. **Class identity** — `container.resolve(UserService)`. Reference\n * equality, never collides. Type-safe via `Constructor<T>` overload.\n * 2. **`createToken<T>(name)`** — frozen object identity, never collides.\n * Type-safe via the `InjectionToken<T>` overload.\n * 3. **Symbol** — `Symbol('foo')` is unique per call but `Symbol.for('foo')`\n * is interned and CAN collide. Discouraged.\n * 4. **Raw string `@Inject('foo')`** — high collision risk, untyped.\n * Reserved for legacy code and runtime-computed token names.\n *\n * Prefer (1) and (2). Use (4) only when you have to.\n *\n * @example\n * ```ts\n * // src/tokens.ts\n * export const DATABASE_URL = createToken<string>('config.database.url')\n * export const FEATURE_FLAGS = createToken<FeatureFlags>('app.features')\n *\n * // bootstrap\n * container.registerInstance(DATABASE_URL, process.env.DATABASE_URL!)\n *\n * // anywhere else\n * const url = container.resolve(DATABASE_URL) // typed as `string`\n * ```\n *\n * @module @forinda/kickjs/core/token\n */\n\n/**\n * Symbol marker stamped on every InjectionToken so the container can\n * detect them at runtime regardless of which package created them.\n *\n * Uses `Symbol.for()` so cross-package isolation (different copies of\n * `@forinda/kickjs` loaded under different module identities) still\n * shares the same marker.\n */\nexport const INJECTION_TOKEN: unique symbol = Symbol.for('@kickjs/InjectionToken') as never\n\n/**\n * A type-safe DI token. Each `createToken()` call returns a unique frozen\n * object identified by reference, not by the `name` string. Two\n * `createToken<X>('foo')` calls in different files produce two distinct\n * tokens that the container treats independently.\n *\n * The phantom `_type` parameter is never assigned at runtime — it exists\n * solely to thread the type through `container.resolve(token)`.\n */\nexport interface InjectionToken<T> {\n readonly [INJECTION_TOKEN]: true\n /** Descriptive name used in error messages, logs, and devtools */\n readonly name: string\n /** Phantom — never assigned at runtime */\n readonly _type?: T\n}\n\n/**\n * Create a collision-safe injection token.\n *\n * The returned token is a frozen object identified by reference, not by\n * the `name` string. The `name` is used only for error messages, logs,\n * and debugging.\n */\nexport function createToken<T>(name: string): InjectionToken<T> {\n return Object.freeze({\n [INJECTION_TOKEN]: true as const,\n name,\n })\n}\n\n/** Type guard — returns `true` if `value` is an InjectionToken */\nexport function isInjectionToken(value: unknown): value is InjectionToken<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[INJECTION_TOKEN] === true\n )\n}\n","import 'reflect-metadata'\nimport {\n Constructor,\n Scope,\n METADATA,\n type ClassKind,\n type PostConstructStatus,\n} from './interfaces'\n// Slot indirection, NOT `./assets` — that module's eager node:fs import\n// would poison the edge-safe web entry graph (see asset-resolver-slot.ts).\nimport { resolveAssetViaSlot as resolveAsset } from './asset-resolver-slot'\nimport {\n envValueMissingError,\n noProviderError,\n requestScopeMiddlewareMissingError,\n requestScopeOutsideRequestError,\n} from './kick-errors'\nimport { createLogger } from './logger'\nimport {\n getClassMeta,\n getClassMetaOrUndefined,\n getMetaMap,\n getMetaRecord,\n getMethodMetaOrUndefined,\n} from './metadata'\nimport { isInjectionToken, type InjectionToken } from './token'\n\nconst log = createLogger('Container')\n\n/** Internal registration entry that tracks how a dependency should be resolved */\ninterface Registration {\n target: Constructor\n scope: Scope\n instance?: any\n factory?: () => any\n // Observability fields\n kind: ClassKind\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n /** Lazily built instantiation plan — see {@link Container.buildPlan}. */\n plan?: InstantiationPlan\n}\n\n/**\n * Precomputed instantiation recipe for a class. Class metadata is immutable\n * after definition, so the Reflect reads (5 per create) and the derived\n * structures only need computing ONCE — previously they re-ran on every\n * REQUEST-scoped resolve (i.e. every request) and every TRANSIENT resolve.\n */\ninterface InstantiationPlan {\n /** Ordered, validated constructor parameter tokens. */\n ctorTokens: any[]\n /** `@Autowired` properties: [property, resolved token]. */\n autowired: Array<[string, any]>\n /** `@Value` properties: [property, env config]. */\n values: Array<[string, { envKey: string; defaultValue?: any }]>\n /** `@Asset` properties: [property, pre-split namespace/key]. */\n assets: Array<[string, { namespace: string; key: string }]>\n /** `@PostConstruct` method name, if any. */\n postConstruct?: string | symbol\n}\n\n/** Create a Registration with observability defaults */\nfunction createReg(\n fields: Partial<Registration> & Pick<Registration, 'target' | 'scope'>,\n): Registration {\n return {\n kind: 'unknown',\n resolveCount: 0,\n postConstructStatus: 'skipped',\n dependencies: [],\n ...fields,\n }\n}\n\n/** Format a token for display in error messages */\nexport function tokenName(token: any): string {\n if (typeof token === 'symbol') return token.toString()\n if (isInjectionToken(token)) return token.name\n return token?.name || String(token)\n}\n\n// ── Persistent Store ────────────────────────────────────────────────────\n// globalThis-based storage that survives BOTH Container.reset() AND Vite's\n// ssrLoadModule() re-evaluation. When Vite re-evaluates a module, the\n// Container class gets a new identity (new statics). But globalThis persists.\n//\n// This is how DB connections, Redis clients, WsAdapter instances, and any\n// other stateful bindings survive HMR without being dropped.\n//\n// Pattern from: React Router (globalThis for state), Vinxi (globalThis.app)\n\n// ── Change Event System ─────────────────────────────────────────────────\n// Batched (50ms debounce) so `kick g module` creating 10+ files emits ONE\n// notification, not 10. Any adapter can subscribe: Swagger, DevTools, WS, etc.\n\n/** A single container change event */\nexport interface ContainerChangeEvent {\n token: string\n event: 'registered' | 'resolved' | 'invalidated'\n kind?: ClassKind\n timestamp: number\n}\n\n/** Listener that receives batched change events */\nexport type ContainerChangeListener = (changes: ContainerChangeEvent[]) => void\n\n/**\n * Type-safe registry of string DI tokens. Augmented by `kick typegen` —\n * the generated `.kickjs/types/registry.d.ts` adds entries like:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface KickJsRegistry {\n * UserService: import('../../src/modules/users/user.service').UserService\n * }\n * }\n * ```\n *\n * Once typegen has run, `container.resolve('UserService')` returns the\n * correct type instead of `any`. Class-token resolution\n * (`container.resolve(UserService)`) is already type-safe via the\n * `Constructor<T>` overload and does not need typegen.\n */\nexport interface KickJsRegistry {}\n\ninterface PersistentStore {\n factories: Array<{ token: any; factory: () => any; scope: Scope }>\n instances: Array<{ token: any; instance: any }>\n /** Resolved instances keyed by string name — survives class identity changes */\n resolvedInstances: Map<string, any>\n}\n\nfunction getPersistentStore(): PersistentStore {\n if (!(globalThis as any).__kickjs_persistent) {\n ;(globalThis as any).__kickjs_persistent = {\n factories: [],\n instances: [],\n resolvedInstances: new Map(),\n } satisfies PersistentStore\n }\n return (globalThis as any).__kickjs_persistent\n}\n\n/**\n * Inversion-of-Control (IoC) container that manages dependency registration,\n * resolution, and lifecycle. Implements the Singleton pattern so all parts of\n * the application share a single container instance.\n *\n * Supports constructor injection, property injection (@Autowired),\n * factory registrations, and lifecycle hooks (@PostConstruct).\n *\n * Persistent state (DB connections, Redis, etc.) is stored on globalThis so\n * it survives both Container.reset() and Vite module re-evaluation.\n */\nexport class Container {\n private static instance: Container\n private registrations = new Map<any, Registration>()\n private resolving = new Set<any>()\n\n // ── Reactive change tracking ──────────────────────────────────────\n private changeListeners = new Set<ContainerChangeListener>()\n private pendingChanges: ContainerChangeEvent[] = []\n private notifyTimer: ReturnType<typeof setTimeout> | null = null\n private static readonly DEBOUNCE_MS = 50\n\n /** Callback set by the decorators module to flush pending registrations */\n static _onReady: ((container: Container) => void) | null = null\n /** Callback invoked on reset so decorators can update their container reference */\n static _onReset: ((container: Container) => void) | null = null\n\n /**\n * Seeds a NEWLY CREATED container with the decorator registrations, without\n * adopting it as the global one. `_onReset` does both; an isolated container\n * must only have the first half, or it would capture every subsequent\n * decorator registration in the process.\n */\n static _onCreate: ((container: Container) => void) | null = null\n /**\n * Environment resolver for @Value decorator. Set by the unified\n * `@forinda/kickjs` config layer to return Zod-validated, type-coerced\n * env values instead of raw process.env strings.\n */\n static _envResolver: ((key: string) => any) | null = null\n /**\n * Request store provider for REQUEST-scoped DI. Set by the HTTP layer\n * inside `@forinda/kickjs` to return the current request's\n * AsyncLocalStorage store. Returns { instances: Map, values: Map } or\n * null if outside a request.\n */\n /**\n * Whether this container is the global singleton.\n *\n * The persistent store exists to carry the GLOBAL container's registrations\n * across HMR and module re-evaluation. An isolated container is per-test and\n * must neither write to it — a `registerInstance` override was replayed into\n * every later `Container.reset()` — nor read from it, which would pull global\n * state into a container created to be free of it.\n */\n private isGlobal = false\n\n static _requestStoreProvider:\n | (() => { instances: Map<any, any>; values: Map<any, any> } | null)\n | null = null\n\n static getInstance(): Container {\n if (!Container.instance) {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n }\n // Flush any decorator registrations that queued before the container existed\n if (Container._onReady) {\n Container._onReady(Container.instance)\n Container._onReady = null\n }\n return Container.instance\n }\n\n /**\n * Resets the container by replacing the singleton with a fresh instance.\n * Persistent registrations (DB, Redis, etc.) are replayed from globalThis\n * so they survive both reset() and Vite module re-evaluation.\n */\n static reset(): void {\n Container.instance = new Container()\n Container.instance.isGlobal = true\n // Notify decorators so they update their container reference\n Container._onReset?.(Container.instance)\n // Replay persistent registrations from globalThis store\n const store = getPersistentStore()\n for (const { token, factory, scope } of store.factories) {\n if (!Container.instance.has(token)) {\n // Check if we have a previously resolved instance for this token\n const name = tokenName(token)\n const existing = store.resolvedInstances.get(name)\n Container.instance.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existing, // Reuse resolved instance if available\n resolveCount: existing ? 1 : 0,\n postConstructStatus: existing ? 'completed' : 'skipped',\n }),\n )\n }\n }\n for (const { token, instance } of store.instances) {\n if (!Container.instance.has(token)) {\n Container.instance.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n }),\n )\n }\n }\n }\n\n /**\n * Create an isolated container instance (not the global singleton).\n * Useful for concurrent tests that must not share DI state.\n *\n * @example\n * ```ts\n * const container = Container.create()\n * container.register(UserService, UserService)\n * const svc = container.resolve(UserService)\n * ```\n */\n static create(): Container {\n const container = new Container()\n // Decorators register at class-definition time, into whichever container\n // was global then. Without this replay an isolated container is missing\n // every @Service / @Repository / @Controller in the process — and\n // `isolated: true` is what the generated docs recommend for test\n // isolation, so the failure reached adopters as `No provider for X`.\n Container._onCreate?.(container)\n return container\n }\n\n /** Register a class constructor under the given token */\n register(token: any, target: Constructor, scope: Scope = Scope.SINGLETON): void {\n const kind = getClassMeta<ClassKind>(METADATA.CLASS_KIND, target, 'unknown')\n const dependencies = this.extractDependencies(target)\n const reg = createReg({ target, scope, kind, dependencies })\n this.registrations.set(token, reg)\n // Store a name-based fallback so HMR class re-creation (new identity)\n // can still resolve by the original class name.\n if (typeof token === 'function' && token.name) {\n this.registrations.set(`__hmr__${token.name}`, reg)\n }\n this.emit(token, 'registered', kind)\n }\n\n /** Register a factory function under the given token */\n registerFactory(token: any, factory: () => any, scope: Scope = Scope.SINGLETON): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n // Check if we already have a resolved instance from a previous HMR cycle.\n // Only reuse if the factory hasn't changed (same reference). When a new\n // factory is provided (e.g. swapping implementations), clear the stale\n // instance so the new factory runs on next resolve.\n // Reads are global-only too. An isolated container adopting a cached\n // instance from the global store would be contaminated on creation — the\n // same leak, in the other direction.\n const existingEntry = this.isGlobal\n ? store.factories.find((r) => tokenName(r.token) === name)\n : undefined\n const factoryUnchanged = existingEntry?.factory === factory\n const existingInstance =\n this.isGlobal && factoryUnchanged ? store.resolvedInstances.get(name) : undefined\n\n // Clear stale resolved instance when factory changes\n if (this.isGlobal && !factoryUnchanged) {\n store.resolvedInstances.delete(name)\n }\n\n this.registrations.set(\n token,\n createReg({\n target: Object as any,\n scope,\n factory,\n kind: 'factory',\n instance: existingInstance,\n resolveCount: existingInstance ? 1 : 0,\n postConstructStatus: existingInstance ? 'completed' : 'skipped',\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only, or an isolated container's factories are replayed\n // into the next Container.reset().\n if (this.isGlobal) {\n const idx = store.factories.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.factories[idx] = { token, factory, scope }\n } else {\n store.factories.push({ token, factory, scope })\n }\n }\n this.emit(name, 'registered', 'factory')\n }\n\n /** Register a pre-constructed singleton instance */\n registerInstance(token: any, instance: any): void {\n const store = getPersistentStore()\n const name = tokenName(token)\n\n this.registrations.set(\n token,\n createReg({\n target: instance.constructor,\n scope: Scope.SINGLETON,\n instance,\n kind: 'instance',\n postConstructStatus: 'completed',\n resolveCount: 1,\n firstResolvedAt: Date.now(),\n lastResolvedAt: Date.now(),\n }),\n )\n // Track in globalThis for persistence across HMR + module re-evaluation —\n // global container only. An isolated one that wrote here had its overrides\n // replayed into the next Container.reset().\n if (this.isGlobal) {\n store.resolvedInstances.set(name, instance)\n const idx = store.instances.findIndex((r) => tokenName(r.token) === name)\n if (idx >= 0) {\n store.instances[idx] = { token, instance }\n } else {\n store.instances.push({ token, instance })\n }\n }\n this.emit(name, 'registered', 'instance')\n }\n\n /** Check if a binding exists for the given token */\n has(token: any): boolean {\n return this.registrations.has(token)\n }\n\n /** Return a snapshot of all registered tokens with their scope and instantiation status */\n getRegistrations(): Array<{\n token: string\n scope: string\n kind: ClassKind\n instantiated: boolean\n resolveCount: number\n lastResolvedAt?: number\n firstResolvedAt?: number\n resolveDurationMs?: number\n postConstructStatus: PostConstructStatus\n dependencies: string[]\n }> {\n const entries: Array<ReturnType<Container['getRegistrations']>[number]> = []\n for (const [token, reg] of this.registrations) {\n entries.push({\n token: tokenName(token),\n scope: reg.scope,\n kind: reg.kind,\n // Singletons check cached instance; transients check resolveCount\n instantiated:\n reg.scope === Scope.SINGLETON ? reg.instance !== undefined : reg.resolveCount > 0,\n resolveCount: reg.resolveCount,\n lastResolvedAt: reg.lastResolvedAt,\n firstResolvedAt: reg.firstResolvedAt,\n resolveDurationMs: reg.resolveDurationMs,\n postConstructStatus: reg.postConstructStatus,\n dependencies: reg.dependencies,\n })\n }\n return entries\n }\n\n /**\n * Resolve a dependency by its token.\n *\n * Four overloads, in order of preference:\n * - **`InjectionToken<T>`** (from `createToken<T>(name)`) → returns `T`.\n * Collision-safe by construction; no codegen required.\n * - **Class constructor** → returns an instance of that class. Always\n * type-safe; no codegen required.\n * - **String token in `KickJsRegistry`** (populated by `kick typegen`) →\n * returns the augmented type.\n * - **Anything else** → returns `any`. Use this only for legacy code or\n * string tokens that haven't been registered with typegen yet.\n */\n resolve<T>(token: InjectionToken<T>): T\n resolve<T>(token: Constructor<T>): T\n resolve<K extends keyof KickJsRegistry & string>(token: K): KickJsRegistry[K]\n resolve<T = any>(token: any): T\n resolve<T = any>(token: any): T {\n let reg = this.registrations.get(token)\n // HMR fallback: when Vite re-evaluates a module, decorated classes get new\n // identity. Try resolving by class name if the primary token lookup fails.\n if (!reg && typeof token === 'function' && token.name) {\n reg = this.registrations.get(`__hmr__${token.name}`)\n }\n if (!reg) {\n throw noProviderError(tokenName(token))\n }\n\n if (reg.scope === Scope.SINGLETON && reg.instance !== undefined) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return reg.instance\n }\n\n // REQUEST scope: one instance per HTTP request, cached in AsyncLocalStorage\n if (reg.scope === Scope.REQUEST) {\n if (!Container._requestStoreProvider) {\n throw requestScopeMiddlewareMissingError(tokenName(token))\n }\n const store = Container._requestStoreProvider()\n if (!store) {\n throw requestScopeOutsideRequestError(tokenName(token))\n }\n // Check for pre-registered request values (user, tenant, etc.)\n if (store.values.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.values.get(token) as T\n }\n // Check per-request instance cache\n if (store.instances.has(token)) {\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n this.emit(token, 'resolved', reg.kind)\n return store.instances.get(token) as T\n }\n // Factory-registered binding: invoke the factory once per request\n // and cache the result alongside class-instantiated REQUEST tokens.\n // Without this branch, registerFactory(token, fn, Scope.REQUEST)\n // silently falls through to createInstance(reg) which `new`s the\n // factory's placeholder `target: Object` and returns an empty {}\n // — the factory function never runs.\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n // Create instance and cache for this request only\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n store.instances.set(token, instance)\n this.emit(token, 'resolved', reg.kind)\n return instance as T\n }\n\n if (reg.factory) {\n const start = performance.now()\n const instance = reg.factory()\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n // Persist resolved factory instances so they survive module\n // re-evaluation — global container only, for the same reason.\n if (this.isGlobal) {\n getPersistentStore().resolvedInstances.set(tokenName(token), instance)\n }\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n }\n\n if (this.resolving.has(token)) {\n const chain = [...this.resolving].map(tokenName)\n chain.push(tokenName(token))\n throw new Error(`Circular dependency detected: ${chain.join(' -> ')}`)\n }\n this.resolving.add(token)\n\n try {\n const start = performance.now()\n const instance = this.createInstance(reg)\n reg.resolveDurationMs = performance.now() - start\n reg.resolveCount++\n reg.lastResolvedAt = Date.now()\n if (!reg.firstResolvedAt) reg.firstResolvedAt = Date.now()\n if (reg.scope === Scope.SINGLETON) {\n reg.instance = instance\n }\n this.emit(token, 'resolved', reg.kind)\n return instance\n } finally {\n this.resolving.delete(token)\n }\n }\n\n /** Lifecycle hook called during Application.setup() after module registration */\n bootstrap(): void {\n // Reserved for future use — adapters and modules register via\n // container.register(), registerFactory(), and registerInstance().\n }\n\n // ── Reactive Change API ─────────────────────────────────────────────\n\n /**\n * Subscribe to container changes. Returns an unsubscribe function.\n *\n * Changes are batched (50ms debounce) so bulk operations like\n * `kick g module` creating 10+ files emit ONE batch, not 10 events.\n *\n * Any adapter can subscribe:\n * - SwaggerAdapter: re-build spec when controllers change\n * - DevToolsAdapter: push SSE events to dashboard\n * - WsAdapter: re-discover namespaces when controllers change\n * - OtelAdapter: emit tracing spans for DI resolution\n * - Custom user adapters\n *\n * @example\n * ```ts\n * const unsub = container.onChange((changes) => {\n * for (const c of changes) {\n * console.log(`${c.event}: ${c.token} (${c.kind})`)\n * }\n * })\n * // later:\n * unsub()\n * ```\n */\n onChange(callback: ContainerChangeListener): () => void {\n this.changeListeners.add(callback)\n return () => this.changeListeners.delete(callback)\n }\n\n /**\n * Invalidate a specific registration. Clears the cached instance so the\n * next resolve() creates a fresh one. Also invalidates dependents\n * (anything that injected this token) by walking the dependency graph.\n *\n * Called by the Vite HMR plugin when a module file changes.\n * Skips persistent registrations (DB connections, etc.).\n */\n invalidate(token: any): void {\n const name = tokenName(token)\n // Try both the token itself and the HMR fallback name\n const reg = this.registrations.get(token) ?? this.registrations.get(`__hmr__${name}`)\n if (!reg) return\n\n // Don't invalidate persistent registrations (DB, Redis, etc.)\n const store = getPersistentStore()\n if (store.resolvedInstances.has(name)) return\n\n // Clear cached instance — next resolve() will re-create\n reg.instance = undefined\n reg.resolveCount = 0\n reg.postConstructStatus = 'skipped'\n this.emit(name, 'invalidated', reg.kind)\n\n // Walk dependency graph — invalidate anything that injected this token\n for (const [depToken, depReg] of this.registrations) {\n if (depReg.dependencies.includes(name)) {\n this.invalidate(depToken)\n }\n }\n }\n\n /**\n * Batch-emit a change event (debounced, flushed after 50ms of quiet).\n * Takes the RAW token — `tokenName()` is only computed after the\n * zero-listener guard, so the hot cached-singleton resolve path never pays\n * the name derivation when nobody is watching (the production default).\n */\n private emit(rawToken: any, event: ContainerChangeEvent['event'], kind?: ClassKind): void {\n // Zero-listener fast path. Nobody subscribes to container changes unless\n // DevTools (or a test) calls `onChange` — the production default. Without\n // this guard, every `resolve()` (including the hot cached-singleton path)\n // would scan `pendingChanges`, push an entry, and reschedule a `setTimeout`\n // only for the eventual flush to iterate an empty listener set and discard\n // the batch. Short-circuiting here keeps `resolve()` allocation- and\n // timer-free when no one is watching. When a listener attaches later,\n // subsequent emits work exactly as before — past resolves had no observer\n // to notify anyway.\n if (this.changeListeners.size === 0) return\n const token = tokenName(rawToken)\n\n // Dedupe within the pending batch: a high-throughput TRANSIENT\n // token that resolves 1000× per request would otherwise queue\n // 1000 identical 'resolved' events. The downstream listener only\n // cares that the token's state changed, so collapse to one entry\n // (the earliest timestamp wins; the latest update is reflected\n // in resolveCount/lastResolvedAt on the registration itself,\n // which the listener reads at notify time).\n const existing = this.pendingChanges.find((p) => p.token === token && p.event === event)\n if (existing) {\n existing.timestamp = Date.now()\n } else {\n this.pendingChanges.push({ token, event, kind, timestamp: Date.now() })\n }\n\n if (this.notifyTimer) clearTimeout(this.notifyTimer)\n this.notifyTimer = setTimeout(() => {\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }, Container.DEBOUNCE_MS)\n // Don't let a pending debounce window hold the event loop open — flush is\n // best-effort telemetry, not critical work.\n this.notifyTimer.unref?.()\n }\n\n /** Flush pending change events immediately (useful in tests) */\n flushChanges(): void {\n if (this.notifyTimer) {\n clearTimeout(this.notifyTimer)\n this.notifyTimer = null\n }\n const batch = this.pendingChanges.splice(0)\n if (batch.length === 0) return\n for (const listener of this.changeListeners) {\n try {\n listener(batch)\n } catch {\n // Don't let one broken listener break others\n }\n }\n }\n\n /**\n * Build the one-time instantiation plan for a registration. Runs on the\n * FIRST createInstance for the class — the metadata it reads is immutable\n * after class definition, so every later create (REQUEST scope: every\n * request) skips all Reflect reads and re-derivation.\n *\n * Scope validation (SINGLETON must not ctor-inject REQUEST) lives here\n * too: it's a static property of the (parent, dep) registration pair, so\n * checking it once at plan build is equivalent to the old per-create loop.\n */\n private buildPlan(reg: Registration): InstantiationPlan {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, reg.target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, reg.target)\n\n // When emitDecoratorMetadata is unavailable (tsx, ts-node without\n // SWC), paramTypes is empty even though @Inject set tokens on\n // specific constructor indices. Derive the constructor arity from\n // the @Inject metadata so constructor injection still works.\n const maxInjectIndex =\n Object.keys(injectTokens).length > 0\n ? Math.max(...Object.keys(injectTokens).map(Number)) + 1\n : 0\n const paramCount = Math.max(paramTypes.length, maxInjectIndex)\n\n const ctorTokens: any[] = []\n for (let index = 0; index < paramCount; index++) {\n const token = injectTokens[index] || paramTypes[index]\n if (!token) {\n throw new Error(\n `Cannot resolve constructor parameter at index ${index} of ${tokenName(reg.target)}. ` +\n `No @Inject token and no design:paramtypes metadata (tsx/ts-node don't emit it). ` +\n `Add @Inject(Token) to every constructor parameter.`,\n )\n }\n // Scope validation: SINGLETON cannot inject REQUEST-scoped dependencies\n if (reg.scope === Scope.SINGLETON) {\n const depReg = this.registrations.get(token)\n if (depReg && depReg.scope === Scope.REQUEST) {\n // The fix depends on the parent: @Controller() takes no options and\n // is always SINGLETON, so \"change the parent's scope\" is advice a\n // controller cannot follow. @Autowired works for every parent — its\n // getter re-resolves REQUEST deps per access (injectProperties).\n const parent = tokenName(reg.target)\n const dep = tokenName(token)\n const kind = getClassMetaOrUndefined<ClassKind>(METADATA.CLASS_KIND, reg.target)\n const fix =\n kind === 'controller'\n ? `@Controller() is always SINGLETON, so inject \"${dep}\" with @Autowired() instead of the constructor.`\n : `Give \"${parent}\" TRANSIENT or REQUEST scope (e.g. @Service({ scope: Scope.REQUEST })), ` +\n `or inject \"${dep}\" with @Autowired() instead of the constructor.`\n throw new Error(\n `Cannot inject REQUEST-scoped \"${dep}\" into SINGLETON \"${parent}\". ` +\n `Singletons outlive requests. ${fix}`,\n )\n }\n }\n ctorTokens.push(token)\n }\n\n // @Autowired properties — resolve each property's token once.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, reg.target.prototype)\n const autowired: InstantiationPlan['autowired'] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, reg.target.prototype, prop)\n if (resolvedToken) autowired.push([prop, resolvedToken])\n }\n\n // @Value properties.\n const valueProps = getMetaMap<string, { envKey: string; defaultValue?: any }>(\n METADATA.VALUE,\n reg.target.prototype,\n )\n const values: InstantiationPlan['values'] = [...valueProps.entries()]\n\n // @Asset properties — pre-split namespace/key (and fail fast on bad keys).\n const assetProps = getMetaMap<string, { assetKey: string }>(\n METADATA.ASSET,\n reg.target.prototype,\n )\n const assets: InstantiationPlan['assets'] = []\n for (const [prop, config] of assetProps) {\n const slashIdx = config.assetKey.indexOf('/')\n if (slashIdx === -1) {\n throw new Error(\n `@Asset('${config.assetKey}'): asset key must include a '/' separator (e.g. 'mails/welcome').`,\n )\n }\n assets.push([\n prop,\n { namespace: config.assetKey.slice(0, slashIdx), key: config.assetKey.slice(slashIdx + 1) },\n ])\n }\n\n const postConstruct = getClassMetaOrUndefined<string | symbol>(\n METADATA.POST_CONSTRUCT,\n reg.target.prototype,\n )\n\n return { ctorTokens, autowired, values, assets, postConstruct }\n }\n\n private createInstance(reg: Registration): any {\n const plan = (reg.plan ??= this.buildPlan(reg))\n\n const args: any[] = []\n for (const token of plan.ctorTokens) {\n args.push(this.resolve(token))\n }\n\n const instance = new reg.target(...args)\n\n // Property injection via @Autowired / @Value / @Asset\n this.injectProperties(instance, plan)\n\n // @PostConstruct lifecycle hook\n const postConstruct = plan.postConstruct\n if (postConstruct && typeof instance[postConstruct] === 'function') {\n try {\n const result = instance[postConstruct]()\n if (result && typeof result.then === 'function') {\n reg.postConstructStatus = 'pending'\n ;(result as Promise<void>)\n .then(() => {\n reg.postConstructStatus = 'completed'\n })\n .catch(() => {\n reg.postConstructStatus = 'failed'\n })\n log.warn(\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() returned a Promise ` +\n `but the container is synchronous. The async operation will not be awaited.`,\n )\n } else {\n reg.postConstructStatus = 'completed'\n }\n } catch (err) {\n reg.postConstructStatus = 'failed'\n log.error(\n err,\n `@PostConstruct on ${tokenName(reg.target)}.${String(postConstruct)}() threw an error`,\n )\n }\n }\n\n return instance\n }\n\n /**\n * Extract every dependency token name a class declares — both\n * constructor params (`@Inject` + emitted `design:paramtypes`) and\n * `@Autowired` properties (read lazily at injection time, but\n * captured here so devtools surfaces a complete dependency graph).\n *\n * Without the property branch, classes that rely entirely on\n * `@Autowired` show `dependencies: []` in the introspection panel\n * even though they have real DI deps — see /_debug topology.\n */\n private extractDependencies(target: Constructor): string[] {\n const paramTypes = getClassMeta<Constructor[]>(METADATA.PARAM_TYPES, target, [])\n const injectTokens = getMetaRecord<any>(METADATA.INJECT, target)\n const ctorDeps = paramTypes.map((type, index) => {\n const token = injectTokens[index] || type\n return tokenName(token)\n })\n\n // @Autowired properties live on `target.prototype`. Token may be\n // explicit (`@Autowired('SOME_TOKEN')`) or fall back to the\n // emitted `design:type` metadata of the property.\n const autowiredProps = getMetaMap<string, any>(METADATA.AUTOWIRED, target.prototype)\n const propDeps: string[] = []\n for (const [prop, token] of autowiredProps) {\n const resolvedToken =\n token || getMethodMetaOrUndefined(METADATA.PROPERTY_TYPE, target.prototype, prop)\n if (resolvedToken) propDeps.push(tokenName(resolvedToken))\n }\n\n // De-dupe — a class can declare the same token in both surfaces.\n return [...new Set([...ctorDeps, ...propDeps])]\n }\n\n private injectProperties(instance: any, plan: InstantiationPlan): void {\n // @Autowired — lazy DI property injection. The getter re-resolves per\n // access by design (a SINGLETON may lazily read a REQUEST-scoped dep at\n // call time). For SINGLETON deps that laziness buys nothing after the\n // first read — the container returns the same cached instance forever —\n // so the getter memoizes itself into a plain data property then. REQUEST\n // and TRANSIENT deps keep the live getter (memoizing would pin the first\n // request's instance across requests).\n for (const [prop, resolvedToken] of plan.autowired) {\n Object.defineProperty(instance, prop, {\n get: () => {\n const value = this.resolve(resolvedToken)\n const depReg = this.registrations.get(resolvedToken)\n if (depReg?.scope === Scope.SINGLETON) {\n Object.defineProperty(instance, prop, {\n value,\n enumerable: true,\n configurable: true,\n writable: false,\n })\n }\n return value\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Value — lazy environment variable injection\n for (const [prop, config] of plan.values) {\n Object.defineProperty(instance, prop, {\n get() {\n // Use the registered env resolver if available (wired by the\n // config layer in @forinda/kickjs). Returns Zod-validated,\n // type-coerced values (e.g. PORT as number).\n if (Container._envResolver) {\n const val = Container._envResolver(config.envKey)\n if (val !== undefined) return val\n }\n\n // Fallback to raw process.env for apps that haven't called loadEnv()\n const val = process.env[config.envKey]\n if (val !== undefined) return val\n if (config.defaultValue !== undefined) return config.defaultValue\n throw envValueMissingError(config.envKey)\n },\n enumerable: true,\n configurable: true,\n })\n }\n\n // @Asset — lazy asset path injection. Same lazy-\n // getter pattern as @Value: resolve on every property access via\n // the asset manager's cached resolver, not at class instantiation.\n // namespace/key were pre-split at plan build.\n for (const [prop, { namespace, key }] of plan.assets) {\n Object.defineProperty(instance, prop, {\n get() {\n return resolveAsset(namespace, key)\n },\n enumerable: true,\n configurable: true,\n })\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v9.0
|
|
2
|
+
* @forinda/kickjs v9.2.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
*/
|
|
11
11
|
var MissingContributorError=class extends Error{key;dependent;route;dependentDefinedAt;constructor(key,dependent,route,dependentDefinedAt){let where=route?` on route ${route}`:``,declaredAt=formatDeclaredAt(dependentDefinedAt);super(`Missing context contributor '${key}' required by '${dependent}'${where}${declaredAt}`),this.name=`MissingContributorError`,this.key=key,this.dependent=dependent,this.route=route,this.dependentDefinedAt=dependentDefinedAt}},MissingContextValueError=class extends Error{key;route;constructor(key,route){let where=route?` on ${route}`:``;super(`No context value for '${key}'${where}\n Nothing wrote this key before the handler ran. Either the contributor\n that produces it isn't applied to this route, or it ran and resolved\n to undefined (check \`optional: true\` contributors).`),this.name=`MissingContextValueError`,this.key=key,this.route=route}};function formatDeclaredAt(stack){if(!stack)return``;let lines=stack.split(`
|
|
12
12
|
`);for(let line of lines){let trimmed=line.trim();if(trimmed.startsWith(`at `)&&!trimmed.includes(`context-decorator.`)&&!trimmed.includes(`context-errors.`))return`\n declared at ${trimmed.replace(/^at /,``)}`}return``}var ContributorCycleError=class extends Error{cycle;route;constructor(cycle,route){let where=route?` on route ${route}`:``;super(`Cycle in context contributors${where}: ${cycle.join(` → `)}`),this.name=`ContributorCycleError`,this.cycle=Object.freeze([...cycle]),this.route=route}},DuplicateContributorError=class extends Error{key;sources;constructor(key,sources){super(`Duplicate context contributor for key '${key}' at the same precedence level. Sources: ${sources.join(`, `)}`),this.name=`DuplicateContributorError`,this.key=key,this.sources=Object.freeze([...sources])}};export{MissingContributorError as i,DuplicateContributorError as n,MissingContextValueError as r,ContributorCycleError as t};
|
|
13
|
-
//# sourceMappingURL=context-errors-
|
|
13
|
+
//# sourceMappingURL=context-errors-DYJjhalQ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-errors-
|
|
1
|
+
{"version":3,"file":"context-errors-DYJjhalQ.mjs","names":[],"sources":["../src/core/context-errors.ts"],"sourcesContent":["/**\n * Errors thrown by the Context Contributor pipeline.\n *\n * `MissingContributorError`, `ContributorCycleError`, and\n * `DuplicateContributorError` are startup-time errors raised during\n * pipeline build (topo-sort + validation), not per-request — a\n * misconfigured app fails to boot rather than fails a request. Per the\n * locked design in `architecture.md` §20.4, the pipeline is validated\n * once when each route is mounted and the resolved order is cached on\n * the route.\n *\n * `MissingContextValueError` is the one request-time error here: it is\n * raised by `ctx.require(key)` when a value a handler declared it needs\n * is absent. See its doc comment for why that case can't be caught at\n * startup today.\n */\n\n/**\n * A contributor declared `dependsOn: ['<key>']` but no other contributor\n * (at any precedence level) produces that key for the same route.\n *\n * @example\n * ```text\n * MissingContributorError: Missing context contributor 'tenant' required by 'project' on route GET /projects/:id\n * declared at src/contributors/load-project.ts:42:18\n * ```\n */\nexport class MissingContributorError extends Error {\n readonly key: string\n readonly dependent: string\n readonly route?: string\n readonly dependentDefinedAt?: string\n\n constructor(key: string, dependent: string, route?: string, dependentDefinedAt?: string) {\n const where = route ? ` on route ${route}` : ''\n const declaredAt = formatDeclaredAt(dependentDefinedAt)\n super(`Missing context contributor '${key}' required by '${dependent}'${where}${declaredAt}`)\n this.name = 'MissingContributorError'\n this.key = key\n this.dependent = dependent\n this.route = route\n this.dependentDefinedAt = dependentDefinedAt\n }\n}\n\n/**\n * `ctx.require(key)` found no value under `key` in the per-request\n * metadata store.\n *\n * Thrown at request time, by design. `ctx.get(key)` returns\n * `T | undefined` for every key, so the usual workaround is a\n * non-null assertion — `ctx.get('tenant')!` — which compiles whether or\n * not the route actually carries the contributor that produces it. On\n * an authorization value that is the worst possible thing to leave\n * untypeable: dropping the decorator produces no diagnostic anywhere,\n * and the handler reads `undefined` as though it were a real value.\n *\n * `ctx.require()` converts that silent hole into a loud, named failure\n * that points at the missing contributor.\n *\n * A handler typed with a generated route type (`Ctx<KickRoutes…>`) also\n * gets compile-time narrowing, so a dropped decorator fails `tsc` rather\n * than reaching this error at all. This error remains the backstop for\n * routes typegen couldn't prove (module/adapter/bootstrap registrations),\n * handlers typed as plain `RequestContext`, and contributors that ran but\n * resolved to `undefined`. See `architecture.md` §20.14.\n *\n * @example\n * ```text\n * MissingContextValueError: No context value for 'tenantPerm' on GET /projects/:id\n * Nothing wrote this key before the handler ran. Either the contributor\n * that produces it isn't applied to this route, or it ran and resolved\n * to undefined (check `optional: true` contributors).\n * ```\n */\nexport class MissingContextValueError extends Error {\n readonly key: string\n readonly route?: string\n\n constructor(key: string, route?: string) {\n const where = route ? ` on ${route}` : ''\n super(\n `No context value for '${key}'${where}\\n` +\n ` Nothing wrote this key before the handler ran. Either the contributor\\n` +\n ` that produces it isn't applied to this route, or it ran and resolved\\n` +\n ` to undefined (check \\`optional: true\\` contributors).`,\n )\n this.name = 'MissingContextValueError'\n this.key = key\n this.route = route\n }\n}\n\n/**\n * Slice the first non-framework frame off a captured `Error.stack`\n * and format it as `\\n declared at <frame>` for inclusion in\n * boot-time error messages. Returns `''` when no useful frame is\n * available (hand-rolled registrations, missing stacks).\n */\nfunction formatDeclaredAt(stack: string | undefined): string {\n if (!stack) return ''\n const lines = stack.split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed.startsWith('at ')) continue\n // Skip frames inside this file or the decorator factory — the\n // adopter's call site is the next frame after those.\n if (trimmed.includes('context-decorator.')) continue\n if (trimmed.includes('context-errors.')) continue\n return `\\n declared at ${trimmed.replace(/^at /, '')}`\n }\n return ''\n}\n\n/**\n * Topo-sort detected a cycle in `dependsOn` declarations. The `cycle` field\n * lists the keys in the order they form the loop, with the first key\n * repeated at the end so the path reads naturally.\n *\n * @example\n * ```text\n * ContributorCycleError: Cycle in context contributors on route GET /projects/:id: tenant → project → tenant\n * ```\n */\nexport class ContributorCycleError extends Error {\n readonly cycle: readonly string[]\n readonly route?: string\n\n constructor(cycle: readonly string[], route?: string) {\n const where = route ? ` on route ${route}` : ''\n super(`Cycle in context contributors${where}: ${cycle.join(' → ')}`)\n this.name = 'ContributorCycleError'\n this.cycle = Object.freeze([...cycle])\n this.route = route\n }\n}\n\n/**\n * Two contributors at the same precedence level both produce the same key\n * for the same route. Precedence resolution (method > class > module >\n * adapter > global, per architecture.md §20.4) silently drops\n * lower-precedence duplicates; this error fires only when ties occur\n * within a single level — e.g., two method-level decorators declaring\n * `key: 'tenant'`.\n *\n * `sources` is best-effort identifying labels for each conflicting\n * registration (typically the decorator's call site or registering\n * module/adapter name).\n *\n * @example\n * ```text\n * DuplicateContributorError: Duplicate context contributor for key 'tenant' at the same precedence level. Sources: LoadTenantFromHeader, LoadTenantFromSubdomain\n * ```\n */\nexport class DuplicateContributorError extends Error {\n readonly key: string\n readonly sources: readonly string[]\n\n constructor(key: string, sources: readonly string[]) {\n super(\n `Duplicate context contributor for key '${key}' at the same precedence level. ` +\n `Sources: ${sources.join(', ')}`,\n )\n this.name = 'DuplicateContributorError'\n this.key = key\n this.sources = Object.freeze([...sources])\n }\n}\n"],"mappings":";;;;;;;;;;AA2BA,IAAa,wBAAb,cAA6C,KAAM,CACjD,IACA,UACA,MACA,mBAEA,YAAY,IAAa,UAAmB,MAAgB,mBAA6B,CACvF,IAAM,MAAQ,MAAQ,aAAa,QAAU,GACvC,WAAa,iBAAiB,kBAAkB,EACtD,MAAM,gCAAgC,IAAI,iBAAiB,UAAU,GAAG,QAAQ,YAAY,EAC5F,KAAK,KAAO,0BACZ,KAAK,IAAM,IACX,KAAK,UAAY,UACjB,KAAK,MAAQ,MACb,KAAK,mBAAqB,kBAC5B,CACF,EAgCa,yBAAb,cAA8C,KAAM,CAClD,IACA,MAEA,YAAY,IAAa,MAAgB,CACvC,IAAM,MAAQ,MAAQ,OAAO,QAAU,GACvC,MACE,yBAAyB,IAAI,GAAG,MAAM,iNAIxC,EACA,KAAK,KAAO,2BACZ,KAAK,IAAM,IACX,KAAK,MAAQ,KACf,CACF,EAQA,SAAS,iBAAiB,MAAmC,CAC3D,GAAI,CAAC,MAAO,MAAO,GACnB,IAAM,MAAQ,MAAM,MAAM;CAAI,EAC9B,IAAK,IAAM,QAAQ,MAAO,CACxB,IAAM,QAAU,KAAK,KAAK,EACrB,WAAQ,WAAW,KAAK,GAGzB,SAAQ,SAAS,oBAAoB,GACrC,SAAQ,SAAS,iBAAiB,EACtC,MAAO,qBAAqB,QAAQ,QAAQ,OAAQ,EAAE,GACxD,CACA,MAAO,EACT,CAYA,IAAa,sBAAb,cAA2C,KAAM,CAC/C,MACA,MAEA,YAAY,MAA0B,MAAgB,CACpD,IAAM,MAAQ,MAAQ,aAAa,QAAU,GAC7C,MAAM,gCAAgC,MAAM,IAAI,MAAM,KAAK,KAAK,GAAG,EACnE,KAAK,KAAO,wBACZ,KAAK,MAAQ,OAAO,OAAO,CAAC,GAAG,KAAK,CAAC,EACrC,KAAK,MAAQ,KACf,CACF,EAmBa,0BAAb,cAA+C,KAAM,CACnD,IACA,QAEA,YAAY,IAAa,QAA4B,CACnD,MACE,0CAA0C,IAAI,2CAChC,QAAQ,KAAK,IAAI,GACjC,EACA,KAAK,KAAO,4BACZ,KAAK,IAAM,IACX,KAAK,QAAU,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC,CAC3C,CACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @forinda/kickjs v9.2.0
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Felix Orinda
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*
|
|
9
|
+
* @license MIT
|
|
10
|
+
*/
|
|
11
|
+
import{createLogger}from"./core/logger.mjs";import{r as reportError}from"./observers-COlQfinD.mjs";import{normalizeProblem}from"./core/errors.mjs";import{t as ROUTE_SLOT}from"./route-flag-Bb7n9Mnp.mjs";import{r as MissingContextValueError}from"./context-errors-DYJjhalQ.mjs";import{t as resolveClientIp}from"./client-ip-pcINkkPr.mjs";import{a as parseQuery}from"./query-B5DTq_4W.mjs";import{AsyncLocalStorage}from"node:async_hooks";const requestStore=new AsyncLocalStorage;function getRequestStore(){let store=requestStore.getStore();if(!store)throw Error(`No active request context. REQUEST-scoped services can only be resolved during an HTTP request.`);return store}function getRequestValue(key){return requestStore.getStore()?.values.get(key)}const log=createLogger(`waitUntil`),pending=new Set;function waitUntil(promise){let requestId=requestStore.getStore()?.requestId,tracked=Promise.resolve(promise).then(()=>{},err=>{log.error({err,requestId},`Background work passed to waitUntil() failed`),reportError(err,{source:`background`,requestId})});pending.add(tracked),tracked.finally(()=>pending.delete(tracked))}async function settleBackgroundWork(){for(;pending.size>0;)await Promise.all(pending)}function pendingBackgroundWork(){return pending.size}function forwardBackgroundWork(platform){platform?.waitUntil?.(settleBackgroundWork())}function publishMatchedRoute(req,entry){req[ROUTE_SLOT]={method:entry.method,path:entry.path,controller:entry.meta.controller,handlerName:entry.meta.handlerName,flags:entry.meta.flags??EMPTY_FLAGS,pattern:entry.meta.pattern??entry.path}}const EMPTY_FLAGS=new Map;function isBigIntJsonError(err){return err instanceof TypeError&&/BigInt/.test(err.message)}function stringifyJson(data){try{return JSON.stringify(data)}catch(err){if(!isBigIntJsonError(err))throw err;return JSON.stringify(data,(_key,value)=>typeof value==`bigint`?value.toString():value)}}const signalControllerKey=Symbol.for(`kickjs.requestContext.signalController`),readOnlyProxyCacheKey=Symbol.for(`kickjs.requestContext.readOnlyProxyCache`);function isPlainObjectOrArray(v){if(typeof v!=`object`||!v)return!1;if(Array.isArray(v))return!0;let proto=Object.getPrototypeOf(v);return proto===Object.prototype||proto===null}function makeReadOnlyProxy(target,req,label){if(process.env.NODE_ENV===`production`||!isPlainObjectOrArray(target))return target;let reqObj=req,cache=reqObj[readOnlyProxyCacheKey];cache||(cache=new WeakMap,reqObj[readOnlyProxyCacheKey]=cache);let targetObj=target,cached=cache.get(targetObj);if(cached)return cached;let warnWrite=(verb,prop)=>{console.warn(`[kickjs] Attempted to ${verb} \`ctx.${label}.${String(prop)}\` — request data is read-only. Stash computed values via \`ctx.set('key', value)\` or a Context Contributor, or reach \`ctx.req.${label}\` if you genuinely need the raw mutable handle.`)},proxy=new Proxy(targetObj,{get(t,prop,receiver){let value=Reflect.get(t,prop,receiver);return isPlainObjectOrArray(value)?makeReadOnlyProxy(value,req,`${label}.${String(prop)}`):value},set(_t,prop){return warnWrite(`assign`,prop),!0},deleteProperty(_t,prop){return warnWrite(`delete`,prop),!0},defineProperty(_t,prop){return warnWrite(`Object.defineProperty on`,prop),!0}});return cache.set(targetObj,proxy),proxy}var RequestContext=class{req;res;next;_response;get route(){return this.req?.[ROUTE_SLOT]}constructor(req,res,next,responseDriver){this.req=req,this.res=res,this.next=next,this._response=responseDriver??res}_qsMemo;_problem;metadataReadOnly(){return requestStore.getStore()?.values??null}metadataForWrite(){let store=requestStore.getStore();if(store)return store.values;throw Error(`RequestContext.set called outside an AsyncLocalStorage frame. Mount requestScopeMiddleware() before constructing RequestContext, or wrap test setup in requestStore.run({ requestId, instances: new Map(), values: new Map() }, () => ...).`)}get body(){return makeReadOnlyProxy(this.req.body,this.req,`body`)}get params(){return makeReadOnlyProxy(this.req.params,this.req,`params`)}get query(){return makeReadOnlyProxy(this.req.query,this.req,`query`)}get headers(){return makeReadOnlyProxy(this.req.headers,this.req,`headers`)}get requestId(){return this.req.requestId??this.req.headers[`x-request-id`]}get signal(){let req=this.req,cached=req[signalControllerKey];if(cached)return cached.signal;let ctrl=new AbortController;req[signalControllerKey]=ctrl;let abort=()=>ctrl.abort(`request closed`);return this.req.once(`close`,abort),this._response.once(`close`,abort),ctrl.signal}get session(){return this.req.session}get user(){return this.metadataReadOnly()?.get(`user`)??this.req.user}get tenantId(){return this.user?.tenantId}get roles(){let u=this.user;return u?.tenantRoles??u?.roles??[]}qs(fieldConfig,options){let memo=this._qsMemo;if(memo&&memo.config===fieldConfig&&memo.options===options)return memo.result;let effective={...options,onReject:options?.onReject??(rejection=>{let id=this.requestId?`[${this.requestId}] `:``;console.warn(`${id}query ${rejection.kind} '${rejection.field}' rejected (${rejection.reason})`)})},result=parseQuery(this.req.query,fieldConfig,effective);return this._qsMemo={config:fieldConfig,options,result},result}get file(){let f=this.req.file;return f?makeReadOnlyProxy(f,this.req,`file`):void 0}get files(){let f=this.req.files;if(Array.isArray(f))return makeReadOnlyProxy(f,this.req,`files`)}get(key){return this.metadataReadOnly()?.get(key)}require(key){let value=this.metadataReadOnly()?.get(key);if(value===void 0)throw new MissingContextValueError(key,this.routeLabel());return value}routeLabel(){let req=this.req,method=typeof req?.method==`string`?req.method:void 0,url=typeof req?.url==`string`?req.url:void 0;return method&&url?`${method} ${url}`:method??url}set(key,value){this.metadataForWrite().set(key,value)}setHeader(name,value){return this._response.setHeader(name,value),this}json(data,status=200){return this.sendJson(status,data)}created(data){return this.sendJson(201,data)}sendJson(status,data){try{return this._response.status(status).json(data)}catch(err){if(!isBigIntJsonError(err)||this._response.headersSent)throw err;return this._response.status(status).json(JSON.parse(stringifyJson(data)))}}noContent(){return this._response.status(204).end()}notFound(message=`Not Found`){return this._response.status(404).json({message})}badRequest(message){return this._response.status(400).json({message})}get problem(){if(this._problem)return this._problem;let send=input=>this.sendProblem(input);return send.badRequest=(input={})=>this.sendProblem({status:400,...input}),send.unauthorized=(input={})=>this.sendProblem({status:401,...input}),send.forbidden=(input={})=>this.sendProblem({status:403,...input}),send.notFound=(input={})=>this.sendProblem({status:404,...input}),send.conflict=(input={})=>this.sendProblem({status:409,...input}),send.unprocessable=(input={})=>this.sendProblem({status:422,...input}),send.tooManyRequests=(input={})=>this.sendProblem({status:429,...input}),send.internal=(input={})=>this.sendProblem({status:500,...input}),send.validation=(issues,input={})=>{let errors=(issues||[]).map(issue=>({field:issue.path?.join?.(`.`)??``,message:issue.message??``,code:issue.code}));return this.sendProblem({status:422,detail:issues?.[0]?.message,...input,errors})},this._problem=send,this._problem}sendProblem(input){let body=normalizeProblem(input);return this._response.setHeader(`Content-Type`,`application/problem+json`),this._response.status(body.status).json(body)}get ip(){return resolveClientIp(this.req)}redirect(url,status=302){return this._response.status(status).setHeader(`location`,url),this._response.end()}html(content,status=200){return this._response.status(status).type(`text/html; charset=utf-8`).send(content)}download(buffer,filename,contentType=`application/octet-stream`){return this._response.setHeader(`Content-Disposition`,contentDisposition(filename)),this._response.setHeader(`Content-Type`,contentType),this._response.send(buffer)}render(template,data={}){return this._response.render(template,data)}async paginate(fetcher,fieldConfig){let parsed=this.qs(fieldConfig),{data,total}=await fetcher(parsed),{page,limit}=parsed.pagination,totalPages=Math.ceil(total/limit)||1,response={data,meta:{page,limit,total,totalPages,hasNext:page<totalPages,hasPrev:page>1}};return this.json(response),response}async sendResponse(response){let headers={};response.headers.forEach((value,name)=>{name!==`set-cookie`&&(headers[name]=value)});let cookies=response.headers.getSetCookie();if(cookies.length>0&&(headers[`set-cookie`]=cookies),!response.body){this._response.status(response.status);for(let[name,value]of Object.entries(headers))this._response.setHeader(name,value);this._response.send(``);return}this._response.writeHead(response.status,headers),this._response.flushHeaders();let reader=response.body.getReader(),stop=()=>{reader.cancel().catch(()=>{})};this.signal.addEventListener(`abort`,stop,{once:!0});try{for(;;){let{done,value}=await reader.read();if(done)break;this._response.write(Buffer.from(value))===!1&&!this.signal.aborted&&await new Promise(resolve=>{let resume=()=>{this.signal.removeEventListener(`abort`,resume),resolve()};this._response.once(`drain`,resume),this.signal.addEventListener(`abort`,resume,{once:!0})})}}finally{this.signal.removeEventListener(`abort`,stop),this._response.end()}}waitUntil(promise){waitUntil(promise)}sse(){this._response.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`,"X-Accel-Buffering":`no`}),this._response.flushHeaders();let closeCallbacks=[];return this.req.on(`close`,()=>{for(let cb of closeCallbacks)cb()}),{send:(data,event,id)=>{id&&this._response.write(`id: ${id}\n`),event&&this._response.write(`event: ${event}\n`),this._response.write(`data: ${JSON.stringify(data)}\n\n`)},comment:text=>{this._response.write(`: ${text}\n\n`)},onClose:fn=>{closeCallbacks.push(fn)},close:()=>{this._response.end()}}}};function contentDisposition(filename){return`attachment; filename="${filename.replace(/[^\x20-\x7e]|["\\]/g,`_`)}"; filename*=UTF-8''${encodeURIComponent(filename).replace(/['()*]/g,c=>`%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`}export{forwardBackgroundWork as a,waitUntil as c,requestStore as d,stringifyJson as i,getRequestStore as l,contentDisposition as n,pendingBackgroundWork as o,publishMatchedRoute as r,settleBackgroundWork as s,RequestContext as t,getRequestValue as u};
|
|
12
|
+
//# sourceMappingURL=context-gpvcMlG5.mjs.map
|