@forinda/kickjs 8.7.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +27 -164
  2. package/dist/{adapter-DK8WDonT.d.mts → adapter-DhjoIYVI.d.mts} +23 -103
  3. package/dist/adapter-DhjoIYVI.d.mts.map +1 -0
  4. package/dist/application-5IWcGDdu.mjs +12 -0
  5. package/dist/application-5IWcGDdu.mjs.map +1 -0
  6. package/dist/{application-B6Gp1nqK.d.mts → application-DqD58rhF.d.mts} +17 -9
  7. package/dist/{application-B6Gp1nqK.d.mts.map → application-DqD58rhF.d.mts.map} +1 -1
  8. package/dist/{bootstrap-Dds2PVAn.mjs → bootstrap-CiMR9rTB.mjs} +3 -3
  9. package/dist/{bootstrap-Dds2PVAn.mjs.map → bootstrap-CiMR9rTB.mjs.map} +1 -1
  10. package/dist/{client-ip-BuY7_pDD.mjs → client-ip-B-LhFjcb.mjs} +2 -2
  11. package/dist/{client-ip-BuY7_pDD.mjs.map → client-ip-B-LhFjcb.mjs.map} +1 -1
  12. package/dist/config/index.d.mts +2 -2
  13. package/dist/config/index.mjs +2 -2
  14. package/dist/config-DNkSu_Hl.mjs +12 -0
  15. package/dist/config-DNkSu_Hl.mjs.map +1 -0
  16. package/dist/{container-Cqow_bOU.mjs → container-BfmPa7gi.mjs} +3 -3
  17. package/dist/{container-Cqow_bOU.mjs.map → container-BfmPa7gi.mjs.map} +1 -1
  18. package/dist/{context-CHuuc9r6.mjs → context-DivVCLKt.mjs} +3 -3
  19. package/dist/context-DivVCLKt.mjs.map +1 -0
  20. package/dist/{context-errors-VduncZFQ.mjs → context-errors-MoctmZdH.mjs} +2 -2
  21. package/dist/{context-errors-VduncZFQ.mjs.map → context-errors-MoctmZdH.mjs.map} +1 -1
  22. package/dist/{contributor-runner-BoVekHyj.mjs → contributor-runner-DJEVHfm_.mjs} +3 -3
  23. package/dist/{contributor-runner-BoVekHyj.mjs.map → contributor-runner-DJEVHfm_.mjs.map} +1 -1
  24. package/dist/core/adapter.d.mts +1 -1
  25. package/dist/core/adapter.mjs +1 -1
  26. package/dist/core/app-module.mjs +1 -1
  27. package/dist/core/cache.mjs +1 -1
  28. package/dist/core/container.mjs +2 -2
  29. package/dist/core/cron.mjs +2 -2
  30. package/dist/core/decorators.mjs +2 -2
  31. package/dist/core/errors.mjs +1 -1
  32. package/dist/core/interfaces.mjs +1 -1
  33. package/dist/core/logger.mjs +1 -1
  34. package/dist/core/path.mjs +1 -1
  35. package/dist/core/plugin.d.mts +1 -1
  36. package/dist/core/plugin.mjs +1 -1
  37. package/dist/core/reactivity.mjs +1 -1
  38. package/dist/core-BpHbZ7P8.mjs +12 -0
  39. package/dist/{core-Chc61ahe.mjs.map → core-BpHbZ7P8.mjs.map} +1 -1
  40. package/dist/{csrf-D-8W1iv7.mjs → csrf-BVEqmJ4c.mjs} +3 -3
  41. package/dist/{csrf-D-8W1iv7.mjs.map → csrf-BVEqmJ4c.mjs.map} +1 -1
  42. package/dist/{decorate-D9n58F0b.mjs → decorate-DlbfE8dD.mjs} +1 -1
  43. package/dist/{define-adapter-BJunWqi0.mjs → define-adapter-CyVSGUFp.mjs} +2 -2
  44. package/dist/{define-adapter-BJunWqi0.mjs.map → define-adapter-CyVSGUFp.mjs.map} +1 -1
  45. package/dist/{disposables-BFIEp5VA.mjs → disposables-CLTUN_9Z.mjs} +2 -2
  46. package/dist/{disposables-BFIEp5VA.mjs.map → disposables-CLTUN_9Z.mjs.map} +1 -1
  47. package/dist/{env-8BLpzy-M.mjs → env-BjxVzWFa.mjs} +4 -4
  48. package/dist/env-BjxVzWFa.mjs.map +1 -0
  49. package/dist/{error-handler-DxF2EogM.d.mts → error-handler-DiueQHes.d.mts} +2 -2
  50. package/dist/{error-handler-DxF2EogM.d.mts.map → error-handler-DiueQHes.d.mts.map} +1 -1
  51. package/dist/{handler-MSvgGVk0.mjs → handler-BEqBMxZH.mjs} +3 -3
  52. package/dist/{handler-MSvgGVk0.mjs.map → handler-BEqBMxZH.mjs.map} +1 -1
  53. package/dist/http/application.d.mts +1 -1
  54. package/dist/http/application.mjs +2 -2
  55. package/dist/http/bootstrap.d.mts +1 -1
  56. package/dist/http/bootstrap.mjs +2 -2
  57. package/dist/http/context.d.mts +2 -2
  58. package/dist/http/context.mjs +2 -2
  59. package/dist/http/middleware/cors.mjs +1 -1
  60. package/dist/http/middleware/csrf.mjs +2 -2
  61. package/dist/http/middleware/error-handler.d.mts +1 -1
  62. package/dist/http/middleware/error-handler.mjs +2 -2
  63. package/dist/http/middleware/error-handler.mjs.map +1 -1
  64. package/dist/http/middleware/helmet.mjs +1 -1
  65. package/dist/http/middleware/rate-limit.mjs +2 -2
  66. package/dist/http/middleware/request-id.mjs +1 -1
  67. package/dist/http/middleware/request-logger.mjs +2 -2
  68. package/dist/http/middleware/session.d.mts +1 -1
  69. package/dist/http/middleware/session.mjs +2 -2
  70. package/dist/http/middleware/session.mjs.map +1 -1
  71. package/dist/http/middleware/spa.d.mts +1 -1
  72. package/dist/http/middleware/spa.mjs +2 -2
  73. package/dist/http/middleware/upload.d.mts +1 -1
  74. package/dist/http/middleware/upload.mjs +3 -3
  75. package/dist/http/middleware/upload.mjs.map +1 -1
  76. package/dist/http/middleware/validate.mjs +1 -1
  77. package/dist/http/middleware/views.d.mts +1 -1
  78. package/dist/http/middleware/views.mjs +2 -2
  79. package/dist/http/query/index.mjs +2 -2
  80. package/dist/http/router-builder.d.mts +1 -1
  81. package/dist/http/router-builder.mjs +2 -2
  82. package/dist/http/runtimes/fastify.d.mts +1 -1
  83. package/dist/http/runtimes/fastify.mjs +2 -2
  84. package/dist/http/runtimes/h3-web.d.mts +1 -1
  85. package/dist/http/runtimes/h3-web.mjs +2 -2
  86. package/dist/http/runtimes/h3.d.mts +1 -1
  87. package/dist/http/runtimes/h3.mjs +2 -2
  88. package/dist/{index-ClP0EE9p.d.mts → index-CDn-p3tq.d.mts} +26 -17
  89. package/dist/index-CDn-p3tq.d.mts.map +1 -0
  90. package/dist/index.d.mts +9 -9
  91. package/dist/index.mjs +2 -2
  92. package/dist/index.mjs.map +1 -1
  93. package/dist/jobs-D9pQyr8d.mjs +12 -0
  94. package/dist/jobs-D9pQyr8d.mjs.map +1 -0
  95. package/dist/{kv-stores-BDcvhJBw.mjs → kv-stores-9ntcqpGU.mjs} +3 -3
  96. package/dist/{kv-stores-BDcvhJBw.mjs.map → kv-stores-9ntcqpGU.mjs.map} +1 -1
  97. package/dist/{kv-stores-B-1WZ2Fh.d.mts → kv-stores-CetFF9N_.d.mts} +104 -4
  98. package/dist/kv-stores-CetFF9N_.d.mts.map +1 -0
  99. package/dist/{metadata-BClL-Eb0.mjs → metadata-CW58nTQM.mjs} +2 -2
  100. package/dist/{metadata-BClL-Eb0.mjs.map → metadata-CW58nTQM.mjs.map} +1 -1
  101. package/dist/{module-registry-kVXS3peW.mjs → module-registry-BLB0gwEq.mjs} +2 -2
  102. package/dist/{module-registry-kVXS3peW.mjs.map → module-registry-BLB0gwEq.mjs.map} +1 -1
  103. package/dist/{observers-C5Jid4FV.mjs → observers-Ac2ZdgCN.mjs} +2 -2
  104. package/dist/{observers-C5Jid4FV.mjs.map → observers-Ac2ZdgCN.mjs.map} +1 -1
  105. package/dist/{query-1Q69dPhY.mjs → query-U25WhMfT.mjs} +2 -2
  106. package/dist/{query-1Q69dPhY.mjs.map → query-U25WhMfT.mjs.map} +1 -1
  107. package/dist/{respond-DBRvpEI5.mjs → respond-CUPNdYfi.mjs} +2 -2
  108. package/dist/{respond-DBRvpEI5.mjs.map → respond-CUPNdYfi.mjs.map} +1 -1
  109. package/dist/{route-flag-BCBTtP_4.mjs → route-flag-BeSiIg98.mjs} +3 -3
  110. package/dist/{route-flag-BCBTtP_4.mjs.map → route-flag-BeSiIg98.mjs.map} +1 -1
  111. package/dist/{route-policy-FeJHstvF.mjs → route-policy-Ch0TlY_K.mjs} +2 -2
  112. package/dist/{route-policy-FeJHstvF.mjs.map → route-policy-Ch0TlY_K.mjs.map} +1 -1
  113. package/dist/{router-builder-Brup4e82.mjs → router-builder-BL0kmIIu.mjs} +3 -3
  114. package/dist/{router-builder-Brup4e82.mjs.map → router-builder-BL0kmIIu.mjs.map} +1 -1
  115. package/dist/{router-builder-CUs9Gq4X.d.mts → router-builder-BSBSYgYW.d.mts} +2 -2
  116. package/dist/{router-builder-CUs9Gq4X.d.mts.map → router-builder-BSBSYgYW.d.mts.map} +1 -1
  117. package/dist/{runtime-CV0SS-X5.mjs → runtime-npBaLlo7.mjs} +3 -3
  118. package/dist/{runtime-CV0SS-X5.mjs.map → runtime-npBaLlo7.mjs.map} +1 -1
  119. package/dist/{session-BHgRyfqZ.d.mts → session-4Tb6q0Ny.d.mts} +11 -1
  120. package/dist/session-4Tb6q0Ny.d.mts.map +1 -0
  121. package/dist/{upload-BukLMawc.d.mts → upload-DjhnyHHm.d.mts} +2 -14
  122. package/dist/{upload-BukLMawc.d.mts.map → upload-DjhnyHHm.d.mts.map} +1 -1
  123. package/dist/{upload-config-BBp5QfWQ.mjs → upload-config-DvN699K_.mjs} +2 -2
  124. package/dist/{upload-config-BBp5QfWQ.mjs.map → upload-config-DvN699K_.mjs.map} +1 -1
  125. package/dist/web.d.mts +20 -3
  126. package/dist/web.d.mts.map +1 -1
  127. package/dist/web.mjs +2 -2
  128. package/dist/web.mjs.map +1 -1
  129. package/package.json +3 -3
  130. package/dist/adapter-DK8WDonT.d.mts.map +0 -1
  131. package/dist/application-D2scwBz-.mjs +0 -12
  132. package/dist/application-D2scwBz-.mjs.map +0 -1
  133. package/dist/config-DBebCpg_.mjs +0 -12
  134. package/dist/config-DBebCpg_.mjs.map +0 -1
  135. package/dist/context-CHuuc9r6.mjs.map +0 -1
  136. package/dist/core-Chc61ahe.mjs +0 -12
  137. package/dist/env-8BLpzy-M.mjs.map +0 -1
  138. package/dist/index-ClP0EE9p.d.mts.map +0 -1
  139. package/dist/kv-stores-B-1WZ2Fh.d.mts.map +0 -1
  140. package/dist/session-BHgRyfqZ.d.mts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-BjxVzWFa.mjs","names":[],"sources":["../src/config/env.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { z } from 'zod'\nimport { detectSchema, type InferSchemaOutput } from '@forinda/kickjs-schema'\nimport { Container } from '../core/container'\nimport type { EnvKey } from '../core/decorators'\n\n/**\n * Lazy `zod` accessor. `zod` is an **optional** peer of\n * `@forinda/kickjs` — only the Zod-based env helpers (`baseEnvSchema`,\n * `defineEnv`, `loadEnv`) need it. Apps that validate env via Valibot /\n * Yup / Standard Schema go through `loadEnvFromSchema` (which only\n * touches the validator-agnostic `detectSchema`) and never load zod.\n *\n * Importing `zod` at module top-level made it a *hard* dependency of\n * the whole framework: `import { anything } from '@forinda/kickjs'`\n * eagerly evaluated this module, so a non-Zod app with no `zod`\n * installed crashed at module load / build time (\"Cannot find module\n * 'zod'\"). Deferring the require to first use keeps the import out of\n * the eager graph.\n */\nlet _zod: typeof import('zod') | undefined\nfunction getZod(): typeof import('zod') {\n if (_zod) return _zod\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n _zod = require('zod') as typeof import('zod')\n } catch {\n throw new Error(\n \"@forinda/kickjs: the 'zod' peer dependency is required for \" +\n 'baseEnvSchema / defineEnv / loadEnv but is not installed. ' +\n 'Install it (`pnpm add zod`), or define your env with a non-Zod ' +\n 'schema and call loadEnvFromSchema() instead — @forinda/kickjs-schema ' +\n 'supports Valibot, Yup, and any Standard Schema validator without zod.',\n )\n }\n return _zod\n}\n\n/**\n * Lazily load `.env` via dotenv if it is installed in the consumer app.\n * `dotenv` is an **optional peer dependency** of `@forinda/kickjs` —\n * apps that want `.env` file support install it themselves (the CLI\n * generators add it to every scaffolded `package.json`). Apps that\n * inject env via the shell, Docker, or a secret manager can skip it\n * entirely and `process.env` is read as-is.\n *\n * We call `dotenv.config()` directly rather than `require('dotenv/config')`\n * so that a re-evaluation of this module (e.g. Vite SSR full-reload after\n * editing `.env`) can re-read the file. `dotenv/config` is a side-effect\n * shim that CJS caches aggressively, so the second `require` is a no-op\n * and new keys never reach `process.env` — the root cause of the\n * \"DATABASE_URL undefined until restart\" bug on Windows dev.\n *\n * Which file(s) get read is decided by {@link resolveEnvFiles}.\n */\nfunction isTestRun(): boolean {\n return process.env['NODE_ENV'] === 'test' || !!process.env['VITEST']\n}\n\n/**\n * The mode whose env files apply. A test run is always mode `test`;\n * otherwise `NODE_ENV`, defaulting to the same `development` value\n * `baseEnvSchema` uses.\n *\n * The {@link isTestRun} check has to come FIRST, and the two predicates\n * have to agree. They did not: this read `NODE_ENV ?? (VITEST ? 'test'\n * : 'development')`, so an explicitly-set `NODE_ENV=development` won\n * file selection while `isTestRun()` — which counts `VITEST` on its own\n * — still gated the backfill warning. A vitest run with\n * `NODE_ENV=development` exported (common in a shell profile or a CI\n * image) therefore skipped the `.env.test` sitting right there, loaded\n * the developer's `.env`, and printed a warning telling the user to\n * create the very file it had just ignored. Worst of both: the alarm\n * without the protection.\n *\n * So `VITEST` outranks a non-test `NODE_ENV` for file selection. To run\n * a suite deliberately against another mode's files, name them with\n * `KICKJS_ENV_FILE`, which still bypasses all of this.\n */\nfunction envMode(): string {\n if (isTestRun()) return 'test'\n return process.env['NODE_ENV'] ?? 'development'\n}\n\nconst GENERIC_ENV_FILES = ['.env.local', '.env'] as const\n\n/**\n * Which env files dotenv should read, relative to `process.cwd()`, in\n * **highest-precedence-first** order. `null` means \"read nothing\".\n *\n * The cascade follows the convention Vite popularised, where a\n * mode-specific file outranks every generic one:\n *\n * ```\n * .env.[mode].local > .env.[mode] > .env.local > .env\n * ```\n *\n * Vars already present in `process.env` still outrank all of them —\n * that is what `override: false` at the call site buys, and it matches\n * Vite (\"environment variables that already exist … have the highest\n * priority and will not be overwritten by `.env` files\").\n *\n * **Test mode is the one exception, and deliberately so.** When the mode\n * is `test` and a `.env.test` / `.env.test.local` exists, that file is\n * treated as an explicit opt-in to an isolated environment and the\n * generic files are dropped rather than layered under it. Layering is\n * exactly the hole this closes: with `override: false`, every key a test\n * runner forgets to pin is silently backfilled from the developer's\n * `.env`, so a suite can pin its database URL and still reach live\n * development services through the keys it forgot. For `development` and\n * `production` the layering is what people expect and want (shared base\n * plus overrides), and there is no dev-resource-in-a-test failure mode\n * to guard against, so those cascade normally.\n *\n * `KICKJS_ENV_FILE` overrides the decision entirely — a comma-separated\n * list of files (highest precedence first), or `off` to skip dotenv\n * altogether (for apps that inject env via the shell, Docker, or a\n * secret manager).\n */\nfunction resolveEnvFiles(): string[] | null {\n const explicit = process.env['KICKJS_ENV_FILE']\n if (explicit === 'off' || explicit === 'none') return null\n if (explicit) {\n return explicit\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean)\n }\n\n const exists = (f: string): boolean => existsSync(resolve(process.cwd(), f))\n const mode = envMode()\n const modeFiles = [`.env.${mode}.local`, `.env.${mode}`]\n\n // A test-scoped file present = isolate. Without one we still read the\n // generic files (so an existing project is unchanged) and warn.\n const isolate = mode === 'test' && modeFiles.some(exists)\n const chain = isolate ? modeFiles : [...modeFiles, ...GENERIC_ENV_FILES]\n\n const found = chain.filter(exists)\n return found.length > 0 ? found : null\n}\n\n/** One-shot guard so the test-backfill warning cannot spam a suite. */\nlet warnedTestBackfill = false\n\n/**\n * Of the keys dotenv parsed out of the files, the ones whose value in\n * `process.env` actually differs from `before` — i.e. the ones dotenv\n * really applied.\n *\n * This distinction is the whole point of the warning. `result.parsed`\n * is everything READ from the files, not everything APPLIED: under\n * `override: false` a key already set by the test runner keeps the\n * runner's value and the file's copy is discarded. Warning about those\n * would name vars the user correctly pinned, which is exactly backwards\n * — the message exists to say which vars leaked IN, and listing pinned\n * ones alongside them makes it untrustworthy noise.\n *\n * Exported for tests only; not re-exported from the package index, so\n * it is not public API.\n */\nexport function appliedKeys(before: NodeJS.ProcessEnv, parsed: Record<string, string>): string[] {\n return Object.keys(parsed).filter((k) => process.env[k] !== before[k])\n}\n\nfunction tryLoadDotenv(override = false): void {\n const files = resolveEnvFiles()\n if (!files) return\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const dotenv = require('dotenv')\n // `quiet: true` suppresses dotenv 17's tip banner that goes to stdout.\n // Required for CLI tools whose stdout is parsed by callers (`kick explain\n // --json`, etc) — the banner would otherwise corrupt the JSON output.\n //\n // dotenv resolves array precedence positionally, and the DIRECTION\n // depends on `override`: with `override: false` the first file to set a\n // key wins; with `override: true` each file overwrites the last, so the\n // final one wins. `files` is ordered highest-precedence-first, so the\n // override path has to reverse it — otherwise a `reloadEnv()` would\n // silently invert the cascade and let `.env` beat `.env.production`.\n const path = override ? files.toReversed() : files\n\n // Isolation is opt-in, and nobody discovers an opt-in. A test run that\n // read the generic files says so once, on stderr — but only about the\n // vars it actually pulled in, so snapshot process.env first. Skipped\n // entirely when no warning could fire, to keep the copy off the boot\n // path of a normal run.\n const usedGeneric = files.some((f) => (GENERIC_ENV_FILES as readonly string[]).includes(f))\n const mayWarn = !warnedTestBackfill && isTestRun() && usedGeneric\n const before = mayWarn ? { ...process.env } : undefined\n\n const result = dotenv.config({ path, override, quiet: true })\n\n if (mayWarn && result.parsed) {\n const applied = appliedKeys(before!, result.parsed)\n // Only latch the one-shot guard once something is actually reported;\n // latching on a run that had nothing to say would swallow the real\n // warning from a later reload.\n if (applied.length > 0) {\n warnedTestBackfill = true\n console.warn(\n `[kickjs] test run took ${applied.length} env var(s) from ` +\n `${files.join(', ')} (${applied.slice(0, 5).join(', ')}` +\n `${applied.length > 5 ? ', …' : ''}). Create a .env.test to isolate the ` +\n 'suite, or set KICKJS_ENV_FILE=off.',\n )\n }\n }\n } catch {\n // dotenv not installed — fall through, env vars must come from\n // the environment directly. This is a valid deployment style.\n }\n}\ntryLoadDotenv()\n\n/**\n * Build the base env schema. Memoized so repeated calls reuse one\n * schema instance (matters for the `cachedSchema === s` identity check\n * in {@link loadEnv}). Loads zod lazily via {@link getZod}.\n */\n// Build the concrete base schema. This function is the single source of\n// the *type* of `baseEnvSchema` (via `ReturnType<typeof buildBaseEnvSchema>`)\n// AND its runtime value (via `getBaseEnvSchema`). It is NEVER called at\n// module load — only lazily through `getBaseEnvSchema()` — so referencing\n// its return type stays compile-time-only and pulls no eager zod import.\n// Spelling the type this way (rather than `z.ZodObject<any>`) preserves the\n// precise PORT/NODE_ENV/LOG_LEVEL shape, which `defineEnv` composition and\n// the `kick typegen` `KickEnv` inference depend on.\nfunction buildBaseEnvSchema() {\n const z = getZod()\n return z.object({\n // Server\n PORT: z.coerce.number().default(3000),\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n LOG_LEVEL: z.string().default('info'),\n })\n}\n\n/** Precise type of the base env schema — no runtime value, no eager zod. */\nexport type BaseEnvSchema = ReturnType<typeof buildBaseEnvSchema>\n\n/**\n * Memoized base schema. Reuses one instance so the `cachedSchema === s`\n * identity check in {@link loadEnv} holds. Loads zod lazily.\n */\nlet _baseEnvSchema: BaseEnvSchema | undefined\nexport function getBaseEnvSchema(): BaseEnvSchema {\n if (!_baseEnvSchema) _baseEnvSchema = buildBaseEnvSchema()\n return _baseEnvSchema\n}\n\n/**\n * Base environment schema with common server variables.\n * Users extend this with their own application-specific vars.\n *\n * Back-compat lazy view over {@link getBaseEnvSchema}: constructing the\n * Proxy does NOT load zod — zod is only required the moment a property\n * (`.extend`, `.parse`, `.shape`, …) is actually accessed. This keeps\n * `import { baseEnvSchema } from '@forinda/kickjs'` zero-cost for apps\n * that don't use the Zod env path, while keeping the precise schema type.\n */\nexport const baseEnvSchema: BaseEnvSchema = new Proxy({} as BaseEnvSchema, {\n get(_t, prop) {\n const real = getBaseEnvSchema() as unknown as Record<string | symbol, unknown>\n const value = real[prop]\n // Bind methods to the real schema so zod's internal `this` works.\n return typeof value === 'function' ? (value as (...a: unknown[]) => unknown).bind(real) : value\n },\n has(_t, prop) {\n return prop in (getBaseEnvSchema() as object)\n },\n}) as BaseEnvSchema\n\n/** Cached env config to avoid re-parsing — keyed by schema reference */\nlet cachedEnv: any = null\n/**\n * Values {@link withEnv} lays over the parsed env while its callback runs.\n * Kept apart from the cache so a re-parse during the callback (a different\n * schema, a reload) can't drop them.\n */\nlet envOverlay: Record<string, unknown> | null = null\n\nfunction overlaid<T>(env: T): T {\n return envOverlay ? ({ ...(env as object), ...envOverlay } as T) : env\n}\n\n/** What `@Value()` reads: an overlay value first, then the parsed env. */\nfunction resolveEnvKey(key: string): unknown {\n return envOverlay && key in envOverlay ? envOverlay[key] : cachedEnv?.[key]\n}\nlet cachedSchema: any = null\n\n/**\n * Define a custom env schema. The base schema (`PORT`, `NODE_ENV`,\n * `LOG_LEVEL`) is always merged in, so users never need to remember to\n * extend it themselves — both styles below produce the same result and\n * both surface the base keys in `KickEnv`.\n *\n * Returns a Zod schema that validates `process.env`.\n *\n * @example\n * ```ts\n * // Style A — return a fresh object; base fields are merged automatically\n * export default defineEnv(() =>\n * z.object({\n * DATABASE_URL: z.string().url(),\n * JWT_SECRET: z.string().min(32),\n * })\n * )\n *\n * // Style B — explicitly extend `base` (still works, identical result)\n * export default defineEnv((base) =>\n * base.extend({\n * DATABASE_URL: z.string().url(),\n * })\n * )\n * ```\n *\n * User-defined keys override base keys when they collide, so a project\n * can re-type `PORT` or tighten `NODE_ENV` without losing the rest of\n * the base shape.\n */\nexport function defineEnv<T extends z.ZodRawShape>(\n extend: (base: typeof baseEnvSchema) => z.ZodObject<T>,\n): z.ZodObject<typeof baseEnvSchema.shape & T> {\n const base = getBaseEnvSchema()\n const userSchema = extend(base as typeof baseEnvSchema)\n // Always merge the base in. `extend` lets the user's shape override\n // any colliding base keys, which preserves the escape hatch for\n // projects that want to re-type `PORT` etc.\n return base.extend(userSchema.shape) as z.ZodObject<typeof baseEnvSchema.shape & T>\n}\n\n/**\n * Parse and validate `process.env` against a Zod schema. Caches result per schema.\n *\n * **No-arg behaviour is sticky.** The first time `loadEnv(schema)` is called\n * with an extended schema, the cache holds that schema. Subsequent\n * `loadEnv()` calls (no arg) will reuse the cached extended schema rather\n * than falling back to `baseEnvSchema`.\n *\n * Stickiness matters because `ConfigService` and the `@Value()` decorator\n * both read through the no-arg form internally. Without it, resolving\n * either of them after a `loadEnv(extendedSchema)` call would silently\n * downgrade the env to the base shape and drop every user-defined key.\n *\n * To force a re-parse with a different schema, pass the new schema explicitly.\n * To start fresh, call `resetEnvCache()` (full reset) or `reloadEnv()`\n * (re-parse against the same schema after a `.env` file change).\n *\n * **Typing:**\n * - With an explicit schema → returns `z.infer<typeof schema>`.\n * - With no schema and `KickEnv` populated by typegen → returns\n * `KickEnv` (the project-wide typed env shape).\n * - With no schema and `KickEnv` empty → returns the base `Env`.\n */\nexport function loadEnv<T extends z.ZodObject<any>>(schema: T): z.infer<T>\nexport function loadEnv(): [EnvKey] extends [never] ? Env : KickEnv\nexport function loadEnv<T extends z.ZodObject<any>>(schema?: T): any {\n // Sticky: when called with no arg, prefer the most recently cached\n // schema over re-parsing with the base schema.\n const s = schema ?? cachedSchema ?? getBaseEnvSchema()\n // Re-parse if schema changed or no cache yet\n if (cachedEnv && cachedSchema === s) return overlaid(cachedEnv)\n cachedSchema = s\n cachedEnv = s.parse(process.env)\n\n // Register env resolver so @Value() reads validated values\n Container._envResolver = resolveEnvKey\n\n return overlaid(cachedEnv)\n}\n\n/**\n * Get a single typed environment variable value, read from the env schema\n * the app registered — the one source of truth, set once with\n * `loadEnv(envSchema)` in `src/env.ts`.\n *\n * - **`getEnv(key)`** returns the parsed value: `getEnv('PORT')` is a number\n * once `kick typegen` has run.\n * - **`getEnv(key, fallback)`** returns `fallback` when the value is unset\n * (`undefined` or `null`) — for keys the schema leaves optional.\n *\n * @example\n * ```ts\n * const port = getEnv('PORT') // number\n * const region = getEnv('S3_REGION', 'eu-west-1') // string, never undefined\n * ```\n *\n * A default every caller shares belongs in the schema (`.default(...)`);\n * the fallback is for a value one call site can live without.\n */\nexport function getEnv<K extends EnvKey>(key: K): KickEnv[K]\nexport function getEnv<K extends EnvKey>(\n key: K,\n fallback: NonNullable<KickEnv[K]>,\n): NonNullable<KickEnv[K]>\nexport function getEnv(key: string, fallback?: unknown): any {\n // The old second argument was a schema. Still honoured, so existing calls\n // keep working, but the schema belongs in one place: loadEnv(envSchema).\n if (isZodObject(fallback)) {\n if (!warnedSchemaArg) {\n warnedSchemaArg = true\n process.emitWarning(\n 'getEnv(key, schema) is deprecated: register the schema once with loadEnv(envSchema); ' +\n 'the second argument is now a fallback value.',\n 'DeprecationWarning',\n )\n }\n return (loadEnv(fallback) as Record<string, unknown>)[key]\n }\n const value = (loadEnv() as Record<string, unknown>)[key]\n return value ?? fallback\n}\n\nlet warnedSchemaArg = false\n\nfunction isZodObject(value: unknown): value is z.ZodObject<any> {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { safeParse?: unknown }).safeParse === 'function' &&\n 'shape' in value\n )\n}\n\n/**\n * Run `fn` with some env values replaced — for tests. The overrides are\n * parsed values (`{ TRUST_PROXY: true }`), laid over the current env for the\n * duration of the call and taken away afterwards, whatever `fn` does.\n * `getEnv`, `ConfigService` and `@Value()` all see them. No `.env` file is\n * read. Not for concurrent use: the env is process-wide.\n */\nexport async function withEnv<T>(\n overrides: Record<string, unknown>,\n fn: () => T | Promise<T>,\n): Promise<T> {\n loadEnv() // registers the resolver @Value() reads through\n const previous = envOverlay\n // Nested calls layer: the inner overrides win, the outer ones come back after.\n envOverlay = { ...previous, ...overrides }\n try {\n return await fn()\n } finally {\n envOverlay = previous\n }\n}\n\n/**\n * Reload env from `process.env`. Re-reads `.env` via dotenv (if\n * installed), then re-parses the result against the **same** schema\n * the project registered at startup. Called during HMR rebuild and by\n * `envWatchPlugin` to pick up `.env` file changes mid-session.\n *\n * Crucially, this preserves the registered schema. Wiping it would\n * silently downgrade `ConfigService` and `@Value()` back to the base\n * schema on the next read, so every user-defined key would start\n * resolving as `undefined` after the first `.env` save — which was the\n * original \"config disappears in dev\" bug.\n *\n * If you need to fully forget the schema (e.g. tests that swap schemas\n * between cases), call {@link resetEnvCache} instead.\n */\nexport function reloadEnv(): void {\n // Re-read the env file(s) into process.env if dotenv is installed.\n // Routed through the same resolver as boot so a reload can never\n // disagree with startup about which file is authoritative — the\n // inline `dotenv.config()` this replaced always read `.env`, so\n // under a test run it could clobber pinned `.env.test` values with\n // the developer's own.\n tryLoadDotenv(true)\n\n // Drop the parsed snapshot but keep the schema. Re-parse eagerly so\n // existing consumers (including ConfigService getters and @Value\n // resolvers that may already hold references to the env state) see\n // the new values on their very next read instead of having to\n // re-trigger any cache-population step.\n cachedEnv = null\n if (cachedSchema) {\n if (typeof cachedSchema.parse === 'function') {\n loadEnv(cachedSchema)\n } else {\n loadEnvFromSchema(cachedSchema)\n }\n }\n}\n\n/**\n * Fully reset the env cache, including the registered schema. Use this\n * in tests that need to swap schemas between cases — `reloadEnv()`\n * deliberately preserves the schema to avoid the \"user keys vanish on\n * `.env` reload\" footgun.\n */\nexport function resetEnvCache(): void {\n cachedEnv = null\n cachedSchema = null\n // Also re-arm the one-shot backfill warning. This is the \"start fresh\"\n // hook, and a suite that swaps env between cases should get told each\n // time it picks up the generic files, not only the first.\n warnedTestBackfill = false\n // Drop the resolver too. It closes over `cachedEnv`, so leaving it\n // installed left `@Value()` reading through a live closure over a\n // null cache — indistinguishable from \"key not set\" but with no\n // schema to re-parse against.\n Container._envResolver = null\n}\n\n/**\n * Schema-agnostic env loader. Accepts any schema supported by\n * `@forinda/kickjs-schema` (Zod, Valibot, Yup, Joi, Standard Schema,\n * KickSchema adapters, or plain validator functions).\n *\n * Unlike `loadEnv()` which uses Zod's `.parse()` and throws a\n * `ZodError`, this function uses `detectSchema().safeParse()` and\n * throws a plain `Error` with structured issue messages on failure.\n *\n * @example\n * ```ts\n * import * as v from 'valibot'\n * import { fromValibot } from '@forinda/kickjs-schema/valibot'\n *\n * const envSchema = fromValibot(v.object({\n * PORT: v.pipe(v.string(), v.transform(Number)),\n * NODE_ENV: v.picklist(['development', 'production', 'test']),\n * }))\n *\n * export const env = loadEnvFromSchema(envSchema)\n * ```\n */\n/**\n * Single inferring signature. `TSchema` is inferred from the call site;\n * the conditional return type maps the unknown-input case (TSchema\n * resolves to `unknown` only when the argument is `unknown`-typed\n * because nothing on the value side carried a richer brand) to\n * `Record<string, unknown>` rather than letting `InferSchemaOutput`\n * fall through to `unknown`. The previous two-overload form had the\n * generic overload win for `unknown`-typed inputs (`unknown` is\n * assignable to any `TSchema`), so the documented \"Record\" fallback\n * never actually applied.\n */\nexport function loadEnvFromSchema<TSchema>(\n schema: TSchema,\n): unknown extends TSchema ? Record<string, unknown> : InferSchemaOutput<TSchema>\nexport function loadEnvFromSchema(schema: unknown): unknown {\n const wrapped = detectSchema(schema)\n const result = wrapped.safeParse(process.env)\n if (!result.success) {\n const details = result.issues\n .map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n')\n throw new Error(`Environment validation failed:\\n${details}`)\n }\n cachedEnv = result.data\n cachedSchema = schema\n Container._envResolver = resolveEnvKey\n return overlaid(result.data)\n}\n\n/**\n * Output type of {@link baseEnvSchema}. Hand-written (rather than\n * `z.infer<typeof baseEnvSchema>`) so the type is available without the\n * eager `zod` value import — `baseEnvSchema` is now a lazy Proxy typed\n * as `z.ZodObject<any>`, from which `z.infer` could not recover the\n * precise shape anyway.\n */\nexport type Env = {\n PORT: number\n NODE_ENV: 'development' | 'production' | 'test'\n LOG_LEVEL: string\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
 
2
- import { S as RuntimeResponse } from "./adapter-DK8WDonT.mjs";
2
+ import { S as RuntimeResponse } from "./adapter-DhjoIYVI.mjs";
3
3
  //#region src/http/middleware/error-handler.d.ts
4
4
  /**
5
5
  * The slice of the request this handler reads, spelled from node's
@@ -60,4 +60,4 @@ declare function notFoundHandler(routes?: readonly MountedRoute[]): (req: ErrorR
60
60
  declare function errorHandler(): (err: any, req: ErrorRequest, res: RuntimeResponse, _next: () => void) => RuntimeResponse | undefined;
61
61
  //#endregion
62
62
  export { errorHandler as n, notFoundHandler as r, MountedRoute as t };
63
- //# sourceMappingURL=error-handler-DxF2EogM.d.mts.map
63
+ //# sourceMappingURL=error-handler-DiueQHes.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-handler-DxF2EogM.d.mts","names":[],"sources":["../src/http/middleware/error-handler.ts"],"mappings":";;;;;;;;;;;UAUU;EACR;EACA;;EAEA;EACA,SAAS;;;UAaM;EACf;;EAEA;;;;;;;;;;;;;;;;iBAiCc,gBAAgB,kBAAiB,kBAAmB,KAMrD,cAAY,KAAO,iBAAe,sBAAmB;;;;;;;;;;;;;;;;;;;;;iBA6CpD,iBAAY,UAEV,KAAO,cAAY,KAAO,iBAAe,sBAAmB"}
1
+ {"version":3,"file":"error-handler-DiueQHes.d.mts","names":[],"sources":["../src/http/middleware/error-handler.ts"],"mappings":";;;;;;;;;;;UAUU;EACR;EACA;;EAEA;EACA,SAAS;;;UAaM;EACf;;EAEA;;;;;;;;;;;;;;;;iBAiCc,gBAAgB,kBAAiB,kBAAmB,KAMrD,cAAY,KAAO,iBAAe,sBAAmB;;;;;;;;;;;;;;;;;;;;;iBA6CpD,iBAAY,UAEV,KAAO,cAAY,KAAO,iBAAe,sBAAmB"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,6 +8,6 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{createLogger,describeError}from"./core/logger.mjs";import{HttpException}from"./core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute,u as classifyMediaType}from"./runtime-CV0SS-X5.mjs";import{c as requestStore,t as RequestContext}from"./context-CHuuc9r6.mjs";import{validate}from"./http/middleware/validate.mjs";import{t as applyUploadConfig}from"./upload-config-BBp5QfWQ.mjs";var WebRequestShim=class{method;url;headers;body;params={};query={};requestId;file;files;signal;constructor(request,url){this.method=request.method,this.url=url.pathname+url.search,this.signal=request.signal;let headers={};request.headers.forEach((value,key)=>{headers[key]=value}),this.headers=headers;let query={};for(let key of url.searchParams.keys()){let all=url.searchParams.getAll(key);query[key]=all.length>1?all:all[0]}this.query=query}once(event,listener){return event===`close`&&this.signal.addEventListener(`abort`,listener,{once:!0}),this}on(event,listener){return this.once(event,listener)}};const encoder=new TextEncoder;function toBytes(chunk){return chunk instanceof Uint8Array?chunk:encoder.encode(String(chunk))}var WebResponseDriver=class{signal;ready;settled=!1;_status=200;headers=new Headers;_headersSent=!1;writer;resolveReady;constructor(signal){this.signal=signal,this.ready=new Promise(resolve=>{this.resolveReady=resolve})}status(code){return this._status=code,this}json(data){return this.headers.has(`content-type`)||this.headers.set(`content-type`,`application/json; charset=utf-8`),this.finish(JSON.stringify(data))}send(data){return typeof data==`object`&&data&&!(data instanceof Uint8Array)?this.json(data):this.finish(data)}type(contentType){return this.headers.set(`content-type`,contentType),this}setHeader(name,value){if(Array.isArray(value)){this.headers.delete(name);for(let v of value)this.headers.append(name,String(v))}else this.headers.set(name,String(value));return this}render(){throw Error(`ctx.render() is not supported on the web runtime (no view engine).`)}writeHead(statusCode,headers){if(this._status=statusCode,headers)for(let[name,value]of Object.entries(headers))this.setHeader(name,value);return this.startStream(),this}flushHeaders(){return this.startStream(),this}write(chunk){return this.startStream(),this.writer.write(toBytes(chunk)).catch(()=>{}),!0}end(data){return this.writer?(data!==void 0&&this.write(data),this.writer.close().catch(()=>{}),this.writer=void 0,this):this.finish(data)}once(event,listener){return event===`close`&&this.signal&&this.signal.addEventListener(`abort`,listener,{once:!0}),this}get headersSent(){return this._headersSent}finish(body){if(this.settled)return this;this.settled=!0,this._headersSent=!0;let bodyAllowed=this._status!==204&&this._status!==304;return this.resolveReady(new Response(bodyAllowed?body??null:null,{status:this._status,headers:this.headers})),this}startStream(){if(this.writer||this.settled)return;let{readable,writable}=new TransformStream;this.writer=writable.getWriter(),this.settled=!0,this._headersSent=!0,this.signal?.addEventListener(`abort`,()=>{this.writer?.abort().catch(()=>{}),this.writer=void 0},{once:!0}),this.resolveReady(new Response(readable,{status:this._status,headers:this.headers}))}};const log=createLogger(`WebPipeline`),NOOP_NEXT=()=>{},BODY_METHODS=new Set([`POST`,`PUT`,`PATCH`,`DELETE`]);function defaultErrorResponse(err,driver){let status=typeof err?.status==`number`?err.status:typeof err?.statusCode==`number`?err.statusCode:500;if(status>=500){log.error(err,`Unhandled error in web pipeline — ${describeError(err)}`);let stack=err?.stack;driver.status(status).json({error:`Internal Server Error`,...isProduction()?{}:{message:describeError(err),...typeof stack==`string`?{stack:stack.split(`
11
+ import{createLogger,describeError}from"./core/logger.mjs";import{HttpException}from"./core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute,u as classifyMediaType}from"./runtime-npBaLlo7.mjs";import{l as requestStore,t as RequestContext}from"./context-DivVCLKt.mjs";import{validate}from"./http/middleware/validate.mjs";import{t as applyUploadConfig}from"./upload-config-DvN699K_.mjs";var WebRequestShim=class{method;url;headers;body;params={};query={};requestId;file;files;signal;constructor(request,url){this.method=request.method,this.url=url.pathname+url.search,this.signal=request.signal;let headers={};request.headers.forEach((value,key)=>{headers[key]=value}),this.headers=headers;let query={};for(let key of url.searchParams.keys()){let all=url.searchParams.getAll(key);query[key]=all.length>1?all:all[0]}this.query=query}once(event,listener){return event===`close`&&this.signal.addEventListener(`abort`,listener,{once:!0}),this}on(event,listener){return this.once(event,listener)}};const encoder=new TextEncoder;function toBytes(chunk){return chunk instanceof Uint8Array?chunk:encoder.encode(String(chunk))}var WebResponseDriver=class{signal;ready;settled=!1;_status=200;headers=new Headers;_headersSent=!1;writer;resolveReady;constructor(signal){this.signal=signal,this.ready=new Promise(resolve=>{this.resolveReady=resolve})}status(code){return this._status=code,this}json(data){return this.headers.has(`content-type`)||this.headers.set(`content-type`,`application/json; charset=utf-8`),this.finish(JSON.stringify(data))}send(data){return typeof data==`object`&&data&&!(data instanceof Uint8Array)?this.json(data):this.finish(data)}type(contentType){return this.headers.set(`content-type`,contentType),this}setHeader(name,value){if(Array.isArray(value)){this.headers.delete(name);for(let v of value)this.headers.append(name,String(v))}else this.headers.set(name,String(value));return this}render(){throw Error(`ctx.render() is not supported on the web runtime (no view engine).`)}writeHead(statusCode,headers){if(this._status=statusCode,headers)for(let[name,value]of Object.entries(headers))this.setHeader(name,value);return this.startStream(),this}flushHeaders(){return this.startStream(),this}write(chunk){return this.startStream(),this.writer.write(toBytes(chunk)).catch(()=>{}),!0}end(data){return this.writer?(data!==void 0&&this.write(data),this.writer.close().catch(()=>{}),this.writer=void 0,this):this.finish(data)}once(event,listener){return event===`close`&&this.signal&&this.signal.addEventListener(`abort`,listener,{once:!0}),this}get headersSent(){return this._headersSent}finish(body){if(this.settled)return this;this.settled=!0,this._headersSent=!0;let bodyAllowed=this._status!==204&&this._status!==304;return this.resolveReady(new Response(bodyAllowed?body??null:null,{status:this._status,headers:this.headers})),this}startStream(){if(this.writer||this.settled)return;let{readable,writable}=new TransformStream;this.writer=writable.getWriter(),this.settled=!0,this._headersSent=!0,this.signal?.addEventListener(`abort`,()=>{this.writer?.abort().catch(()=>{}),this.writer=void 0},{once:!0}),this.resolveReady(new Response(readable,{status:this._status,headers:this.headers}))}};const log=createLogger(`WebPipeline`),NOOP_NEXT=()=>{},BODY_METHODS=new Set([`POST`,`PUT`,`PATCH`,`DELETE`]);function defaultErrorResponse(err,driver){let status=typeof err?.status==`number`?err.status:typeof err?.statusCode==`number`?err.statusCode:500;if(status>=500){log.error(err,`Unhandled error in web pipeline — ${describeError(err)}`);let stack=err?.stack;driver.status(status).json({error:`Internal Server Error`,...isProduction()?{}:{message:describeError(err),...typeof stack==`string`?{stack:stack.split(`
12
12
  `)}:{}}});return}driver.status(status).json({error:err?.message??`Request failed`})}function isProduction(){return typeof process<`u`&&process.env?.NODE_ENV===`production`}function compileWebRoute(entry,hooks){let validator=entry.meta.validation?validate(entry.meta.validation):void 0,upload=entry.meta.upload&&entry.meta.upload.mode!==`none`?entry.meta.upload:null;return async({request,url,params})=>{let req=new WebRequestShim(request,url);req.params=params;let bodyError;if(upload){let form=await request.formData().catch(()=>void 0);if(form){let rawParts=[],fields={};for(let[name,value]of form.entries())typeof value==`string`?fields[name]=value:rawParts.push({fieldname:name,filename:value.name??``,mimetype:value.type||`application/octet-stream`,buffer:new Uint8Array(await value.arrayBuffer())});let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);req.file=file,req.files=files,req.body=fields}}else if(BODY_METHODS.has(request.method)){let raw=request.body===null?``:await request.text().catch(()=>``),kind=classifyMediaType(req.headers[`content-type`]);if(raw!==``&&kind===`unsupported`)bodyError=unsupportedMediaTypeError(req.headers[`content-type`]);else if(raw!==``&&kind!==`multipart`){if(kind===`json`)try{req.body=JSON.parse(raw)}catch(err){bodyError=HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`)}else req.body=kind===`urlencoded`?Object.fromEntries(new URLSearchParams(raw)):raw}}let store=createRequestStore(req.headers[`x-request-id`]);req.requestId=store.requestId;let driver=new WebResponseDriver(request.signal);driver.setHeader(`x-request-id`,store.requestId),publishMatchedRoute(req,entry);let pipeline=requestStore.run(store,async()=>{if(bodyError)throw bodyError;let ctx=new RequestContext(req,driver,NOOP_NEXT,driver);if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),driver.settled))return;validator&&await new Promise((resolve,reject)=>{validator(req,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||driver.settled)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),driver.settled)return;let result=await entry.handler(ctx);driver.settled||applyHandlerResult(ctx,result)});try{try{await Promise.race([pipeline,driver.ready])}catch(err){driver.settled||(hooks?.onError&&await hooks.onError(err,req,driver),driver.settled||defaultErrorResponse(err,driver))}return driver.settled||driver.status(404).json({error:`Not Found`}),await driver.ready}finally{Promise.resolve(pipeline).catch(()=>{}).then(()=>disposeRequestStore(store))}}}export{WebRequestShim as n,WebResponseDriver as r,compileWebRoute as t};
13
- //# sourceMappingURL=handler-MSvgGVk0.mjs.map
13
+ //# sourceMappingURL=handler-BEqBMxZH.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"handler-MSvgGVk0.mjs","names":[],"sources":["../src/http/web/driver.ts","../src/http/web/handler.ts"],"sourcesContent":["// Web-standard driver pair — the request/response bridge between web\n// primitives (WHATWG Request/Response) and the KickJS RequestContext surface.\n//\n// Deliberately imports NOTHING from node: this module is the edge-safe core\n// shared by the h3 v2 runtime (`runtimes/h3-web.ts`, node bootstrap) and the\n// `@forinda/kickjs/web` fetch entry (edge/Bun/Deno).\n\nimport type { RuntimeResponse } from '../runtime'\n\n/**\n * Presents a web `Request` as the express-shaped `req` object that\n * `RequestContext` (and route validation) reads: plain-object `headers`,\n * assignable `body`/`params`/`query`, and `on/once('close')` bridged from\n * the request's `AbortSignal` — the only EventEmitter surface the context\n * layer uses (`ctx.signal`, SSE close).\n */\nexport class WebRequestShim {\n readonly method: string\n readonly url: string\n /** Lower-cased plain header object (RequestContext indexes it directly). */\n readonly headers: Record<string, string>\n body: unknown\n params: Record<string, string> = {}\n query: Record<string, unknown> = {}\n requestId?: string\n file?: unknown\n files?: unknown;\n // Ad-hoc props middleware may set (session, user, ...) — index signature\n // keeps assignment open like a node req object.\n [key: string]: unknown\n\n private readonly signal: AbortSignal\n\n constructor(request: Request, url: URL) {\n this.method = request.method\n // Express-shaped `req.url` is path + search, not the absolute URL.\n this.url = url.pathname + url.search\n this.signal = request.signal\n const headers: Record<string, string> = {}\n request.headers.forEach((value, key) => {\n headers[key] = value\n })\n this.headers = headers\n const query: Record<string, unknown> = {}\n for (const key of url.searchParams.keys()) {\n const all = url.searchParams.getAll(key)\n query[key] = all.length > 1 ? all : all[0]\n }\n this.query = query\n }\n\n /** `req.once('close')` — abort of the underlying request signal. */\n once(event: string, listener: () => void): this {\n if (event === 'close') this.signal.addEventListener('abort', listener, { once: true })\n return this\n }\n\n /** `req.on('close')` — same bridge; signals only abort once anyway. */\n on(event: string, listener: () => void): this {\n return this.once(event, listener)\n }\n}\n\nconst encoder = new TextEncoder()\n\nfunction toBytes(chunk: unknown): Uint8Array {\n if (chunk instanceof Uint8Array) return chunk\n return encoder.encode(String(chunk))\n}\n\n/**\n * `RuntimeResponse` over a web `Response`.\n *\n * Two modes:\n * - **Buffered** (default): `status/json/send/type/setHeader` record state;\n * the terminal call resolves {@link ready} with a complete `Response`.\n * - **Streaming**: the first `writeHead`/`flushHeaders`/`write` call switches\n * to a `TransformStream` and resolves {@link ready} IMMEDIATELY with a\n * streamed `Response` — SSE works on edge this way; `write` feeds the\n * writer, `end` closes it.\n *\n * A class so the method table lives on a shared prototype (one allocation\n * per request — same rationale as the Fastify/h3 driver classes).\n */\nexport class WebResponseDriver implements RuntimeResponse {\n /** Resolves with the final (or streamed) Response. */\n readonly ready: Promise<Response>\n /** True once a terminal/streaming action produced the Response. */\n settled = false\n\n private _status = 200\n private readonly headers = new Headers()\n private _headersSent = false\n private writer?: WritableStreamDefaultWriter<Uint8Array>\n private resolveReady!: (response: Response) => void\n\n constructor(private readonly signal?: AbortSignal) {\n this.ready = new Promise<Response>((resolve) => {\n this.resolveReady = resolve\n })\n }\n\n status(code: number): this {\n this._status = code\n return this\n }\n\n json(data: unknown): this {\n if (!this.headers.has('content-type'))\n this.headers.set('content-type', 'application/json; charset=utf-8')\n return this.finish(JSON.stringify(data))\n }\n\n send(data: unknown): this {\n if (\n data !== undefined &&\n data !== null &&\n typeof data === 'object' &&\n !(data instanceof Uint8Array)\n ) {\n return this.json(data)\n }\n return this.finish(data as string | Uint8Array | null | undefined)\n }\n\n type(contentType: string): this {\n this.headers.set('content-type', contentType)\n return this\n }\n\n setHeader(name: string, value: unknown): this {\n if (Array.isArray(value)) {\n this.headers.delete(name)\n for (const v of value) this.headers.append(name, String(v))\n } else {\n this.headers.set(name, String(value))\n }\n return this\n }\n\n render(): never {\n throw new Error('ctx.render() is not supported on the web runtime (no view engine).')\n }\n\n writeHead(statusCode: number, headers?: Record<string, string | number | string[]>): this {\n this._status = statusCode\n if (headers) {\n for (const [name, value] of Object.entries(headers)) this.setHeader(name, value)\n }\n this.startStream()\n return this\n }\n\n flushHeaders(): this {\n this.startStream()\n return this\n }\n\n write(chunk: unknown): boolean {\n this.startStream()\n // Fire-and-forget: backpressure is absorbed by the stream's queue. Write\n // failures (client gone) surface via the abort listener, not here.\n void this.writer!.write(toBytes(chunk)).catch(() => {})\n return true\n }\n\n end(data?: unknown): this {\n if (this.writer) {\n if (data !== undefined) this.write(data)\n void this.writer.close().catch(() => {})\n this.writer = undefined\n return this\n }\n return this.finish(data as string | Uint8Array | null | undefined)\n }\n\n once(event: string, listener: (...args: unknown[]) => void): this {\n if (event === 'close' && this.signal)\n this.signal.addEventListener('abort', listener as () => void, { once: true })\n return this\n }\n\n get headersSent(): boolean {\n return this._headersSent\n }\n\n /** Buffered terminal: build the final Response exactly once. */\n private finish(body?: string | Uint8Array | null): this {\n if (this.settled) return this\n this.settled = true\n this._headersSent = true\n // 204/304 must not carry a body per the Response constructor contract.\n const bodyAllowed = this._status !== 204 && this._status !== 304\n this.resolveReady(\n new Response(bodyAllowed ? (body ?? null) : null, {\n status: this._status,\n headers: this.headers,\n }),\n )\n return this\n }\n\n /** Switch to streaming mode and resolve `ready` with the streamed Response. */\n private startStream(): void {\n if (this.writer || this.settled) return\n const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>()\n this.writer = writable.getWriter()\n this.settled = true\n this._headersSent = true\n // Abort from the client tears the stream down so the writer stops queueing.\n this.signal?.addEventListener(\n 'abort',\n () => {\n void this.writer?.abort().catch(() => {})\n this.writer = undefined\n },\n { once: true },\n )\n this.resolveReady(new Response(readable, { status: this._status, headers: this.headers }))\n }\n}\n","// Engine-neutral web pipeline: run one KickJS RouteEntry against a WHATWG\n// Request and produce a WHATWG Response. Shared by the h3 v2 runtime\n// (node bootstrap) and the `@forinda/kickjs/web` fetch entry (edge/Bun/Deno).\n// Edge-safe: no node imports beyond the sanctioned ALS request store.\n\nimport { HttpException } from '../../core/errors'\nimport { classifyMediaType, unsupportedMediaTypeError } from '../body-policy'\nimport { RequestContext } from '../context'\nimport { publishMatchedRoute } from '../runtime'\nimport { applyHandlerResult } from '../reply'\nimport { requestStore } from '../request-store'\nimport { createRequestStore, disposeRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload-config'\nimport type { RouteEntry, RuntimeResponse } from '../runtime'\nimport { WebRequestShim, WebResponseDriver } from './driver'\nimport { createLogger, describeError } from '../../core/logger'\n\nconst log = createLogger('WebPipeline')\nconst NOOP_NEXT = (): void => {}\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\nexport interface WebRouteInvocation {\n request: Request\n url: URL\n params: Record<string, string>\n}\n\nexport interface WebRouteHooks {\n /**\n * Error bridge — receives pipeline rejections (validation failures,\n * handler throws) with the shim + driver so an engine can dispatch the\n * framework's connect-style error handler. When absent (or when it\n * doesn't settle the driver), a minimal JSON error is produced from\n * `err.status`/`err.statusCode` (500 default).\n */\n onError?: (err: unknown, req: WebRequestShim, res: WebResponseDriver) => void | Promise<void>\n}\n\n/**\n * Fallback error shape when no engine error handler settles the response.\n *\n * This path used to be completely silent: a bare\n * `{ error: 'Internal Server Error' }` with no log line anywhere, so a\n * failure that reached here left no trace on either side. It is a\n * last-resort branch, which is exactly when diagnostics matter most.\n */\nfunction defaultErrorResponse(err: unknown, driver: WebResponseDriver): void {\n const status =\n typeof (err as { status?: number })?.status === 'number'\n ? (err as { status: number }).status\n : typeof (err as { statusCode?: number })?.statusCode === 'number'\n ? (err as { statusCode: number }).statusCode\n : 500\n\n if (status >= 500) {\n log.error(err, `Unhandled error in web pipeline — ${describeError(err)}`)\n const stack = (err as Error)?.stack\n driver.status(status).json({\n error: 'Internal Server Error',\n ...(isProduction()\n ? {}\n : {\n message: describeError(err),\n ...(typeof stack === 'string' ? { stack: stack.split('\\n') } : {}),\n }),\n })\n return\n }\n\n driver.status(status).json({ error: (err as Error)?.message ?? 'Request failed' })\n}\n\n/**\n * Read per-call, not at module scope: the web entry is imported once and\n * reused across invocations, and edge runtimes may not expose `process`.\n */\nfunction isProduction(): boolean {\n return typeof process !== 'undefined' && process.env?.NODE_ENV === 'production'\n}\n\n/**\n * Compile a RouteEntry into a `(invocation) => Promise<Response>` function.\n * Everything derivable at route-build time (validator, upload config) is\n * hoisted here — per-request work is shim + store + pipeline only.\n */\nexport function compileWebRoute(\n entry: RouteEntry,\n hooks?: WebRouteHooks,\n): (invocation: WebRouteInvocation) => Promise<Response> {\n const validator = entry.meta.validation ? validate(entry.meta.validation) : undefined\n const upload = entry.meta.upload && entry.meta.upload.mode !== 'none' ? entry.meta.upload : null\n\n return async ({ request, url, params }) => {\n const req = new WebRequestShim(request, url)\n req.params = params\n let bodyError: unknown\n\n // Body: uploads consume the stream as FormData; everything else parses\n // by content-type.\n if (upload) {\n const form = await request.formData().catch(() => undefined)\n if (form) {\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for (const [name, value] of form.entries()) {\n if (typeof value === 'string') {\n fields[name] = value\n } else {\n rawParts.push({\n fieldname: name,\n filename: value.name ?? '',\n mimetype: value.type || 'application/octet-stream',\n buffer: new Uint8Array(await value.arrayBuffer()) as unknown as Buffer,\n })\n }\n }\n const { file, files } = applyUploadConfig(rawParts, entry.meta.upload!)\n req.file = file\n req.files = files\n req.body = fields\n }\n } else if (BODY_METHODS.has(request.method)) {\n // Read once — a Request body is a stream and cannot be consumed twice.\n // A read that fails (client aborted mid-flight) is treated as an absent\n // body: there is nobody left to answer, and it is not a malformed one.\n const raw = request.body === null ? '' : await request.text().catch(() => '')\n\n // An empty body is ABSENT, not unparseable — the same call the node h3\n // runtime makes on `content-length: 0`. Distinguishing the two is the\n // whole point: `.catch(() => undefined)` around the parse could not,\n // so a malformed payload answered 200 with the handler running against\n // `undefined` (#605), the defect #586 removed from the node runtime.\n const kind = classifyMediaType(req.headers['content-type'])\n if (raw !== '' && kind === 'unsupported') {\n // Held for the same reason as a parse failure: no driver yet.\n bodyError = unsupportedMediaTypeError(req.headers['content-type'])\n } else if (raw !== '' && kind !== 'multipart') {\n if (kind === 'json') {\n try {\n req.body = JSON.parse(raw)\n } catch (err) {\n // Held, not thrown: the driver that turns an error into a response\n // does not exist yet, so throwing here escapes the route entirely\n // and surfaces as a 500. Rethrown as the pipeline's first act.\n bodyError = HttpException.badRequest(\n err instanceof Error ? err.message : 'Request body could not be parsed',\n )\n }\n } else if (kind === 'urlencoded') {\n req.body = Object.fromEntries(new URLSearchParams(raw))\n } else {\n req.body = raw\n }\n }\n }\n\n const store = createRequestStore(req.headers['x-request-id'])\n req.requestId = store.requestId\n\n const driver = new WebResponseDriver(request.signal)\n driver.setHeader('x-request-id', store.requestId)\n\n publishMatchedRoute(req, entry)\n\n const pipeline = requestStore.run(store, async () => {\n if (bodyError) throw bodyError\n\n const ctx = new RequestContext(\n req as never,\n driver as never,\n NOOP_NEXT,\n driver as RuntimeResponse,\n )\n\n // `beforeValidation` contributors (#677) — ahead of validation and middleware.\n if (entry.earlyContributorRunner) {\n await entry.earlyContributorRunner(ctx)\n if (driver.settled) return\n }\n\n if (validator) {\n await new Promise<void>((resolve, reject) => {\n validator(req as never, undefined as never, (err?: unknown) =>\n err ? reject(err) : resolve(),\n )\n })\n }\n\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || driver.settled) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (driver.settled) return\n const result = await entry.handler(ctx)\n // Return-value handlers (reply.ts): auto-send when nothing was written.\n // `undefined` falls through to the canonical 404 below.\n if (!driver.settled) applyHandlerResult(ctx, result)\n })\n\n try {\n // Streaming responses resolve `ready` mid-pipeline (SSE); buffered ones\n // resolve at the terminal ctx call. Race so both shapes return promptly.\n try {\n await Promise.race([pipeline, driver.ready])\n } catch (err) {\n if (!driver.settled) {\n if (hooks?.onError) await hooks.onError(err, req, driver)\n if (!driver.settled) defaultErrorResponse(err, driver)\n }\n }\n if (!driver.settled) {\n // Pipeline finished without responding — canonical 404 shape.\n driver.status(404).json({ error: 'Not Found' })\n }\n return await driver.ready\n } finally {\n // @PreDestroy teardown. For buffered responses this runs post-response;\n // for streams it runs when the pipeline (not the stream) completes —\n // stream lifetime is governed by the client abort signal.\n void Promise.resolve(pipeline)\n .catch(() => {})\n .then(() => disposeRequestStore(store))\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"handler-BEqBMxZH.mjs","names":[],"sources":["../src/http/web/driver.ts","../src/http/web/handler.ts"],"sourcesContent":["// Web-standard driver pair — the request/response bridge between web\n// primitives (WHATWG Request/Response) and the KickJS RequestContext surface.\n//\n// Deliberately imports NOTHING from node: this module is the edge-safe core\n// shared by the h3 v2 runtime (`runtimes/h3-web.ts`, node bootstrap) and the\n// `@forinda/kickjs/web` fetch entry (edge/Bun/Deno).\n\nimport type { RuntimeResponse } from '../runtime'\n\n/**\n * Presents a web `Request` as the express-shaped `req` object that\n * `RequestContext` (and route validation) reads: plain-object `headers`,\n * assignable `body`/`params`/`query`, and `on/once('close')` bridged from\n * the request's `AbortSignal` — the only EventEmitter surface the context\n * layer uses (`ctx.signal`, SSE close).\n */\nexport class WebRequestShim {\n readonly method: string\n readonly url: string\n /** Lower-cased plain header object (RequestContext indexes it directly). */\n readonly headers: Record<string, string>\n body: unknown\n params: Record<string, string> = {}\n query: Record<string, unknown> = {}\n requestId?: string\n file?: unknown\n files?: unknown;\n // Ad-hoc props middleware may set (session, user, ...) — index signature\n // keeps assignment open like a node req object.\n [key: string]: unknown\n\n private readonly signal: AbortSignal\n\n constructor(request: Request, url: URL) {\n this.method = request.method\n // Express-shaped `req.url` is path + search, not the absolute URL.\n this.url = url.pathname + url.search\n this.signal = request.signal\n const headers: Record<string, string> = {}\n request.headers.forEach((value, key) => {\n headers[key] = value\n })\n this.headers = headers\n const query: Record<string, unknown> = {}\n for (const key of url.searchParams.keys()) {\n const all = url.searchParams.getAll(key)\n query[key] = all.length > 1 ? all : all[0]\n }\n this.query = query\n }\n\n /** `req.once('close')` — abort of the underlying request signal. */\n once(event: string, listener: () => void): this {\n if (event === 'close') this.signal.addEventListener('abort', listener, { once: true })\n return this\n }\n\n /** `req.on('close')` — same bridge; signals only abort once anyway. */\n on(event: string, listener: () => void): this {\n return this.once(event, listener)\n }\n}\n\nconst encoder = new TextEncoder()\n\nfunction toBytes(chunk: unknown): Uint8Array {\n if (chunk instanceof Uint8Array) return chunk\n return encoder.encode(String(chunk))\n}\n\n/**\n * `RuntimeResponse` over a web `Response`.\n *\n * Two modes:\n * - **Buffered** (default): `status/json/send/type/setHeader` record state;\n * the terminal call resolves {@link ready} with a complete `Response`.\n * - **Streaming**: the first `writeHead`/`flushHeaders`/`write` call switches\n * to a `TransformStream` and resolves {@link ready} IMMEDIATELY with a\n * streamed `Response` — SSE works on edge this way; `write` feeds the\n * writer, `end` closes it.\n *\n * A class so the method table lives on a shared prototype (one allocation\n * per request — same rationale as the Fastify/h3 driver classes).\n */\nexport class WebResponseDriver implements RuntimeResponse {\n /** Resolves with the final (or streamed) Response. */\n readonly ready: Promise<Response>\n /** True once a terminal/streaming action produced the Response. */\n settled = false\n\n private _status = 200\n private readonly headers = new Headers()\n private _headersSent = false\n private writer?: WritableStreamDefaultWriter<Uint8Array>\n private resolveReady!: (response: Response) => void\n\n constructor(private readonly signal?: AbortSignal) {\n this.ready = new Promise<Response>((resolve) => {\n this.resolveReady = resolve\n })\n }\n\n status(code: number): this {\n this._status = code\n return this\n }\n\n json(data: unknown): this {\n if (!this.headers.has('content-type'))\n this.headers.set('content-type', 'application/json; charset=utf-8')\n return this.finish(JSON.stringify(data))\n }\n\n send(data: unknown): this {\n if (\n data !== undefined &&\n data !== null &&\n typeof data === 'object' &&\n !(data instanceof Uint8Array)\n ) {\n return this.json(data)\n }\n return this.finish(data as string | Uint8Array | null | undefined)\n }\n\n type(contentType: string): this {\n this.headers.set('content-type', contentType)\n return this\n }\n\n setHeader(name: string, value: unknown): this {\n if (Array.isArray(value)) {\n this.headers.delete(name)\n for (const v of value) this.headers.append(name, String(v))\n } else {\n this.headers.set(name, String(value))\n }\n return this\n }\n\n render(): never {\n throw new Error('ctx.render() is not supported on the web runtime (no view engine).')\n }\n\n writeHead(statusCode: number, headers?: Record<string, string | number | string[]>): this {\n this._status = statusCode\n if (headers) {\n for (const [name, value] of Object.entries(headers)) this.setHeader(name, value)\n }\n this.startStream()\n return this\n }\n\n flushHeaders(): this {\n this.startStream()\n return this\n }\n\n write(chunk: unknown): boolean {\n this.startStream()\n // Fire-and-forget: backpressure is absorbed by the stream's queue. Write\n // failures (client gone) surface via the abort listener, not here.\n void this.writer!.write(toBytes(chunk)).catch(() => {})\n return true\n }\n\n end(data?: unknown): this {\n if (this.writer) {\n if (data !== undefined) this.write(data)\n void this.writer.close().catch(() => {})\n this.writer = undefined\n return this\n }\n return this.finish(data as string | Uint8Array | null | undefined)\n }\n\n once(event: string, listener: (...args: unknown[]) => void): this {\n if (event === 'close' && this.signal)\n this.signal.addEventListener('abort', listener as () => void, { once: true })\n return this\n }\n\n get headersSent(): boolean {\n return this._headersSent\n }\n\n /** Buffered terminal: build the final Response exactly once. */\n private finish(body?: string | Uint8Array | null): this {\n if (this.settled) return this\n this.settled = true\n this._headersSent = true\n // 204/304 must not carry a body per the Response constructor contract.\n const bodyAllowed = this._status !== 204 && this._status !== 304\n this.resolveReady(\n new Response(bodyAllowed ? (body ?? null) : null, {\n status: this._status,\n headers: this.headers,\n }),\n )\n return this\n }\n\n /** Switch to streaming mode and resolve `ready` with the streamed Response. */\n private startStream(): void {\n if (this.writer || this.settled) return\n const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>()\n this.writer = writable.getWriter()\n this.settled = true\n this._headersSent = true\n // Abort from the client tears the stream down so the writer stops queueing.\n this.signal?.addEventListener(\n 'abort',\n () => {\n void this.writer?.abort().catch(() => {})\n this.writer = undefined\n },\n { once: true },\n )\n this.resolveReady(new Response(readable, { status: this._status, headers: this.headers }))\n }\n}\n","// Engine-neutral web pipeline: run one KickJS RouteEntry against a WHATWG\n// Request and produce a WHATWG Response. Shared by the h3 v2 runtime\n// (node bootstrap) and the `@forinda/kickjs/web` fetch entry (edge/Bun/Deno).\n// Edge-safe: no node imports beyond the sanctioned ALS request store.\n\nimport { HttpException } from '../../core/errors'\nimport { classifyMediaType, unsupportedMediaTypeError } from '../body-policy'\nimport { RequestContext } from '../context'\nimport { publishMatchedRoute } from '../runtime'\nimport { applyHandlerResult } from '../reply'\nimport { requestStore } from '../request-store'\nimport { createRequestStore, disposeRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload-config'\nimport type { RouteEntry, RuntimeResponse } from '../runtime'\nimport { WebRequestShim, WebResponseDriver } from './driver'\nimport { createLogger, describeError } from '../../core/logger'\n\nconst log = createLogger('WebPipeline')\nconst NOOP_NEXT = (): void => {}\nconst BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\nexport interface WebRouteInvocation {\n request: Request\n url: URL\n params: Record<string, string>\n}\n\nexport interface WebRouteHooks {\n /**\n * Error bridge — receives pipeline rejections (validation failures,\n * handler throws) with the shim + driver so an engine can dispatch the\n * framework's connect-style error handler. When absent (or when it\n * doesn't settle the driver), a minimal JSON error is produced from\n * `err.status`/`err.statusCode` (500 default).\n */\n onError?: (err: unknown, req: WebRequestShim, res: WebResponseDriver) => void | Promise<void>\n}\n\n/**\n * Fallback error shape when no engine error handler settles the response.\n *\n * This path used to be completely silent: a bare\n * `{ error: 'Internal Server Error' }` with no log line anywhere, so a\n * failure that reached here left no trace on either side. It is a\n * last-resort branch, which is exactly when diagnostics matter most.\n */\nfunction defaultErrorResponse(err: unknown, driver: WebResponseDriver): void {\n const status =\n typeof (err as { status?: number })?.status === 'number'\n ? (err as { status: number }).status\n : typeof (err as { statusCode?: number })?.statusCode === 'number'\n ? (err as { statusCode: number }).statusCode\n : 500\n\n if (status >= 500) {\n log.error(err, `Unhandled error in web pipeline — ${describeError(err)}`)\n const stack = (err as Error)?.stack\n driver.status(status).json({\n error: 'Internal Server Error',\n ...(isProduction()\n ? {}\n : {\n message: describeError(err),\n ...(typeof stack === 'string' ? { stack: stack.split('\\n') } : {}),\n }),\n })\n return\n }\n\n driver.status(status).json({ error: (err as Error)?.message ?? 'Request failed' })\n}\n\n/**\n * Read per-call, not at module scope: the web entry is imported once and\n * reused across invocations, and edge runtimes may not expose `process`.\n */\nfunction isProduction(): boolean {\n return typeof process !== 'undefined' && process.env?.NODE_ENV === 'production'\n}\n\n/**\n * Compile a RouteEntry into a `(invocation) => Promise<Response>` function.\n * Everything derivable at route-build time (validator, upload config) is\n * hoisted here — per-request work is shim + store + pipeline only.\n */\nexport function compileWebRoute(\n entry: RouteEntry,\n hooks?: WebRouteHooks,\n): (invocation: WebRouteInvocation) => Promise<Response> {\n const validator = entry.meta.validation ? validate(entry.meta.validation) : undefined\n const upload = entry.meta.upload && entry.meta.upload.mode !== 'none' ? entry.meta.upload : null\n\n return async ({ request, url, params }) => {\n const req = new WebRequestShim(request, url)\n req.params = params\n let bodyError: unknown\n\n // Body: uploads consume the stream as FormData; everything else parses\n // by content-type.\n if (upload) {\n const form = await request.formData().catch(() => undefined)\n if (form) {\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for (const [name, value] of form.entries()) {\n if (typeof value === 'string') {\n fields[name] = value\n } else {\n rawParts.push({\n fieldname: name,\n filename: value.name ?? '',\n mimetype: value.type || 'application/octet-stream',\n buffer: new Uint8Array(await value.arrayBuffer()) as unknown as Buffer,\n })\n }\n }\n const { file, files } = applyUploadConfig(rawParts, entry.meta.upload!)\n req.file = file\n req.files = files\n req.body = fields\n }\n } else if (BODY_METHODS.has(request.method)) {\n // Read once — a Request body is a stream and cannot be consumed twice.\n // A read that fails (client aborted mid-flight) is treated as an absent\n // body: there is nobody left to answer, and it is not a malformed one.\n const raw = request.body === null ? '' : await request.text().catch(() => '')\n\n // An empty body is ABSENT, not unparseable — the same call the node h3\n // runtime makes on `content-length: 0`. Distinguishing the two is the\n // whole point: `.catch(() => undefined)` around the parse could not,\n // so a malformed payload answered 200 with the handler running against\n // `undefined` (#605), the defect #586 removed from the node runtime.\n const kind = classifyMediaType(req.headers['content-type'])\n if (raw !== '' && kind === 'unsupported') {\n // Held for the same reason as a parse failure: no driver yet.\n bodyError = unsupportedMediaTypeError(req.headers['content-type'])\n } else if (raw !== '' && kind !== 'multipart') {\n if (kind === 'json') {\n try {\n req.body = JSON.parse(raw)\n } catch (err) {\n // Held, not thrown: the driver that turns an error into a response\n // does not exist yet, so throwing here escapes the route entirely\n // and surfaces as a 500. Rethrown as the pipeline's first act.\n bodyError = HttpException.badRequest(\n err instanceof Error ? err.message : 'Request body could not be parsed',\n )\n }\n } else if (kind === 'urlencoded') {\n req.body = Object.fromEntries(new URLSearchParams(raw))\n } else {\n req.body = raw\n }\n }\n }\n\n const store = createRequestStore(req.headers['x-request-id'])\n req.requestId = store.requestId\n\n const driver = new WebResponseDriver(request.signal)\n driver.setHeader('x-request-id', store.requestId)\n\n publishMatchedRoute(req, entry)\n\n const pipeline = requestStore.run(store, async () => {\n if (bodyError) throw bodyError\n\n const ctx = new RequestContext(\n req as never,\n driver as never,\n NOOP_NEXT,\n driver as RuntimeResponse,\n )\n\n // `beforeValidation` contributors (#677) — ahead of validation and middleware.\n if (entry.earlyContributorRunner) {\n await entry.earlyContributorRunner(ctx)\n if (driver.settled) return\n }\n\n if (validator) {\n await new Promise<void>((resolve, reject) => {\n validator(req as never, undefined as never, (err?: unknown) =>\n err ? reject(err) : resolve(),\n )\n })\n }\n\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || driver.settled) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (driver.settled) return\n const result = await entry.handler(ctx)\n // Return-value handlers (reply.ts): auto-send when nothing was written.\n // `undefined` falls through to the canonical 404 below.\n if (!driver.settled) applyHandlerResult(ctx, result)\n })\n\n try {\n // Streaming responses resolve `ready` mid-pipeline (SSE); buffered ones\n // resolve at the terminal ctx call. Race so both shapes return promptly.\n try {\n await Promise.race([pipeline, driver.ready])\n } catch (err) {\n if (!driver.settled) {\n if (hooks?.onError) await hooks.onError(err, req, driver)\n if (!driver.settled) defaultErrorResponse(err, driver)\n }\n }\n if (!driver.settled) {\n // Pipeline finished without responding — canonical 404 shape.\n driver.status(404).json({ error: 'Not Found' })\n }\n return await driver.ready\n } finally {\n // @PreDestroy teardown. For buffered responses this runs post-response;\n // for streams it runs when the pipeline (not the stream) completes —\n // stream lifetime is governed by the client abort signal.\n void Promise.resolve(pipeline)\n .catch(() => {})\n .then(() => disposeRequestStore(store))\n }\n }\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
 
2
- import { a as ShutdownOptions, i as ServerOptions, n as ApplicationOptions, r as MiddlewareEntry, t as Application } from "../application-B6Gp1nqK.mjs";
2
+ import { a as ShutdownOptions, i as ServerOptions, n as ApplicationOptions, r as MiddlewareEntry, t as Application } from "../application-DqD58rhF.mjs";
3
3
  export { Application, ApplicationOptions, MiddlewareEntry, ServerOptions, ShutdownOptions };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as Application}from"../application-D2scwBz-.mjs";export{Application};
11
+ import{t as Application}from"../application-5IWcGDdu.mjs";export{Application};
@@ -1,3 +1,3 @@
1
1
 
2
- import { F as bootstrap } from "../application-B6Gp1nqK.mjs";
2
+ import { F as bootstrap } from "../application-DqD58rhF.mjs";
3
3
  export { bootstrap };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as bootstrap}from"../bootstrap-Dds2PVAn.mjs";export{bootstrap};
11
+ import{t as bootstrap}from"../bootstrap-CiMR9rTB.mjs";export{bootstrap};
@@ -1,4 +1,4 @@
1
1
 
2
- import { D as RequestContext, E as DeepReadonly, O as RouteShape, T as Ctx, k as SseHandler } from "../adapter-DK8WDonT.mjs";
2
+ import { A as contentDisposition, D as RequestContext, E as DeepReadonly, O as RouteShape, T as Ctx, k as SseHandler } from "../adapter-DhjoIYVI.mjs";
3
3
  import { b as ContextMeta } from "../app-module-DCA65GZZ.mjs";
4
- export { type ContextMeta, Ctx, DeepReadonly, RequestContext, RouteShape, SseHandler };
4
+ export { type ContextMeta, Ctx, DeepReadonly, RequestContext, RouteShape, SseHandler, contentDisposition };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{t as RequestContext}from"../context-CHuuc9r6.mjs";export{RequestContext};
11
+ import{n as contentDisposition,t as RequestContext}from"../context-DivVCLKt.mjs";export{RequestContext,contentDisposition};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,4 +8,4 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{n as csrfGuard,t as csrf}from"../../csrf-D-8W1iv7.mjs";export{csrf,csrfGuard};
11
+ import{n as csrfGuard,t as csrf}from"../../csrf-BVEqmJ4c.mjs";export{csrf,csrfGuard};
@@ -1,3 +1,3 @@
1
1
 
2
- import { n as errorHandler, r as notFoundHandler, t as MountedRoute } from "../../error-handler-DxF2EogM.mjs";
2
+ import { n as errorHandler, r as notFoundHandler, t as MountedRoute } from "../../error-handler-DiueQHes.mjs";
3
3
  export { MountedRoute, errorHandler, notFoundHandler };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,6 +8,6 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{createLogger,describeError}from"../../core/logger.mjs";import{HttpException,ProblemException,normalizeProblem}from"../../core/errors.mjs";import"../../core-Chc61ahe.mjs";const log=createLogger(`ErrorHandler`);function patternToRegExp(pattern){let source=pattern.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\/:[^/]+/g,`/[^/]+`).replace(/\*/g,`.*`);return RegExp(`^${source}/?$`)}function pathOf(req){let url=req.originalUrl??req.url??`/`,cut=url.search(/[?#]/);return cut===-1?url:url.slice(0,cut)}function notFoundHandler(routes=[]){let matchers=routes.map(route=>({method:route.method.toUpperCase(),matches:patternToRegExp(route.path)}));return(req,res,_next)=>{let path=pathOf(req),method=(req.method??`GET`).toUpperCase(),allowed=[...new Set(matchers.filter(m=>m.matches.test(path)).map(m=>m.method))];if(allowed.length>0&&!allowed.includes(method)){let verbs=allowed.toSorted().join(`, `);return res.setHeader(`Allow`,verbs),res.setHeader(`Content-Type`,`application/problem+json`),res.status(405).json(normalizeProblem({status:405,detail:`${method} is not supported for this resource. Allowed: ${verbs}.`}))}return res.setHeader(`Content-Type`,`application/problem+json`),res.status(404).json(normalizeProblem({status:404}))}}function errorHandler(){let isProduction=process.env.NODE_ENV===`production`;return(err,req,res,_next)=>{if(res.headersSent){log.warn(`Error after headers sent: ${err?.message||`Unknown`}`);return}if(err instanceof ProblemException){if(err.problem.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let body=normalizeProblem(err.problem);return res.setHeader(`Content-Type`,`application/problem+json`),res.status(body.status).json(body)}if(err?.name===`ZodError`){let firstIssue=err.issues?.[0];return res.status(422).json({message:firstIssue?.message||`Validation failed`,...isProduction?{}:{errors:err.issues}})}if(err instanceof HttpException){if(err.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let exposeDetails=!isProduction&&err.details!==void 0;return res.setHeader(`Content-Type`,`application/problem+json`),res.status(err.status).json(normalizeProblem({status:err.status,detail:err.message,...exposeDetails?{errors:err.details}:{}}))}let status=err.status||err.statusCode||500,requestId=req.requestId??req.headers[`x-request-id`];if(log.error(err,`${req.method} ${req.originalUrl??req.url} — ${describeError(err)}${requestId?` [${requestId}]`:``}`),status!==500)return res.status(status).json({message:err.message||`Error`,...requestId?{requestId}:{}});res.status(500).json({message:`Internal Server Error`,...requestId?{requestId}:{},...isProduction?{}:{error:describeError(err),...typeof err?.stack==`string`?{stack:err.stack.split(`
11
+ import{createLogger,describeError}from"../../core/logger.mjs";import{HttpException,ProblemException,normalizeProblem}from"../../core/errors.mjs";import"../../core-BpHbZ7P8.mjs";const log=createLogger(`ErrorHandler`);function patternToRegExp(pattern){let source=pattern.replace(/[.+?^${}()|[\]\\]/g,`\\$&`).replace(/\/:[^/]+/g,`/[^/]+`).replace(/\*/g,`.*`);return RegExp(`^${source}/?$`)}function pathOf(req){let url=req.originalUrl??req.url??`/`,cut=url.search(/[?#]/);return cut===-1?url:url.slice(0,cut)}function notFoundHandler(routes=[]){let matchers=routes.map(route=>({method:route.method.toUpperCase(),matches:patternToRegExp(route.path)}));return(req,res,_next)=>{let path=pathOf(req),method=(req.method??`GET`).toUpperCase(),allowed=[...new Set(matchers.filter(m=>m.matches.test(path)).map(m=>m.method))];if(allowed.length>0&&!allowed.includes(method)){let verbs=allowed.toSorted().join(`, `);return res.setHeader(`Allow`,verbs),res.setHeader(`Content-Type`,`application/problem+json`),res.status(405).json(normalizeProblem({status:405,detail:`${method} is not supported for this resource. Allowed: ${verbs}.`}))}return res.setHeader(`Content-Type`,`application/problem+json`),res.status(404).json(normalizeProblem({status:404}))}}function errorHandler(){let isProduction=process.env.NODE_ENV===`production`;return(err,req,res,_next)=>{if(res.headersSent){log.warn(`Error after headers sent: ${err?.message||`Unknown`}`);return}if(err instanceof ProblemException){if(err.problem.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let body=normalizeProblem(err.problem);return res.setHeader(`Content-Type`,`application/problem+json`),res.status(body.status).json(body)}if(err?.name===`ZodError`){let firstIssue=err.issues?.[0];return res.status(422).json({message:firstIssue?.message||`Validation failed`,...isProduction?{}:{errors:err.issues}})}if(err instanceof HttpException){if(err.status>=500&&log.error(err,err.message),err.headers)for(let[k,v]of Object.entries(err.headers))res.setHeader(k,v);let exposeDetails=!isProduction&&err.details!==void 0;return res.setHeader(`Content-Type`,`application/problem+json`),res.status(err.status).json(normalizeProblem({status:err.status,detail:err.message,...exposeDetails?{errors:err.details}:{}}))}let requestId=req.requestId??req.headers[`x-request-id`],declared=Number(err.status??err.statusCode);if(Number.isInteger(declared)&&declared>=400&&declared<500)return log.warn(`${req.method} ${req.originalUrl??req.url} — ${declared} ${describeError(err)}`),res.setHeader(`Content-Type`,`application/problem+json`),res.status(declared).json(normalizeProblem({status:declared,detail:err.message||void 0,...requestId?{requestId}:{}}));let status=Number.isInteger(declared)&&declared>=500&&declared<600?declared:500;log.error(err,`${req.method} ${req.originalUrl??req.url} — ${describeError(err)}${requestId?` [${requestId}]`:``}`),res.status(status).json({message:`Internal Server Error`,...requestId?{requestId}:{},...isProduction?{}:{error:describeError(err),...typeof err?.stack==`string`?{stack:err.stack.split(`
12
12
  `)}:{}}})}}export{errorHandler,notFoundHandler};
13
13
  //# sourceMappingURL=error-handler.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-handler.mjs","names":[],"sources":["../../../src/http/middleware/error-handler.ts"],"sourcesContent":["import type { RuntimeResponse } from '../runtime'\n\n/**\n * The slice of the request this handler reads, spelled from node's\n * `IncomingMessage` rather than Express's `Request`.\n *\n * Fastify and h3 hand the error handler `request.raw` — a plain\n * `IncomingMessage` — so anything Express-only is `undefined` there. This is\n * the default handler for EVERY runtime, so it must not assume Express.\n */\ninterface ErrorRequest {\n method?: string\n url?: string\n /** Express-only; absent on the raw node request the other runtimes pass. */\n originalUrl?: string\n headers: Record<string, string | string[] | undefined>\n}\nimport {\n HttpException,\n ProblemException,\n normalizeProblem,\n createLogger,\n describeError,\n} from '../../core'\n\nconst log = createLogger('ErrorHandler')\n\n/** A mounted route, as the catch-all needs to see it. */\nexport interface MountedRoute {\n method: string\n /** Full path as mounted, `:param` segments included. */\n path: string\n}\n\n/** `/things/:id` → `^/things/[^/]+$`, so a concrete path can be matched. */\nfunction patternToRegExp(pattern: string): RegExp {\n const source = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\/:[^/]+/g, '/[^/]+')\n .replace(/\\*/g, '.*')\n return new RegExp(`^${source}/?$`)\n}\n\n/** Path without query string or hash. */\nfunction pathOf(req: ErrorRequest): string {\n const url = req.originalUrl ?? req.url ?? '/'\n const cut = url.search(/[?#]/)\n return cut === -1 ? url : url.slice(0, cut)\n}\n\n/**\n * Catch-all for unmatched routes.\n *\n * Answers RFC 9457 problem details, like every other error the framework\n * produces — the catch-all was the one path still emitting a bare\n * `{ message }`, so a client parsing `application/problem+json` had to special\n * case it.\n *\n * Distinguishes a path that does not exist from a path that does not accept\n * this method. `DELETE /things/1` where only `GET` is mounted is a **405** with\n * an `Allow` header, not a 404: the resource is there, the verb is not, and\n * 404 tells a client to stop looking for something that exists. Requires the\n * mounted route table, so an app that does not pass one keeps answering 404.\n */\nexport function notFoundHandler(routes: readonly MountedRoute[] = []) {\n const matchers = routes.map((route) => ({\n method: route.method.toUpperCase(),\n matches: patternToRegExp(route.path),\n }))\n\n return (req: ErrorRequest, res: RuntimeResponse, _next: () => void) => {\n const path = pathOf(req)\n const method = (req.method ?? 'GET').toUpperCase()\n const allowed = [...new Set(matchers.filter((m) => m.matches.test(path)).map((m) => m.method))]\n\n if (allowed.length > 0 && !allowed.includes(method)) {\n // Sorted once: the header and the detail must list the same verbs in the\n // same order, and `sort()` would mutate `allowed` under the second read.\n const verbs = allowed.toSorted().join(', ')\n // RFC 9110 §15.5.6 requires Allow on a 405.\n res.setHeader('Allow', verbs)\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(405).json(\n normalizeProblem({\n status: 405,\n detail: `${method} is not supported for this resource. Allowed: ${verbs}.`,\n }),\n )\n }\n\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(404).json(normalizeProblem({ status: 404 }))\n }\n}\n\n/**\n * Global error handler.\n *\n * Default behavior strips error `details` from the response in production\n * (NODE_ENV === 'production') so internal context (DB errors, validation\n * issues, custom payloads) does not leak to clients. Apps that want to\n * expose details in production — for client-facing field-level validation,\n * for example — should pass their own `onError` to `bootstrap()` and decide\n * the policy explicitly.\n *\n * Unexpected 500s carry `requestId` in every environment (the correlation\n * handle back to the log line) and, outside production, the error summary\n * plus stack. Production bodies stay opaque.\n *\n * {@link ProblemException} and plain {@link HttpException} both emit\n * `application/problem+json` per RFC 9457. `ProblemException` carries a\n * caller-supplied `type` / `title` / extensions; an `HttpException`'s message\n * becomes `detail`, with `title` and `type` defaulted from the status. One\n * shape, so a client never has to branch on which one the app threw.\n */\nexport function errorHandler() {\n const isProduction = process.env.NODE_ENV === 'production'\n return (err: any, req: ErrorRequest, res: RuntimeResponse, _next: () => void) => {\n // Don't write after headers are already sent\n if (res.headersSent) {\n log.warn(`Error after headers sent: ${err?.message || 'Unknown'}`)\n return\n }\n\n // RFC 9457 Problem Details — checked before HttpException because\n // ProblemException extends HttpException; instanceof on the base\n // would otherwise swallow it.\n if (err instanceof ProblemException) {\n if (err.problem.status >= 500) {\n log.error(err, err.message)\n }\n if (err.headers) {\n for (const [k, v] of Object.entries(err.headers)) {\n res.setHeader(k, v)\n }\n }\n const body = normalizeProblem(err.problem)\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(body.status).json(body)\n }\n\n // Zod validation errors\n if (err?.name === 'ZodError') {\n const firstIssue = err.issues?.[0]\n return res.status(422).json({\n message: firstIssue?.message || 'Validation failed',\n ...(isProduction ? {} : { errors: err.issues }),\n })\n }\n\n // HttpException (expected application errors) — RFC 9457, same as\n // ProblemException. It used to answer a bare `{ message }`, which made it\n // THE path a client parsing `application/problem+json` had to special-case\n // (#611) — the exact situation #587 removed from the catch-all. The\n // message becomes `detail`; `title` and `type` default from the status.\n if (err instanceof HttpException) {\n if (err.status >= 500) {\n log.error(err, err.message)\n }\n if (err.headers) {\n for (const [k, v] of Object.entries(err.headers)) {\n res.setHeader(k, v)\n }\n }\n const exposeDetails = !isProduction && err.details !== undefined\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(err.status).json(\n normalizeProblem({\n status: err.status,\n detail: err.message,\n // Extension member, permitted by RFC 9457 §3.2. Held back in\n // production for the same reason as before: `details` can carry the\n // shape of the request that failed.\n ...(exposeDetails ? { errors: err.details } : {}),\n }),\n )\n }\n\n // Unexpected errors — always log\n const status = err.status || err.statusCode || 500\n const requestId = (req as any).requestId ?? req.headers['x-request-id']\n log.error(\n err,\n // `originalUrl` is Express-only. Fastify and h3 pass `request.raw`, so\n // this logged `GET undefined — <error>` on every error under those\n // runtimes — the path silently dropped from the one line meant to\n // identify the failing request.\n `${req.method} ${req.originalUrl ?? req.url} — ${describeError(err)}${\n requestId ? ` [${requestId}]` : ''\n }`,\n )\n\n if (status !== 500) {\n return res.status(status).json({\n message: err.message || 'Error',\n ...(requestId ? { requestId } : {}),\n })\n }\n\n // A 500 body must never carry the raw error in production — it can\n // contain table names, SQL, connection strings, or user data. But\n // returning a bare `{ message: 'Internal Server Error' }` in\n // development means the one place a developer is looking tells them\n // nothing, and the failure has to be re-diagnosed from the database.\n //\n // Development gets the full picture. Production gets the requestId,\n // which is the correlation handle back to the (now stack-carrying)\n // log line — without it an opaque 500 can't even be tied to its own\n // log entry.\n res.status(500).json({\n message: 'Internal Server Error',\n ...(requestId ? { requestId } : {}),\n ...(isProduction\n ? {}\n : {\n error: describeError(err),\n ...(typeof err?.stack === 'string' ? { stack: err.stack.split('\\n') } : {}),\n }),\n })\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"error-handler.mjs","names":[],"sources":["../../../src/http/middleware/error-handler.ts"],"sourcesContent":["import type { RuntimeResponse } from '../runtime'\n\n/**\n * The slice of the request this handler reads, spelled from node's\n * `IncomingMessage` rather than Express's `Request`.\n *\n * Fastify and h3 hand the error handler `request.raw` — a plain\n * `IncomingMessage` — so anything Express-only is `undefined` there. This is\n * the default handler for EVERY runtime, so it must not assume Express.\n */\ninterface ErrorRequest {\n method?: string\n url?: string\n /** Express-only; absent on the raw node request the other runtimes pass. */\n originalUrl?: string\n headers: Record<string, string | string[] | undefined>\n}\nimport {\n HttpException,\n ProblemException,\n normalizeProblem,\n createLogger,\n describeError,\n} from '../../core'\n\nconst log = createLogger('ErrorHandler')\n\n/** A mounted route, as the catch-all needs to see it. */\nexport interface MountedRoute {\n method: string\n /** Full path as mounted, `:param` segments included. */\n path: string\n}\n\n/** `/things/:id` → `^/things/[^/]+$`, so a concrete path can be matched. */\nfunction patternToRegExp(pattern: string): RegExp {\n const source = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\/:[^/]+/g, '/[^/]+')\n .replace(/\\*/g, '.*')\n return new RegExp(`^${source}/?$`)\n}\n\n/** Path without query string or hash. */\nfunction pathOf(req: ErrorRequest): string {\n const url = req.originalUrl ?? req.url ?? '/'\n const cut = url.search(/[?#]/)\n return cut === -1 ? url : url.slice(0, cut)\n}\n\n/**\n * Catch-all for unmatched routes.\n *\n * Answers RFC 9457 problem details, like every other error the framework\n * produces — the catch-all was the one path still emitting a bare\n * `{ message }`, so a client parsing `application/problem+json` had to special\n * case it.\n *\n * Distinguishes a path that does not exist from a path that does not accept\n * this method. `DELETE /things/1` where only `GET` is mounted is a **405** with\n * an `Allow` header, not a 404: the resource is there, the verb is not, and\n * 404 tells a client to stop looking for something that exists. Requires the\n * mounted route table, so an app that does not pass one keeps answering 404.\n */\nexport function notFoundHandler(routes: readonly MountedRoute[] = []) {\n const matchers = routes.map((route) => ({\n method: route.method.toUpperCase(),\n matches: patternToRegExp(route.path),\n }))\n\n return (req: ErrorRequest, res: RuntimeResponse, _next: () => void) => {\n const path = pathOf(req)\n const method = (req.method ?? 'GET').toUpperCase()\n const allowed = [...new Set(matchers.filter((m) => m.matches.test(path)).map((m) => m.method))]\n\n if (allowed.length > 0 && !allowed.includes(method)) {\n // Sorted once: the header and the detail must list the same verbs in the\n // same order, and `sort()` would mutate `allowed` under the second read.\n const verbs = allowed.toSorted().join(', ')\n // RFC 9110 §15.5.6 requires Allow on a 405.\n res.setHeader('Allow', verbs)\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(405).json(\n normalizeProblem({\n status: 405,\n detail: `${method} is not supported for this resource. Allowed: ${verbs}.`,\n }),\n )\n }\n\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(404).json(normalizeProblem({ status: 404 }))\n }\n}\n\n/**\n * Global error handler.\n *\n * Default behavior strips error `details` from the response in production\n * (NODE_ENV === 'production') so internal context (DB errors, validation\n * issues, custom payloads) does not leak to clients. Apps that want to\n * expose details in production — for client-facing field-level validation,\n * for example — should pass their own `onError` to `bootstrap()` and decide\n * the policy explicitly.\n *\n * Unexpected 500s carry `requestId` in every environment (the correlation\n * handle back to the log line) and, outside production, the error summary\n * plus stack. Production bodies stay opaque.\n *\n * {@link ProblemException} and plain {@link HttpException} both emit\n * `application/problem+json` per RFC 9457. `ProblemException` carries a\n * caller-supplied `type` / `title` / extensions; an `HttpException`'s message\n * becomes `detail`, with `title` and `type` defaulted from the status. One\n * shape, so a client never has to branch on which one the app threw.\n */\nexport function errorHandler() {\n const isProduction = process.env.NODE_ENV === 'production'\n return (err: any, req: ErrorRequest, res: RuntimeResponse, _next: () => void) => {\n // Don't write after headers are already sent\n if (res.headersSent) {\n log.warn(`Error after headers sent: ${err?.message || 'Unknown'}`)\n return\n }\n\n // RFC 9457 Problem Details — checked before HttpException because\n // ProblemException extends HttpException; instanceof on the base\n // would otherwise swallow it.\n if (err instanceof ProblemException) {\n if (err.problem.status >= 500) {\n log.error(err, err.message)\n }\n if (err.headers) {\n for (const [k, v] of Object.entries(err.headers)) {\n res.setHeader(k, v)\n }\n }\n const body = normalizeProblem(err.problem)\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(body.status).json(body)\n }\n\n // Zod validation errors\n if (err?.name === 'ZodError') {\n const firstIssue = err.issues?.[0]\n return res.status(422).json({\n message: firstIssue?.message || 'Validation failed',\n ...(isProduction ? {} : { errors: err.issues }),\n })\n }\n\n // HttpException (expected application errors) — RFC 9457, same as\n // ProblemException. It used to answer a bare `{ message }`, which made it\n // THE path a client parsing `application/problem+json` had to special-case\n // (#611) — the exact situation #587 removed from the catch-all. The\n // message becomes `detail`; `title` and `type` default from the status.\n if (err instanceof HttpException) {\n if (err.status >= 500) {\n log.error(err, err.message)\n }\n if (err.headers) {\n for (const [k, v] of Object.entries(err.headers)) {\n res.setHeader(k, v)\n }\n }\n const exposeDetails = !isProduction && err.details !== undefined\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(err.status).json(\n normalizeProblem({\n status: err.status,\n detail: err.message,\n // Extension member, permitted by RFC 9457 §3.2. Held back in\n // production for the same reason as before: `details` can carry the\n // shape of the request that failed.\n ...(exposeDetails ? { errors: err.details } : {}),\n }),\n )\n }\n\n const requestId = (req as any).requestId ?? req.headers['x-request-id']\n const declared = Number(err.status ?? err.statusCode)\n\n // Any error that says it's a client error — a database UniqueViolationError\n // (409), a BYO auth check's 401 — is answered like an HttpException: RFC\n // 9457, its message as `detail`, and a warning rather than an error log.\n // It used to fall through to the catch-all: logged at ERROR with a stack,\n // answered with a bare `{ message }` no problem+json client could parse.\n if (Number.isInteger(declared) && declared >= 400 && declared < 500) {\n log.warn(`${req.method} ${req.originalUrl ?? req.url} — ${declared} ${describeError(err)}`)\n res.setHeader('Content-Type', 'application/problem+json')\n return res.status(declared).json(\n normalizeProblem({\n status: declared,\n detail: err.message || undefined,\n ...(requestId ? { requestId } : {}),\n }),\n )\n }\n\n // Unexpected errors — always log. A declared 5xx keeps its status but\n // gets the same guarded body as a 500.\n const status = Number.isInteger(declared) && declared >= 500 && declared < 600 ? declared : 500\n log.error(\n err,\n // `originalUrl` is Express-only. Fastify and h3 pass `request.raw`, so\n // this logged `GET undefined — <error>` on every error under those\n // runtimes — the path silently dropped from the one line meant to\n // identify the failing request.\n `${req.method} ${req.originalUrl ?? req.url} — ${describeError(err)}${\n requestId ? ` [${requestId}]` : ''\n }`,\n )\n\n // A 500 body must never carry the raw error in production — it can\n // contain table names, SQL, connection strings, or user data. But\n // returning a bare `{ message: 'Internal Server Error' }` in\n // development means the one place a developer is looking tells them\n // nothing, and the failure has to be re-diagnosed from the database.\n //\n // Development gets the full picture. Production gets the requestId,\n // which is the correlation handle back to the (now stack-carrying)\n // log line — without it an opaque 500 can't even be tied to its own\n // log entry.\n res.status(status).json({\n message: 'Internal Server Error',\n ...(requestId ? { requestId } : {}),\n ...(isProduction\n ? {}\n : {\n error: describeError(err),\n ...(typeof err?.stack === 'string' ? { stack: err.stack.split('\\n') } : {}),\n }),\n })\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{r as registerDisposable}from"../../disposables-BFIEp5VA.mjs";import{n as assertFlagTest,s as matchesFlagTest}from"../../route-flag-BCBTtP_4.mjs";import{n as bindRoutePolicy}from"../../route-policy-FeJHstvF.mjs";import{n as resolvePathname,t as resolveClientIp}from"../../client-ip-BuY7_pDD.mjs";import{r as sendJson}from"../../respond-DBRvpEI5.mjs";var MemoryStore=class{windowMs;hits=new Map;cleanupTimer;constructor(windowMs){this.windowMs=windowMs,this.cleanupTimer=setInterval(()=>this.cleanup(),windowMs),this.cleanupTimer.unref&&this.cleanupTimer.unref(),registerDisposable(()=>this.dispose())}dispose(){clearInterval(this.cleanupTimer),this.hits.clear()}async increment(key){let now=Date.now(),entry=this.hits.get(key);if(entry&&entry.resetTime.getTime()>now)return entry.totalHits++,{totalHits:entry.totalHits,resetTime:entry.resetTime};let resetTime=new Date(now+this.windowMs),newEntry={totalHits:1,resetTime};return this.hits.set(key,newEntry),{totalHits:1,resetTime}}async decrement(key){let entry=this.hits.get(key);entry&&entry.totalHits>0&&entry.totalHits--}async reset(key){this.hits.delete(key)}cleanup(){let now=Date.now();for(let[key,entry]of this.hits)entry.resetTime.getTime()<=now&&this.hits.delete(key)}};function rateLimit(options={}){let max=options.max??100,windowMs=options.windowMs??6e4,message=options.message??`Too Many Requests`,statusCode=options.statusCode??429,keyGenerator=options.keyGenerator??(req=>resolveClientIp(req)??`global`),sendHeaders=options.headers??!0,store=options.store??new MemoryStore(windowMs),skip=options.skip,skipPaths=new Set(options.skipPaths??[]),exemptWhen=options.exemptWhen;exemptWhen!==void 0&&assertFlagTest(exemptWhen,`rateLimit({ exemptWhen })`);let policy;return bindRoutePolicy(async(req,res,next)=>{let pathname=resolvePathname(req);if(skipPaths.has(pathname))return next();if(exemptWhen!==void 0&&policy){let flags=policy.lookup(String(req.method??`GET`),pathname);if(matchesFlagTest(exemptWhen,flags))return next()}if(skip&&skip(req))return next();let key=keyGenerator(req),{totalHits,resetTime}=await store.increment(key),remaining=Math.max(0,max-totalHits);if(sendHeaders&&(res.setHeader(`RateLimit-Limit`,max),res.setHeader(`RateLimit-Remaining`,remaining),res.setHeader(`RateLimit-Reset`,Math.ceil(resetTime.getTime()/1e3))),totalHits>max)return sendJson(res,statusCode,{message});next()},table=>{policy=table})}export{rateLimit};
11
+ import{r as registerDisposable}from"../../disposables-CLTUN_9Z.mjs";import{n as assertFlagTest,s as matchesFlagTest}from"../../route-flag-BeSiIg98.mjs";import{n as bindRoutePolicy}from"../../route-policy-Ch0TlY_K.mjs";import{n as resolvePathname,t as resolveClientIp}from"../../client-ip-B-LhFjcb.mjs";import{r as sendJson}from"../../respond-CUPNdYfi.mjs";var MemoryStore=class{windowMs;hits=new Map;cleanupTimer;constructor(windowMs){this.windowMs=windowMs,this.cleanupTimer=setInterval(()=>this.cleanup(),windowMs),this.cleanupTimer.unref&&this.cleanupTimer.unref(),registerDisposable(()=>this.dispose())}dispose(){clearInterval(this.cleanupTimer),this.hits.clear()}async increment(key){let now=Date.now(),entry=this.hits.get(key);if(entry&&entry.resetTime.getTime()>now)return entry.totalHits++,{totalHits:entry.totalHits,resetTime:entry.resetTime};let resetTime=new Date(now+this.windowMs),newEntry={totalHits:1,resetTime};return this.hits.set(key,newEntry),{totalHits:1,resetTime}}async decrement(key){let entry=this.hits.get(key);entry&&entry.totalHits>0&&entry.totalHits--}async reset(key){this.hits.delete(key)}cleanup(){let now=Date.now();for(let[key,entry]of this.hits)entry.resetTime.getTime()<=now&&this.hits.delete(key)}};function rateLimit(options={}){let max=options.max??100,windowMs=options.windowMs??6e4,message=options.message??`Too Many Requests`,statusCode=options.statusCode??429,keyGenerator=options.keyGenerator??(req=>resolveClientIp(req)??`global`),sendHeaders=options.headers??!0,store=options.store??new MemoryStore(windowMs),skip=options.skip,skipPaths=new Set(options.skipPaths??[]),exemptWhen=options.exemptWhen;exemptWhen!==void 0&&assertFlagTest(exemptWhen,`rateLimit({ exemptWhen })`);let policy;return bindRoutePolicy(async(req,res,next)=>{let pathname=resolvePathname(req);if(skipPaths.has(pathname))return next();if(exemptWhen!==void 0&&policy){let flags=policy.lookup(String(req.method??`GET`),pathname);if(matchesFlagTest(exemptWhen,flags))return next()}if(skip&&skip(req))return next();let key=keyGenerator(req),{totalHits,resetTime}=await store.increment(key),remaining=Math.max(0,max-totalHits);if(sendHeaders&&(res.setHeader(`RateLimit-Limit`,max),res.setHeader(`RateLimit-Remaining`,remaining),res.setHeader(`RateLimit-Reset`,Math.ceil(resetTime.getTime()/1e3))),totalHits>max)return sendJson(res,statusCode,{message});next()},table=>{policy=table})}export{rateLimit};
12
12
  //# sourceMappingURL=rate-limit.mjs.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{createLogger}from"../../core/logger.mjs";import"../../core-Chc61ahe.mjs";function pathnameOf(url){if(!url)return`/`;let q=url.indexOf(`?`);return(q===-1?url:url.slice(0,q))||`/`}function requestLogger(options={}){let log=createLogger(options.name??`HTTP`),level=options.level??`info`,skip=options.skip??[];return(req,res,next)=>{let pathname=req.path??pathnameOf(req.url);if(skip.some(prefix=>pathname.startsWith(prefix)))return next();let start=Date.now();res.on(`finish`,()=>{let duration=Date.now()-start,requestId=req.requestId||req.headers[`x-request-id`]||`-`,status=res.statusCode,path=req.originalUrl??req.url??`/`;log[level](`${req.method} ${path} ${status} ${duration}ms ${requestId}`)}),next()}}export{requestLogger};
11
+ import{createLogger}from"../../core/logger.mjs";import"../../core-BpHbZ7P8.mjs";function pathnameOf(url){if(!url)return`/`;let q=url.indexOf(`?`);return(q===-1?url:url.slice(0,q))||`/`}function requestLogger(options={}){let log=createLogger(options.name??`HTTP`),level=options.level??`info`,skip=options.skip??[];return(req,res,next)=>{let pathname=req.path??pathnameOf(req.url);if(skip.some(prefix=>pathname.startsWith(prefix)))return next();let start=Date.now();res.on(`finish`,()=>{let duration=Date.now()-start,requestId=req.requestId||req.headers[`x-request-id`]||`-`,status=res.statusCode,path=req.originalUrl??req.url??`/`;log[level](`${req.method} ${path} ${status} ${duration}ms ${requestId}`)}),next()}}export{requestLogger};
12
12
  //# sourceMappingURL=request-logger.mjs.map
@@ -1,3 +1,3 @@
1
1
 
2
- import { a as session, i as SessionStore, n as SessionData, r as SessionOptions, t as Session } from "../../session-BHgRyfqZ.mjs";
2
+ import { a as session, i as SessionStore, n as SessionData, r as SessionOptions, t as Session } from "../../session-4Tb6q0Ny.mjs";
3
3
  export { Session, SessionData, SessionOptions, SessionStore, session };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs v8.7.0
2
+ * @forinda/kickjs v9.0.1
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{r as registerDisposable}from"../../disposables-BFIEp5VA.mjs";import{i as setCookie,t as parseCookieHeader}from"../../respond-DBRvpEI5.mjs";import{createHmac,randomUUID,timingSafeEqual}from"node:crypto";var MemoryStore=class{sessions=new Map;cleanupInterval;constructor(){this.cleanupInterval=setInterval(()=>this.purge(),6e4),this.cleanupInterval.unref(),registerDisposable(()=>this.dispose())}dispose(){clearInterval(this.cleanupInterval),this.sessions.clear()}async get(sid){let entry=this.sessions.get(sid);return entry?Date.now()>entry.expires?(this.sessions.delete(sid),null):entry.data:null}async set(sid,data,maxAge){this.sessions.set(sid,{data,expires:Date.now()+maxAge})}async destroy(sid){this.sessions.delete(sid)}async touch(sid,maxAge){let entry=this.sessions.get(sid);entry&&(entry.expires=Date.now()+maxAge)}purge(){let now=Date.now();for(let[sid,entry]of this.sessions)now>entry.expires&&this.sessions.delete(sid)}};function sign(value,secret){return`s:${value}.${createHmac(`sha256`,secret).update(value).digest(`base64url`)}`}function unsign(signed,secret){if(!signed.startsWith(`s:`))return!1;let raw=signed.slice(2),dotIndex=raw.lastIndexOf(`.`);if(dotIndex===-1)return!1;let value=raw.slice(0,dotIndex),providedSig=raw.slice(dotIndex+1),expectedSig=createHmac(`sha256`,secret).update(value).digest(`base64url`),a=Buffer.from(providedSig),b=Buffer.from(expectedSig);return a.length!==b.length||!timingSafeEqual(a,b)?!1:value}function session(options){let{secret,cookieName=`kick.sid`,maxAge=864e5,rolling=!1,saveUninitialized=!0,store=new MemoryStore,cookie:cookieOpts={}}=options,cookieDefaults={httpOnly:cookieOpts.httpOnly??!0,secure:cookieOpts.secure??process.env.NODE_ENV===`production`,sameSite:cookieOpts.sameSite??`lax`,path:cookieOpts.path??`/`,...cookieOpts.domain?{domain:cookieOpts.domain}:{},maxAge};return async(req,res,next)=>{let cookies=req.cookies;if(!cookies){let header=req.headers.cookie;cookies=header?parseCookieHeader(header):{}}let signedCookie=cookies[cookieName],sid=!1,sessionData=null,isNew=!1;signedCookie&&(sid=unsign(signedCookie,secret),sid&&(sessionData=await store.get(sid))),(!sid||!sessionData)&&(sid=randomUUID(),sessionData={},isNew=!0);let currentSid=sid,currentData={...sessionData},destroyed=!1,sessionObj={get id(){return currentSid},data:currentData,async regenerate(){await store.destroy(currentSid),currentSid=randomUUID(),currentData={},sessionObj.data=currentData,await store.set(currentSid,currentData,maxAge),setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults)},async destroy(){await store.destroy(currentSid),destroyed=!0,currentData={},sessionObj.data=currentData,res.clearCookie(cookieName,{path:cookieDefaults.path})},async save(){destroyed||await store.set(currentSid,currentData,maxAge)}};req.session=sessionObj,isNew&&setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults),res.on(`finish`,async()=>{destroyed||(!isNew||saveUninitialized||Object.keys(currentData).length!==0)&&(await store.set(currentSid,currentData,maxAge),rolling&&!isNew&&store.touch&&await store.touch(currentSid,maxAge))}),rolling&&!isNew&&setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults),next()}}export{session};
11
+ import{r as registerDisposable}from"../../disposables-CLTUN_9Z.mjs";import{i as setCookie,t as parseCookieHeader}from"../../respond-CUPNdYfi.mjs";import{createHmac,randomUUID,timingSafeEqual}from"node:crypto";var MemoryStore=class{sessions=new Map;cleanupInterval;constructor(){this.cleanupInterval=setInterval(()=>this.purge(),6e4),this.cleanupInterval.unref(),registerDisposable(()=>this.dispose())}dispose(){clearInterval(this.cleanupInterval),this.sessions.clear()}async get(sid){let entry=this.sessions.get(sid);return entry?Date.now()>entry.expires?(this.sessions.delete(sid),null):entry.data:null}async set(sid,data,maxAge){this.sessions.set(sid,{data,expires:Date.now()+maxAge})}async destroy(sid){this.sessions.delete(sid)}async touch(sid,maxAge){let entry=this.sessions.get(sid);entry&&(entry.expires=Date.now()+maxAge)}purge(){let now=Date.now();for(let[sid,entry]of this.sessions)now>entry.expires&&this.sessions.delete(sid)}};function sign(value,secret){return`s:${value}.${createHmac(`sha256`,secret).update(value).digest(`base64url`)}`}function unsign(signed,secret){if(!signed.startsWith(`s:`))return!1;let raw=signed.slice(2),dotIndex=raw.lastIndexOf(`.`);if(dotIndex===-1)return!1;let value=raw.slice(0,dotIndex),providedSig=raw.slice(dotIndex+1),expectedSig=createHmac(`sha256`,secret).update(value).digest(`base64url`),a=Buffer.from(providedSig),b=Buffer.from(expectedSig);return a.length!==b.length||!timingSafeEqual(a,b)?!1:value}function session(options){let{secret,cookieName=`kick.sid`,maxAge=864e5,rolling=!1,saveUninitialized=!0,store=new MemoryStore,cookie:cookieOpts={}}=options,cookieDefaults={httpOnly:cookieOpts.httpOnly??!0,secure:cookieOpts.secure??process.env.NODE_ENV===`production`,sameSite:cookieOpts.sameSite??`lax`,path:cookieOpts.path??`/`,...cookieOpts.domain?{domain:cookieOpts.domain}:{},maxAge};return async(req,res,next)=>{let cookies=req.cookies;if(!cookies){let header=req.headers.cookie;cookies=header?parseCookieHeader(header):{}}let signedCookie=cookies[cookieName],sid=!1,sessionData=null,isNew=!1;signedCookie&&(sid=unsign(signedCookie,secret),sid&&(sessionData=await store.get(sid))),(!sid||!sessionData)&&(sid=randomUUID(),sessionData={},isNew=!0);let currentSid=sid,currentData={...sessionData},destroyed=!1,sessionObj={get id(){return currentSid},data:currentData,async regenerate(){await store.destroy(currentSid),currentSid=randomUUID(),currentData={},sessionObj.data=currentData,await store.set(currentSid,currentData,maxAge),setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults)},async destroy(){await store.destroy(currentSid),destroyed=!0,currentData={},sessionObj.data=currentData,setCookie(res,cookieName,``,{...cookieDefaults,maxAge:0,expires:new Date(0)})},async save(){destroyed||await store.set(currentSid,currentData,maxAge)}};req.session=sessionObj,isNew&&setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults),res.on(`finish`,async()=>{destroyed||(!isNew||saveUninitialized||Object.keys(currentData).length!==0)&&(await store.set(currentSid,currentData,maxAge),rolling&&!isNew&&store.touch&&await store.touch(currentSid,maxAge))}),rolling&&!isNew&&setCookie(res,cookieName,sign(currentSid,secret),cookieDefaults),next()}}export{session};
12
12
  //# sourceMappingURL=session.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.mjs","names":[],"sources":["../../../src/http/middleware/session.ts"],"sourcesContent":["import { parseCookieHeader, setCookie } from './respond'\nimport { randomUUID, createHmac, timingSafeEqual } from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\n\nimport { registerDisposable } from '../../core/disposables'\n\nexport interface SessionData {\n [key: string]: unknown\n}\n\nexport interface SessionStore {\n get(sid: string): Promise<SessionData | null>\n set(sid: string, data: SessionData, maxAge: number): Promise<void>\n destroy(sid: string): Promise<void>\n touch?(sid: string, maxAge: number): Promise<void>\n}\n\nexport interface Session {\n id: string\n data: SessionData\n regenerate(): Promise<void>\n destroy(): Promise<void>\n save(): Promise<void>\n}\n\nexport interface SessionOptions {\n /** Secret used to sign the session cookie (required) */\n secret: string\n /** Cookie name (default: 'kick.sid') */\n cookieName?: string\n /** Session max age in milliseconds (default: 86400000 = 24h) */\n maxAge?: number\n /** Reset maxAge on every response (default: false) */\n rolling?: boolean\n /** Save new sessions that have not been modified (default: true) */\n saveUninitialized?: boolean\n /** Cookie options */\n cookie?: {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n path?: string\n domain?: string\n }\n /** Custom session store (default: in-memory store with TTL cleanup) */\n store?: SessionStore\n}\n\n// ── In-Memory Store ───────────────────────────────────────────────────\n\nclass MemoryStore implements SessionStore {\n private readonly sessions = new Map<string, { data: SessionData; expires: number }>()\n private readonly cleanupInterval: ReturnType<typeof setInterval>\n\n constructor() {\n // Purge expired sessions every 60 seconds\n this.cleanupInterval = setInterval(() => this.purge(), 60_000)\n this.cleanupInterval.unref()\n // Symmetric teardown: Application.shutdown() drains the disposables\n // registry — without this, every in-process app re-create (tests, HMR)\n // leaked one live interval + its session Map.\n registerDisposable(() => this.dispose())\n }\n\n /** Clear the cleanup interval and drop all sessions. */\n dispose(): void {\n clearInterval(this.cleanupInterval)\n this.sessions.clear()\n }\n\n async get(sid: string): Promise<SessionData | null> {\n const entry = this.sessions.get(sid)\n if (!entry) return null\n if (Date.now() > entry.expires) {\n this.sessions.delete(sid)\n return null\n }\n return entry.data\n }\n\n async set(sid: string, data: SessionData, maxAge: number): Promise<void> {\n this.sessions.set(sid, { data, expires: Date.now() + maxAge })\n }\n\n async destroy(sid: string): Promise<void> {\n this.sessions.delete(sid)\n }\n\n async touch(sid: string, maxAge: number): Promise<void> {\n const entry = this.sessions.get(sid)\n if (entry) {\n entry.expires = Date.now() + maxAge\n }\n }\n\n private purge() {\n const now = Date.now()\n for (const [sid, entry] of this.sessions) {\n if (now > entry.expires) {\n this.sessions.delete(sid)\n }\n }\n }\n}\n\n// ── Cookie Signing ────────────────────────────────────────────────────\n\nfunction sign(value: string, secret: string): string {\n const signature = createHmac('sha256', secret).update(value).digest('base64url')\n return `s:${value}.${signature}`\n}\n\nfunction unsign(signed: string, secret: string): string | false {\n if (!signed.startsWith('s:')) return false\n const raw = signed.slice(2)\n const dotIndex = raw.lastIndexOf('.')\n if (dotIndex === -1) return false\n\n const value = raw.slice(0, dotIndex)\n const providedSig = raw.slice(dotIndex + 1)\n const expectedSig = createHmac('sha256', secret).update(value).digest('base64url')\n\n const a = Buffer.from(providedSig)\n const b = Buffer.from(expectedSig)\n\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n return false\n }\n\n return value\n}\n\n// ── Middleware Factory ────────────────────────────────────────────────\n\n/**\n * Session management middleware.\n *\n * Attaches a `req.session` object with `id`, `data`, `regenerate()`,\n * `destroy()`, and `save()` methods. Session IDs are signed with\n * HMAC-SHA256 to prevent cookie tampering.\n *\n * Reads the session cookie from `req.cookies` when an upstream cookie\n * parser is present; otherwise parses `req.headers.cookie` directly, so\n * no cookie parser is required upstream.\n *\n * @example\n * ```ts\n * import { session } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middlewares: [\n * session({ secret: process.env.SESSION_SECRET! }),\n * // ... other middleware\n * ],\n * })\n * ```\n */\nexport function session(options: SessionOptions) {\n const {\n secret,\n cookieName = 'kick.sid',\n maxAge = 86_400_000,\n rolling = false,\n saveUninitialized = true,\n store = new MemoryStore(),\n cookie: cookieOpts = {},\n } = options\n\n const cookieDefaults = {\n httpOnly: cookieOpts.httpOnly ?? true,\n secure: cookieOpts.secure ?? process.env.NODE_ENV === 'production',\n sameSite: cookieOpts.sameSite ?? ('lax' as const),\n path: cookieOpts.path ?? '/',\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n maxAge,\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n let cookies = (req as any).cookies as Record<string, string> | undefined\n if (!cookies) {\n const header = req.headers.cookie\n cookies = header ? parseCookieHeader(header) : {}\n }\n const signedCookie = cookies[cookieName]\n let sid: string | false = false\n let sessionData: SessionData | null = null\n let isNew = false\n\n // Attempt to recover existing session\n if (signedCookie) {\n sid = unsign(signedCookie, secret)\n if (sid) {\n sessionData = await store.get(sid)\n }\n }\n\n // Create new session if none found\n if (!sid || !sessionData) {\n sid = randomUUID()\n sessionData = {}\n isNew = true\n }\n\n let currentSid = sid\n let currentData = { ...sessionData }\n let destroyed = false\n\n const sessionObj: Session = {\n get id() {\n return currentSid\n },\n data: currentData,\n\n async regenerate() {\n await store.destroy(currentSid)\n currentSid = randomUUID()\n currentData = {}\n sessionObj.data = currentData\n await store.set(currentSid, currentData, maxAge)\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n },\n\n async destroy() {\n await store.destroy(currentSid)\n destroyed = true\n currentData = {}\n sessionObj.data = currentData\n res.clearCookie(cookieName, { path: cookieDefaults.path })\n },\n\n async save() {\n if (!destroyed) {\n await store.set(currentSid, currentData, maxAge)\n }\n },\n }\n\n ;(req as any).session = sessionObj\n\n // Set cookie for new sessions\n if (isNew) {\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n // Auto-save on response finish\n res.on('finish', async () => {\n if (destroyed) return\n if (isNew && !saveUninitialized && Object.keys(currentData).length === 0) return\n\n await store.set(currentSid, currentData, maxAge)\n\n if (rolling && !isNew) {\n if (store.touch) {\n await store.touch(currentSid, maxAge)\n }\n }\n })\n\n // Rolling: refresh cookie on every response\n if (rolling && !isNew) {\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n next()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session.mjs","names":[],"sources":["../../../src/http/middleware/session.ts"],"sourcesContent":["import { parseCookieHeader, setCookie } from './respond'\nimport { randomUUID, createHmac, timingSafeEqual } from 'node:crypto'\nimport type { Request, Response, NextFunction } from 'express'\n\nimport { registerDisposable } from '../../core/disposables'\n\n/**\n * What a session holds. Augment it to type your keys — undeclared keys stay\n * readable as `unknown`:\n *\n * ```ts\n * declare module '@forinda/kickjs' {\n * interface SessionData { userId?: string; cart?: string[] }\n * }\n * ```\n */\nexport interface SessionData {\n [key: string]: unknown\n}\n\nexport interface SessionStore {\n get(sid: string): Promise<SessionData | null>\n set(sid: string, data: SessionData, maxAge: number): Promise<void>\n destroy(sid: string): Promise<void>\n touch?(sid: string, maxAge: number): Promise<void>\n}\n\nexport interface Session {\n id: string\n data: SessionData\n regenerate(): Promise<void>\n destroy(): Promise<void>\n save(): Promise<void>\n}\n\nexport interface SessionOptions {\n /** Secret used to sign the session cookie (required) */\n secret: string\n /** Cookie name (default: 'kick.sid') */\n cookieName?: string\n /** Session max age in milliseconds (default: 86400000 = 24h) */\n maxAge?: number\n /** Reset maxAge on every response (default: false) */\n rolling?: boolean\n /** Save new sessions that have not been modified (default: true) */\n saveUninitialized?: boolean\n /** Cookie options */\n cookie?: {\n httpOnly?: boolean\n secure?: boolean\n sameSite?: 'strict' | 'lax' | 'none'\n path?: string\n domain?: string\n }\n /** Custom session store (default: in-memory store with TTL cleanup) */\n store?: SessionStore\n}\n\n// ── In-Memory Store ───────────────────────────────────────────────────\n\nclass MemoryStore implements SessionStore {\n private readonly sessions = new Map<string, { data: SessionData; expires: number }>()\n private readonly cleanupInterval: ReturnType<typeof setInterval>\n\n constructor() {\n // Purge expired sessions every 60 seconds\n this.cleanupInterval = setInterval(() => this.purge(), 60_000)\n this.cleanupInterval.unref()\n // Symmetric teardown: Application.shutdown() drains the disposables\n // registry — without this, every in-process app re-create (tests, HMR)\n // leaked one live interval + its session Map.\n registerDisposable(() => this.dispose())\n }\n\n /** Clear the cleanup interval and drop all sessions. */\n dispose(): void {\n clearInterval(this.cleanupInterval)\n this.sessions.clear()\n }\n\n async get(sid: string): Promise<SessionData | null> {\n const entry = this.sessions.get(sid)\n if (!entry) return null\n if (Date.now() > entry.expires) {\n this.sessions.delete(sid)\n return null\n }\n return entry.data\n }\n\n async set(sid: string, data: SessionData, maxAge: number): Promise<void> {\n this.sessions.set(sid, { data, expires: Date.now() + maxAge })\n }\n\n async destroy(sid: string): Promise<void> {\n this.sessions.delete(sid)\n }\n\n async touch(sid: string, maxAge: number): Promise<void> {\n const entry = this.sessions.get(sid)\n if (entry) {\n entry.expires = Date.now() + maxAge\n }\n }\n\n private purge() {\n const now = Date.now()\n for (const [sid, entry] of this.sessions) {\n if (now > entry.expires) {\n this.sessions.delete(sid)\n }\n }\n }\n}\n\n// ── Cookie Signing ────────────────────────────────────────────────────\n\nfunction sign(value: string, secret: string): string {\n const signature = createHmac('sha256', secret).update(value).digest('base64url')\n return `s:${value}.${signature}`\n}\n\nfunction unsign(signed: string, secret: string): string | false {\n if (!signed.startsWith('s:')) return false\n const raw = signed.slice(2)\n const dotIndex = raw.lastIndexOf('.')\n if (dotIndex === -1) return false\n\n const value = raw.slice(0, dotIndex)\n const providedSig = raw.slice(dotIndex + 1)\n const expectedSig = createHmac('sha256', secret).update(value).digest('base64url')\n\n const a = Buffer.from(providedSig)\n const b = Buffer.from(expectedSig)\n\n if (a.length !== b.length || !timingSafeEqual(a, b)) {\n return false\n }\n\n return value\n}\n\n// ── Middleware Factory ────────────────────────────────────────────────\n\n/**\n * Session management middleware.\n *\n * Attaches a `req.session` object with `id`, `data`, `regenerate()`,\n * `destroy()`, and `save()` methods. Session IDs are signed with\n * HMAC-SHA256 to prevent cookie tampering.\n *\n * Reads the session cookie from `req.cookies` when an upstream cookie\n * parser is present; otherwise parses `req.headers.cookie` directly, so\n * no cookie parser is required upstream.\n *\n * @example\n * ```ts\n * import { session } from '@forinda/kickjs'\n *\n * bootstrap({\n * modules,\n * middlewares: [\n * session({ secret: process.env.SESSION_SECRET! }),\n * // ... other middleware\n * ],\n * })\n * ```\n */\nexport function session(options: SessionOptions) {\n const {\n secret,\n cookieName = 'kick.sid',\n maxAge = 86_400_000,\n rolling = false,\n saveUninitialized = true,\n store = new MemoryStore(),\n cookie: cookieOpts = {},\n } = options\n\n const cookieDefaults = {\n httpOnly: cookieOpts.httpOnly ?? true,\n secure: cookieOpts.secure ?? process.env.NODE_ENV === 'production',\n sameSite: cookieOpts.sameSite ?? ('lax' as const),\n path: cookieOpts.path ?? '/',\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n maxAge,\n }\n\n return async (req: Request, res: Response, next: NextFunction) => {\n let cookies = (req as any).cookies as Record<string, string> | undefined\n if (!cookies) {\n const header = req.headers.cookie\n cookies = header ? parseCookieHeader(header) : {}\n }\n const signedCookie = cookies[cookieName]\n let sid: string | false = false\n let sessionData: SessionData | null = null\n let isNew = false\n\n // Attempt to recover existing session\n if (signedCookie) {\n sid = unsign(signedCookie, secret)\n if (sid) {\n sessionData = await store.get(sid)\n }\n }\n\n // Create new session if none found\n if (!sid || !sessionData) {\n sid = randomUUID()\n sessionData = {}\n isNew = true\n }\n\n let currentSid = sid\n let currentData = { ...sessionData }\n let destroyed = false\n\n const sessionObj: Session = {\n get id() {\n return currentSid\n },\n data: currentData,\n\n async regenerate() {\n await store.destroy(currentSid)\n currentSid = randomUUID()\n currentData = {}\n sessionObj.data = currentData\n await store.set(currentSid, currentData, maxAge)\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n },\n\n async destroy() {\n await store.destroy(currentSid)\n destroyed = true\n currentData = {}\n sessionObj.data = currentData\n // Expire the cookie the same way it was set — `res.clearCookie` is\n // Express-only and threw under Fastify and h3.\n setCookie(res, cookieName, '', { ...cookieDefaults, maxAge: 0, expires: new Date(0) })\n },\n\n async save() {\n if (!destroyed) {\n await store.set(currentSid, currentData, maxAge)\n }\n },\n }\n\n ;(req as any).session = sessionObj\n\n // Set cookie for new sessions\n if (isNew) {\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n // Auto-save on response finish\n res.on('finish', async () => {\n if (destroyed) return\n if (isNew && !saveUninitialized && Object.keys(currentData).length === 0) return\n\n await store.set(currentSid, currentData, maxAge)\n\n if (rolling && !isNew) {\n if (store.touch) {\n await store.touch(currentSid, maxAge)\n }\n }\n })\n\n // Rolling: refresh cookie on every response\n if (rolling && !isNew) {\n setCookie(res, cookieName, sign(currentSid, secret), cookieDefaults)\n }\n\n next()\n }\n}\n"],"mappings":";;;;;;;;;;iNA4DA,IAAM,YAAN,KAA0C,CACxC,SAA4B,IAAI,IAChC,gBAEA,aAAc,CAEZ,KAAK,gBAAkB,gBAAkB,KAAK,MAAM,EAAG,GAAM,EAC7D,KAAK,gBAAgB,MAAM,EAI3B,uBAAyB,KAAK,QAAQ,CAAC,CACzC,CAGA,SAAgB,CACd,cAAc,KAAK,eAAe,EAClC,KAAK,SAAS,MAAM,CACtB,CAEA,MAAM,IAAI,IAA0C,CAClD,IAAM,MAAQ,KAAK,SAAS,IAAI,GAAG,EAMnC,OALK,MACD,KAAK,IAAI,EAAI,MAAM,SACrB,KAAK,SAAS,OAAO,GAAG,EACjB,MAEF,MAAM,KALM,IAMrB,CAEA,MAAM,IAAI,IAAa,KAAmB,OAA+B,CACvE,KAAK,SAAS,IAAI,IAAK,CAAE,KAAM,QAAS,KAAK,IAAI,EAAI,MAAO,CAAC,CAC/D,CAEA,MAAM,QAAQ,IAA4B,CACxC,KAAK,SAAS,OAAO,GAAG,CAC1B,CAEA,MAAM,MAAM,IAAa,OAA+B,CACtD,IAAM,MAAQ,KAAK,SAAS,IAAI,GAAG,EAC/B,QACF,MAAM,QAAU,KAAK,IAAI,EAAI,OAEjC,CAEA,OAAgB,CACd,IAAM,IAAM,KAAK,IAAI,EACrB,IAAK,GAAM,CAAC,IAAK,SAAU,KAAK,SAC1B,IAAM,MAAM,SACd,KAAK,SAAS,OAAO,GAAG,CAG9B,CACF,EAIA,SAAS,KAAK,MAAe,OAAwB,CAEnD,MAAO,KAAK,MAAM,GADA,WAAW,SAAU,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,WACvC,GAC/B,CAEA,SAAS,OAAO,OAAgB,OAAgC,CAC9D,GAAI,CAAC,OAAO,WAAW,IAAI,EAAG,MAAO,GACrC,IAAM,IAAM,OAAO,MAAM,CAAC,EACpB,SAAW,IAAI,YAAY,GAAG,EACpC,GAAI,WAAa,GAAI,MAAO,GAE5B,IAAM,MAAQ,IAAI,MAAM,EAAG,QAAQ,EAC7B,YAAc,IAAI,MAAM,SAAW,CAAC,EACpC,YAAc,WAAW,SAAU,MAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,WAAW,EAE3E,EAAI,OAAO,KAAK,WAAW,EAC3B,EAAI,OAAO,KAAK,WAAW,EAMjC,OAJI,EAAE,SAAW,EAAE,QAAU,CAAC,gBAAgB,EAAG,CAAC,EACzC,GAGF,KACT,CA4BA,SAAgB,QAAQ,QAAyB,CAC/C,GAAM,CACJ,OACA,WAAa,WACb,OAAS,MACT,QAAU,GACV,kBAAoB,GACpB,MAAQ,IAAI,YACZ,OAAQ,WAAa,CAAC,GACpB,QAEE,eAAiB,CACrB,SAAU,WAAW,UAAY,GACjC,OAAQ,WAAW,QAAU,QAAQ,IAAI,WAAa,aACtD,SAAU,WAAW,UAAa,MAClC,KAAM,WAAW,MAAQ,IACzB,GAAI,WAAW,OAAS,CAAE,OAAQ,WAAW,MAAO,EAAI,CAAC,EACzD,MACF,EAEA,OAAO,MAAO,IAAc,IAAe,OAAuB,CAChE,IAAI,QAAW,IAAY,QAC3B,GAAI,CAAC,QAAS,CACZ,IAAM,OAAS,IAAI,QAAQ,OAC3B,QAAU,OAAS,kBAAkB,MAAM,EAAI,CAAC,CAClD,CACA,IAAM,aAAe,QAAQ,YACzB,IAAsB,GACtB,YAAkC,KAClC,MAAQ,GAGR,eACF,IAAM,OAAO,aAAc,MAAM,EAC7B,MACF,YAAc,MAAM,MAAM,IAAI,GAAG,KAKjC,CAAC,KAAO,CAAC,eACX,IAAM,WAAW,EACjB,YAAc,CAAC,EACf,MAAQ,IAGV,IAAI,WAAa,IACb,YAAc,CAAE,GAAG,WAAY,EAC/B,UAAY,GAEV,WAAsB,CAC1B,IAAI,IAAK,CACP,OAAO,UACT,EACA,KAAM,YAEN,MAAM,YAAa,CACjB,MAAM,MAAM,QAAQ,UAAU,EAC9B,WAAa,WAAW,EACxB,YAAc,CAAC,EACf,WAAW,KAAO,YAClB,MAAM,MAAM,IAAI,WAAY,YAAa,MAAM,EAC/C,UAAU,IAAK,WAAY,KAAK,WAAY,MAAM,EAAG,cAAc,CACrE,EAEA,MAAM,SAAU,CACd,MAAM,MAAM,QAAQ,UAAU,EAC9B,UAAY,GACZ,YAAc,CAAC,EACf,WAAW,KAAO,YAGlB,UAAU,IAAK,WAAY,GAAI,CAAE,GAAG,eAAgB,OAAQ,EAAG,QAAS,IAAI,KAAK,CAAC,CAAE,CAAC,CACvF,EAEA,MAAM,MAAO,CACN,WACH,MAAM,MAAM,IAAI,WAAY,YAAa,MAAM,CAEnD,CACF,EAEC,IAAa,QAAU,WAGpB,OACF,UAAU,IAAK,WAAY,KAAK,WAAY,MAAM,EAAG,cAAc,EAIrE,IAAI,GAAG,SAAU,SAAY,CACvB,YACA,QAAU,mBAAqB,OAAO,KAAK,WAAW,CAAC,CAAC,SAAW,KAEvE,MAAM,MAAM,IAAI,WAAY,YAAa,MAAM,EAE3C,SAAW,CAAC,OACV,MAAM,OACR,MAAM,MAAM,MAAM,WAAY,MAAM,EAG1C,CAAC,EAGG,SAAW,CAAC,OACd,UAAU,IAAK,WAAY,KAAK,WAAY,MAAM,EAAG,cAAc,EAGrE,KAAK,CACP,CACF"}
@@ -1,3 +1,3 @@
1
1
 
2
- import { f as SpaAdapter, m as resolveClientDir, p as SpaAdapterOptions } from "../../application-B6Gp1nqK.mjs";
2
+ import { f as SpaAdapter, m as resolveClientDir, p as SpaAdapterOptions } from "../../application-DqD58rhF.mjs";
3
3
  export { SpaAdapter, SpaAdapterOptions, resolveClientDir };