@warlock.js/core 5.24.0 → 5.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/bin/warlock.js +0 -0
  3. package/esm/application/application.d.mts +21 -0
  4. package/esm/application/application.d.mts.map +1 -1
  5. package/esm/application/application.mjs +29 -1
  6. package/esm/application/application.mjs.map +1 -1
  7. package/esm/application/boot-signal.d.mts +2 -1
  8. package/esm/application/boot-signal.d.mts.map +1 -1
  9. package/esm/application/boot-signal.mjs.map +1 -1
  10. package/esm/application/index.d.mts +1 -0
  11. package/esm/application/index.mjs +1 -0
  12. package/esm/application/roles.d.mts +72 -0
  13. package/esm/application/roles.d.mts.map +1 -0
  14. package/esm/application/roles.mjs +124 -0
  15. package/esm/application/roles.mjs.map +1 -0
  16. package/esm/cli/commands/add.command.mjs.map +1 -1
  17. package/esm/cli/commands/create-database.command.mjs.map +1 -1
  18. package/esm/cli/commands/dev-server.command.mjs.map +1 -1
  19. package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
  20. package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
  21. package/esm/cli/commands/generate/utils/path-resolver.mjs.map +1 -1
  22. package/esm/cli/commands/migrate.command.mjs.map +1 -1
  23. package/esm/cli/commands/routes.command.mjs.map +1 -1
  24. package/esm/cli/commands/seed.command.mjs.map +1 -1
  25. package/esm/cli/commands/start-production.command.mjs +54 -5
  26. package/esm/cli/commands/start-production.command.mjs.map +1 -1
  27. package/esm/cli/commands/storage-put.action.mjs.map +1 -1
  28. package/esm/cli/commands/storage-put.command.mjs.map +1 -1
  29. package/esm/cli/commands/typings-generator.command.mjs +17 -1
  30. package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
  31. package/esm/cli/commands/update.command.mjs.map +1 -1
  32. package/esm/cli/commands-loader.mjs.map +1 -1
  33. package/esm/config/config-loader.d.mts.map +1 -1
  34. package/esm/config/config-loader.mjs +3 -1
  35. package/esm/config/config-loader.mjs.map +1 -1
  36. package/esm/connectors/connectors-manager.mjs.map +1 -1
  37. package/esm/connectors/database-connector.mjs.map +1 -1
  38. package/esm/connectors/herald-connector.mjs.map +1 -1
  39. package/esm/connectors/http-connector.d.mts.map +1 -1
  40. package/esm/connectors/http-connector.mjs +7 -0
  41. package/esm/connectors/http-connector.mjs.map +1 -1
  42. package/esm/connectors/logger-connector.mjs.map +1 -1
  43. package/esm/connectors/mail-connector.mjs.map +1 -1
  44. package/esm/connectors/types.d.mts +8 -0
  45. package/esm/connectors/types.d.mts.map +1 -1
  46. package/esm/connectors/types.mjs.map +1 -1
  47. package/esm/database/create-database-action.mjs.map +1 -1
  48. package/esm/database/drop-tables-action.mjs.map +1 -1
  49. package/esm/database/migrate-action.mjs +2 -1
  50. package/esm/database/migrate-action.mjs.map +1 -1
  51. package/esm/database/models/database-log/database-log.mjs.map +1 -1
  52. package/esm/database/seed-command-action.mjs +2 -1
  53. package/esm/database/seed-command-action.mjs.map +1 -1
  54. package/esm/dev-server/dependency-graph.mjs.map +1 -1
  55. package/esm/dev-server/development-server.mjs +2 -0
  56. package/esm/dev-server/development-server.mjs.map +1 -1
  57. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  58. package/esm/dev-server/layer-executor.mjs.map +1 -1
  59. package/esm/dev-server/manifest-manager.mjs.map +1 -1
  60. package/esm/dev-server/module-loader.mjs +10 -5
  61. package/esm/dev-server/module-loader.mjs.map +1 -1
  62. package/esm/dev-server/register-installed-devtools.mjs +47 -0
  63. package/esm/dev-server/register-installed-devtools.mjs.map +1 -0
  64. package/esm/dev-server/run-connector-typings.mjs +28 -0
  65. package/esm/dev-server/run-connector-typings.mjs.map +1 -0
  66. package/esm/dev-server/special-file-patterns.mjs +3 -1
  67. package/esm/dev-server/special-file-patterns.mjs.map +1 -1
  68. package/esm/dev-server/special-files-collector.d.mts +2 -2
  69. package/esm/dev-server/special-files-collector.d.mts.map +1 -1
  70. package/esm/dev-server/special-files-collector.mjs +7 -4
  71. package/esm/dev-server/special-files-collector.mjs.map +1 -1
  72. package/esm/encryption/password.mjs.map +1 -1
  73. package/esm/generations/add-command.action.mjs +3 -1
  74. package/esm/generations/add-command.action.mjs.map +1 -1
  75. package/esm/generations/features/access.feature.mjs.map +1 -1
  76. package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
  77. package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
  78. package/esm/generations/features/ai-google.feature.mjs.map +1 -1
  79. package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
  80. package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
  81. package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
  82. package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
  83. package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
  84. package/esm/generations/features/ai.feature.mjs.map +1 -1
  85. package/esm/generations/features/devtools.feature.mjs +23 -0
  86. package/esm/generations/features/devtools.feature.mjs.map +1 -0
  87. package/esm/generations/features/herald.feature.mjs.map +1 -1
  88. package/esm/generations/features/image.feature.mjs.map +1 -1
  89. package/esm/generations/features/index.mjs +2 -0
  90. package/esm/generations/features/index.mjs.map +1 -1
  91. package/esm/generations/features/mail.feature.mjs.map +1 -1
  92. package/esm/generations/features/mongodb.feature.mjs.map +1 -1
  93. package/esm/generations/features/notifications.feature.mjs.map +1 -1
  94. package/esm/generations/features/postgres.feature.mjs.map +1 -1
  95. package/esm/generations/features/react-email.feature.mjs.map +1 -1
  96. package/esm/generations/features/react.feature.mjs.map +1 -1
  97. package/esm/generations/features/redis.feature.mjs.map +1 -1
  98. package/esm/generations/features/s3.feature.mjs.map +1 -1
  99. package/esm/generations/features/scheduler.feature.mjs.map +1 -1
  100. package/esm/generations/features/ses.feature.mjs.map +1 -1
  101. package/esm/generations/features/socket.feature.mjs.map +1 -1
  102. package/esm/generations/features/test.feature.mjs +6 -2
  103. package/esm/generations/features/test.feature.mjs.map +1 -1
  104. package/esm/generations/features/types.mjs.map +1 -1
  105. package/esm/generations/features/web.feature.mjs.map +1 -1
  106. package/esm/http/database/RequestLog.mjs.map +1 -1
  107. package/esm/http/index.d.mts +2 -1
  108. package/esm/http/index.mjs +2 -1
  109. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  110. package/esm/http/middleware/inject-request-context.mjs +9 -1
  111. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  112. package/esm/http/response.d.mts +5 -5
  113. package/esm/http/response.d.mts.map +1 -1
  114. package/esm/http/response.mjs.map +1 -1
  115. package/esm/http/server-rebuilt.d.mts +20 -0
  116. package/esm/http/server-rebuilt.d.mts.map +1 -0
  117. package/esm/http/server-rebuilt.mjs +21 -0
  118. package/esm/http/server-rebuilt.mjs.map +1 -0
  119. package/esm/http/tracing/index.mjs +1 -1
  120. package/esm/http/tracing/tracing-dispatcher.d.mts +13 -6
  121. package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -1
  122. package/esm/http/tracing/tracing-dispatcher.mjs +46 -14
  123. package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -1
  124. package/esm/http/tracing/tracing.type.d.mts +2 -1
  125. package/esm/http/tracing/tracing.type.d.mts.map +1 -1
  126. package/esm/http/types.d.mts +9 -0
  127. package/esm/http/types.d.mts.map +1 -1
  128. package/esm/image/image.mjs.map +1 -1
  129. package/esm/index.d.mts +4 -2
  130. package/esm/index.mjs +4 -2
  131. package/esm/loader/app-module-importer.d.mts +17 -0
  132. package/esm/loader/app-module-importer.d.mts.map +1 -0
  133. package/esm/loader/app-module-importer.mjs +20 -0
  134. package/esm/loader/app-module-importer.mjs.map +1 -0
  135. package/esm/logger/logger.mjs.map +1 -1
  136. package/esm/mail/events.d.mts +7 -0
  137. package/esm/mail/events.d.mts.map +1 -1
  138. package/esm/mail/events.mjs +7 -0
  139. package/esm/mail/events.mjs.map +1 -1
  140. package/esm/mail/send-mail.d.mts.map +1 -1
  141. package/esm/mail/send-mail.mjs +23 -2
  142. package/esm/mail/send-mail.mjs.map +1 -1
  143. package/esm/mail/types.d.mts +5 -1
  144. package/esm/mail/types.d.mts.map +1 -1
  145. package/esm/mail/types.mjs.map +1 -1
  146. package/esm/manifest/manifest-manager.mjs.map +1 -1
  147. package/esm/production/build-contributions.mjs +19 -3
  148. package/esm/production/build-contributions.mjs.map +1 -1
  149. package/esm/production/production-builder.mjs +28 -8
  150. package/esm/production/production-builder.mjs.map +1 -1
  151. package/esm/production/production-supervisor.mjs +6 -0
  152. package/esm/production/production-supervisor.mjs.map +1 -1
  153. package/esm/production/resolve-build-config.d.mts +1 -1
  154. package/esm/production/resolve-build-config.mjs +15 -0
  155. package/esm/production/resolve-build-config.mjs.map +1 -1
  156. package/esm/production/route-registration-child.mjs +14 -2
  157. package/esm/production/route-registration-child.mjs.map +1 -1
  158. package/esm/production/route-registration-snapshot.mjs +9 -2
  159. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  160. package/esm/resource/resource-field-builder.mjs +1 -1
  161. package/esm/resource/resource-field-builder.mjs.map +1 -1
  162. package/esm/resource/resource.d.mts +5 -0
  163. package/esm/resource/resource.d.mts.map +1 -1
  164. package/esm/resource/resource.mjs +17 -6
  165. package/esm/resource/resource.mjs.map +1 -1
  166. package/esm/router/router.d.mts.map +1 -1
  167. package/esm/router/router.mjs +1 -0
  168. package/esm/router/router.mjs.map +1 -1
  169. package/esm/router/types.d.mts +2 -1
  170. package/esm/router/types.d.mts.map +1 -1
  171. package/esm/tests/index.d.mts +3 -2
  172. package/esm/tests/index.mjs +3 -2
  173. package/esm/tests/vitest-setup.d.mts +16 -1
  174. package/esm/tests/vitest-setup.d.mts.map +1 -1
  175. package/esm/tests/vitest-setup.mjs +20 -2
  176. package/esm/tests/vitest-setup.mjs.map +1 -1
  177. package/esm/use-cases/use-case-events.mjs.map +1 -1
  178. package/esm/utils/is-test-file.mjs +15 -0
  179. package/esm/utils/is-test-file.mjs.map +1 -0
  180. package/esm/utils/normalized-path.d.mts +3 -1
  181. package/esm/utils/normalized-path.d.mts.map +1 -1
  182. package/esm/utils/normalized-path.mjs +4 -2
  183. package/esm/utils/normalized-path.mjs.map +1 -1
  184. package/esm/utils/paths.mjs.map +1 -1
  185. package/esm/validation/plugins/database-plugin.mjs.map +1 -1
  186. package/esm/validation/validateAll.mjs +9 -3
  187. package/esm/validation/validateAll.mjs.map +1 -1
  188. package/esm/warlock-config/default-configurations.mjs +1 -0
  189. package/esm/warlock-config/default-configurations.mjs.map +1 -1
  190. package/esm/warlock-config/types.d.mts +7 -0
  191. package/esm/warlock-config/types.d.mts.map +1 -1
  192. package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
  193. package/llms-full.txt +264 -139
  194. package/package.json +15 -11
  195. package/skills/configure-app/SKILL.md +2 -0
  196. package/skills/define-resource/SKILL.md +4 -0
  197. package/skills/request-tracing/SKILL.md +38 -15
  198. package/skills/run-app/SKILL.md +71 -34
  199. package/skills/send-mail/SKILL.md +55 -25
  200. package/skills/test-http/SKILL.md +19 -8
  201. package/skills/test-service/SKILL.md +61 -40
  202. package/skills/warlock-conventions/SKILL.md +26 -18
  203. package/skills/write-cli-command/SKILL.md +50 -45
@@ -1 +1 @@
1
- {"version":3,"file":"web.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/web.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { ensureDirectoryAsync, fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\r\nimport type { CommandActionData } from \"../../commands/types\";\r\nimport { rootPath, srcPath } from \"../../utils\";\r\nimport { insertConnectorEntry } from \"./shared/insert-connector-entry\";\r\nimport { relocateConflictingHomeRoute } from \"./shared/relocate-conflicting-home-route\";\r\nimport { resolveContactScaffold } from \"./shared/resolve-contact-scaffold\";\r\nimport {\r\n webContactControllerStub,\r\n webContactRoutesStub,\r\n webHomePageStub,\r\n webHomeRegisterStub,\r\n webRootStub,\r\n} from \"../stubs\";\r\nimport { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\r\n\r\n/** The one vite range shared by every feature (web's peer range is `>=7.3.5 <8`). */\r\nexport const VITE_RANGE = \"^7.3.5\";\r\n\r\n/**\r\n * Register the WebConnector in `warlock.config.ts`, and ONLY there.\r\n *\r\n * It belongs to the config array or to app code, never both. Both halves are\r\n * registered before app code loads — the CLI preloader in dev, the generated\r\n * entry in production — so also calling `connectorsManager.register(...)` in\r\n * `src/app/main.ts` boots the connector twice and installs every page route\r\n * twice. That surfaces at PRODUCTION boot as `Route name \"...\" is already\r\n * taken`, because pages and API routes share one route-name namespace.\r\n *\r\n * The config array is the half to prefer: `warlock build` reads the same array\r\n * to drain each connector's build contribution, so \"built for\" and \"boots with\"\r\n * cannot drift.\r\n *\r\n * String surgery rather than a TypeScript parse: `warlock.config.ts` is an\r\n * app-owned file that may carry any formatting, and a parse-and-print would\r\n * reformat the parts we did not come to change.\r\n */\r\nasync function registerWebConnector(): Promise<void> {\r\n const configPath = rootPath(\"warlock.config.ts\");\r\n\r\n if (!(await fileExistsAsync(configPath))) {\r\n console.log(\r\n `${colors.yellowBright(\"warlock.config.ts\")} not found — add this yourself:\\n` +\r\n ` import { webConnector } from \"@warlock.js/web/connector\";\\n` +\r\n ` export default defineConfig({ connectors: [webConnector()] });`,\r\n );\r\n\r\n return;\r\n }\r\n\r\n const current = await getFileAsync(configPath);\r\n\r\n if (current.includes(\"webConnector\")) {\r\n console.log(`${colors.yellowBright(\"webConnector\")} already registered, skipping...`);\r\n\r\n return;\r\n }\r\n\r\n const importLine = 'import { webConnector } from \"@warlock.js/web/connector\";';\r\n let next = current.includes(importLine) ? current : `${importLine}\\n${current}`;\r\n\r\n // An existing `connectors: [` gains one entry; otherwise the key is added to\r\n // the object `defineConfig` receives.\r\n const insertion = insertConnectorEntry(next, \"webConnector()\");\r\n\r\n if (insertion.status === \"already-present\") {\r\n return;\r\n }\r\n\r\n if (insertion.status === \"added\") {\r\n next = insertion.next;\r\n } else if (next.includes(\"defineConfig({\")) {\r\n next = next.replace(\"defineConfig({\", \"defineConfig({\\n connectors: [webConnector()],\");\r\n } else {\r\n console.log(\r\n `${colors.yellowBright(\"warlock.config.ts\")} has no recognisable defineConfig({...}) — ` +\r\n \"add `connectors: [webConnector()]` yourself.\",\r\n );\r\n\r\n return;\r\n }\r\n\r\n await putFileAsync(configPath, next);\r\n console.log(`${colors.green(\"✓\")} Registered webConnector in warlock.config.ts`);\r\n}\r\n\r\n/**\r\n * Scaffold the smallest page layer that renders, and register the connector.\r\n *\r\n * `src/web/root.tsx` is the sentinel for \"already scaffolded\" — the framework\r\n * ships a default root, so its presence means a human has been here.\r\n */\r\nasync function completeWebInstallation(_options: CommandActionData) {\r\n const rootFile = srcPath(\"web/root.tsx\");\r\n\r\n if (await fileExistsAsync(rootFile)) {\r\n console.log(`${colors.yellowBright(\"src/web\")} already scaffolded, skipping...`);\r\n } else {\r\n await ensureDirectoryAsync(srcPath(\"web\"));\r\n await putFileAsync(rootFile, webRootStub);\r\n console.log(`${colors.green(\"✓\")} Created src/web/root.tsx`);\r\n\r\n const collision = await relocateConflictingHomeRoute();\r\n\r\n if (collision.outcome === \"relocated\") {\r\n console.log(\r\n `${colors.green(\"✓\")} Moved the existing ${colors.yellowBright('GET \"/\"')} route to ` +\r\n `${colors.yellowBright('\"/welcome\"')} in ${colors.yellowBright(`src/${collision.relativePath}`)} — ` +\r\n \"the new page owns `/` now, and the JSON welcome route still answers at /welcome.\",\r\n );\r\n }\r\n\r\n // The page is written ONLY when `/` is provably free. Writing it while\r\n // another handler holds `/` produces a homepage that 500s on first request,\r\n // which is precisely the outcome a scaffolder must never hand back.\r\n if (collision.outcome === \"conflict\" || collision.outcome === \"failed\") {\r\n const verb =\r\n collision.outcome === \"failed\" ? colors.redBright(\"✗\") : colors.yellowBright(\"!\");\r\n\r\n console.log(\r\n `${verb} Did not create src/web/index.page.tsx: ${collision.reason}.\\n` +\r\n ` The page stub declares ${colors.yellowBright('route.path = \"/\"')}, and two handlers on one ` +\r\n \"path is a 500 at request time, not a startup error.\\n\" +\r\n ` Free up ${colors.yellowBright('GET \"/\"')} under src/app — move it to a path of its own, ` +\r\n \"or remove it — then create src/web/index.page.tsx yourself. Giving the page a `route` other \" +\r\n \"than `/` works too.\",\r\n );\r\n\r\n // Non-zero on BOTH branches. The page layer this command exists to\r\n // scaffold was not scaffolded, and a 0 here is the exact \"looked like it\r\n // worked\" signal that put `/` in this state to begin with — a conflict we\r\n // declined to guess at is still an incomplete install, not a success.\r\n //\r\n // `exitCode` rather than `exit(1)`: the connector below still has to be\r\n // registered, and any other feature in the same `warlock add` invocation\r\n // still has to install, or the project is left half-wired on top of this.\r\n process.exitCode = 1;\r\n } else {\r\n await putFileAsync(srcPath(\"web/index.page.tsx\"), webHomePageStub);\r\n await putFileAsync(srcPath(\"web/index.register.ts\"), webHomeRegisterStub);\r\n\r\n // Unlike root.tsx above, the contact controller and its routes file are\r\n // ordinary application files a project can already have — independently\r\n // of ever having run `warlock add web`. Each is guarded on its OWN\r\n // existence, not on the (already-consumed) root.tsx sentinel, so an\r\n // existing `contact` module is skipped rather than clobbered. See\r\n // resolveContactScaffold's doc comment for why.\r\n const contactPlan = resolveContactScaffold({\r\n controllerExists: await fileExistsAsync(\r\n srcPath(\"app/contact/controllers/contact.controller.ts\"),\r\n ),\r\n routesExists: await fileExistsAsync(srcPath(\"app/contact/routes.ts\")),\r\n });\r\n\r\n if (contactPlan.writeController) {\r\n await ensureDirectoryAsync(srcPath(\"app/contact/controllers\"));\r\n await putFileAsync(\r\n srcPath(\"app/contact/controllers/contact.controller.ts\"),\r\n webContactControllerStub,\r\n );\r\n }\r\n\r\n if (contactPlan.writeRoutes) {\r\n await ensureDirectoryAsync(srcPath(\"app/contact\"));\r\n await putFileAsync(srcPath(\"app/contact/routes.ts\"), webContactRoutesStub);\r\n }\r\n\r\n console.log(`${colors.green(\"✓\")} Created src/web/index.page.tsx`);\r\n\r\n for (const message of contactPlan.messages) {\r\n console.log(message);\r\n }\r\n }\r\n }\r\n\r\n await registerWebConnector();\r\n}\r\n\r\nexport const webFeature: FeatureDefinition = {\r\n description:\r\n \"Installs @warlock.js/web — SSR React pages served by the Warlock HTTP server. Scaffolds src/web (root.tsx + a home page) and registers the WebConnector in warlock.config.ts. Pages are opt-in: a Warlock app is an API until you add this.\",\r\n dependencies: {\r\n \"@warlock.js/web\": INSTALLED_WARLOCK_VERSION,\r\n \"@mongez/http\": \"^3.5.0\",\r\n \"@mongez/react-form\": \"^4.0.0\",\r\n \"@mongez/react-localization\": \"^3.4.7\",\r\n react: \"^19.2.3\",\r\n \"react-dom\": \"^19.2.3\",\r\n },\r\n devDependencies: {\r\n \"@types/react\": \"^19.2.7\",\r\n \"@types/react-dom\": \"^19.2.3\",\r\n // Loaded through `await import()` by the dev server only, so both are\r\n // optional peers of `web` rather than hard dependencies.\r\n vite: VITE_RANGE,\r\n \"@vitejs/plugin-react\": \"^5.2.0\",\r\n },\r\n onExecuting: completeWebInstallation,\r\n};\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"web.feature.mjs","names":[],"sources":["../../../../../../../../core/src/generations/features/web.feature.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { ensureDirectoryAsync, fileExistsAsync, getFileAsync, putFileAsync } from \"@warlock.js/fs\";\nimport type { CommandActionData } from \"../../commands/types\";\nimport { rootPath, srcPath } from \"../../utils\";\nimport { insertConnectorEntry } from \"./shared/insert-connector-entry\";\nimport { relocateConflictingHomeRoute } from \"./shared/relocate-conflicting-home-route\";\nimport { resolveContactScaffold } from \"./shared/resolve-contact-scaffold\";\nimport {\n webContactControllerStub,\n webContactRoutesStub,\n webHomePageStub,\n webHomeRegisterStub,\n webRootStub,\n} from \"../stubs\";\nimport { type FeatureDefinition, INSTALLED_WARLOCK_VERSION } from \"./types\";\n\n/** The one vite range shared by every feature (web's peer range is `>=7.3.5 <8`). */\nexport const VITE_RANGE = \"^7.3.5\";\n\n/**\n * Register the WebConnector in `warlock.config.ts`, and ONLY there.\n *\n * It belongs to the config array or to app code, never both. Both halves are\n * registered before app code loads — the CLI preloader in dev, the generated\n * entry in production — so also calling `connectorsManager.register(...)` in\n * `src/app/main.ts` boots the connector twice and installs every page route\n * twice. That surfaces at PRODUCTION boot as `Route name \"...\" is already\n * taken`, because pages and API routes share one route-name namespace.\n *\n * The config array is the half to prefer: `warlock build` reads the same array\n * to drain each connector's build contribution, so \"built for\" and \"boots with\"\n * cannot drift.\n *\n * String surgery rather than a TypeScript parse: `warlock.config.ts` is an\n * app-owned file that may carry any formatting, and a parse-and-print would\n * reformat the parts we did not come to change.\n */\nasync function registerWebConnector(): Promise<void> {\n const configPath = rootPath(\"warlock.config.ts\");\n\n if (!(await fileExistsAsync(configPath))) {\n console.log(\n `${colors.yellowBright(\"warlock.config.ts\")} not found — add this yourself:\\n` +\n ` import { webConnector } from \"@warlock.js/web/connector\";\\n` +\n ` export default defineConfig({ connectors: [webConnector()] });`,\n );\n\n return;\n }\n\n const current = await getFileAsync(configPath);\n\n if (current.includes(\"webConnector\")) {\n console.log(`${colors.yellowBright(\"webConnector\")} already registered, skipping...`);\n\n return;\n }\n\n const importLine = 'import { webConnector } from \"@warlock.js/web/connector\";';\n let next = current.includes(importLine) ? current : `${importLine}\\n${current}`;\n\n // An existing `connectors: [` gains one entry; otherwise the key is added to\n // the object `defineConfig` receives.\n const insertion = insertConnectorEntry(next, \"webConnector()\");\n\n if (insertion.status === \"already-present\") {\n return;\n }\n\n if (insertion.status === \"added\") {\n next = insertion.next;\n } else if (next.includes(\"defineConfig({\")) {\n next = next.replace(\"defineConfig({\", \"defineConfig({\\n connectors: [webConnector()],\");\n } else {\n console.log(\n `${colors.yellowBright(\"warlock.config.ts\")} has no recognisable defineConfig({...}) — ` +\n \"add `connectors: [webConnector()]` yourself.\",\n );\n\n return;\n }\n\n await putFileAsync(configPath, next);\n console.log(`${colors.green(\"✓\")} Registered webConnector in warlock.config.ts`);\n}\n\n/**\n * Scaffold the smallest page layer that renders, and register the connector.\n *\n * `src/web/root.tsx` is the sentinel for \"already scaffolded\" — the framework\n * ships a default root, so its presence means a human has been here.\n */\nasync function completeWebInstallation(_options: CommandActionData) {\n const rootFile = srcPath(\"web/root.tsx\");\n\n if (await fileExistsAsync(rootFile)) {\n console.log(`${colors.yellowBright(\"src/web\")} already scaffolded, skipping...`);\n } else {\n await ensureDirectoryAsync(srcPath(\"web\"));\n await putFileAsync(rootFile, webRootStub);\n console.log(`${colors.green(\"✓\")} Created src/web/root.tsx`);\n\n const collision = await relocateConflictingHomeRoute();\n\n if (collision.outcome === \"relocated\") {\n console.log(\n `${colors.green(\"✓\")} Moved the existing ${colors.yellowBright('GET \"/\"')} route to ` +\n `${colors.yellowBright('\"/welcome\"')} in ${colors.yellowBright(`src/${collision.relativePath}`)} — ` +\n \"the new page owns `/` now, and the JSON welcome route still answers at /welcome.\",\n );\n }\n\n // The page is written ONLY when `/` is provably free. Writing it while\n // another handler holds `/` produces a homepage that 500s on first request,\n // which is precisely the outcome a scaffolder must never hand back.\n if (collision.outcome === \"conflict\" || collision.outcome === \"failed\") {\n const verb =\n collision.outcome === \"failed\" ? colors.redBright(\"✗\") : colors.yellowBright(\"!\");\n\n console.log(\n `${verb} Did not create src/web/index.page.tsx: ${collision.reason}.\\n` +\n ` The page stub declares ${colors.yellowBright('route.path = \"/\"')}, and two handlers on one ` +\n \"path is a 500 at request time, not a startup error.\\n\" +\n ` Free up ${colors.yellowBright('GET \"/\"')} under src/app — move it to a path of its own, ` +\n \"or remove it — then create src/web/index.page.tsx yourself. Giving the page a `route` other \" +\n \"than `/` works too.\",\n );\n\n // Non-zero on BOTH branches. The page layer this command exists to\n // scaffold was not scaffolded, and a 0 here is the exact \"looked like it\n // worked\" signal that put `/` in this state to begin with — a conflict we\n // declined to guess at is still an incomplete install, not a success.\n //\n // `exitCode` rather than `exit(1)`: the connector below still has to be\n // registered, and any other feature in the same `warlock add` invocation\n // still has to install, or the project is left half-wired on top of this.\n process.exitCode = 1;\n } else {\n await putFileAsync(srcPath(\"web/index.page.tsx\"), webHomePageStub);\n await putFileAsync(srcPath(\"web/index.register.ts\"), webHomeRegisterStub);\n\n // Unlike root.tsx above, the contact controller and its routes file are\n // ordinary application files a project can already have — independently\n // of ever having run `warlock add web`. Each is guarded on its OWN\n // existence, not on the (already-consumed) root.tsx sentinel, so an\n // existing `contact` module is skipped rather than clobbered. See\n // resolveContactScaffold's doc comment for why.\n const contactPlan = resolveContactScaffold({\n controllerExists: await fileExistsAsync(\n srcPath(\"app/contact/controllers/contact.controller.ts\"),\n ),\n routesExists: await fileExistsAsync(srcPath(\"app/contact/routes.ts\")),\n });\n\n if (contactPlan.writeController) {\n await ensureDirectoryAsync(srcPath(\"app/contact/controllers\"));\n await putFileAsync(\n srcPath(\"app/contact/controllers/contact.controller.ts\"),\n webContactControllerStub,\n );\n }\n\n if (contactPlan.writeRoutes) {\n await ensureDirectoryAsync(srcPath(\"app/contact\"));\n await putFileAsync(srcPath(\"app/contact/routes.ts\"), webContactRoutesStub);\n }\n\n console.log(`${colors.green(\"✓\")} Created src/web/index.page.tsx`);\n\n for (const message of contactPlan.messages) {\n console.log(message);\n }\n }\n }\n\n await registerWebConnector();\n}\n\nexport const webFeature: FeatureDefinition = {\n description:\n \"Installs @warlock.js/web — SSR React pages served by the Warlock HTTP server. Scaffolds src/web (root.tsx + a home page) and registers the WebConnector in warlock.config.ts. Pages are opt-in: a Warlock app is an API until you add this.\",\n dependencies: {\n \"@warlock.js/web\": INSTALLED_WARLOCK_VERSION,\n \"@mongez/http\": \"^3.5.0\",\n \"@mongez/react-form\": \"^4.0.0\",\n \"@mongez/react-localization\": \"^3.4.7\",\n react: \"^19.2.3\",\n \"react-dom\": \"^19.2.3\",\n },\n devDependencies: {\n \"@types/react\": \"^19.2.7\",\n \"@types/react-dom\": \"^19.2.3\",\n // Loaded through `await import()` by the dev server only, so both are\n // optional peers of `web` rather than hard dependencies.\n vite: VITE_RANGE,\n \"@vitejs/plugin-react\": \"^5.2.0\",\n },\n onExecuting: completeWebInstallation,\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RequestLog.mjs","names":[],"sources":["../../../../../../../../core/src/http/database/RequestLog.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\r\nimport { type Infer, v } from \"@warlock.js/seal\";\r\n\r\nconst schema = v.object({\r\n statusCode: v.number(),\r\n responseTime: v.number(),\r\n responseSize: v.number(),\r\n responseBody: v.record(v.any()),\r\n responseHeaders: v.record(v.any()),\r\n ip: v.string(),\r\n method: v.string(),\r\n route: v.string(),\r\n requestHeaders: v.record(v.any()),\r\n userAgent: v.string(),\r\n referer: v.string(),\r\n requestBody: v.record(v.any()),\r\n requestParams: v.record(v.any()),\r\n requestQuery: v.record(v.any()),\r\n});\r\n\r\ntype RequestLogSchema = Infer.Output<typeof schema>;\r\n\r\nexport class RequestLog extends Model<RequestLogSchema> {\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public static table = \"request_logs\";\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public static schema = schema;\r\n}\r\n"],"mappings":";;;;AAGA,MAAM,SAAS,EAAE,OAAO;CACtB,YAAY,EAAE,OAAO;CACrB,cAAc,EAAE,OAAO;CACvB,cAAc,EAAE,OAAO;CACvB,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;CAC9B,iBAAiB,EAAE,OAAO,EAAE,IAAI,CAAC;CACjC,IAAI,EAAE,OAAO;CACb,QAAQ,EAAE,OAAO;CACjB,OAAO,EAAE,OAAO;CAChB,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC;CAChC,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;CAC7B,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC;CAC/B,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;AAChC,CAAC;AAID,IAAa,aAAb,cAAgC,MAAwB;;eAIhC;;;gBAKC;;AACzB"}
1
+ {"version":3,"file":"RequestLog.mjs","names":[],"sources":["../../../../../../../../core/src/http/database/RequestLog.ts"],"sourcesContent":["import { Model } from \"@warlock.js/cascade\";\nimport { type Infer, v } from \"@warlock.js/seal\";\n\nconst schema = v.object({\n statusCode: v.number(),\n responseTime: v.number(),\n responseSize: v.number(),\n responseBody: v.record(v.any()),\n responseHeaders: v.record(v.any()),\n ip: v.string(),\n method: v.string(),\n route: v.string(),\n requestHeaders: v.record(v.any()),\n userAgent: v.string(),\n referer: v.string(),\n requestBody: v.record(v.any()),\n requestParams: v.record(v.any()),\n requestQuery: v.record(v.any()),\n});\n\ntype RequestLogSchema = Infer.Output<typeof schema>;\n\nexport class RequestLog extends Model<RequestLogSchema> {\n /**\n * {@inheritdoc}\n */\n public static table = \"request_logs\";\n\n /**\n * {@inheritdoc}\n */\n public static schema = schema;\n}\n"],"mappings":";;;;AAGA,MAAM,SAAS,EAAE,OAAO;CACtB,YAAY,EAAE,OAAO;CACrB,cAAc,EAAE,OAAO;CACvB,cAAc,EAAE,OAAO;CACvB,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;CAC9B,iBAAiB,EAAE,OAAO,EAAE,IAAI,CAAC;CACjC,IAAI,EAAE,OAAO;CACb,QAAQ,EAAE,OAAO;CACjB,OAAO,EAAE,OAAO;CAChB,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC;CAChC,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;CAC7B,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC;CAC/B,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;AAChC,CAAC;AAID,IAAa,aAAb,cAAgC,MAAwB;;eAIhC;;;gBAKC;;AACzB"}
@@ -38,7 +38,8 @@ import { registerHttpPlugins } from "./plugins.mjs";
38
38
  import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort } from "./boot-port-preflight.mjs";
39
39
  import { PortInUseError, assertPortIsAvailable, isPortAvailable } from "./port-preflight.mjs";
40
40
  import { HttpReadyReport, getHttpReadyReport, resetHttpReadyReport, setHttpReadyReport } from "./ready-report.mjs";
41
+ import { HttpServerRebuiltListener, notifyHttpServerRebuilt, onHttpServerRebuilt } from "./server-rebuilt.mjs";
41
42
  import { deriveTraceId, parseTraceparentTraceId } from "./tracing/trace-id.mjs";
42
- import { TracingContextSource, buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, resetTracingConfigForTests, resolveTracingConfig } from "./tracing/tracing-dispatcher.mjs";
43
+ import { TracingContextSource, buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, registerTracingHooks, resetTracingConfigForTests, resolveTracingConfig } from "./tracing/tracing-dispatcher.mjs";
43
44
  import { RequestContextStore, requestContext, useCurrentUser, useRequest, useRequestStore } from "./context/request-context.mjs";
44
45
  import { requestMemo } from "./context/request-memo.mjs";
@@ -5,7 +5,7 @@ import { isAllowedCsrfOrigin, normalizeOrigin, originOf, ownOrigin, resolveCsrfO
5
5
  import { requiresDefaultCsrfGuard, runDefaultCsrfGuard } from "./csrf-default-guard.mjs";
6
6
  import { BadRequestError, ConflictError, CookieJarUnavailableError, ForbiddenError, HttpError, NotAcceptableError, NotAllowedError, RequestUserMovedError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "./errors/errors.mjs";
7
7
  import { deriveTraceId, parseTraceparentTraceId } from "./tracing/trace-id.mjs";
8
- import { buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, resetTracingConfigForTests, resolveTracingConfig } from "./tracing/tracing-dispatcher.mjs";
8
+ import { buildTracingContext, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, isTracingEnabled, registerTracingHooks, resetTracingConfigForTests, resolveTracingConfig } from "./tracing/tracing-dispatcher.mjs";
9
9
  import "./tracing/index.mjs";
10
10
  import { createRequestStore, t } from "./middleware/inject-request-context.mjs";
11
11
  import { defaultHttpConfigurations, httpConfig } from "./config.mjs";
@@ -33,6 +33,7 @@ import "./middleware/index.mjs";
33
33
  import { PortInUseError, assertPortIsAvailable, isPortAvailable } from "./port-preflight.mjs";
34
34
  import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort } from "./boot-port-preflight.mjs";
35
35
  import { getHttpReadyReport, resetHttpReadyReport, setHttpReadyReport } from "./ready-report.mjs";
36
+ import { notifyHttpServerRebuilt, onHttpServerRebuilt } from "./server-rebuilt.mjs";
36
37
  import { requestMemo } from "./context/request-memo.mjs";
37
38
 
38
39
  export { };
@@ -1 +1 @@
1
- {"version":3,"file":"inject-request-context.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"mappings":";;;;;;;AA8CA;;;iBAAgB,kBAAA,CACd,OAAA,EAAS,OAAA,OACT,QAAA,EAAU,QAAA,GACT,OAAA,CAAQ,gBAAA;;;;iBA4JK,CAAA,CAAE,OAAA,UAAiB,YAAkB"}
1
+ {"version":3,"file":"inject-request-context.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"mappings":";;;;;;;AA8CA;;;iBAAgB,kBAAA,CACd,OAAA,EAAS,OAAA,OACT,QAAA,EAAU,QAAA,GACT,OAAA,CAAQ,gBAAA;;;;iBA2KK,CAAA,CAAE,OAAA,UAAiB,YAAkB"}
@@ -97,7 +97,15 @@ function handleRequestError(error, response, request) {
97
97
  if (environment() === "development") payload.stack = error.stack;
98
98
  return response.setStatusCode(error.status).send(payload);
99
99
  }
100
- if (error instanceof DatabaseWriterValidationError) return response.badRequest({ errors: error.errors });
100
+ if (error instanceof DatabaseWriterValidationError) {
101
+ log.error("http", "model-write-validation-error", error, {
102
+ requestId: request.id,
103
+ errors: error.errors
104
+ });
105
+ const status = config.get("http.modelValidationErrorStatus", 500);
106
+ if (status < 500) return response.setStatusCode(status).send({ errors: error.errors });
107
+ return response.setStatusCode(status).send({ error: "Internal server error." });
108
+ }
101
109
  reportUnhandledRequestError(error, request);
102
110
  return response.serverError({ error: "Internal server error." });
103
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"inject-request-context.mjs","names":["requestContextInstance"],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\n * Request Context Middleware\n *\n * Creates a unified context for each request using the ContextManager.\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\n */\nimport { trans } from \"@mongez/localization\";\nimport { type GenericObject } from \"@mongez/reinforcements\";\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\nimport { contextManager } from \"@warlock.js/context\";\nimport { log } from \"@warlock.js/logger\";\nimport config from \"@mongez/config\";\nimport { environment } from \"../../utils\";\nimport { requestContext as requestContextInstance } from \"../context/request-context\";\nimport { applyCspHeader } from \"../csp\";\nimport { runDefaultCsrfGuard } from \"../csrf-default-guard\";\nimport { HttpError } from \"../errors\";\nimport { type Request } from \"../request\";\nimport { type Response } from \"../response\";\nimport { buildTracingContext, dispatchPhase, isTracingEnabled } from \"../tracing\";\nimport { type ReturnedResponse } from \"./../types\";\n\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\n\n/**\n * Echo `request.id` back as a response header so the FE / proxies / log\n * aggregators can correlate by the same value the server logs against.\n *\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\n * Skip when `http.requestId.enabled` is explicitly false.\n */\nfunction stampRequestIdHeader(request: Request, response: Response) {\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\n\n if (requestIdConfig.enabled === false) return;\n\n const headerName = requestIdConfig.header || \"X-Request-Id\";\n\n response.header(headerName, request.id);\n}\n\n/**\n * Create request store and execute middleware + handler\n *\n * Runs all registered contexts together using ContextManager.\n */\nexport function createRequestStore(\n request: Request<any>,\n response: Response,\n): Promise<ReturnedResponse> {\n stampRequestIdHeader(request, response);\n\n // Stamped this early — before any middleware or handler runs — so the\n // header carries the SAME nonce the web layer later reads off\n // `request.nonce` for its `<script>` tags: `request.nonce` caches on first\n // read, and this is deliberately the first read. A no-op when the app\n // hasn't opted into `http.csp` (see `csp.ts`).\n applyCspHeader(request, response);\n\n // Build all context stores using the immutable API\n // Each context defines its own store initialization via buildStore()\n const httpContextStore = contextManager.buildStores({ request, response });\n\n // Run all contexts together!\n return contextManager.runAll(httpContextStore, async () => {\n try {\n // Default CSRF-Origin guard (SECURITY, card 8a752ab2) — the earliest\n // seam common to every request, BEFORE the route's own middleware\n // (`request.runMiddleware()` below) and before any app handler, so it\n // applies even to a route that never attaches\n // `authMiddleware(\"cookie:*\")`. See `../csrf-default-guard.ts` for the\n // full scope rule and `{ csrf: false }` exemption.\n const csrfGuardResponse = await runDefaultCsrfGuard(request, response, t, (reason) => {\n log.error(\n \"http\",\n \"csrf-default-guard\",\n new Error(`Default CSRF Origin guard refused request: ${reason}`),\n );\n });\n\n if (csrfGuardResponse) {\n return csrfGuardResponse as ReturnedResponse;\n }\n\n // Run middleware chain\n const result = await request.runMiddleware();\n\n if (result) {\n return result as ReturnedResponse;\n }\n\n // Execute route handler\n request.trigger(\"executingAction\", request.route);\n\n const handler = request.getHandler();\n\n request.log(\"Executing Handler\", \"info\");\n\n const tracingEnabled = isTracingEnabled();\n const handlerStartedAt = tracingEnabled ? performance.now() : 0;\n\n const output = await handler({ request, response });\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(request), {\n name: \"handler\",\n durationMs: performance.now() - handlerStartedAt,\n });\n }\n\n request.log(\"Handler Executed Successfully\", \"success\");\n\n request.trigger(\"executedAction\", request.route);\n\n return output as ReturnedResponse;\n } catch (error) {\n request.log(error, \"error\");\n return handleRequestError(error, response, request);\n }\n });\n}\n\n/**\n * Unconditional floor for an unhandled request error (canon 8d3c13a8): route it\n * through the logger, and when no logger channel is configured (nothing would\n * record it) fall back to `console.error` so the only copy is never lost.\n * @internal\n */\nfunction reportUnhandledRequestError(error: unknown, request: Request<any>) {\n log.error(\"http\", \"unhandled-request-error\", error, { requestId: request.id });\n\n if (log.channels.length === 0) {\n console.error(`[http] unhandled-request-error (requestId: ${request.id})`, error);\n }\n}\n\n/**\n * Handle request errors\n * @internal\n */\nfunction handleRequestError(\n error: unknown,\n response: Response,\n request: Request<any>,\n): ReturnedResponse {\n // Availability floor, not a cache-policy nit: `handleRequestError` is the\n // single funnel every unhandled error in every Warlock app passes through\n // (`createRequestStore`'s catch above), and none of the branches below set\n // a `Cache-Control` header. Without this, an error response — a 500 as\n // much as a 401/403/404 carrying per-request/-user state — can be stored\n // by a shared cache or CDN and replayed to other requests/users long after\n // the condition that caused it is gone: a cached 500 becomes an outage\n // that outlives its cause; a cached 401/403 becomes a leak across users.\n // Set once, here, before any branch runs — not per branch — because the\n // branches below do not partition by status. `ResourceNotFoundError`,\n // `UnAuthorizedError`, `ForbiddenError`, `BadRequestError` and `ServerError`\n // all extend `HttpError`, so the `HttpError` branch answers for every one of\n // them, and a raw `HttpError` can carry any caller-chosen status, 4xx or 5xx.\n // Gating the floor on the eventual status would mean re-deriving that status\n // per branch — one rule meeting one form while others reach the same output.\n // Applying it once, unconditionally, is both simpler and safer.\n response.header(\"Cache-Control\", \"private, no-store\");\n\n if (error instanceof HttpError) {\n // 5xx HttpErrors (e.g. `ServerError`) are server faults: send them to the\n // logger channels too, tagged with the request id.\n if (error.status >= 500) {\n log.error(\"http\", \"request-error\", error, { requestId: request.id });\n }\n\n const payload: GenericObject = {\n error: error.message,\n };\n if (error.payload) {\n payload.payload = error.payload;\n }\n\n if (environment() === \"development\") {\n payload.stack = error.stack;\n }\n\n return response.setStatusCode(error.status).send(payload);\n }\n\n if (error instanceof DatabaseWriterValidationError) {\n return response.badRequest({\n errors: error.errors,\n });\n }\n\n // Last resort: the error matched none of the known shapes above, so the\n // client gets a deliberately opaque message. Without this line the error\n // itself is discarded here — no stack, no message, nothing in any log — and\n // an unrecognised failure becomes indistinguishable from a working server\n // returning 500. Never swallow the only copy of an error (`65e476ee`).\n reportUnhandledRequestError(error, request);\n\n return response.serverError({\n error: \"Internal server error.\",\n });\n}\n\n/**\n * Translate a keyword (uses request context for locale)\n */\nexport function t(keyword: string, placeholders?: any) {\n return (\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\n trans(keyword, placeholders)\n );\n}\n\n// `fromRequest` was removed in v5. It cached computed values as dynamic\n// properties on the Request instance, which only compiled because of the\n// `[key: string]: any` index signature that v5 deletes (eed20184). Use\n// `requestMemo(key, fn)` from `../context/request-memo` instead — same\n// per-request lifetime, single-flight, and it never touches the Request object.\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkB,OAAO,IAAI,kBAAkB,CAAC,CAAwB;CAE9E,IAAI,gBAAgB,YAAY,OAAO;CAEvC,MAAM,aAAa,gBAAgB,UAAU;CAE7C,SAAS,OAAO,YAAY,QAAQ,EAAE;AACxC;;;;;;AAOA,SAAgB,mBACd,SACA,UAC2B;CAC3B,qBAAqB,SAAS,QAAQ;CAOtC,eAAe,SAAS,QAAQ;CAIhC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAOF,MAAM,oBAAoB,MAAM,oBAAoB,SAAS,UAAU,IAAI,WAAW;IACpF,IAAI,MACF,QACA,sCACA,IAAI,MAAM,8CAA8C,QAAQ,CAClE;GACF,CAAC;GAED,IAAI,mBACF,OAAO;GAIT,MAAM,SAAS,MAAM,QAAQ,cAAc;GAE3C,IAAI,QACF,OAAO;GAIT,QAAQ,QAAQ,mBAAmB,QAAQ,KAAK;GAEhD,MAAM,UAAU,QAAQ,WAAW;GAEnC,QAAQ,IAAI,qBAAqB,MAAM;GAEvC,MAAM,iBAAiB,iBAAiB;GACxC,MAAM,mBAAmB,iBAAiB,YAAY,IAAI,IAAI;GAE9D,MAAM,SAAS,MAAM,QAAQ;IAAE;IAAS;GAAS,CAAC;GAElD,IAAI,gBACF,cAAc,oBAAoB,OAAO,GAAG;IAC1C,MAAM;IACN,YAAY,YAAY,IAAI,IAAI;GAClC,CAAC;GAGH,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,IAAI,OAAO,OAAO;GAC1B,OAAO,mBAAmB,OAAO,UAAU,OAAO;EACpD;CACF,CAAC;AACH;;;;;;;AAQA,SAAS,4BAA4B,OAAgB,SAAuB;CAC1E,IAAI,MAAM,QAAQ,2BAA2B,OAAO,EAAE,WAAW,QAAQ,GAAG,CAAC;CAE7E,IAAI,IAAI,SAAS,WAAW,GAC1B,QAAQ,MAAM,8CAA8C,QAAQ,GAAG,IAAI,KAAK;AAEpF;;;;;AAMA,SAAS,mBACP,OACA,UACA,SACkB;CAiBlB,SAAS,OAAO,iBAAiB,mBAAmB;CAEpD,IAAI,iBAAiB,WAAW;EAG9B,IAAI,MAAM,UAAU,KAClB,IAAI,MAAM,QAAQ,iBAAiB,OAAO,EAAE,WAAW,QAAQ,GAAG,CAAC;EAGrE,MAAM,UAAyB,EAC7B,OAAO,MAAM,QACf;EACA,IAAI,MAAM,SACR,QAAQ,UAAU,MAAM;EAG1B,IAAI,YAAY,MAAM,eACpB,QAAQ,QAAQ,MAAM;EAGxB,OAAO,SAAS,cAAc,MAAM,MAAM,CAAC,CAAC,KAAK,OAAO;CAC1D;CAEA,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAQH,4BAA4B,OAAO,OAAO;CAE1C,OAAO,SAAS,YAAY,EAC1B,OAAO,yBACT,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEA,eAAuB,WAAW,CAAC,EAAE,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B"}
1
+ {"version":3,"file":"inject-request-context.mjs","names":["requestContextInstance"],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\n * Request Context Middleware\n *\n * Creates a unified context for each request using the ContextManager.\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\n */\nimport { trans } from \"@mongez/localization\";\nimport { type GenericObject } from \"@mongez/reinforcements\";\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\nimport { contextManager } from \"@warlock.js/context\";\nimport { log } from \"@warlock.js/logger\";\nimport config from \"@mongez/config\";\nimport { environment } from \"../../utils\";\nimport { requestContext as requestContextInstance } from \"../context/request-context\";\nimport { applyCspHeader } from \"../csp\";\nimport { runDefaultCsrfGuard } from \"../csrf-default-guard\";\nimport { HttpError } from \"../errors\";\nimport { type Request } from \"../request\";\nimport { type Response } from \"../response\";\nimport { buildTracingContext, dispatchPhase, isTracingEnabled } from \"../tracing\";\nimport { type ReturnedResponse } from \"./../types\";\n\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\n\n/**\n * Echo `request.id` back as a response header so the FE / proxies / log\n * aggregators can correlate by the same value the server logs against.\n *\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\n * Skip when `http.requestId.enabled` is explicitly false.\n */\nfunction stampRequestIdHeader(request: Request, response: Response) {\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\n\n if (requestIdConfig.enabled === false) return;\n\n const headerName = requestIdConfig.header || \"X-Request-Id\";\n\n response.header(headerName, request.id);\n}\n\n/**\n * Create request store and execute middleware + handler\n *\n * Runs all registered contexts together using ContextManager.\n */\nexport function createRequestStore(\n request: Request<any>,\n response: Response,\n): Promise<ReturnedResponse> {\n stampRequestIdHeader(request, response);\n\n // Stamped this early — before any middleware or handler runs — so the\n // header carries the SAME nonce the web layer later reads off\n // `request.nonce` for its `<script>` tags: `request.nonce` caches on first\n // read, and this is deliberately the first read. A no-op when the app\n // hasn't opted into `http.csp` (see `csp.ts`).\n applyCspHeader(request, response);\n\n // Build all context stores using the immutable API\n // Each context defines its own store initialization via buildStore()\n const httpContextStore = contextManager.buildStores({ request, response });\n\n // Run all contexts together!\n return contextManager.runAll(httpContextStore, async () => {\n try {\n // Default CSRF-Origin guard (SECURITY, card 8a752ab2) — the earliest\n // seam common to every request, BEFORE the route's own middleware\n // (`request.runMiddleware()` below) and before any app handler, so it\n // applies even to a route that never attaches\n // `authMiddleware(\"cookie:*\")`. See `../csrf-default-guard.ts` for the\n // full scope rule and `{ csrf: false }` exemption.\n const csrfGuardResponse = await runDefaultCsrfGuard(request, response, t, (reason) => {\n log.error(\n \"http\",\n \"csrf-default-guard\",\n new Error(`Default CSRF Origin guard refused request: ${reason}`),\n );\n });\n\n if (csrfGuardResponse) {\n return csrfGuardResponse as ReturnedResponse;\n }\n\n // Run middleware chain\n const result = await request.runMiddleware();\n\n if (result) {\n return result as ReturnedResponse;\n }\n\n // Execute route handler\n request.trigger(\"executingAction\", request.route);\n\n const handler = request.getHandler();\n\n request.log(\"Executing Handler\", \"info\");\n\n const tracingEnabled = isTracingEnabled();\n const handlerStartedAt = tracingEnabled ? performance.now() : 0;\n\n const output = await handler({ request, response });\n\n if (tracingEnabled) {\n dispatchPhase(buildTracingContext(request), {\n name: \"handler\",\n durationMs: performance.now() - handlerStartedAt,\n });\n }\n\n request.log(\"Handler Executed Successfully\", \"success\");\n\n request.trigger(\"executedAction\", request.route);\n\n return output as ReturnedResponse;\n } catch (error) {\n request.log(error, \"error\");\n return handleRequestError(error, response, request);\n }\n });\n}\n\n/**\n * Unconditional floor for an unhandled request error (canon 8d3c13a8): route it\n * through the logger, and when no logger channel is configured (nothing would\n * record it) fall back to `console.error` so the only copy is never lost.\n * @internal\n */\nfunction reportUnhandledRequestError(error: unknown, request: Request<any>) {\n log.error(\"http\", \"unhandled-request-error\", error, { requestId: request.id });\n\n if (log.channels.length === 0) {\n console.error(`[http] unhandled-request-error (requestId: ${request.id})`, error);\n }\n}\n\n/**\n * Handle request errors\n * @internal\n */\nfunction handleRequestError(\n error: unknown,\n response: Response,\n request: Request<any>,\n): ReturnedResponse {\n // Availability floor, not a cache-policy nit: `handleRequestError` is the\n // single funnel every unhandled error in every Warlock app passes through\n // (`createRequestStore`'s catch above), and none of the branches below set\n // a `Cache-Control` header. Without this, an error response — a 500 as\n // much as a 401/403/404 carrying per-request/-user state — can be stored\n // by a shared cache or CDN and replayed to other requests/users long after\n // the condition that caused it is gone: a cached 500 becomes an outage\n // that outlives its cause; a cached 401/403 becomes a leak across users.\n // Set once, here, before any branch runs — not per branch — because the\n // branches below do not partition by status. `ResourceNotFoundError`,\n // `UnAuthorizedError`, `ForbiddenError`, `BadRequestError` and `ServerError`\n // all extend `HttpError`, so the `HttpError` branch answers for every one of\n // them, and a raw `HttpError` can carry any caller-chosen status, 4xx or 5xx.\n // Gating the floor on the eventual status would mean re-deriving that status\n // per branch — one rule meeting one form while others reach the same output.\n // Applying it once, unconditionally, is both simpler and safer.\n response.header(\"Cache-Control\", \"private, no-store\");\n\n if (error instanceof HttpError) {\n // 5xx HttpErrors (e.g. `ServerError`) are server faults: send them to the\n // logger channels too, tagged with the request id.\n if (error.status >= 500) {\n log.error(\"http\", \"request-error\", error, { requestId: request.id });\n }\n\n const payload: GenericObject = {\n error: error.message,\n };\n if (error.payload) {\n payload.payload = error.payload;\n }\n\n if (environment() === \"development\") {\n payload.stack = error.stack;\n }\n\n return response.setStatusCode(error.status).send(payload);\n }\n\n if (error instanceof DatabaseWriterValidationError) {\n log.error(\"http\", \"model-write-validation-error\", error, {\n requestId: request.id,\n errors: error.errors,\n });\n\n const status = config.get(\"http.modelValidationErrorStatus\", 500);\n\n // A 4xx status means the app treats this as the client's fault, so the\n // field errors are useful to it; a 5xx stays opaque.\n if (status < 500) {\n return response.setStatusCode(status).send({\n errors: error.errors,\n });\n }\n\n return response.setStatusCode(status).send({\n error: \"Internal server error.\",\n });\n }\n\n // Last resort: the error matched none of the known shapes above, so the\n // client gets a deliberately opaque message. Without this line the error\n // itself is discarded here — no stack, no message, nothing in any log — and\n // an unrecognised failure becomes indistinguishable from a working server\n // returning 500. Never swallow the only copy of an error (`65e476ee`).\n reportUnhandledRequestError(error, request);\n\n return response.serverError({\n error: \"Internal server error.\",\n });\n}\n\n/**\n * Translate a keyword (uses request context for locale)\n */\nexport function t(keyword: string, placeholders?: any) {\n return (\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\n trans(keyword, placeholders)\n );\n}\n\n// `fromRequest` was removed in v5. It cached computed values as dynamic\n// properties on the Request instance, which only compiled because of the\n// `[key: string]: any` index signature that v5 deletes (eed20184). Use\n// `requestMemo(key, fn)` from `../context/request-memo` instead — same\n// per-request lifetime, single-flight, and it never touches the Request object.\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkB,OAAO,IAAI,kBAAkB,CAAC,CAAwB;CAE9E,IAAI,gBAAgB,YAAY,OAAO;CAEvC,MAAM,aAAa,gBAAgB,UAAU;CAE7C,SAAS,OAAO,YAAY,QAAQ,EAAE;AACxC;;;;;;AAOA,SAAgB,mBACd,SACA,UAC2B;CAC3B,qBAAqB,SAAS,QAAQ;CAOtC,eAAe,SAAS,QAAQ;CAIhC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAOF,MAAM,oBAAoB,MAAM,oBAAoB,SAAS,UAAU,IAAI,WAAW;IACpF,IAAI,MACF,QACA,sCACA,IAAI,MAAM,8CAA8C,QAAQ,CAClE;GACF,CAAC;GAED,IAAI,mBACF,OAAO;GAIT,MAAM,SAAS,MAAM,QAAQ,cAAc;GAE3C,IAAI,QACF,OAAO;GAIT,QAAQ,QAAQ,mBAAmB,QAAQ,KAAK;GAEhD,MAAM,UAAU,QAAQ,WAAW;GAEnC,QAAQ,IAAI,qBAAqB,MAAM;GAEvC,MAAM,iBAAiB,iBAAiB;GACxC,MAAM,mBAAmB,iBAAiB,YAAY,IAAI,IAAI;GAE9D,MAAM,SAAS,MAAM,QAAQ;IAAE;IAAS;GAAS,CAAC;GAElD,IAAI,gBACF,cAAc,oBAAoB,OAAO,GAAG;IAC1C,MAAM;IACN,YAAY,YAAY,IAAI,IAAI;GAClC,CAAC;GAGH,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAO;GACd,QAAQ,IAAI,OAAO,OAAO;GAC1B,OAAO,mBAAmB,OAAO,UAAU,OAAO;EACpD;CACF,CAAC;AACH;;;;;;;AAQA,SAAS,4BAA4B,OAAgB,SAAuB;CAC1E,IAAI,MAAM,QAAQ,2BAA2B,OAAO,EAAE,WAAW,QAAQ,GAAG,CAAC;CAE7E,IAAI,IAAI,SAAS,WAAW,GAC1B,QAAQ,MAAM,8CAA8C,QAAQ,GAAG,IAAI,KAAK;AAEpF;;;;;AAMA,SAAS,mBACP,OACA,UACA,SACkB;CAiBlB,SAAS,OAAO,iBAAiB,mBAAmB;CAEpD,IAAI,iBAAiB,WAAW;EAG9B,IAAI,MAAM,UAAU,KAClB,IAAI,MAAM,QAAQ,iBAAiB,OAAO,EAAE,WAAW,QAAQ,GAAG,CAAC;EAGrE,MAAM,UAAyB,EAC7B,OAAO,MAAM,QACf;EACA,IAAI,MAAM,SACR,QAAQ,UAAU,MAAM;EAG1B,IAAI,YAAY,MAAM,eACpB,QAAQ,QAAQ,MAAM;EAGxB,OAAO,SAAS,cAAc,MAAM,MAAM,CAAC,CAAC,KAAK,OAAO;CAC1D;CAEA,IAAI,iBAAiB,+BAA+B;EAClD,IAAI,MAAM,QAAQ,gCAAgC,OAAO;GACvD,WAAW,QAAQ;GACnB,QAAQ,MAAM;EAChB,CAAC;EAED,MAAM,SAAS,OAAO,IAAI,mCAAmC,GAAG;EAIhE,IAAI,SAAS,KACX,OAAO,SAAS,cAAc,MAAM,CAAC,CAAC,KAAK,EACzC,QAAQ,MAAM,OAChB,CAAC;EAGH,OAAO,SAAS,cAAc,MAAM,CAAC,CAAC,KAAK,EACzC,OAAO,yBACT,CAAC;CACH;CAOA,4BAA4B,OAAO,OAAO;CAE1C,OAAO,SAAS,YAAY,EAC1B,OAAO,yBACT,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEA,eAAuB,WAAW,CAAC,EAAE,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B"}
@@ -117,7 +117,7 @@ declare class Response {
117
117
  /**
118
118
  * Get raw response
119
119
  */
120
- get raw(): import("node:http").ServerResponse<import("node:http").IncomingMessage>;
120
+ get raw(): import("http").ServerResponse<import("http").IncomingMessage>;
121
121
  /**
122
122
  * Get Current response body
123
123
  */
@@ -204,9 +204,9 @@ declare class Response {
204
204
  */
205
205
  protected fireSentEvents(statusCode?: number): void;
206
206
  /** Send a bare 304 and fire the lifecycle. */
207
- protected send304(): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
207
+ protected send304(): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
208
208
  /** Send a raw stream/buffer body and fire the lifecycle. */
209
- protected sendRaw(body: any): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
209
+ protected sendRaw(body: any): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
210
210
  /**
211
211
  * Replay a previously-captured response shape — used by cache-pattern
212
212
  * middlewares (idempotency, response cache) to send a cached response
@@ -477,7 +477,7 @@ declare class Response {
477
477
  /**
478
478
  * Send a no content response with status code 204
479
479
  */
480
- noContent(): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
480
+ noContent(): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
481
481
  /**
482
482
  * Send an accepted response with status code 202
483
483
  * Used for async operations that have been accepted but not yet processed
@@ -521,7 +521,7 @@ declare class Response {
521
521
  * Send buffer as a response
522
522
  * Useful for dynamically generated content (e.g., resized images, generated PDFs)
523
523
  */
524
- sendBuffer(buffer: Buffer, options?: number | SendBufferOptions): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
524
+ sendBuffer(buffer: Buffer, options?: number | SendBufferOptions): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
525
525
  /**
526
526
  * Send an Image instance as a response
527
527
  * Automatically detects image format and sets content type
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.mts","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"mappings":"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"}
1
+ {"version":3,"file":"response.d.mts","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"response.mjs","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"sourcesContent":["import type { CookieSerializeOptions } from \"@fastify/cookie\";\r\nimport type { OutgoingHttpHeaders } from \"node:http\";\r\nimport config from \"@mongez/config\";\r\nimport type { EventSubscription } from \"@mongez/events\";\r\nimport events from \"@mongez/events\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { isIterable, isPlainObject, isScalar } from \"@mongez/supportive-is\";\r\nimport type { LogLevel } from \"@warlock.js/logger\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { ValidationResult } from \"@warlock.js/seal\";\r\nimport type { FastifyReply } from \"fastify\";\r\nimport fs from \"fs\";\r\nimport mime from \"mime\";\r\nimport path from \"path\";\r\nimport type React from \"react\";\r\nimport { type ReactNode } from \"react\";\r\nimport { Application } from \"../application/application\";\r\nimport {\r\n LOCALE_COOKIE_NAME,\r\n LOCALE_PREFERENCE_COOKIE_NAME,\r\n resolveLocaleConfiguration,\r\n} from \"../config/locale-configuration\";\r\nimport { UnknownLocaleError } from \"../errors/unknown-locale-error\";\r\nimport type { Route } from \"../router\";\r\nimport { StorageFile } from \"../storage\";\r\nimport { renderReact } from \"./../react\";\r\nimport type { Request } from \"./request\";\r\nimport { matchesIfNoneMatch } from \"./uploads/matches-if-none-match\";\r\nimport { flushPendingCookies } from \"./flush-pending-cookies\";\r\nimport { streamReactResponse, type PipeableReactStream } from \"./stream-react-response\";\r\nimport type { ResponseEvent, ResponseSSEController, ResponseStreamController } from \"./types\";\r\nimport type { XMLable } from \"./xmlable\";\r\n\r\ntype CookieValue = string | number | boolean | Record<string, any> | Array<any>;\r\n\r\n/**\r\n * Cookie options accepted by `response.cookie()`.\r\n *\r\n * Extends Fastify's `CookieSerializeOptions` with `raw` — set to `true` to\r\n * skip the default `JSON.stringify` of the value and write it as-is. Use for\r\n * plain-string cookies (session tokens, opaque IDs) that shouldn't be JSON-quoted.\r\n *\r\n * When `raw: true`, non-string values are coerced via `String(value)`. The\r\n * read side (`request.cookie(name)`) tries `JSON.parse` first and falls back\r\n * to the raw string on parse failure, so round-tripping a raw string cookie\r\n * Just Works.\r\n */\r\nexport type CookieOptions = CookieSerializeOptions & {\r\n /**\r\n * Skip JSON.stringify and write the value as-is.\r\n *\r\n * @default false\r\n */\r\n raw?: boolean;\r\n};\r\n\r\nexport enum ResponseStatus {\r\n OK = 200,\r\n CREATED = 201,\r\n ACCEPTED = 202,\r\n MOVED_PERMANENTLY = 301,\r\n FOUND = 302,\r\n SEE_OTHER = 303,\r\n NOT_MODIFIED = 304,\r\n TEMPORARY_REDIRECT = 307,\r\n PERMANENT_REDIRECT = 308,\r\n NO_CONTENT = 204,\r\n BAD_REQUEST = 400,\r\n UNAUTHORIZED = 401,\r\n FORBIDDEN = 403,\r\n NOT_FOUND = 404,\r\n METHOD_NOT_ALLOWED = 405,\r\n CONFLICT = 409,\r\n TOO_MANY_REQUESTS = 429,\r\n INTERNAL_SERVER_ERROR = 500,\r\n SERVICE_UNAVAILABLE = 503,\r\n}\r\n\r\n/**\r\n * Options for sending files\r\n */\r\nexport type SendFileOptions = {\r\n cacheTime?: number;\r\n immutable?: boolean;\r\n inline?: boolean;\r\n filename?: string;\r\n};\r\n\r\n/**\r\n * Options for sending buffers\r\n */\r\nexport type SendBufferOptions = SendFileOptions & {\r\n contentType?: string;\r\n etag?: string;\r\n};\r\n\r\n/**\r\n * The cookie flags every response cookie gets unless something overrides them.\r\n *\r\n * Computed per call rather than hoisted to a constant because `secure` depends\r\n * on the environment, which is not known at module-evaluation time.\r\n *\r\n * `secure` is relaxed in development only: a `Secure` cookie is dropped by the\r\n * browser over plain http, which would silently break every local login. It\r\n * stays on everywhere else, including test and staging.\r\n */\r\nfunction secureCookieDefaults(): CookieSerializeOptions {\r\n return {\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n secure: !Application.isDevelopment,\r\n };\r\n}\r\n\r\nexport class Response {\r\n /**\r\n * Current route\r\n */\r\n protected route!: Route;\r\n\r\n /**\r\n * Underlying Fastify reply — a public escape hatch to capabilities the\r\n * framework's high-level helpers don't yet cover.\r\n *\r\n * **Prefer framework methods first**: `response.send()`, `response.header()`,\r\n * `response.cookie()`, `response.sendFile()`, `response.stream()`, etc.\r\n * They wire status codes, content-type detection, the event lifecycle, and\r\n * the cache-pattern replay path correctly.\r\n *\r\n * **Reach for `baseResponse` only** when the framework genuinely lacks a\r\n * helper for what you need — and when you do, file an issue so we can add\r\n * it. Streaming and SSE are the precedent here: they bypass `send()`\r\n * deliberately because the framework didn't ship chunked-write support\r\n * natively at the time. Reaching here for non-streaming work means a\r\n * missing helper, not an answer.\r\n */\r\n public baseResponse!: FastifyReply;\r\n\r\n /**\r\n * Current status code\r\n */\r\n protected currentStatusCode = 200;\r\n\r\n /**\r\n * Current response body\r\n */\r\n protected currentBody: any;\r\n\r\n /**\r\n * Request object\r\n */\r\n public request!: Request;\r\n\r\n /**\r\n * Internal events related to this particular response object\r\n */\r\n protected events = new Map<string, any[]>();\r\n\r\n /**\r\n * Parsed body\r\n * This will return the parsed body of the response\r\n * Please note that if this property is called before the response is sent, it will return undefined\r\n */\r\n public parsedBody: any;\r\n\r\n /**\r\n * Get raw response\r\n */\r\n public get raw() {\r\n return this.baseResponse.raw;\r\n }\r\n\r\n /**\r\n * Get Current response body\r\n */\r\n public get body() {\r\n return this.currentBody;\r\n }\r\n\r\n /**\r\n * Set response body\r\n */\r\n public set body(body: any) {\r\n this.currentBody = body;\r\n }\r\n\r\n /**\r\n * Add event on sending response\r\n */\r\n public onSending(callback: any) {\r\n this.events.set(\"sending\", [...(this.events.get(\"sending\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Add event on sent response\r\n */\r\n public onSent(callback: any) {\r\n this.events.set(\"sent\", [...(this.events.get(\"sent\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the Fastify response object\r\n */\r\n public setResponse(response: FastifyReply) {\r\n this.baseResponse = response;\r\n\r\n // Listen to the 'finish' event to track when response is fully sent\r\n // This works for all response types: JSON, streams, buffers, files, etc.\r\n this.baseResponse.raw.once(\"finish\", () => {\r\n this.request.endTime = Date.now();\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Reset the response state\r\n */\r\n public reset() {\r\n this.route = {} as Route;\r\n this.currentBody = null;\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n /**\r\n * Set current route\r\n */\r\n public setRoute(route: Route) {\r\n this.route = route;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the content type\r\n */\r\n public get contentType() {\r\n return this.baseResponse.getHeader(\"Content-Type\");\r\n }\r\n\r\n /**\r\n * Set the content type\r\n */\r\n public setContentType(contentType: string) {\r\n this.baseResponse.header(\"Content-Type\", contentType);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the status code\r\n */\r\n public get statusCode(): number {\r\n return this.currentStatusCode ?? this.baseResponse.statusCode;\r\n }\r\n\r\n /**\r\n * Check if response status is ok\r\n */\r\n public get isOk() {\r\n return this.currentStatusCode >= 200 && this.currentStatusCode < 300;\r\n }\r\n\r\n /**\r\n * Check if the response has been sent\r\n */\r\n public get sent() {\r\n return this.baseResponse.sent;\r\n }\r\n\r\n /**\r\n * Add a listener to the response event\r\n */\r\n public static on(\r\n event: ResponseEvent,\r\n listener: (response: Response) => void,\r\n ): EventSubscription {\r\n return events.subscribe(`response.${event}`, listener);\r\n }\r\n\r\n /**\r\n * Trigger the response event\r\n */\r\n protected static async trigger(event: ResponseEvent, ...args: any[]) {\r\n // make a timeout to make sure the request events is executed first\r\n return new Promise((resolve) => {\r\n setImmediate(async () => {\r\n await events.triggerAllAsync(`response.${event}`, ...args);\r\n resolve(true);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Parse body\r\n */\r\n protected async parseBody() {\r\n return await this.parse(this.currentBody);\r\n }\r\n\r\n /**\r\n * Parse the given value\r\n */\r\n public async parse(value: any): Promise<any> {\r\n // if it is a falsy value, return it\r\n if (!value || isScalar(value)) return value;\r\n\r\n // binary and stream bodies pass through untouched (Buffer has a `toJSON`)\r\n if (Buffer.isBuffer(value) || value instanceof Uint8Array || typeof value.pipe === \"function\") {\r\n return value;\r\n }\r\n\r\n // a Map serialises as a plain object rather than an array of pairs\r\n if (value instanceof Map) {\r\n return await this.parse(Object.fromEntries(value));\r\n }\r\n\r\n // if it has a `toJSON` method, call it and await the result then return it\r\n if (value.toJSON) {\r\n value.request = this.request;\r\n return await value.toJSON();\r\n }\r\n\r\n // if it is iterable, an array or array-like object then parse each item\r\n if (isIterable(value)) {\r\n const values = Array.from(value);\r\n\r\n return Promise.all(\r\n values.map(async (item: any) => {\r\n return await this.parse(item);\r\n }),\r\n );\r\n }\r\n\r\n // if not plain object, then return it\r\n if (!isPlainObject(value)) {\r\n return value;\r\n }\r\n\r\n // loop over the object and check if the value and call `parse` on it\r\n // parse into a copy so caller-owned (e.g. cached) objects are never rewritten\r\n const parsed: Record<string, any> = {};\r\n\r\n for (const key in value) {\r\n parsed[key] = await this.parse(value[key]);\r\n }\r\n\r\n return parsed;\r\n }\r\n\r\n /**\r\n * Make a log message\r\n */\r\n public log(message: string, level: LogLevel = \"info\") {\r\n if (!config.get(\"http.log\")) return;\r\n\r\n log.log({\r\n module: \"response\",\r\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.request.id}`,\r\n message,\r\n type: level,\r\n context: {\r\n request: this.request,\r\n response: this,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Check if returning response is json\r\n */\r\n public get isJson() {\r\n return this.getHeader(\"Content-Type\") === \"application/json\";\r\n }\r\n\r\n /**\r\n * Send the response\r\n * @param data - Response data\r\n * @param statusCode - HTTP status code\r\n * @param triggerEvents - Whether to trigger response events (default: true)\r\n */\r\n public async send(data?: any, statusCode?: number, triggerEvents = true): Promise<Response> {\r\n // Defensive guard against double-send. The underlying Fastify reply silently\r\n // ignores subsequent sends once `sent === true`, which has historically hidden\r\n // middleware bugs (cache-pattern replay paths that returned `baseResponse.send`\r\n // ended up re-entering `Response.send` with the FastifyReply as the body).\r\n // Surfacing the misuse via `error`-level log makes the bug loud without\r\n // crashing production traffic.\r\n if (this.baseResponse.sent) {\r\n log.error(\r\n \"response\",\r\n \"send\",\r\n `send() called on already-sent response (request:${this.request?.id ?? \"unknown\"}) — likely a middleware bug`,\r\n );\r\n\r\n return this;\r\n }\r\n\r\n if (statusCode) {\r\n this.currentStatusCode = statusCode;\r\n }\r\n\r\n if (data === this) return this;\r\n\r\n if (data !== undefined) {\r\n this.currentBody = data;\r\n }\r\n\r\n if (!this.currentStatusCode) {\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n this.log(\"Sending response\");\r\n // Auto-pick `application/json` only when no content-type was set by the caller.\r\n // This preserves explicit overrides (e.g. `application/vnd.api+json` from a\r\n // cache replay, `application/problem+json` from an RFC 7807 error response)\r\n // while keeping the convenience default for the common object-body path.\r\n if (Array.isArray(this.currentBody) || isPlainObject(this.currentBody)) {\r\n if (!this.baseResponse.getHeader(\"Content-Type\")) {\r\n this.setContentType(\"application/json\");\r\n }\r\n }\r\n\r\n if (triggerEvents) {\r\n await Response.trigger(\"sending\", this);\r\n\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isJson) {\r\n await Response.trigger(\"sendingJson\", this);\r\n for (const callback of this.events.get(\"sendingJson\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isOk) {\r\n await Response.trigger(\"sendingSuccessJson\", this);\r\n for (const callback of this.events.get(\"sendingSuccessJson\") || []) {\r\n await callback(this);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // parse the body and make sure it is transformed to sync data instead of async data\r\n if (typeof this.currentBody !== \"string\") {\r\n this.parsedBody = await this.parseBody();\r\n } else {\r\n this.parsedBody = data;\r\n }\r\n\r\n // Set the status first\r\n this.baseResponse.status(this.currentStatusCode);\r\n\r\n // Then send the response with the parsed body\r\n await this.baseResponse.send(this.parsedBody);\r\n\r\n this.log(\"Response sent\");\r\n\r\n if (triggerEvents) this.fireSentEvents();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Fire the post-send lifecycle (`sent`, `success`, status-specific and `error`\r\n * events). Every helper that writes to the Fastify reply directly (noContent,\r\n * redirect, sendFile, sendBuffer, sendImage, downloadFile) goes through here.\r\n */\r\n protected fireSentEvents(statusCode?: number) {\r\n if (statusCode) this.currentStatusCode = statusCode;\r\n\r\n // trigger the sent event\r\n Response.trigger(\"sent\", this);\r\n\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // trigger the success event if the status code is 2xx\r\n if (this.currentStatusCode >= 200 && this.currentStatusCode < 300) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // trigger the successCreate event if the status code is 201\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n // trigger the badRequest event if the status code is 400\r\n if (this.currentStatusCode === 400) {\r\n Response.trigger(\"badRequest\", this);\r\n }\r\n\r\n // trigger the unauthorized event if the status code is 401\r\n if (this.currentStatusCode === 401) {\r\n Response.trigger(\"unauthorized\", this);\r\n }\r\n\r\n // trigger the forbidden event if the status code is 403\r\n if (this.currentStatusCode === 403) {\r\n Response.trigger(\"forbidden\", this);\r\n }\r\n\r\n // trigger the notFound event if the status code is 404\r\n if (this.currentStatusCode === 404) {\r\n Response.trigger(\"notFound\", this);\r\n }\r\n\r\n // trigger the content too large event if the status code is 413\r\n if (this.currentStatusCode === 413) {\r\n Response.trigger(\"contentTooLarge\", this);\r\n }\r\n\r\n // trigger the throttled event if the status code is 429\r\n if (this.currentStatusCode === 429) {\r\n Response.trigger(\"throttled\", this);\r\n }\r\n\r\n // trigger the serverError event if the status code is 500\r\n if (this.currentStatusCode === 500) {\r\n Response.trigger(\"serverError\", this);\r\n }\r\n\r\n // trigger the error event if the status code is 4xx or 5xx\r\n if (this.currentStatusCode >= 400) {\r\n Response.trigger(\"error\", this);\r\n }\r\n }\r\n\r\n /** Send a bare 304 and fire the lifecycle. */\r\n protected send304() {\r\n const reply = this.baseResponse.status(304).send();\r\n\r\n this.fireSentEvents(304);\r\n\r\n return reply;\r\n }\r\n\r\n /** Send a raw stream/buffer body and fire the lifecycle. */\r\n protected sendRaw(body: any) {\r\n const reply = this.baseResponse.send(body);\r\n\r\n this.fireSentEvents(this.baseResponse.statusCode);\r\n\r\n return reply;\r\n }\r\n\r\n /**\r\n * Replay a previously-captured response shape — used by cache-pattern\r\n * middlewares (idempotency, response cache) to send a cached response\r\n * without re-running the controller.\r\n *\r\n * Preserves the cached status code, content-type, and any extra headers,\r\n * then sends the body through the standard `send()` pipeline so the full\r\n * event lifecycle still fires (`sent`, `success`, status-specific events).\r\n * That keeps cross-cutting observers (logger, metrics, audit) consistent\r\n * between fresh and replayed responses.\r\n *\r\n * @example\r\n * // Inside a cache-pattern middleware on HIT:\r\n * return response.header(\"X-Cache\", \"HIT\").replay({\r\n * status: cached.status,\r\n * body: cached.body,\r\n * contentType: cached.contentType,\r\n * });\r\n */\r\n public replay(cached: {\r\n status: number;\r\n body: unknown;\r\n contentType?: string;\r\n headers?: Record<string, string>;\r\n }): Promise<Response> {\r\n this.setStatusCode(cached.status);\r\n\r\n if (cached.contentType) {\r\n this.setContentType(cached.contentType);\r\n }\r\n\r\n if (cached.headers) {\r\n for (const [name, value] of Object.entries(cached.headers)) {\r\n this.header(name, value);\r\n }\r\n }\r\n\r\n return this.send(cached.body);\r\n }\r\n\r\n /**\r\n * Send html response\r\n */\r\n public html(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/html\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Render the given react component\r\n */\r\n public render(element: React.ReactElement | React.ComponentType, status = 200) {\r\n return this.setStatusCode(status).html(renderReact(element));\r\n }\r\n\r\n /**\r\n * Send an xml response.\r\n *\r\n * Accepts either a raw XML string or an `XMLable` — anything with a\r\n * `toXML()` method, structurally typed so packages like\r\n * `@warlock.js/sitemap` never need to import core. This is the BOUNDED\r\n * path only: a very large document should be streamed from generated\r\n * files instead of passed through here.\r\n */\r\n public xml(body: string | XMLable, statusCode?: number) {\r\n if (typeof body === \"string\") {\r\n return this.setContentType(\"application/xml\").send(body, statusCode);\r\n }\r\n\r\n if (typeof body?.toXML !== \"function\") {\r\n throw new TypeError(\r\n \"response.xml() expects a string or an XMLable value (an object with a toXML() method).\",\r\n );\r\n }\r\n\r\n return this.setContentType(\"application/xml\").send(body.toXML(), statusCode);\r\n }\r\n\r\n /**\r\n * Send plain text response\r\n */\r\n public text(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/plain\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Create a streaming response for progressive/chunked data sending\r\n *\r\n * This method allows you to send data in chunks and control when the response ends.\r\n * Perfect for Server-Sent Events (SSE), progressive rendering, or streaming large responses.\r\n *\r\n * @example\r\n * ```ts\r\n * const stream = response.stream(\"text/html\");\r\n * stream.send(\"<html><body>\");\r\n * stream.send(\"<h1>Hello</h1>\");\r\n * stream.render(<MyComponent />);\r\n * stream.send(\"</body></html>\");\r\n * stream.end();\r\n * ```\r\n *\r\n * @param contentType - The content type for the stream (default: \"text/plain\")\r\n * @returns Stream controller with send(), render(), and end() methods\r\n */\r\n public stream(contentType = \"text/plain\"): ResponseStreamController {\r\n // Set headers using the response API\r\n this.setContentType(contentType);\r\n this.header(\"Transfer-Encoding\", \"chunked\");\r\n this.header(\"Cache-Control\", \"no-cache\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Content-Type-Options\", \"nosniff\");\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const chunks: any[] = [];\r\n\r\n // Write headers to start the stream\r\n // Note: We use raw here because we need chunked encoding control\r\n // This is the only valid use case for bypassing Fastify's abstraction\r\n this.flushPendingCookies();\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n return {\r\n /**\r\n * Send a chunk of data to the client\r\n * @param data - Data to send (string, Buffer, or any serializable data)\r\n */\r\n send: (data: any) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot send data: stream has already ended\");\r\n }\r\n\r\n this.baseResponse.raw.write(data);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Render a React component and send it as a chunk\r\n * @param element - React element or component to render\r\n */\r\n render: (element: ReactNode) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot render: stream has already ended\");\r\n }\r\n\r\n const html = renderReact(element);\r\n chunks.push(html);\r\n this.baseResponse.raw.write(html);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * End the stream and trigger completion events\r\n */\r\n end: () => {\r\n if (isEnded) {\r\n return this;\r\n }\r\n\r\n isEnded = true;\r\n\r\n // Store the streamed content for logging/debugging\r\n this.currentBody = chunks;\r\n this.parsedBody = chunks;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"Stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // Trigger status-specific events\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Pipe a React server stream (`renderToPipeableStream`) onto this\r\n * response — the Stage 1 streaming SSR seam. Writes the already-committed\r\n * status and headers, then pipes; aborts the React render if the client\r\n * disconnects before the stream finishes.\r\n *\r\n * This is the ONLY sanctioned way for `@warlock.js/web` to put a React\r\n * stream on the wire: it never touches `response.raw` itself, it calls\r\n * this method, which does (`stream-react-response.ts`).\r\n *\r\n * @example\r\n * ```ts\r\n * const pipeableStream = await renderPageToPipeableStream(element);\r\n * await response.streamReact(pipeableStream);\r\n * ```\r\n */\r\n public streamReact(pipeableStream: PipeableReactStream): Promise<void> {\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.flushPendingCookies();\r\n\r\n return streamReactResponse({\r\n raw: this.baseResponse.raw,\r\n statusCode: this.statusCode,\r\n headers: this.getHeaders() as OutgoingHttpHeaders,\r\n pipeableStream,\r\n }).then(() => {\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Create a Server-Sent Events (SSE) stream\r\n *\r\n * SSE is a standard for pushing real-time updates from server to client.\r\n * Perfect for live notifications, progress updates, or real-time data feeds.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n *\r\n * // Send events\r\n * sse.send(\"message\", { text: \"Hello!\" });\r\n * sse.send(\"notification\", { type: \"info\", message: \"Update available\" }, \"msg-123\");\r\n *\r\n * // Keep connection alive\r\n * const keepAlive = setInterval(() => sse.comment(\"ping\"), 30000);\r\n *\r\n * // Clean up when done\r\n * clearInterval(keepAlive);\r\n * sse.end();\r\n * ```\r\n *\r\n * @returns SSE controller with send(), comment(), and end() methods\r\n */\r\n public sse(): ResponseSSEController {\r\n // Set SSE-specific headers\r\n this.setContentType(\"text/event-stream\");\r\n this.header(\"Cache-Control\", \"no-cache, no-store, must-revalidate\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Accel-Buffering\", \"no\"); // Disable nginx buffering\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting SSE stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const events: any[] = [];\r\n const disconnectHandlers: Array<() => void> = [];\r\n\r\n // Write headers to start the stream\r\n this.flushPendingCookies();\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n // Detect client disconnect — set isEnded silently and invoke cleanup handlers.\r\n // Without this, background jobs keep writing to a dead socket after the client drops.\r\n this.baseResponse.raw.on(\"close\", () => {\r\n if (!isEnded) {\r\n isEnded = true;\r\n this.log(\"SSE client disconnected\");\r\n for (const handler of disconnectHandlers) {\r\n handler();\r\n }\r\n }\r\n });\r\n\r\n const controller: ResponseSSEController = {\r\n /**\r\n * Send an SSE event\r\n * @param event - Event name (e.g., \"message\", \"chunk\", \"done\")\r\n * @param data - Event data (will be JSON stringified)\r\n * @param id - Optional event ID for client-side Last-Event-ID tracking (reconnect support)\r\n */\r\n send: (event: string, data: any, id?: string): ResponseSSEController => {\r\n // Silent no-op after disconnect — background jobs should not crash when\r\n // the client drops mid-stream. The onDisconnect handler handles cleanup.\r\n if (isEnded) return controller;\r\n\r\n let message = \"\";\r\n if (id) message += `id: ${id}\\n`;\r\n message += `event: ${event}\\n`;\r\n message += `data: ${JSON.stringify(data)}\\n\\n`;\r\n\r\n events.push({ event, data, id });\r\n this.baseResponse.raw.write(message);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Send a comment (keeps connection alive, invisible to client)\r\n * Useful for preventing timeout on long-lived connections\r\n * @param text - Comment text\r\n */\r\n comment: (text: string): ResponseSSEController => {\r\n // Silent no-op after disconnect\r\n if (isEnded) return controller;\r\n\r\n this.baseResponse.raw.write(`: ${text}\\n\\n`);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * End the SSE stream and trigger completion events\r\n */\r\n end: (): ResponseSSEController => {\r\n if (isEnded) return controller;\r\n\r\n isEnded = true;\r\n\r\n // Store the events for logging/debugging\r\n this.currentBody = events;\r\n this.parsedBody = events;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"SSE stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Register a handler to be called when the client disconnects.\r\n * Use this to clean up EventEmitter listeners, cancel background jobs, etc.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n * const listener = (chunk) => sse.send(\"chunk\", { chunk });\r\n * eventBus.on(aiMessageId, listener);\r\n * sse.onDisconnect(() => eventBus.off(aiMessageId, listener));\r\n * ```\r\n */\r\n onDisconnect: (handler: () => void): ResponseSSEController => {\r\n disconnectHandlers.push(handler);\r\n return controller;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended (either via end() or client disconnect)\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n\r\n return controller;\r\n }\r\n\r\n /**\r\n * Set the status code\r\n */\r\n public setStatusCode(statusCode: number) {\r\n this.currentStatusCode = statusCode;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Redirect the user to another route\r\n */\r\n public redirect(url: string, statusCode = 302) {\r\n this.baseResponse.redirect(url, statusCode);\r\n this.fireSentEvents(statusCode);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Permanent redirect\r\n */\r\n public permanentRedirect(url: string) {\r\n this.baseResponse.redirect(url, 301);\r\n this.fireSentEvents(301);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the response time\r\n */\r\n public getResponseTime() {\r\n return this.baseResponse.elapsedTime;\r\n }\r\n\r\n /**\r\n * Remove a specific header\r\n */\r\n public removeHeader(key: string) {\r\n this.baseResponse.removeHeader(key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get a specific header\r\n */\r\n public getHeader(key: string) {\r\n return this.baseResponse.getHeader(key);\r\n }\r\n\r\n /**\r\n * Get the response headers\r\n */\r\n /**\r\n * Serialise cookies parked by `response.cookie()` into the `set-cookie`\r\n * header. Must run right before any raw `writeHead`, which bypasses\r\n * `@fastify/cookie`'s `onSend` flush.\r\n */\r\n public flushPendingCookies() {\r\n flushPendingCookies(this.baseResponse);\r\n\r\n return this;\r\n }\r\n\r\n public getHeaders() {\r\n return this.baseResponse.getHeaders();\r\n }\r\n\r\n /**\r\n * Set multiple headers\r\n */\r\n public headers(headers: Record<string, string>) {\r\n this.baseResponse.headers(headers);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the response header\r\n */\r\n public header(key: string, value: any) {\r\n this.baseResponse.header(key, value);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set a cookie on the response.\r\n *\r\n * Values are JSON-stringified by default so structured cookies round-trip\r\n * cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the\r\n * JSON wrapping for plain-string cookies (session tokens, opaque IDs).\r\n *\r\n * **Secure by default.** `httpOnly: true`, `sameSite: \"lax\"`, and — outside\r\n * development — `secure: true` are applied unless you override them. These\r\n * are the flags whose absence never fails a test and is fatal in production:\r\n * without `httpOnly` any injected script can read the cookie, without\r\n * `secure` it travels in cleartext, without `sameSite` it rides along on\r\n * cross-site requests. Opting out is explicit, per call or via\r\n * `http.cookies.options`.\r\n *\r\n * Precedence, lowest to highest: framework defaults → `http.cookies.options`\r\n * → the per-call `options` argument.\r\n *\r\n * @example\r\n * // JSON-wrapped (default) — round-trips with request.cookie()\r\n * response.cookie(\"prefs\", { theme: \"dark\" }, { maxAge: 3600 });\r\n *\r\n * @example\r\n * // Raw string — no JSON quoting; useful for tokens / opaque IDs\r\n * response.cookie(\"session\", \"abc.def.ghi\", { raw: true });\r\n *\r\n * @example\r\n * // Deliberately readable by client-side JS\r\n * response.cookie(\"theme\", \"dark\", { httpOnly: false });\r\n */\r\n public cookie(name: string, value: CookieValue, options: CookieOptions = {}) {\r\n const { raw, ...cookieOptions } = options;\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n const serializedValue = raw ? String(value) : JSON.stringify(value);\r\n\r\n this.baseResponse.setCookie(name, serializedValue, {\r\n ...secureCookieDefaults(),\r\n ...defaultOptions,\r\n ...cookieOptions,\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the active locale for subsequent requests.\r\n *\r\n * Writes the SAME cookie `request.locale` reads (`LOCALE_COOKIE_NAME`,\r\n * owned by the framework, never a string an app hardcodes) — the two sides\r\n * are read from one shared constant so they cannot name the cookie\r\n * differently. Written raw (no JSON quoting), matching how\r\n * `Request.resolveLocale()` reads it back.\r\n *\r\n * Any `Set-Cookie` this emits revokes public cacheability for the response,\r\n * including a page that opted into `public, max-age` — a per-visitor\r\n * locale cookie replayed from a shared cache would hand visitor A's locale\r\n * to visitor B.\r\n *\r\n * @throws {UnknownLocaleError} when `locale` is outside the app's\r\n * configured `app.localeCodes` allow-list. Silently accepting an\r\n * unconfigured locale would set a cookie the app can never actually serve.\r\n *\r\n * @example\r\n * response.setLocale(\"ar\");\r\n */\r\n public setLocale(locale: string) {\r\n const { localeCodes } = resolveLocaleConfiguration(\r\n config.get(\"app.localeCode\"),\r\n config.get(\"app.localeCodes\"),\r\n );\r\n\r\n if (localeCodes !== undefined && !localeCodes.includes(locale)) {\r\n throw new UnknownLocaleError(locale, localeCodes);\r\n }\r\n\r\n // A controller's explicit server-side choice is authoritative. The\r\n // preference cookie is host-only by contract, so clearing it must never\r\n // inherit an application's configured cookie Domain.\r\n this.baseResponse.clearCookie(LOCALE_PREFERENCE_COOKIE_NAME, {\r\n path: \"/\",\r\n sameSite: \"lax\",\r\n httpOnly: false,\r\n domain: undefined,\r\n });\r\n\r\n return this.cookie(LOCALE_COOKIE_NAME, locale, { raw: true });\r\n }\r\n\r\n /**\r\n * Clear a cookie from the response\r\n *\r\n * @example\r\n * response.clearCookie('token', { path: '/' });\r\n */\r\n public clearCookie(name: string, options?: CookieSerializeOptions) {\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n this.baseResponse.clearCookie(name, { ...defaultOptions, ...options });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Clear every cookie NAME the current request sent — best-effort, not\r\n * exhaustive: HTTP gives the server no way to discover a cookie's `Path`\r\n * or `Domain`, only the name, so a cookie originally set on a `path` or\r\n * `domain` other than the one this call targets (the framework default,\r\n * or whatever is passed here / configured via `http.cookies.options`)\r\n * will NOT be deleted, and nothing will report that — the browser just\r\n * silently ignores a `Set-Cookie` whose scope doesn't match. Pass an\r\n * explicit `path` / `domain` for cookies the app knows it owns on a\r\n * non-default scope; call `clearCookie()` per name for anything else.\r\n *\r\n * @example\r\n * response.clearCookies();\r\n * response.clearCookies({ path: '/admin' });\r\n */\r\n public clearCookies(options?: CookieSerializeOptions) {\r\n for (const name of Object.keys(this.request.cookies)) {\r\n this.clearCookie(name, options);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Alias to header method\r\n */\r\n public setHeader(key: string, value: any) {\r\n return this.header(key, value);\r\n }\r\n\r\n /**\r\n * Send an error response with status code 500\r\n */\r\n public serverError(data: any) {\r\n return this.send(data, 500);\r\n }\r\n\r\n /**\r\n * Send a forbidden response with status code 403\r\n */\r\n public forbidden(\r\n data: any = {\r\n error: \"You are not allowed to access this resource, FORBIDDEN\",\r\n },\r\n ) {\r\n return this.send(data, 403);\r\n }\r\n\r\n /**\r\n * Send a service unavailable response with status code 503\r\n */\r\n public serviceUnavailable(data: any) {\r\n return this.send(data, 503);\r\n }\r\n\r\n /**\r\n * Send an unauthorized response with status code 401\r\n */\r\n public unauthorized(\r\n data: any = {\r\n error: \"unauthorized\",\r\n },\r\n ) {\r\n return this.send(data, 401);\r\n }\r\n\r\n /**\r\n * Send a not found response with status code 404\r\n */\r\n public notFound(\r\n data: any = {\r\n error: \"notFound\",\r\n },\r\n ) {\r\n return this.send(data, 404);\r\n }\r\n\r\n /**\r\n * Send a bad request response with status code 400\r\n */\r\n public badRequest(data: any) {\r\n return this.send(data, 400);\r\n }\r\n\r\n /**\r\n * Send a content too large response with status code 413\r\n */\r\n public contentTooLarge(data: any) {\r\n return this.send(data, 413);\r\n }\r\n\r\n /**\r\n * Send a success response with status code 201\r\n */\r\n public successCreate(data: any) {\r\n return this.send(data, 201);\r\n }\r\n\r\n /**\r\n * Send a success response\r\n */\r\n public success(data: any = { success: true }) {\r\n return this.send(data);\r\n }\r\n\r\n /**\r\n * Send a no content response with status code 204\r\n */\r\n public noContent() {\r\n const reply = this.baseResponse.status(204).send();\r\n\r\n this.fireSentEvents(204);\r\n\r\n return reply;\r\n }\r\n\r\n /**\r\n * Send an accepted response with status code 202\r\n * Used for async operations that have been accepted but not yet processed\r\n */\r\n public accepted(data: any = { message: \"Request accepted for processing\" }) {\r\n return this.send(data, 202);\r\n }\r\n\r\n /**\r\n * Send a conflict response with status code 409\r\n */\r\n public conflict(data: any = { error: \"Resource conflict\" }) {\r\n return this.send(data, 409);\r\n }\r\n\r\n /**\r\n * Send a too many requests response with status code 429\r\n */\r\n public tooManyRequests(data: any) {\r\n return this.send(data, 429);\r\n }\r\n\r\n /**\r\n * Send an unprocessable entity response with status code 422\r\n * Used for semantic validation errors\r\n */\r\n public unprocessableEntity(data: any) {\r\n return this.send(data, 422);\r\n }\r\n\r\n /**\r\n * Apply response options (cache, disposition, etag)\r\n * Shared helper for sendFile and sendBuffer\r\n */\r\n /**\r\n * Build an RFC 6266–safe `Content-Disposition` value for a file name.\r\n *\r\n * HTTP header values must be ISO-8859-1; Node's `setHeader` throws\r\n * `ERR_INVALID_CHAR` on any other byte, so a raw `filename=\"تقرير.pdf\"` would\r\n * crash the response (a 500 the moment the name is non-ASCII). RFC 6266 solves\r\n * this with two parameters emitted together:\r\n * - `filename=\"<ascii>\"` — sanitised ASCII fallback for legacy clients\r\n * - `filename*=UTF-8''<pct>` — RFC 5987 ext-value; modern browsers restore\r\n * the real (e.g. Arabic) name.\r\n */\r\n private contentDisposition(type: \"inline\" | \"attachment\", rawName: string): string {\r\n // Strip CR/LF first so a crafted file name can never inject extra headers.\r\n const name = (rawName || \"file\").replace(/[\\r\\n]/g, \"\");\r\n\r\n // ASCII fallback: replace quote/backslash + every non-printable-ASCII byte\r\n // (covers all multibyte characters) so the quoted-string is always legal.\r\n const ascii =\r\n name\r\n .replace(/[\"\\\\]/g, \"_\")\r\n .replace(/[^\\x20-\\x7E]/g, \"_\")\r\n .trim() || \"file\";\r\n\r\n // Pure-ASCII name → the quoted form is enough; no ext-value needed.\r\n if (!/[^\\x20-\\x7E]/.test(name)) {\r\n return `${type}; filename=\"${ascii}\"`;\r\n }\r\n\r\n // RFC 5987 ext-value. `encodeURIComponent` leaves ' ( ) * unescaped, but they\r\n // are not valid `attr-char`, so percent-encode those too.\r\n const encoded = encodeURIComponent(name).replace(\r\n /['()*]/g,\r\n (char) => \"%\" + char.charCodeAt(0).toString(16).toUpperCase(),\r\n );\r\n\r\n return `${type}; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\r\n }\r\n\r\n private applyResponseOptions(options: SendBufferOptions, defaultFilename?: string): boolean {\r\n // Set content type if provided\r\n if (options.contentType) {\r\n this.baseResponse.type(options.contentType);\r\n }\r\n\r\n // Set cache headers if specified\r\n if (options.cacheTime) {\r\n const cacheControl = options.immutable\r\n ? `public, max-age=${options.cacheTime}, immutable`\r\n : `public, max-age=${options.cacheTime}`;\r\n this.header(\"Cache-Control\", cacheControl);\r\n this.header(\"Expires\", new Date(Date.now() + options.cacheTime * 1000).toUTCString());\r\n }\r\n\r\n // Set ETag if provided (for conditional requests)\r\n if (options.etag) {\r\n this.header(\"ETag\", options.etag);\r\n\r\n // Check If-None-Match for conditional request\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n if (ifNoneMatch && matchesIfNoneMatch(ifNoneMatch, options.etag)) {\r\n this.log(\"Content not modified (ETag match), sending 304\");\r\n this.send304();\r\n return true; // Indicates 304 was sent\r\n }\r\n }\r\n\r\n // Set Content-Disposition if inline or filename is specified\r\n if (options.inline !== undefined || options.filename) {\r\n const disposition = options.inline ? \"inline\" : \"attachment\";\r\n const filename = options.filename || defaultFilename || \"file\";\r\n this.header(\"Content-Disposition\", this.contentDisposition(disposition, filename));\r\n }\r\n\r\n return false; // No 304 sent\r\n }\r\n\r\n /**\r\n * Send a file as a response\r\n */\r\n public async sendFile(filePath: string | StorageFile, options?: number | SendFileOptions) {\r\n if (filePath instanceof StorageFile) {\r\n filePath = filePath.absolutePath!;\r\n }\r\n\r\n this.log(`Sending file: ${filePath}`);\r\n\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get file stats for ETag and Last-Modified\r\n const stats = await fs.promises.stat(filePath);\r\n const lastModified = stats.mtime;\r\n\r\n // Generate ETag based on file size and modification time\r\n const etag = `\"${stats.size}-${stats.mtime.getTime()}\"`;\r\n\r\n // Set Last-Modified header\r\n this.header(\"Last-Modified\", lastModified.toUTCString());\r\n this.header(\"ETag\", etag);\r\n\r\n // Set content type\r\n const contentType = this.getFileContentType(filePath);\r\n this.baseResponse.type(contentType);\r\n\r\n // Apply common response options (cache, disposition)\r\n const defaultFilename = path.basename(filePath);\r\n const sent304 = this.applyResponseOptions({ ...opts, etag, contentType }, defaultFilename);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Check conditional request headers\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n const ifModifiedSince = this.request.header(\"if-modified-since\");\r\n\r\n // Handle If-None-Match (ETag validation)\r\n if (ifNoneMatch && matchesIfNoneMatch(ifNoneMatch, etag)) {\r\n this.log(\"File not modified (ETag match), sending 304\");\r\n return this.send304();\r\n }\r\n\r\n // Handle If-Modified-Since (Last-Modified validation)\r\n // RFC 9110: If-Modified-Since is ignored when If-None-Match is present.\r\n // The header has second precision, so floor the mtime to seconds.\r\n if (ifModifiedSince && !ifNoneMatch) {\r\n const modifiedSinceDate = new Date(ifModifiedSince);\r\n if (\r\n Math.floor(lastModified.getTime() / 1000) <=\r\n Math.floor(modifiedSinceDate.getTime() / 1000)\r\n ) {\r\n this.log(\"File not modified (Last-Modified check), sending 304\");\r\n return this.send304();\r\n }\r\n }\r\n\r\n // Use streaming for efficient file sending\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.sendRaw(stream);\r\n } catch (error: any) {\r\n this.log(`Error sending file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error sending file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Send buffer as a response\r\n * Useful for dynamically generated content (e.g., resized images, generated PDFs)\r\n */\r\n public sendBuffer(buffer: Buffer, options?: number | SendBufferOptions) {\r\n this.log(\"Sending buffer\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Apply common response options (cache, disposition, etag)\r\n const sent304 = this.applyResponseOptions(opts);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.sendRaw(buffer);\r\n }\r\n\r\n /**\r\n * Send an Image instance as a response\r\n * Automatically detects image format and sets content type\r\n */\r\n public async sendImage(\r\n image: any, // Type as 'any' to avoid circular dependency with Image class\r\n options?: number | (Omit<SendBufferOptions, \"contentType\"> & { contentType?: string }),\r\n ) {\r\n this.log(\"Sending image\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get image metadata to determine format\r\n const metadata = await image.metadata();\r\n const format = metadata.format || \"jpeg\";\r\n\r\n // Convert image to buffer\r\n const buffer = await image.toBuffer();\r\n\r\n // Auto-set content type if not provided\r\n const contentType = opts.contentType || `image/${format}`;\r\n\r\n // Auto-generate ETag if not provided\r\n // Format: \"format-widthxheight-size\" (e.g., \"jpeg-800x600-45231\")\r\n // This catches changes in dimensions, quality, filters, and format\r\n if (!opts.etag) {\r\n const width = metadata.width || 0;\r\n const height = metadata.height || 0;\r\n opts.etag = `\"${format}-${width}x${height}-${buffer.length}\"`;\r\n }\r\n\r\n // Apply common response options with auto-detected content type\r\n const sent304 = this.applyResponseOptions({ ...opts, contentType });\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.sendRaw(buffer);\r\n }\r\n\r\n /**\r\n * Send file and cache it\r\n * Cache time in seconds\r\n * Cache time will be one year\r\n */\r\n public sendCachedFile(path: string | StorageFile, cacheTime = 31536000) {\r\n return this.sendFile(path, cacheTime);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public download(path: string, filename?: string) {\r\n return this.downloadFile(path, filename);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public async downloadFile(filePath: string, filename?: string) {\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n if (!filename) {\r\n filename = path.basename(filePath);\r\n }\r\n\r\n this.baseResponse.header(\r\n \"Content-Disposition\",\r\n this.contentDisposition(\"attachment\", filename),\r\n );\r\n\r\n // this.baseResponse.header(\"Content-Type\", this.getFileContentType(filePath));\r\n this.baseResponse.header(\"Content-Type\", \"application/octet-stream\");\r\n\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file for download: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.sendRaw(stream);\r\n } catch (error: any) {\r\n this.log(`Error downloading file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error downloading file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Get content type of the given path\r\n */\r\n public getFileContentType(filePath: string) {\r\n const type = mime.getType(filePath) || \"application/octet-stream\";\r\n return type;\r\n }\r\n\r\n /**\r\n * Mark the response as failed\r\n */\r\n public failedSchema(result: ValidationResult) {\r\n const { errors, inputKey, inputError, status } = {\r\n errors: \"errors\",\r\n inputKey: \"input\",\r\n inputError: \"error\",\r\n status: 422,\r\n ...config.get(\"validation.response\", {}),\r\n };\r\n\r\n log.error(\"request\", \"validation\", `${this.request.id} - Validation failed`);\r\n\r\n return this.send(\r\n {\r\n [errors]: result.errors.map((error) => ({\r\n [inputKey]: error.input,\r\n [inputError]: error.error,\r\n })),\r\n },\r\n status,\r\n );\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"response.mjs","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"sourcesContent":["import type { CookieSerializeOptions } from \"@fastify/cookie\";\nimport type { OutgoingHttpHeaders } from \"node:http\";\nimport config from \"@mongez/config\";\nimport type { EventSubscription } from \"@mongez/events\";\nimport events from \"@mongez/events\";\nimport { fileExistsAsync } from \"@warlock.js/fs\";\nimport { isIterable, isPlainObject, isScalar } from \"@mongez/supportive-is\";\nimport type { LogLevel } from \"@warlock.js/logger\";\nimport { log } from \"@warlock.js/logger\";\nimport type { ValidationResult } from \"@warlock.js/seal\";\nimport type { FastifyReply } from \"fastify\";\nimport fs from \"fs\";\nimport mime from \"mime\";\nimport path from \"path\";\nimport type React from \"react\";\nimport { type ReactNode } from \"react\";\nimport { Application } from \"../application/application\";\nimport {\n LOCALE_COOKIE_NAME,\n LOCALE_PREFERENCE_COOKIE_NAME,\n resolveLocaleConfiguration,\n} from \"../config/locale-configuration\";\nimport { UnknownLocaleError } from \"../errors/unknown-locale-error\";\nimport type { Route } from \"../router\";\nimport { StorageFile } from \"../storage\";\nimport { renderReact } from \"./../react\";\nimport type { Request } from \"./request\";\nimport { matchesIfNoneMatch } from \"./uploads/matches-if-none-match\";\nimport { flushPendingCookies } from \"./flush-pending-cookies\";\nimport { streamReactResponse, type PipeableReactStream } from \"./stream-react-response\";\nimport type { ResponseEvent, ResponseSSEController, ResponseStreamController } from \"./types\";\nimport type { XMLable } from \"./xmlable\";\n\ntype CookieValue = string | number | boolean | Record<string, any> | Array<any>;\n\n/**\n * Cookie options accepted by `response.cookie()`.\n *\n * Extends Fastify's `CookieSerializeOptions` with `raw` — set to `true` to\n * skip the default `JSON.stringify` of the value and write it as-is. Use for\n * plain-string cookies (session tokens, opaque IDs) that shouldn't be JSON-quoted.\n *\n * When `raw: true`, non-string values are coerced via `String(value)`. The\n * read side (`request.cookie(name)`) tries `JSON.parse` first and falls back\n * to the raw string on parse failure, so round-tripping a raw string cookie\n * Just Works.\n */\nexport type CookieOptions = CookieSerializeOptions & {\n /**\n * Skip JSON.stringify and write the value as-is.\n *\n * @default false\n */\n raw?: boolean;\n};\n\nexport enum ResponseStatus {\n OK = 200,\n CREATED = 201,\n ACCEPTED = 202,\n MOVED_PERMANENTLY = 301,\n FOUND = 302,\n SEE_OTHER = 303,\n NOT_MODIFIED = 304,\n TEMPORARY_REDIRECT = 307,\n PERMANENT_REDIRECT = 308,\n NO_CONTENT = 204,\n BAD_REQUEST = 400,\n UNAUTHORIZED = 401,\n FORBIDDEN = 403,\n NOT_FOUND = 404,\n METHOD_NOT_ALLOWED = 405,\n CONFLICT = 409,\n TOO_MANY_REQUESTS = 429,\n INTERNAL_SERVER_ERROR = 500,\n SERVICE_UNAVAILABLE = 503,\n}\n\n/**\n * Options for sending files\n */\nexport type SendFileOptions = {\n cacheTime?: number;\n immutable?: boolean;\n inline?: boolean;\n filename?: string;\n};\n\n/**\n * Options for sending buffers\n */\nexport type SendBufferOptions = SendFileOptions & {\n contentType?: string;\n etag?: string;\n};\n\n/**\n * The cookie flags every response cookie gets unless something overrides them.\n *\n * Computed per call rather than hoisted to a constant because `secure` depends\n * on the environment, which is not known at module-evaluation time.\n *\n * `secure` is relaxed in development only: a `Secure` cookie is dropped by the\n * browser over plain http, which would silently break every local login. It\n * stays on everywhere else, including test and staging.\n */\nfunction secureCookieDefaults(): CookieSerializeOptions {\n return {\n httpOnly: true,\n sameSite: \"lax\",\n secure: !Application.isDevelopment,\n };\n}\n\nexport class Response {\n /**\n * Current route\n */\n protected route!: Route;\n\n /**\n * Underlying Fastify reply — a public escape hatch to capabilities the\n * framework's high-level helpers don't yet cover.\n *\n * **Prefer framework methods first**: `response.send()`, `response.header()`,\n * `response.cookie()`, `response.sendFile()`, `response.stream()`, etc.\n * They wire status codes, content-type detection, the event lifecycle, and\n * the cache-pattern replay path correctly.\n *\n * **Reach for `baseResponse` only** when the framework genuinely lacks a\n * helper for what you need — and when you do, file an issue so we can add\n * it. Streaming and SSE are the precedent here: they bypass `send()`\n * deliberately because the framework didn't ship chunked-write support\n * natively at the time. Reaching here for non-streaming work means a\n * missing helper, not an answer.\n */\n public baseResponse!: FastifyReply;\n\n /**\n * Current status code\n */\n protected currentStatusCode = 200;\n\n /**\n * Current response body\n */\n protected currentBody: any;\n\n /**\n * Request object\n */\n public request!: Request;\n\n /**\n * Internal events related to this particular response object\n */\n protected events = new Map<string, any[]>();\n\n /**\n * Parsed body\n * This will return the parsed body of the response\n * Please note that if this property is called before the response is sent, it will return undefined\n */\n public parsedBody: any;\n\n /**\n * Get raw response\n */\n public get raw() {\n return this.baseResponse.raw;\n }\n\n /**\n * Get Current response body\n */\n public get body() {\n return this.currentBody;\n }\n\n /**\n * Set response body\n */\n public set body(body: any) {\n this.currentBody = body;\n }\n\n /**\n * Add event on sending response\n */\n public onSending(callback: any) {\n this.events.set(\"sending\", [...(this.events.get(\"sending\") || []), callback]);\n\n return this;\n }\n\n /**\n * Add event on sent response\n */\n public onSent(callback: any) {\n this.events.set(\"sent\", [...(this.events.get(\"sent\") || []), callback]);\n\n return this;\n }\n\n /**\n * Set the Fastify response object\n */\n public setResponse(response: FastifyReply) {\n this.baseResponse = response;\n\n // Listen to the 'finish' event to track when response is fully sent\n // This works for all response types: JSON, streams, buffers, files, etc.\n this.baseResponse.raw.once(\"finish\", () => {\n this.request.endTime = Date.now();\n });\n\n return this;\n }\n\n /**\n * Reset the response state\n */\n public reset() {\n this.route = {} as Route;\n this.currentBody = null;\n this.currentStatusCode = 200;\n }\n\n /**\n * Set current route\n */\n public setRoute(route: Route) {\n this.route = route;\n\n return this;\n }\n\n /**\n * Get the content type\n */\n public get contentType() {\n return this.baseResponse.getHeader(\"Content-Type\");\n }\n\n /**\n * Set the content type\n */\n public setContentType(contentType: string) {\n this.baseResponse.header(\"Content-Type\", contentType);\n\n return this;\n }\n\n /**\n * Get the status code\n */\n public get statusCode(): number {\n return this.currentStatusCode ?? this.baseResponse.statusCode;\n }\n\n /**\n * Check if response status is ok\n */\n public get isOk() {\n return this.currentStatusCode >= 200 && this.currentStatusCode < 300;\n }\n\n /**\n * Check if the response has been sent\n */\n public get sent() {\n return this.baseResponse.sent;\n }\n\n /**\n * Add a listener to the response event\n */\n public static on(\n event: ResponseEvent,\n listener: (response: Response) => void,\n ): EventSubscription {\n return events.subscribe(`response.${event}`, listener);\n }\n\n /**\n * Trigger the response event\n */\n protected static async trigger(event: ResponseEvent, ...args: any[]) {\n // make a timeout to make sure the request events is executed first\n return new Promise((resolve) => {\n setImmediate(async () => {\n await events.triggerAllAsync(`response.${event}`, ...args);\n resolve(true);\n });\n });\n }\n\n /**\n * Parse body\n */\n protected async parseBody() {\n return await this.parse(this.currentBody);\n }\n\n /**\n * Parse the given value\n */\n public async parse(value: any): Promise<any> {\n // if it is a falsy value, return it\n if (!value || isScalar(value)) return value;\n\n // binary and stream bodies pass through untouched (Buffer has a `toJSON`)\n if (Buffer.isBuffer(value) || value instanceof Uint8Array || typeof value.pipe === \"function\") {\n return value;\n }\n\n // a Map serialises as a plain object rather than an array of pairs\n if (value instanceof Map) {\n return await this.parse(Object.fromEntries(value));\n }\n\n // if it has a `toJSON` method, call it and await the result then return it\n if (value.toJSON) {\n value.request = this.request;\n return await value.toJSON();\n }\n\n // if it is iterable, an array or array-like object then parse each item\n if (isIterable(value)) {\n const values = Array.from(value);\n\n return Promise.all(\n values.map(async (item: any) => {\n return await this.parse(item);\n }),\n );\n }\n\n // if not plain object, then return it\n if (!isPlainObject(value)) {\n return value;\n }\n\n // loop over the object and check if the value and call `parse` on it\n // parse into a copy so caller-owned (e.g. cached) objects are never rewritten\n const parsed: Record<string, any> = {};\n\n for (const key in value) {\n parsed[key] = await this.parse(value[key]);\n }\n\n return parsed;\n }\n\n /**\n * Make a log message\n */\n public log(message: string, level: LogLevel = \"info\") {\n if (!config.get(\"http.log\")) return;\n\n log.log({\n module: \"response\",\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.request.id}`,\n message,\n type: level,\n context: {\n request: this.request,\n response: this,\n },\n });\n }\n\n /**\n * Check if returning response is json\n */\n public get isJson() {\n return this.getHeader(\"Content-Type\") === \"application/json\";\n }\n\n /**\n * Send the response\n * @param data - Response data\n * @param statusCode - HTTP status code\n * @param triggerEvents - Whether to trigger response events (default: true)\n */\n public async send(data?: any, statusCode?: number, triggerEvents = true): Promise<Response> {\n // Defensive guard against double-send. The underlying Fastify reply silently\n // ignores subsequent sends once `sent === true`, which has historically hidden\n // middleware bugs (cache-pattern replay paths that returned `baseResponse.send`\n // ended up re-entering `Response.send` with the FastifyReply as the body).\n // Surfacing the misuse via `error`-level log makes the bug loud without\n // crashing production traffic.\n if (this.baseResponse.sent) {\n log.error(\n \"response\",\n \"send\",\n `send() called on already-sent response (request:${this.request?.id ?? \"unknown\"}) — likely a middleware bug`,\n );\n\n return this;\n }\n\n if (statusCode) {\n this.currentStatusCode = statusCode;\n }\n\n if (data === this) return this;\n\n if (data !== undefined) {\n this.currentBody = data;\n }\n\n if (!this.currentStatusCode) {\n this.currentStatusCode = 200;\n }\n\n this.log(\"Sending response\");\n // Auto-pick `application/json` only when no content-type was set by the caller.\n // This preserves explicit overrides (e.g. `application/vnd.api+json` from a\n // cache replay, `application/problem+json` from an RFC 7807 error response)\n // while keeping the convenience default for the common object-body path.\n if (Array.isArray(this.currentBody) || isPlainObject(this.currentBody)) {\n if (!this.baseResponse.getHeader(\"Content-Type\")) {\n this.setContentType(\"application/json\");\n }\n }\n\n if (triggerEvents) {\n await Response.trigger(\"sending\", this);\n\n for (const callback of this.events.get(\"sending\") || []) {\n await callback(this);\n }\n\n if (this.isJson) {\n await Response.trigger(\"sendingJson\", this);\n for (const callback of this.events.get(\"sendingJson\") || []) {\n await callback(this);\n }\n\n if (this.isOk) {\n await Response.trigger(\"sendingSuccessJson\", this);\n for (const callback of this.events.get(\"sendingSuccessJson\") || []) {\n await callback(this);\n }\n }\n }\n }\n\n // parse the body and make sure it is transformed to sync data instead of async data\n if (typeof this.currentBody !== \"string\") {\n this.parsedBody = await this.parseBody();\n } else {\n this.parsedBody = data;\n }\n\n // Set the status first\n this.baseResponse.status(this.currentStatusCode);\n\n // Then send the response with the parsed body\n await this.baseResponse.send(this.parsedBody);\n\n this.log(\"Response sent\");\n\n if (triggerEvents) this.fireSentEvents();\n\n return this;\n }\n\n /**\n * Fire the post-send lifecycle (`sent`, `success`, status-specific and `error`\n * events). Every helper that writes to the Fastify reply directly (noContent,\n * redirect, sendFile, sendBuffer, sendImage, downloadFile) goes through here.\n */\n protected fireSentEvents(statusCode?: number) {\n if (statusCode) this.currentStatusCode = statusCode;\n\n // trigger the sent event\n Response.trigger(\"sent\", this);\n\n for (const callback of this.events.get(\"sent\") || []) {\n callback(this);\n }\n\n // trigger the success event if the status code is 2xx\n if (this.currentStatusCode >= 200 && this.currentStatusCode < 300) {\n Response.trigger(\"success\", this);\n }\n\n // trigger the successCreate event if the status code is 201\n if (this.currentStatusCode === 201) {\n Response.trigger(\"successCreate\", this);\n }\n\n // trigger the badRequest event if the status code is 400\n if (this.currentStatusCode === 400) {\n Response.trigger(\"badRequest\", this);\n }\n\n // trigger the unauthorized event if the status code is 401\n if (this.currentStatusCode === 401) {\n Response.trigger(\"unauthorized\", this);\n }\n\n // trigger the forbidden event if the status code is 403\n if (this.currentStatusCode === 403) {\n Response.trigger(\"forbidden\", this);\n }\n\n // trigger the notFound event if the status code is 404\n if (this.currentStatusCode === 404) {\n Response.trigger(\"notFound\", this);\n }\n\n // trigger the content too large event if the status code is 413\n if (this.currentStatusCode === 413) {\n Response.trigger(\"contentTooLarge\", this);\n }\n\n // trigger the throttled event if the status code is 429\n if (this.currentStatusCode === 429) {\n Response.trigger(\"throttled\", this);\n }\n\n // trigger the serverError event if the status code is 500\n if (this.currentStatusCode === 500) {\n Response.trigger(\"serverError\", this);\n }\n\n // trigger the error event if the status code is 4xx or 5xx\n if (this.currentStatusCode >= 400) {\n Response.trigger(\"error\", this);\n }\n }\n\n /** Send a bare 304 and fire the lifecycle. */\n protected send304() {\n const reply = this.baseResponse.status(304).send();\n\n this.fireSentEvents(304);\n\n return reply;\n }\n\n /** Send a raw stream/buffer body and fire the lifecycle. */\n protected sendRaw(body: any) {\n const reply = this.baseResponse.send(body);\n\n this.fireSentEvents(this.baseResponse.statusCode);\n\n return reply;\n }\n\n /**\n * Replay a previously-captured response shape — used by cache-pattern\n * middlewares (idempotency, response cache) to send a cached response\n * without re-running the controller.\n *\n * Preserves the cached status code, content-type, and any extra headers,\n * then sends the body through the standard `send()` pipeline so the full\n * event lifecycle still fires (`sent`, `success`, status-specific events).\n * That keeps cross-cutting observers (logger, metrics, audit) consistent\n * between fresh and replayed responses.\n *\n * @example\n * // Inside a cache-pattern middleware on HIT:\n * return response.header(\"X-Cache\", \"HIT\").replay({\n * status: cached.status,\n * body: cached.body,\n * contentType: cached.contentType,\n * });\n */\n public replay(cached: {\n status: number;\n body: unknown;\n contentType?: string;\n headers?: Record<string, string>;\n }): Promise<Response> {\n this.setStatusCode(cached.status);\n\n if (cached.contentType) {\n this.setContentType(cached.contentType);\n }\n\n if (cached.headers) {\n for (const [name, value] of Object.entries(cached.headers)) {\n this.header(name, value);\n }\n }\n\n return this.send(cached.body);\n }\n\n /**\n * Send html response\n */\n public html(data: string, statusCode?: number) {\n return this.setContentType(\"text/html\").send(data, statusCode);\n }\n\n /**\n * Render the given react component\n */\n public render(element: React.ReactElement | React.ComponentType, status = 200) {\n return this.setStatusCode(status).html(renderReact(element));\n }\n\n /**\n * Send an xml response.\n *\n * Accepts either a raw XML string or an `XMLable` — anything with a\n * `toXML()` method, structurally typed so packages like\n * `@warlock.js/sitemap` never need to import core. This is the BOUNDED\n * path only: a very large document should be streamed from generated\n * files instead of passed through here.\n */\n public xml(body: string | XMLable, statusCode?: number) {\n if (typeof body === \"string\") {\n return this.setContentType(\"application/xml\").send(body, statusCode);\n }\n\n if (typeof body?.toXML !== \"function\") {\n throw new TypeError(\n \"response.xml() expects a string or an XMLable value (an object with a toXML() method).\",\n );\n }\n\n return this.setContentType(\"application/xml\").send(body.toXML(), statusCode);\n }\n\n /**\n * Send plain text response\n */\n public text(data: string, statusCode?: number) {\n return this.setContentType(\"text/plain\").send(data, statusCode);\n }\n\n /**\n * Create a streaming response for progressive/chunked data sending\n *\n * This method allows you to send data in chunks and control when the response ends.\n * Perfect for Server-Sent Events (SSE), progressive rendering, or streaming large responses.\n *\n * @example\n * ```ts\n * const stream = response.stream(\"text/html\");\n * stream.send(\"<html><body>\");\n * stream.send(\"<h1>Hello</h1>\");\n * stream.render(<MyComponent />);\n * stream.send(\"</body></html>\");\n * stream.end();\n * ```\n *\n * @param contentType - The content type for the stream (default: \"text/plain\")\n * @returns Stream controller with send(), render(), and end() methods\n */\n public stream(contentType = \"text/plain\"): ResponseStreamController {\n // Set headers using the response API\n this.setContentType(contentType);\n this.header(\"Transfer-Encoding\", \"chunked\");\n this.header(\"Cache-Control\", \"no-cache\");\n this.header(\"Connection\", \"keep-alive\");\n this.header(\"X-Content-Type-Options\", \"nosniff\");\n\n // Trigger sending events\n Response.trigger(\"sending\", this);\n for (const callback of this.events.get(\"sending\") || []) {\n callback(this);\n }\n\n this.log(\"Starting stream\");\n\n // Track stream state\n let isEnded = false;\n const chunks: any[] = [];\n\n // Write headers to start the stream\n // Note: We use raw here because we need chunked encoding control\n // This is the only valid use case for bypassing Fastify's abstraction\n this.flushPendingCookies();\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\n\n return {\n /**\n * Send a chunk of data to the client\n * @param data - Data to send (string, Buffer, or any serializable data)\n */\n send: (data: any) => {\n if (isEnded) {\n throw new Error(\"Cannot send data: stream has already ended\");\n }\n\n this.baseResponse.raw.write(data);\n\n return this;\n },\n\n /**\n * Render a React component and send it as a chunk\n * @param element - React element or component to render\n */\n render: (element: ReactNode) => {\n if (isEnded) {\n throw new Error(\"Cannot render: stream has already ended\");\n }\n\n const html = renderReact(element);\n chunks.push(html);\n this.baseResponse.raw.write(html);\n\n return this;\n },\n\n /**\n * End the stream and trigger completion events\n */\n end: () => {\n if (isEnded) {\n return this;\n }\n\n isEnded = true;\n\n // Store the streamed content for logging/debugging\n this.currentBody = chunks;\n this.parsedBody = chunks;\n\n // End the response\n this.baseResponse.raw.end();\n\n this.log(\"Stream ended\");\n\n // Trigger sent events\n Response.trigger(\"sent\", this);\n for (const callback of this.events.get(\"sent\") || []) {\n callback(this);\n }\n\n // Trigger success event if status is 2xx\n if (this.isOk) {\n Response.trigger(\"success\", this);\n }\n\n // Trigger status-specific events\n if (this.currentStatusCode === 201) {\n Response.trigger(\"successCreate\", this);\n }\n\n return this;\n },\n\n /**\n * Check if the stream has ended\n */\n get ended() {\n return isEnded;\n },\n };\n }\n\n /**\n * Pipe a React server stream (`renderToPipeableStream`) onto this\n * response — the Stage 1 streaming SSR seam. Writes the already-committed\n * status and headers, then pipes; aborts the React render if the client\n * disconnects before the stream finishes.\n *\n * This is the ONLY sanctioned way for `@warlock.js/web` to put a React\n * stream on the wire: it never touches `response.raw` itself, it calls\n * this method, which does (`stream-react-response.ts`).\n *\n * @example\n * ```ts\n * const pipeableStream = await renderPageToPipeableStream(element);\n * await response.streamReact(pipeableStream);\n * ```\n */\n public streamReact(pipeableStream: PipeableReactStream): Promise<void> {\n Response.trigger(\"sending\", this);\n for (const callback of this.events.get(\"sending\") || []) {\n callback(this);\n }\n\n this.flushPendingCookies();\n\n return streamReactResponse({\n raw: this.baseResponse.raw,\n statusCode: this.statusCode,\n headers: this.getHeaders() as OutgoingHttpHeaders,\n pipeableStream,\n }).then(() => {\n Response.trigger(\"sent\", this);\n for (const callback of this.events.get(\"sent\") || []) {\n callback(this);\n }\n\n if (this.isOk) {\n Response.trigger(\"success\", this);\n }\n });\n }\n\n /**\n * Create a Server-Sent Events (SSE) stream\n *\n * SSE is a standard for pushing real-time updates from server to client.\n * Perfect for live notifications, progress updates, or real-time data feeds.\n *\n * @example\n * ```ts\n * const sse = response.sse();\n *\n * // Send events\n * sse.send(\"message\", { text: \"Hello!\" });\n * sse.send(\"notification\", { type: \"info\", message: \"Update available\" }, \"msg-123\");\n *\n * // Keep connection alive\n * const keepAlive = setInterval(() => sse.comment(\"ping\"), 30000);\n *\n * // Clean up when done\n * clearInterval(keepAlive);\n * sse.end();\n * ```\n *\n * @returns SSE controller with send(), comment(), and end() methods\n */\n public sse(): ResponseSSEController {\n // Set SSE-specific headers\n this.setContentType(\"text/event-stream\");\n this.header(\"Cache-Control\", \"no-cache, no-store, must-revalidate\");\n this.header(\"Connection\", \"keep-alive\");\n this.header(\"X-Accel-Buffering\", \"no\"); // Disable nginx buffering\n\n // Trigger sending events\n Response.trigger(\"sending\", this);\n for (const callback of this.events.get(\"sending\") || []) {\n callback(this);\n }\n\n this.log(\"Starting SSE stream\");\n\n // Track stream state\n let isEnded = false;\n const events: any[] = [];\n const disconnectHandlers: Array<() => void> = [];\n\n // Write headers to start the stream\n this.flushPendingCookies();\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\n\n // Detect client disconnect — set isEnded silently and invoke cleanup handlers.\n // Without this, background jobs keep writing to a dead socket after the client drops.\n this.baseResponse.raw.on(\"close\", () => {\n if (!isEnded) {\n isEnded = true;\n this.log(\"SSE client disconnected\");\n for (const handler of disconnectHandlers) {\n handler();\n }\n }\n });\n\n const controller: ResponseSSEController = {\n /**\n * Send an SSE event\n * @param event - Event name (e.g., \"message\", \"chunk\", \"done\")\n * @param data - Event data (will be JSON stringified)\n * @param id - Optional event ID for client-side Last-Event-ID tracking (reconnect support)\n */\n send: (event: string, data: any, id?: string): ResponseSSEController => {\n // Silent no-op after disconnect — background jobs should not crash when\n // the client drops mid-stream. The onDisconnect handler handles cleanup.\n if (isEnded) return controller;\n\n let message = \"\";\n if (id) message += `id: ${id}\\n`;\n message += `event: ${event}\\n`;\n message += `data: ${JSON.stringify(data)}\\n\\n`;\n\n events.push({ event, data, id });\n this.baseResponse.raw.write(message);\n\n return controller;\n },\n\n /**\n * Send a comment (keeps connection alive, invisible to client)\n * Useful for preventing timeout on long-lived connections\n * @param text - Comment text\n */\n comment: (text: string): ResponseSSEController => {\n // Silent no-op after disconnect\n if (isEnded) return controller;\n\n this.baseResponse.raw.write(`: ${text}\\n\\n`);\n\n return controller;\n },\n\n /**\n * End the SSE stream and trigger completion events\n */\n end: (): ResponseSSEController => {\n if (isEnded) return controller;\n\n isEnded = true;\n\n // Store the events for logging/debugging\n this.currentBody = events;\n this.parsedBody = events;\n\n // End the response\n this.baseResponse.raw.end();\n\n this.log(\"SSE stream ended\");\n\n // Trigger sent events\n Response.trigger(\"sent\", this);\n for (const callback of this.events.get(\"sent\") || []) {\n callback(this);\n }\n\n // Trigger success event if status is 2xx\n if (this.isOk) {\n Response.trigger(\"success\", this);\n }\n\n return controller;\n },\n\n /**\n * Register a handler to be called when the client disconnects.\n * Use this to clean up EventEmitter listeners, cancel background jobs, etc.\n *\n * @example\n * ```ts\n * const sse = response.sse();\n * const listener = (chunk) => sse.send(\"chunk\", { chunk });\n * eventBus.on(aiMessageId, listener);\n * sse.onDisconnect(() => eventBus.off(aiMessageId, listener));\n * ```\n */\n onDisconnect: (handler: () => void): ResponseSSEController => {\n disconnectHandlers.push(handler);\n return controller;\n },\n\n /**\n * Check if the stream has ended (either via end() or client disconnect)\n */\n get ended() {\n return isEnded;\n },\n };\n\n return controller;\n }\n\n /**\n * Set the status code\n */\n public setStatusCode(statusCode: number) {\n this.currentStatusCode = statusCode;\n\n return this;\n }\n\n /**\n * Redirect the user to another route\n */\n public redirect(url: string, statusCode = 302) {\n this.baseResponse.redirect(url, statusCode);\n this.fireSentEvents(statusCode);\n\n return this;\n }\n\n /**\n * Permanent redirect\n */\n public permanentRedirect(url: string) {\n this.baseResponse.redirect(url, 301);\n this.fireSentEvents(301);\n\n return this;\n }\n\n /**\n * Get the response time\n */\n public getResponseTime() {\n return this.baseResponse.elapsedTime;\n }\n\n /**\n * Remove a specific header\n */\n public removeHeader(key: string) {\n this.baseResponse.removeHeader(key);\n\n return this;\n }\n\n /**\n * Get a specific header\n */\n public getHeader(key: string) {\n return this.baseResponse.getHeader(key);\n }\n\n /**\n * Get the response headers\n */\n /**\n * Serialise cookies parked by `response.cookie()` into the `set-cookie`\n * header. Must run right before any raw `writeHead`, which bypasses\n * `@fastify/cookie`'s `onSend` flush.\n */\n public flushPendingCookies() {\n flushPendingCookies(this.baseResponse);\n\n return this;\n }\n\n public getHeaders() {\n return this.baseResponse.getHeaders();\n }\n\n /**\n * Set multiple headers\n */\n public headers(headers: Record<string, string>) {\n this.baseResponse.headers(headers);\n\n return this;\n }\n\n /**\n * Set the response header\n */\n public header(key: string, value: any) {\n this.baseResponse.header(key, value);\n\n return this;\n }\n\n /**\n * Set a cookie on the response.\n *\n * Values are JSON-stringified by default so structured cookies round-trip\n * cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the\n * JSON wrapping for plain-string cookies (session tokens, opaque IDs).\n *\n * **Secure by default.** `httpOnly: true`, `sameSite: \"lax\"`, and — outside\n * development — `secure: true` are applied unless you override them. These\n * are the flags whose absence never fails a test and is fatal in production:\n * without `httpOnly` any injected script can read the cookie, without\n * `secure` it travels in cleartext, without `sameSite` it rides along on\n * cross-site requests. Opting out is explicit, per call or via\n * `http.cookies.options`.\n *\n * Precedence, lowest to highest: framework defaults → `http.cookies.options`\n * → the per-call `options` argument.\n *\n * @example\n * // JSON-wrapped (default) — round-trips with request.cookie()\n * response.cookie(\"prefs\", { theme: \"dark\" }, { maxAge: 3600 });\n *\n * @example\n * // Raw string — no JSON quoting; useful for tokens / opaque IDs\n * response.cookie(\"session\", \"abc.def.ghi\", { raw: true });\n *\n * @example\n * // Deliberately readable by client-side JS\n * response.cookie(\"theme\", \"dark\", { httpOnly: false });\n */\n public cookie(name: string, value: CookieValue, options: CookieOptions = {}) {\n const { raw, ...cookieOptions } = options;\n const defaultOptions = config.get(\"http.cookies.options\", {});\n const serializedValue = raw ? String(value) : JSON.stringify(value);\n\n this.baseResponse.setCookie(name, serializedValue, {\n ...secureCookieDefaults(),\n ...defaultOptions,\n ...cookieOptions,\n });\n\n return this;\n }\n\n /**\n * Set the active locale for subsequent requests.\n *\n * Writes the SAME cookie `request.locale` reads (`LOCALE_COOKIE_NAME`,\n * owned by the framework, never a string an app hardcodes) — the two sides\n * are read from one shared constant so they cannot name the cookie\n * differently. Written raw (no JSON quoting), matching how\n * `Request.resolveLocale()` reads it back.\n *\n * Any `Set-Cookie` this emits revokes public cacheability for the response,\n * including a page that opted into `public, max-age` — a per-visitor\n * locale cookie replayed from a shared cache would hand visitor A's locale\n * to visitor B.\n *\n * @throws {UnknownLocaleError} when `locale` is outside the app's\n * configured `app.localeCodes` allow-list. Silently accepting an\n * unconfigured locale would set a cookie the app can never actually serve.\n *\n * @example\n * response.setLocale(\"ar\");\n */\n public setLocale(locale: string) {\n const { localeCodes } = resolveLocaleConfiguration(\n config.get(\"app.localeCode\"),\n config.get(\"app.localeCodes\"),\n );\n\n if (localeCodes !== undefined && !localeCodes.includes(locale)) {\n throw new UnknownLocaleError(locale, localeCodes);\n }\n\n // A controller's explicit server-side choice is authoritative. The\n // preference cookie is host-only by contract, so clearing it must never\n // inherit an application's configured cookie Domain.\n this.baseResponse.clearCookie(LOCALE_PREFERENCE_COOKIE_NAME, {\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: false,\n domain: undefined,\n });\n\n return this.cookie(LOCALE_COOKIE_NAME, locale, { raw: true });\n }\n\n /**\n * Clear a cookie from the response\n *\n * @example\n * response.clearCookie('token', { path: '/' });\n */\n public clearCookie(name: string, options?: CookieSerializeOptions) {\n const defaultOptions = config.get(\"http.cookies.options\", {});\n this.baseResponse.clearCookie(name, { ...defaultOptions, ...options });\n\n return this;\n }\n\n /**\n * Clear every cookie NAME the current request sent — best-effort, not\n * exhaustive: HTTP gives the server no way to discover a cookie's `Path`\n * or `Domain`, only the name, so a cookie originally set on a `path` or\n * `domain` other than the one this call targets (the framework default,\n * or whatever is passed here / configured via `http.cookies.options`)\n * will NOT be deleted, and nothing will report that — the browser just\n * silently ignores a `Set-Cookie` whose scope doesn't match. Pass an\n * explicit `path` / `domain` for cookies the app knows it owns on a\n * non-default scope; call `clearCookie()` per name for anything else.\n *\n * @example\n * response.clearCookies();\n * response.clearCookies({ path: '/admin' });\n */\n public clearCookies(options?: CookieSerializeOptions) {\n for (const name of Object.keys(this.request.cookies)) {\n this.clearCookie(name, options);\n }\n\n return this;\n }\n\n /**\n * Alias to header method\n */\n public setHeader(key: string, value: any) {\n return this.header(key, value);\n }\n\n /**\n * Send an error response with status code 500\n */\n public serverError(data: any) {\n return this.send(data, 500);\n }\n\n /**\n * Send a forbidden response with status code 403\n */\n public forbidden(\n data: any = {\n error: \"You are not allowed to access this resource, FORBIDDEN\",\n },\n ) {\n return this.send(data, 403);\n }\n\n /**\n * Send a service unavailable response with status code 503\n */\n public serviceUnavailable(data: any) {\n return this.send(data, 503);\n }\n\n /**\n * Send an unauthorized response with status code 401\n */\n public unauthorized(\n data: any = {\n error: \"unauthorized\",\n },\n ) {\n return this.send(data, 401);\n }\n\n /**\n * Send a not found response with status code 404\n */\n public notFound(\n data: any = {\n error: \"notFound\",\n },\n ) {\n return this.send(data, 404);\n }\n\n /**\n * Send a bad request response with status code 400\n */\n public badRequest(data: any) {\n return this.send(data, 400);\n }\n\n /**\n * Send a content too large response with status code 413\n */\n public contentTooLarge(data: any) {\n return this.send(data, 413);\n }\n\n /**\n * Send a success response with status code 201\n */\n public successCreate(data: any) {\n return this.send(data, 201);\n }\n\n /**\n * Send a success response\n */\n public success(data: any = { success: true }) {\n return this.send(data);\n }\n\n /**\n * Send a no content response with status code 204\n */\n public noContent() {\n const reply = this.baseResponse.status(204).send();\n\n this.fireSentEvents(204);\n\n return reply;\n }\n\n /**\n * Send an accepted response with status code 202\n * Used for async operations that have been accepted but not yet processed\n */\n public accepted(data: any = { message: \"Request accepted for processing\" }) {\n return this.send(data, 202);\n }\n\n /**\n * Send a conflict response with status code 409\n */\n public conflict(data: any = { error: \"Resource conflict\" }) {\n return this.send(data, 409);\n }\n\n /**\n * Send a too many requests response with status code 429\n */\n public tooManyRequests(data: any) {\n return this.send(data, 429);\n }\n\n /**\n * Send an unprocessable entity response with status code 422\n * Used for semantic validation errors\n */\n public unprocessableEntity(data: any) {\n return this.send(data, 422);\n }\n\n /**\n * Apply response options (cache, disposition, etag)\n * Shared helper for sendFile and sendBuffer\n */\n /**\n * Build an RFC 6266–safe `Content-Disposition` value for a file name.\n *\n * HTTP header values must be ISO-8859-1; Node's `setHeader` throws\n * `ERR_INVALID_CHAR` on any other byte, so a raw `filename=\"تقرير.pdf\"` would\n * crash the response (a 500 the moment the name is non-ASCII). RFC 6266 solves\n * this with two parameters emitted together:\n * - `filename=\"<ascii>\"` — sanitised ASCII fallback for legacy clients\n * - `filename*=UTF-8''<pct>` — RFC 5987 ext-value; modern browsers restore\n * the real (e.g. Arabic) name.\n */\n private contentDisposition(type: \"inline\" | \"attachment\", rawName: string): string {\n // Strip CR/LF first so a crafted file name can never inject extra headers.\n const name = (rawName || \"file\").replace(/[\\r\\n]/g, \"\");\n\n // ASCII fallback: replace quote/backslash + every non-printable-ASCII byte\n // (covers all multibyte characters) so the quoted-string is always legal.\n const ascii =\n name\n .replace(/[\"\\\\]/g, \"_\")\n .replace(/[^\\x20-\\x7E]/g, \"_\")\n .trim() || \"file\";\n\n // Pure-ASCII name → the quoted form is enough; no ext-value needed.\n if (!/[^\\x20-\\x7E]/.test(name)) {\n return `${type}; filename=\"${ascii}\"`;\n }\n\n // RFC 5987 ext-value. `encodeURIComponent` leaves ' ( ) * unescaped, but they\n // are not valid `attr-char`, so percent-encode those too.\n const encoded = encodeURIComponent(name).replace(\n /['()*]/g,\n (char) => \"%\" + char.charCodeAt(0).toString(16).toUpperCase(),\n );\n\n return `${type}; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\n }\n\n private applyResponseOptions(options: SendBufferOptions, defaultFilename?: string): boolean {\n // Set content type if provided\n if (options.contentType) {\n this.baseResponse.type(options.contentType);\n }\n\n // Set cache headers if specified\n if (options.cacheTime) {\n const cacheControl = options.immutable\n ? `public, max-age=${options.cacheTime}, immutable`\n : `public, max-age=${options.cacheTime}`;\n this.header(\"Cache-Control\", cacheControl);\n this.header(\"Expires\", new Date(Date.now() + options.cacheTime * 1000).toUTCString());\n }\n\n // Set ETag if provided (for conditional requests)\n if (options.etag) {\n this.header(\"ETag\", options.etag);\n\n // Check If-None-Match for conditional request\n const ifNoneMatch = this.request.header(\"if-none-match\");\n if (ifNoneMatch && matchesIfNoneMatch(ifNoneMatch, options.etag)) {\n this.log(\"Content not modified (ETag match), sending 304\");\n this.send304();\n return true; // Indicates 304 was sent\n }\n }\n\n // Set Content-Disposition if inline or filename is specified\n if (options.inline !== undefined || options.filename) {\n const disposition = options.inline ? \"inline\" : \"attachment\";\n const filename = options.filename || defaultFilename || \"file\";\n this.header(\"Content-Disposition\", this.contentDisposition(disposition, filename));\n }\n\n return false; // No 304 sent\n }\n\n /**\n * Send a file as a response\n */\n public async sendFile(filePath: string | StorageFile, options?: number | SendFileOptions) {\n if (filePath instanceof StorageFile) {\n filePath = filePath.absolutePath!;\n }\n\n this.log(`Sending file: ${filePath}`);\n\n // Check if file exists first\n if (!(await fileExistsAsync(filePath))) {\n return this.notFound({\n error: \"File Not Found\",\n });\n }\n\n try {\n // Normalize options to object format\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\n\n // Get file stats for ETag and Last-Modified\n const stats = await fs.promises.stat(filePath);\n const lastModified = stats.mtime;\n\n // Generate ETag based on file size and modification time\n const etag = `\"${stats.size}-${stats.mtime.getTime()}\"`;\n\n // Set Last-Modified header\n this.header(\"Last-Modified\", lastModified.toUTCString());\n this.header(\"ETag\", etag);\n\n // Set content type\n const contentType = this.getFileContentType(filePath);\n this.baseResponse.type(contentType);\n\n // Apply common response options (cache, disposition)\n const defaultFilename = path.basename(filePath);\n const sent304 = this.applyResponseOptions({ ...opts, etag, contentType }, defaultFilename);\n if (sent304) return this.baseResponse;\n\n // Check conditional request headers\n const ifNoneMatch = this.request.header(\"if-none-match\");\n const ifModifiedSince = this.request.header(\"if-modified-since\");\n\n // Handle If-None-Match (ETag validation)\n if (ifNoneMatch && matchesIfNoneMatch(ifNoneMatch, etag)) {\n this.log(\"File not modified (ETag match), sending 304\");\n return this.send304();\n }\n\n // Handle If-Modified-Since (Last-Modified validation)\n // RFC 9110: If-Modified-Since is ignored when If-None-Match is present.\n // The header has second precision, so floor the mtime to seconds.\n if (ifModifiedSince && !ifNoneMatch) {\n const modifiedSinceDate = new Date(ifModifiedSince);\n if (\n Math.floor(lastModified.getTime() / 1000) <=\n Math.floor(modifiedSinceDate.getTime() / 1000)\n ) {\n this.log(\"File not modified (Last-Modified check), sending 304\");\n return this.send304();\n }\n }\n\n // Use streaming for efficient file sending\n const stream = fs.createReadStream(filePath);\n\n // Handle stream errors\n stream.on(\"error\", (error) => {\n this.log(`Error reading file: ${error.message}`, \"error\");\n if (!this.baseResponse.sent) {\n this.serverError({\n error: \"Error reading file\",\n message: error.message,\n });\n }\n });\n\n // Send the stream (endTime will be set by finish event listener)\n return this.sendRaw(stream);\n } catch (error: any) {\n this.log(`Error sending file: ${error.message}`, \"error\");\n return this.serverError({\n error: \"Error sending file\",\n message: error.message,\n });\n }\n }\n\n /**\n * Send buffer as a response\n * Useful for dynamically generated content (e.g., resized images, generated PDFs)\n */\n public sendBuffer(buffer: Buffer, options?: number | SendBufferOptions) {\n this.log(\"Sending buffer\");\n\n // Normalize options to object format\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\n\n // Apply common response options (cache, disposition, etag)\n const sent304 = this.applyResponseOptions(opts);\n if (sent304) return this.baseResponse;\n\n // Note: endTime is set in the main send() method for non-streaming responses\n return this.sendRaw(buffer);\n }\n\n /**\n * Send an Image instance as a response\n * Automatically detects image format and sets content type\n */\n public async sendImage(\n image: any, // Type as 'any' to avoid circular dependency with Image class\n options?: number | (Omit<SendBufferOptions, \"contentType\"> & { contentType?: string }),\n ) {\n this.log(\"Sending image\");\n\n // Normalize options to object format\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\n\n // Get image metadata to determine format\n const metadata = await image.metadata();\n const format = metadata.format || \"jpeg\";\n\n // Convert image to buffer\n const buffer = await image.toBuffer();\n\n // Auto-set content type if not provided\n const contentType = opts.contentType || `image/${format}`;\n\n // Auto-generate ETag if not provided\n // Format: \"format-widthxheight-size\" (e.g., \"jpeg-800x600-45231\")\n // This catches changes in dimensions, quality, filters, and format\n if (!opts.etag) {\n const width = metadata.width || 0;\n const height = metadata.height || 0;\n opts.etag = `\"${format}-${width}x${height}-${buffer.length}\"`;\n }\n\n // Apply common response options with auto-detected content type\n const sent304 = this.applyResponseOptions({ ...opts, contentType });\n if (sent304) return this.baseResponse;\n\n // Note: endTime is set in the main send() method for non-streaming responses\n return this.sendRaw(buffer);\n }\n\n /**\n * Send file and cache it\n * Cache time in seconds\n * Cache time will be one year\n */\n public sendCachedFile(path: string | StorageFile, cacheTime = 31536000) {\n return this.sendFile(path, cacheTime);\n }\n\n /**\n * Download the given file path\n */\n public download(path: string, filename?: string) {\n return this.downloadFile(path, filename);\n }\n\n /**\n * Download the given file path\n */\n public async downloadFile(filePath: string, filename?: string) {\n // Check if file exists first\n if (!(await fileExistsAsync(filePath))) {\n return this.notFound({\n error: \"File Not Found\",\n });\n }\n\n try {\n if (!filename) {\n filename = path.basename(filePath);\n }\n\n this.baseResponse.header(\n \"Content-Disposition\",\n this.contentDisposition(\"attachment\", filename),\n );\n\n // this.baseResponse.header(\"Content-Type\", this.getFileContentType(filePath));\n this.baseResponse.header(\"Content-Type\", \"application/octet-stream\");\n\n const stream = fs.createReadStream(filePath);\n\n // Handle stream errors\n stream.on(\"error\", (error) => {\n this.log(`Error reading file for download: ${error.message}`, \"error\");\n if (!this.baseResponse.sent) {\n this.serverError({\n error: \"Error reading file\",\n message: error.message,\n });\n }\n });\n\n // Send the stream (endTime will be set by finish event listener)\n return this.sendRaw(stream);\n } catch (error: any) {\n this.log(`Error downloading file: ${error.message}`, \"error\");\n return this.serverError({\n error: \"Error downloading file\",\n message: error.message,\n });\n }\n }\n\n /**\n * Get content type of the given path\n */\n public getFileContentType(filePath: string) {\n const type = mime.getType(filePath) || \"application/octet-stream\";\n return type;\n }\n\n /**\n * Mark the response as failed\n */\n public failedSchema(result: ValidationResult) {\n const { errors, inputKey, inputError, status } = {\n errors: \"errors\",\n inputKey: \"input\",\n inputError: \"error\",\n status: 422,\n ...config.get(\"validation.response\", {}),\n };\n\n log.error(\"request\", \"validation\", `${this.request.id} - Validation failed`);\n\n return this.send(\n {\n [errors]: result.errors.map((error) => ({\n [inputKey]: error.input,\n [inputError]: error.error,\n })),\n },\n status,\n );\n }\n}\n"],"mappings":"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"}