@warlock.js/core 4.5.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/esm/ai/src/agent/agent.mjs +126 -7
  3. package/esm/ai/src/agent/agent.mjs.map +1 -1
  4. package/esm/ai/src/agent/signature.mjs +57 -0
  5. package/esm/ai/src/agent/signature.mjs.map +1 -0
  6. package/esm/ai/src/agent/snapshot.mjs +101 -0
  7. package/esm/ai/src/agent/snapshot.mjs.map +1 -0
  8. package/esm/ai/src/ai.mjs +37 -1
  9. package/esm/ai/src/ai.mjs.map +1 -1
  10. package/esm/ai/src/errors/agent-drift-error.mjs +31 -0
  11. package/esm/ai/src/errors/agent-drift-error.mjs.map +1 -0
  12. package/esm/ai/src/errors/index.mjs +2 -0
  13. package/esm/ai/src/errors/planner-drift-error.mjs +33 -0
  14. package/esm/ai/src/errors/planner-drift-error.mjs.map +1 -0
  15. package/esm/ai/src/image/image-cost.mjs +55 -0
  16. package/esm/ai/src/image/image-cost.mjs.map +1 -0
  17. package/esm/ai/src/image/image.mjs +112 -0
  18. package/esm/ai/src/image/image.mjs.map +1 -0
  19. package/esm/ai/src/image/index.mjs +4 -0
  20. package/esm/ai/src/index.mjs +20 -0
  21. package/esm/ai/src/planner/planner-run.mjs +161 -6
  22. package/esm/ai/src/planner/planner-run.mjs.map +1 -1
  23. package/esm/ai/src/planner/planner.mjs +25 -1
  24. package/esm/ai/src/planner/planner.mjs.map +1 -1
  25. package/esm/ai/src/planner/snapshot.mjs +95 -0
  26. package/esm/ai/src/planner/snapshot.mjs.map +1 -0
  27. package/esm/ai/src/rag/hybrid/bm25.mjs +51 -0
  28. package/esm/ai/src/rag/hybrid/bm25.mjs.map +1 -0
  29. package/esm/ai/src/rag/hybrid/hybrid-rank.mjs +29 -0
  30. package/esm/ai/src/rag/hybrid/hybrid-rank.mjs.map +1 -0
  31. package/esm/ai/src/rag/hybrid/rrf.mjs +30 -0
  32. package/esm/ai/src/rag/hybrid/rrf.mjs.map +1 -0
  33. package/esm/ai/src/rag/index.mjs +11 -0
  34. package/esm/ai/src/rag/loaders/errors.mjs +25 -0
  35. package/esm/ai/src/rag/loaders/errors.mjs.map +1 -0
  36. package/esm/ai/src/rag/loaders/index.mjs +7 -0
  37. package/esm/ai/src/rag/loaders/load-html.mjs +138 -0
  38. package/esm/ai/src/rag/loaders/load-html.mjs.map +1 -0
  39. package/esm/ai/src/rag/loaders/load-pdf.mjs +150 -0
  40. package/esm/ai/src/rag/loaders/load-pdf.mjs.map +1 -0
  41. package/esm/ai/src/rag/loaders/load-text.mjs +60 -0
  42. package/esm/ai/src/rag/loaders/load-text.mjs.map +1 -0
  43. package/esm/ai/src/rag/loaders/load-web.mjs +89 -0
  44. package/esm/ai/src/rag/loaders/load-web.mjs.map +1 -0
  45. package/esm/ai/src/rag/store/pg-vector-store.mjs +328 -0
  46. package/esm/ai/src/rag/store/pg-vector-store.mjs.map +1 -0
  47. package/esm/ai/src/rag/transforms/multi-query.mjs +41 -0
  48. package/esm/ai/src/rag/transforms/multi-query.mjs.map +1 -0
  49. package/esm/ai/src/speech/index.mjs +3 -0
  50. package/esm/ai/src/speech/speech.mjs +122 -0
  51. package/esm/ai/src/speech/speech.mjs.map +1 -0
  52. package/esm/ai/src/supervisor/entries.mjs +2 -2
  53. package/esm/ai/src/supervisor/entries.mjs.map +1 -1
  54. package/esm/ai/src/transcribe/audio-input.mjs +84 -0
  55. package/esm/ai/src/transcribe/audio-input.mjs.map +1 -0
  56. package/esm/ai/src/transcribe/index.mjs +4 -0
  57. package/esm/ai/src/transcribe/transcribe.mjs +127 -0
  58. package/esm/ai/src/transcribe/transcribe.mjs.map +1 -0
  59. package/esm/ai-openai/src/image.mjs +5 -0
  60. package/esm/ai-openai/src/index.mjs +3 -0
  61. package/esm/ai-openai/src/sdk.mjs +3 -0
  62. package/esm/ai-openai/src/speech.mjs +5 -0
  63. package/esm/ai-openai/src/transcription.mjs +6 -0
  64. package/esm/ai-openai/src/utils/index.mjs +1 -0
  65. package/esm/ai-openai/src/utils/to-openai-messages.mjs +3 -0
  66. package/esm/bootstrap.d.mts.map +1 -1
  67. package/esm/bootstrap.mjs +3 -1
  68. package/esm/bootstrap.mjs.map +1 -1
  69. package/esm/cli/commands/doctor/checks/config.check.mjs +42 -0
  70. package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -0
  71. package/esm/cli/commands/doctor/checks/connectors.check.mjs +28 -0
  72. package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -0
  73. package/esm/cli/commands/doctor/checks/health.check.mjs +35 -0
  74. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -0
  75. package/esm/cli/commands/doctor/checks/index.mjs +25 -0
  76. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -0
  77. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +90 -0
  78. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -0
  79. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +69 -0
  80. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -0
  81. package/esm/cli/commands/doctor/checks/routes.check.mjs +30 -0
  82. package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -0
  83. package/esm/cli/commands/doctor/doctor-command.action.mjs +28 -0
  84. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -0
  85. package/esm/cli/commands/doctor/format-report.mjs +44 -0
  86. package/esm/cli/commands/doctor/format-report.mjs.map +1 -0
  87. package/esm/cli/commands/doctor/run-checks.mjs +50 -0
  88. package/esm/cli/commands/doctor/run-checks.mjs.map +1 -0
  89. package/esm/cli/commands/doctor.command.mjs +29 -0
  90. package/esm/cli/commands/doctor.command.mjs.map +1 -0
  91. package/esm/cli/commands/generate/templates/stubs.mjs +6 -8
  92. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  93. package/esm/cli/commands/routes/format-routes-table.mjs +129 -0
  94. package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -0
  95. package/esm/cli/commands/routes/route-row.mjs +92 -0
  96. package/esm/cli/commands/routes/route-row.mjs.map +1 -0
  97. package/esm/cli/commands/routes/routes-command.action.mjs +43 -0
  98. package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -0
  99. package/esm/cli/commands/routes.command.mjs +51 -0
  100. package/esm/cli/commands/routes.command.mjs.map +1 -0
  101. package/esm/cli/commands/seed.command.mjs +5 -0
  102. package/esm/cli/commands/seed.command.mjs.map +1 -1
  103. package/esm/cli/framework-cli-commands.mjs +4 -0
  104. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  105. package/esm/config/config-getter.mjs +5 -5
  106. package/esm/config/config-getter.mjs.map +1 -1
  107. package/esm/config/config-loader.mjs +2 -2
  108. package/esm/config/config-loader.mjs.map +1 -1
  109. package/esm/connectors/access-connector.mjs +2 -2
  110. package/esm/connectors/access-connector.mjs.map +1 -1
  111. package/esm/connectors/ai-connector.mjs +2 -2
  112. package/esm/connectors/ai-connector.mjs.map +1 -1
  113. package/esm/connectors/cache-connector.mjs +2 -2
  114. package/esm/connectors/cache-connector.mjs.map +1 -1
  115. package/esm/connectors/database-connector.mjs +2 -2
  116. package/esm/connectors/database-connector.mjs.map +1 -1
  117. package/esm/connectors/herald-connector.mjs +2 -2
  118. package/esm/connectors/herald-connector.mjs.map +1 -1
  119. package/esm/connectors/http-connector.d.mts.map +1 -1
  120. package/esm/connectors/http-connector.mjs +9 -7
  121. package/esm/connectors/http-connector.mjs.map +1 -1
  122. package/esm/connectors/logger-connector.mjs +2 -2
  123. package/esm/connectors/logger-connector.mjs.map +1 -1
  124. package/esm/connectors/mail-connector.mjs +2 -2
  125. package/esm/connectors/mail-connector.mjs.map +1 -1
  126. package/esm/connectors/notifications-connector.mjs +2 -2
  127. package/esm/connectors/notifications-connector.mjs.map +1 -1
  128. package/esm/connectors/socket-connector.mjs +3 -3
  129. package/esm/connectors/socket-connector.mjs.map +1 -1
  130. package/esm/database/index.d.mts +2 -1
  131. package/esm/database/index.mjs +1 -0
  132. package/esm/database/seed-command-action.mjs +44 -4
  133. package/esm/database/seed-command-action.mjs.map +1 -1
  134. package/esm/database/seeds/seed-records-table-migration.mjs +36 -0
  135. package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -0
  136. package/esm/database/seeds/seeder.d.mts +22 -5
  137. package/esm/database/seeds/seeder.d.mts.map +1 -1
  138. package/esm/database/seeds/seeder.errors.d.mts +22 -0
  139. package/esm/database/seeds/seeder.errors.d.mts.map +1 -0
  140. package/esm/database/seeds/seeder.errors.mjs +29 -0
  141. package/esm/database/seeds/seeder.errors.mjs.map +1 -0
  142. package/esm/database/seeds/seeder.mjs.map +1 -1
  143. package/esm/database/seeds/seeders.manager.mjs +90 -11
  144. package/esm/database/seeds/seeders.manager.mjs.map +1 -1
  145. package/esm/database/seeds/types.d.mts +117 -1
  146. package/esm/database/seeds/types.d.mts.map +1 -1
  147. package/esm/database/seeds/utils.mjs +6 -1
  148. package/esm/database/seeds/utils.mjs.map +1 -1
  149. package/esm/dev-server/module-loader.d.mts +12 -0
  150. package/esm/dev-server/module-loader.d.mts.map +1 -1
  151. package/esm/dev-server/module-loader.mjs +30 -2
  152. package/esm/dev-server/module-loader.mjs.map +1 -1
  153. package/esm/http/config.mjs +2 -2
  154. package/esm/http/config.mjs.map +1 -1
  155. package/esm/http/createHttpApplication.mjs +2 -2
  156. package/esm/http/createHttpApplication.mjs.map +1 -1
  157. package/esm/http/health.d.mts +14 -0
  158. package/esm/http/health.d.mts.map +1 -1
  159. package/esm/http/health.mjs +16 -0
  160. package/esm/http/health.mjs.map +1 -1
  161. package/esm/http/middleware/idempotency.middleware.mjs +5 -5
  162. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  163. package/esm/http/middleware/inject-request-context.mjs +2 -2
  164. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  165. package/esm/http/middleware/maintenance.middleware.mjs +4 -4
  166. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  167. package/esm/http/plugins.mjs +9 -9
  168. package/esm/http/plugins.mjs.map +1 -1
  169. package/esm/http/response.mjs +5 -5
  170. package/esm/http/response.mjs.map +1 -1
  171. package/esm/http/server.mjs +4 -4
  172. package/esm/http/server.mjs.map +1 -1
  173. package/esm/index.d.mts +4 -3
  174. package/esm/index.mjs +2 -1
  175. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +28 -1
  176. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
  177. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs +39 -0
  178. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
  179. package/esm/repositories/contracts/index.d.mts +1 -1
  180. package/esm/repositories/contracts/query-builder.contract.d.mts +87 -1
  181. package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
  182. package/esm/repositories/index.d.mts +1 -1
  183. package/esm/repositories/repository.manager.d.mts +99 -1
  184. package/esm/repositories/repository.manager.d.mts.map +1 -1
  185. package/esm/repositories/repository.manager.mjs +128 -0
  186. package/esm/repositories/repository.manager.mjs.map +1 -1
  187. package/esm/router/router.d.mts +9 -0
  188. package/esm/router/router.d.mts.map +1 -1
  189. package/esm/router/router.mjs +21 -1
  190. package/esm/router/router.mjs.map +1 -1
  191. package/esm/utils/paths.mjs +2 -2
  192. package/esm/utils/paths.mjs.map +1 -1
  193. package/esm/validation/validateAll.mjs +2 -2
  194. package/esm/validation/validateAll.mjs.map +1 -1
  195. package/llms-full.txt +427 -45
  196. package/llms.txt +4 -2
  197. package/package.json +9 -9
  198. package/skills/README.md +65 -0
  199. package/skills/health-checks/SKILL.md +13 -0
  200. package/skills/use-repository/SKILL.md +38 -1
  201. package/skills/warlock-doctor/SKILL.md +135 -0
  202. package/skills/warlock-routes/SKILL.md +91 -0
  203. package/skills/write-seeder/SKILL.md +143 -45
@@ -1 +1 @@
1
- {"version":3,"file":"module-loader.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/module-loader.ts"],"sourcesContent":["import { pathToFileURL } from \"node:url\";\r\nimport { router } from \"../router/router\";\r\nimport { devLogError, formatModuleNotFoundError } from \"./dev-logger\";\r\nimport type { CleanupFunction, FileManager } from \"./file-manager\";\r\nimport type { SpecialFileType, SpecialFilesCollector } from \"./special-files-collector\";\r\n\r\ndeclare global {\r\n var __currentModuleFile: FileManager | undefined;\r\n}\r\n\r\n/**\r\n * The four \"special\" file kinds that the dev server eagerly imports at boot.\r\n * Order matters: locales first (used by everything), events before main so\r\n * listeners are registered when main runs, routes last so handlers are bound.\r\n */\r\nconst SPECIAL_TYPES: readonly SpecialFileType[] = [\"locale\", \"event\", \"main\", \"route\"] as const;\r\n\r\n/**\r\n * Loads application modules through the ESM loader hook. The hook stamps\r\n * `?v=N` on every import, so each reload is a fresh module without any\r\n * userland cache-busting plumbing.\r\n */\r\nexport class ModuleLoader {\r\n private readonly loadedModules = new Map<string, unknown>();\r\n\r\n constructor(private readonly specialFilesCollector: SpecialFilesCollector) {}\r\n\r\n /**\r\n * Eagerly load every special file at boot, in the canonical order so\r\n * later phases (e.g. routes) see state earlier phases registered.\r\n */\r\n public async loadAll(): Promise<void> {\r\n for (const type of SPECIAL_TYPES) {\r\n for (const file of this.specialFilesCollector.getFilesByType(type)) {\r\n await this.loadModule(file, type);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Import a file through the loader hook. For routes, scopes the import in\r\n * `router.withSourceFile()` so the added routes carry their origin.\r\n */\r\n public async loadModule<T = unknown>(\r\n file: FileManager,\r\n type: string,\r\n ): Promise<T | undefined> {\r\n if (file.relativePath.endsWith(\".env\")) return undefined;\r\n\r\n globalThis.__currentModuleFile = file;\r\n\r\n try {\r\n const fileUrl = pathToFileURL(file.absolutePath).href;\r\n\r\n const load = async () => {\r\n const module = (await import(fileUrl)) as Record<string, unknown>;\r\n this.loadedModules.set(file.absolutePath, module);\r\n this.registerCleanup(file, module);\r\n return module as T;\r\n };\r\n\r\n return type === \"route\"\r\n ? await router.withSourceFile(file.relativePath, load)\r\n : await load();\r\n } catch (error: any) {\r\n if (error.code === \"ERR_MODULE_NOT_FOUND\") {\r\n devLogError(formatModuleNotFoundError(error));\r\n } else {\r\n // Pass the error through so devLogError prints the stack. With dev\r\n // source maps enabled, frames remap to the original `.ts` line/col\r\n // (e.g. the actual call site of an undefined symbol) instead of\r\n // vanishing — a one-line \"x is not defined\" is undebuggable.\r\n devLogError(\r\n `Failed to load ${type}: ${file.relativePath} - ${error?.message || error}`,\r\n error,\r\n );\r\n }\r\n return undefined;\r\n } finally {\r\n globalThis.__currentModuleFile = undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Reload a previously-loaded special file. The version-bump that makes the\r\n * import fresh is done by the caller (layer-executor) before invoking this.\r\n * Routes are removed from the registry first so re-registration is clean.\r\n */\r\n public async reloadModule(file: FileManager): Promise<void> {\r\n const moduleType = this.specialFilesCollector.getFileType(file.relativePath);\r\n if (!moduleType) return;\r\n\r\n this.runCleanup(file);\r\n\r\n if (moduleType === \"route\") {\r\n router.removeRoutesBySourceFile(file.relativePath);\r\n }\r\n\r\n this.loadedModules.delete(file.absolutePath);\r\n await this.loadModule(file, moduleType);\r\n }\r\n\r\n /**\r\n * Run every cleanup hook the file registered (or `$cleanup` on its exports)\r\n * and reset the list. Called once per HMR reload.\r\n */\r\n public runCleanup(file: FileManager): void {\r\n for (const hook of file.cleanup) {\r\n try {\r\n if (typeof hook === \"function\") {\r\n hook();\r\n } else if (hook && typeof (hook as { unsubscribe?: () => void }).unsubscribe === \"function\") {\r\n (hook as { unsubscribe: () => void }).unsubscribe();\r\n }\r\n } catch {\r\n // Cleanup errors shouldn't break HMR.\r\n }\r\n }\r\n file.resetCleanup();\r\n }\r\n\r\n /**\r\n * Clean up after a file whose source was deleted from disk.\r\n */\r\n public cleanupDeletedModule(file: FileManager): void {\r\n this.loadedModules.delete(file.absolutePath);\r\n\r\n if (file.type === \"route\") {\r\n router.removeRoutesBySourceFile(file.relativePath);\r\n }\r\n\r\n this.runCleanup(file);\r\n }\r\n\r\n /**\r\n * Scan a freshly-loaded module for cleanup handlers and register them on\r\n * the FileManager so they run before the next reload. Priority: explicit\r\n * `export function cleanup()` → `.$cleanup` on any exported value.\r\n */\r\n private registerCleanup(file: FileManager, module: Record<string, unknown>): void {\r\n if (typeof module.cleanup === \"function\") {\r\n file.addCleanup(module.cleanup as CleanupFunction);\r\n return;\r\n }\r\n\r\n const cleanups: CleanupFunction[] = [];\r\n\r\n for (const exportedValue of Object.values(module)) {\r\n const cleanup = (exportedValue as { $cleanup?: unknown })?.$cleanup;\r\n if (typeof cleanup === \"function\") {\r\n cleanups.push(cleanup.bind(exportedValue) as CleanupFunction);\r\n }\r\n }\r\n\r\n if (cleanups.length > 0) {\r\n file.addCleanup(cleanups);\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;AAeA,MAAM,gBAA4C;CAAC;CAAU;CAAS;CAAQ;AAAO;;;;;;AAOrF,IAAa,eAAb,MAA0B;CAGxB,YAAY,AAAiB,uBAA8C;EAA9C;uCAFI,IAAI,IAAqB;CAEkB;;;;;CAM5E,MAAa,UAAyB;EACpC,KAAK,MAAM,QAAQ,eACjB,KAAK,MAAM,QAAQ,KAAK,sBAAsB,eAAe,IAAI,GAC/D,MAAM,KAAK,WAAW,MAAM,IAAI;CAGtC;;;;;CAMA,MAAa,WACX,MACA,MACwB;EACxB,IAAI,KAAK,aAAa,SAAS,MAAM,GAAG,OAAO;EAE/C,WAAW,sBAAsB;EAEjC,IAAI;GACF,MAAM,UAAU,cAAc,KAAK,YAAY,CAAC,CAAC;GAEjD,MAAM,OAAO,YAAY;IACvB,MAAM,SAAU,MAAM,OAAO;IAC7B,KAAK,cAAc,IAAI,KAAK,cAAc,MAAM;IAChD,KAAK,gBAAgB,MAAM,MAAM;IACjC,OAAO;GACT;GAEA,OAAO,SAAS,UACZ,MAAM,OAAO,eAAe,KAAK,cAAc,IAAI,IACnD,MAAM,KAAK;EACjB,SAAS,OAAY;GACnB,IAAI,MAAM,SAAS,wBACjB,YAAY,0BAA0B,KAAK,CAAC;QAM5C,YACE,kBAAkB,KAAK,IAAI,KAAK,aAAa,KAAK,OAAO,WAAW,SACpE,KACF;GAEF;EACF,UAAU;GACR,WAAW,sBAAsB;EACnC;CACF;;;;;;CAOA,MAAa,aAAa,MAAkC;EAC1D,MAAM,aAAa,KAAK,sBAAsB,YAAY,KAAK,YAAY;EAC3E,IAAI,CAAC,YAAY;EAEjB,KAAK,WAAW,IAAI;EAEpB,IAAI,eAAe,SACjB,OAAO,yBAAyB,KAAK,YAAY;EAGnD,KAAK,cAAc,OAAO,KAAK,YAAY;EAC3C,MAAM,KAAK,WAAW,MAAM,UAAU;CACxC;;;;;CAMA,AAAO,WAAW,MAAyB;EACzC,KAAK,MAAM,QAAQ,KAAK,SACtB,IAAI;GACF,IAAI,OAAO,SAAS,YAClB,KAAK;QACA,IAAI,QAAQ,OAAQ,KAAsC,gBAAgB,YAC/E,AAAC,KAAqC,YAAY;EAEtD,QAAQ,CAER;EAEF,KAAK,aAAa;CACpB;;;;CAKA,AAAO,qBAAqB,MAAyB;EACnD,KAAK,cAAc,OAAO,KAAK,YAAY;EAE3C,IAAI,KAAK,SAAS,SAChB,OAAO,yBAAyB,KAAK,YAAY;EAGnD,KAAK,WAAW,IAAI;CACtB;;;;;;CAOA,AAAQ,gBAAgB,MAAmB,QAAuC;EAChF,IAAI,OAAO,OAAO,YAAY,YAAY;GACxC,KAAK,WAAW,OAAO,OAA0B;GACjD;EACF;EAEA,MAAM,WAA8B,CAAC;EAErC,KAAK,MAAM,iBAAiB,OAAO,OAAO,MAAM,GAAG;GACjD,MAAM,UAAW,eAA0C;GAC3D,IAAI,OAAO,YAAY,YACrB,SAAS,KAAK,QAAQ,KAAK,aAAa,CAAoB;EAEhE;EAEA,IAAI,SAAS,SAAS,GACpB,KAAK,WAAW,QAAQ;CAE5B;AACF"}
1
+ {"version":3,"file":"module-loader.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/module-loader.ts"],"sourcesContent":["import { pathToFileURL } from \"node:url\";\nimport { router } from \"../router/router\";\nimport { devLogError, formatModuleNotFoundError } from \"./dev-logger\";\nimport type { CleanupFunction, FileManager } from \"./file-manager\";\nimport type { SpecialFileType, SpecialFilesCollector } from \"./special-files-collector\";\n\ndeclare global {\n var __currentModuleFile: FileManager | undefined;\n}\n\n/**\n * Thrown when a special file (route/event/main/locale) fails to import or\n * register. Carries the file that failed and the original cause so a broken\n * module surfaces with its origin instead of vanishing into a 404.\n */\nexport class ModuleLoadError extends Error {\n public constructor(\n public readonly type: string,\n public readonly file: FileManager,\n public readonly cause: unknown,\n ) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n super(`Failed to load ${type}: ${file.relativePath} - ${reason}`);\n this.name = \"ModuleLoadError\";\n }\n}\n\n/**\n * The four \"special\" file kinds that the dev server eagerly imports at boot.\n * Order matters: locales first (used by everything), events before main so\n * listeners are registered when main runs, routes last so handlers are bound.\n */\nconst SPECIAL_TYPES: readonly SpecialFileType[] = [\"locale\", \"event\", \"main\", \"route\"] as const;\n\n/**\n * Loads application modules through the ESM loader hook. The hook stamps\n * `?v=N` on every import, so each reload is a fresh module without any\n * userland cache-busting plumbing.\n */\nexport class ModuleLoader {\n private readonly loadedModules = new Map<string, unknown>();\n\n constructor(private readonly specialFilesCollector: SpecialFilesCollector) {}\n\n /**\n * Eagerly load every special file at boot, in the canonical order so\n * later phases (e.g. routes) see state earlier phases registered.\n *\n * A failing module no longer vanishes silently: each failure is collected\n * (so one broken file doesn't hide the others) and, once every file has been\n * attempted, an aggregate error is thrown. The boot callers\n * (`DevelopmentServer.start`, the HTTP test bootstrap) already wrap this in a\n * try/catch that logs and aborts the boot — so a broken route module aborts\n * boot loudly rather than booting into a surface that 404s.\n */\n public async loadAll(): Promise<void> {\n const failures: ModuleLoadError[] = [];\n\n for (const type of SPECIAL_TYPES) {\n for (const file of this.specialFilesCollector.getFilesByType(type)) {\n try {\n await this.loadModule(file, type);\n } catch (error) {\n failures.push(\n error instanceof ModuleLoadError ? error : new ModuleLoadError(type, file, error),\n );\n }\n }\n }\n\n if (failures.length > 0) {\n throw new AggregateError(\n failures,\n `${failures.length} module(s) failed to load during boot:\\n` +\n failures.map((failure) => ` - ${failure.message}`).join(\"\\n\"),\n );\n }\n }\n\n /**\n * Import a file through the loader hook. For routes, scopes the import in\n * `router.withSourceFile()` so the added routes carry their origin.\n */\n public async loadModule<T = unknown>(\n file: FileManager,\n type: string,\n ): Promise<T | undefined> {\n if (file.relativePath.endsWith(\".env\")) return undefined;\n\n globalThis.__currentModuleFile = file;\n\n try {\n const fileUrl = pathToFileURL(file.absolutePath).href;\n\n const load = async () => {\n const module = (await import(fileUrl)) as Record<string, unknown>;\n this.loadedModules.set(file.absolutePath, module);\n this.registerCleanup(file, module);\n return module as T;\n };\n\n return type === \"route\"\n ? await router.withSourceFile(file.relativePath, load)\n : await load();\n } catch (error: any) {\n if (error.code === \"ERR_MODULE_NOT_FOUND\") {\n devLogError(formatModuleNotFoundError(error));\n } else {\n // Pass the error through so devLogError prints the stack. With dev\n // source maps enabled, frames remap to the original `.ts` line/col\n // (e.g. the actual call site of an undefined symbol) instead of\n // vanishing — a one-line \"x is not defined\" is undebuggable.\n devLogError(\n `Failed to load ${type}: ${file.relativePath} - ${error?.message || error}`,\n error,\n );\n }\n\n // RETHROW after logging. Previously this returned undefined, so a route\n // file that threw on import or registration disappeared with no boot\n // error — the whole surface 404'd silently. Boot callers abort on the\n // throw; the HMR batch-reload handler catches it, keeps the server\n // alive, and shows it loudly.\n throw error instanceof ModuleLoadError ? error : new ModuleLoadError(type, file, error);\n } finally {\n globalThis.__currentModuleFile = undefined;\n }\n }\n\n /**\n * Reload a previously-loaded special file. The version-bump that makes the\n * import fresh is done by the caller (layer-executor) before invoking this.\n * Routes are removed from the registry first so re-registration is clean.\n */\n public async reloadModule(file: FileManager): Promise<void> {\n const moduleType = this.specialFilesCollector.getFileType(file.relativePath);\n if (!moduleType) return;\n\n this.runCleanup(file);\n\n if (moduleType === \"route\") {\n router.removeRoutesBySourceFile(file.relativePath);\n }\n\n this.loadedModules.delete(file.absolutePath);\n await this.loadModule(file, moduleType);\n }\n\n /**\n * Run every cleanup hook the file registered (or `$cleanup` on its exports)\n * and reset the list. Called once per HMR reload.\n */\n public runCleanup(file: FileManager): void {\n for (const hook of file.cleanup) {\n try {\n if (typeof hook === \"function\") {\n hook();\n } else if (hook && typeof (hook as { unsubscribe?: () => void }).unsubscribe === \"function\") {\n (hook as { unsubscribe: () => void }).unsubscribe();\n }\n } catch {\n // Cleanup errors shouldn't break HMR.\n }\n }\n file.resetCleanup();\n }\n\n /**\n * Clean up after a file whose source was deleted from disk.\n */\n public cleanupDeletedModule(file: FileManager): void {\n this.loadedModules.delete(file.absolutePath);\n\n if (file.type === \"route\") {\n router.removeRoutesBySourceFile(file.relativePath);\n }\n\n this.runCleanup(file);\n }\n\n /**\n * Scan a freshly-loaded module for cleanup handlers and register them on\n * the FileManager so they run before the next reload. Priority: explicit\n * `export function cleanup()` → `.$cleanup` on any exported value.\n */\n private registerCleanup(file: FileManager, module: Record<string, unknown>): void {\n if (typeof module.cleanup === \"function\") {\n file.addCleanup(module.cleanup as CleanupFunction);\n return;\n }\n\n const cleanups: CleanupFunction[] = [];\n\n for (const exportedValue of Object.values(module)) {\n const cleanup = (exportedValue as { $cleanup?: unknown })?.$cleanup;\n if (typeof cleanup === \"function\") {\n cleanups.push(cleanup.bind(exportedValue) as CleanupFunction);\n }\n }\n\n if (cleanups.length > 0) {\n file.addCleanup(cleanups);\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAeA,IAAa,kBAAb,cAAqC,MAAM;CACzC,AAAO,YACL,AAAgB,MAChB,AAAgB,MAChB,AAAgB,OAChB;EACA,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACpE,MAAM,kBAAkB,KAAK,IAAI,KAAK,aAAa,KAAK,QAAQ;EALhD;EACA;EACA;EAIhB,KAAK,OAAO;CACd;AACF;;;;;;AAOA,MAAM,gBAA4C;CAAC;CAAU;CAAS;CAAQ;AAAO;;;;;;AAOrF,IAAa,eAAb,MAA0B;CAGxB,YAAY,AAAiB,uBAA8C;EAA9C;uCAFI,IAAI,IAAqB;CAEkB;;;;;;;;;;;;CAa5E,MAAa,UAAyB;EACpC,MAAM,WAA8B,CAAC;EAErC,KAAK,MAAM,QAAQ,eACjB,KAAK,MAAM,QAAQ,KAAK,sBAAsB,eAAe,IAAI,GAC/D,IAAI;GACF,MAAM,KAAK,WAAW,MAAM,IAAI;EAClC,SAAS,OAAO;GACd,SAAS,KACP,iBAAiB,kBAAkB,QAAQ,IAAI,gBAAgB,MAAM,MAAM,KAAK,CAClF;EACF;EAIJ,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,eACR,UACA,GAAG,SAAS,OAAO,4CACjB,SAAS,KAAK,YAAY,OAAO,QAAQ,SAAS,CAAC,CAAC,KAAK,IAAI,CACjE;CAEJ;;;;;CAMA,MAAa,WACX,MACA,MACwB;EACxB,IAAI,KAAK,aAAa,SAAS,MAAM,GAAG,OAAO;EAE/C,WAAW,sBAAsB;EAEjC,IAAI;GACF,MAAM,UAAU,cAAc,KAAK,YAAY,CAAC,CAAC;GAEjD,MAAM,OAAO,YAAY;IACvB,MAAM,SAAU,MAAM,OAAO;IAC7B,KAAK,cAAc,IAAI,KAAK,cAAc,MAAM;IAChD,KAAK,gBAAgB,MAAM,MAAM;IACjC,OAAO;GACT;GAEA,OAAO,SAAS,UACZ,MAAM,OAAO,eAAe,KAAK,cAAc,IAAI,IACnD,MAAM,KAAK;EACjB,SAAS,OAAY;GACnB,IAAI,MAAM,SAAS,wBACjB,YAAY,0BAA0B,KAAK,CAAC;QAM5C,YACE,kBAAkB,KAAK,IAAI,KAAK,aAAa,KAAK,OAAO,WAAW,SACpE,KACF;GAQF,MAAM,iBAAiB,kBAAkB,QAAQ,IAAI,gBAAgB,MAAM,MAAM,KAAK;EACxF,UAAU;GACR,WAAW,sBAAsB;EACnC;CACF;;;;;;CAOA,MAAa,aAAa,MAAkC;EAC1D,MAAM,aAAa,KAAK,sBAAsB,YAAY,KAAK,YAAY;EAC3E,IAAI,CAAC,YAAY;EAEjB,KAAK,WAAW,IAAI;EAEpB,IAAI,eAAe,SACjB,OAAO,yBAAyB,KAAK,YAAY;EAGnD,KAAK,cAAc,OAAO,KAAK,YAAY;EAC3C,MAAM,KAAK,WAAW,MAAM,UAAU;CACxC;;;;;CAMA,AAAO,WAAW,MAAyB;EACzC,KAAK,MAAM,QAAQ,KAAK,SACtB,IAAI;GACF,IAAI,OAAO,SAAS,YAClB,KAAK;QACA,IAAI,QAAQ,OAAQ,KAAsC,gBAAgB,YAC/E,AAAC,KAAqC,YAAY;EAEtD,QAAQ,CAER;EAEF,KAAK,aAAa;CACpB;;;;CAKA,AAAO,qBAAqB,MAAyB;EACnD,KAAK,cAAc,OAAO,KAAK,YAAY;EAE3C,IAAI,KAAK,SAAS,SAChB,OAAO,yBAAyB,KAAK,YAAY;EAGnD,KAAK,WAAW,IAAI;CACtB;;;;;;CAOA,AAAQ,gBAAgB,MAAmB,QAAuC;EAChF,IAAI,OAAO,OAAO,YAAY,YAAY;GACxC,KAAK,WAAW,OAAO,OAA0B;GACjD;EACF;EAEA,MAAM,WAA8B,CAAC;EAErC,KAAK,MAAM,iBAAiB,OAAO,OAAO,MAAM,GAAG;GACjD,MAAM,UAAW,eAA0C;GAC3D,IAAI,OAAO,YAAY,YACrB,SAAS,KAAK,QAAQ,KAAK,aAAa,CAAoB;EAEhE;EAEA,IAAI,SAAS,SAAS,GACpB,KAAK,WAAW,QAAQ;CAE5B;AACF"}
@@ -1,4 +1,4 @@
1
- import baseConfig from "@mongez/config";
1
+ import config from "@mongez/config";
2
2
  import { get } from "@mongez/reinforcements";
3
3
 
4
4
  //#region ../@warlock.js/core/src/http/config.ts
@@ -18,7 +18,7 @@ const defaultHttpConfigurations = {
18
18
  * Get http configurations for the given key
19
19
  */
20
20
  function httpConfig(key) {
21
- return baseConfig.get(`http.${key}`, get(defaultHttpConfigurations, key));
21
+ return config.get(`http.${key}`, get(defaultHttpConfigurations, key));
22
22
  }
23
23
 
24
24
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":["config"],"sources":["../../../../../../../@warlock.js/core/src/http/config.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { get } from \"@mongez/reinforcements\";\nimport type { HttpConfigurations } from \"./types\";\n\n/**\n * Default http configurations\n */\nexport const defaultHttpConfigurations: HttpConfigurations = {\n port: 3000,\n host: \"0.0.0.0\",\n middleware: {\n all: [],\n only: {\n middleware: [],\n },\n except: {\n middleware: [],\n },\n },\n};\n\n/**\n * Get http configurations for the given key\n */\nexport function httpConfig(key: string): any {\n return config.get(`http.${key}`, get(defaultHttpConfigurations, key));\n}\n"],"mappings":";;;;;;;AAOA,MAAa,4BAAgD;CAC3D,MAAM;CACN,MAAM;CACN,YAAY;EACV,KAAK,CAAC;EACN,MAAM,EACJ,YAAY,CAAC,EACf;EACA,QAAQ,EACN,YAAY,CAAC,EACf;CACF;AACF;;;;AAKA,SAAgB,WAAW,KAAkB;CAC3C,OAAOA,WAAO,IAAI,QAAQ,OAAO,IAAI,2BAA2B,GAAG,CAAC;AACtE"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/config.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { get } from \"@mongez/reinforcements\";\nimport type { HttpConfigurations } from \"./types\";\n\n/**\n * Default http configurations\n */\nexport const defaultHttpConfigurations: HttpConfigurations = {\n port: 3000,\n host: \"0.0.0.0\",\n middleware: {\n all: [],\n only: {\n middleware: [],\n },\n except: {\n middleware: [],\n },\n },\n};\n\n/**\n * Get http configurations for the given key\n */\nexport function httpConfig(key: string): any {\n return config.get(`http.${key}`, get(defaultHttpConfigurations, key));\n}\n"],"mappings":";;;;;;;AAOA,MAAa,4BAAgD;CAC3D,MAAM;CACN,MAAM;CACN,YAAY;EACV,KAAK,CAAC;EACN,MAAM,EACJ,YAAY,CAAC,EACf;EACA,QAAQ,EACN,YAAY,CAAC,EACf;CACF;AACF;;;;AAKA,SAAgB,WAAW,KAAkB;CAC3C,OAAO,OAAO,IAAI,QAAQ,OAAO,IAAI,2BAA2B,GAAG,CAAC;AACtE"}
@@ -4,7 +4,7 @@ import { router } from "../router/router.mjs";
4
4
  import "../router/index.mjs";
5
5
  import { registerHttpPlugins } from "./plugins.mjs";
6
6
  import { getHttpServer, startHttpServer } from "./server.mjs";
7
- import baseConfig from "@mongez/config";
7
+ import config from "@mongez/config";
8
8
  import { log } from "@warlock.js/logger";
9
9
 
10
10
  //#region ../@warlock.js/core/src/http/createHttpApplication.ts
@@ -19,7 +19,7 @@ async function createHttpApplication() {
19
19
  port,
20
20
  host: httpConfig("host")
21
21
  });
22
- const baseUrl = baseConfig.get("app.baseUrl");
22
+ const baseUrl = config.get("app.baseUrl");
23
23
  setBaseUrl(baseUrl);
24
24
  log.success("http", "server", `Server is listening on ${baseUrl}`);
25
25
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"createHttpApplication.mjs","names":["config"],"sources":["../../../../../../../@warlock.js/core/src/http/createHttpApplication.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { router } from \"../router\";\nimport { setBaseUrl } from \"../utils/urls\";\nimport { httpConfig } from \"./config\";\nimport { registerHttpPlugins } from \"./plugins\";\nimport { getHttpServer, startHttpServer } from \"./server\";\n\nexport async function createHttpApplication() {\n const server = startHttpServer();\n\n await registerHttpPlugins(server);\n\n router.scan(server);\n\n const port = httpConfig(\"port\");\n\n try {\n log.info(\"http\", \"server\", \"Connecting to the server\");\n // 👇🏻 We can use the url of the server\n await server.listen({\n port,\n host: httpConfig(\"host\"),\n });\n\n const baseUrl = config.get(\"app.baseUrl\");\n\n // update base url\n setBaseUrl(baseUrl);\n\n log.success(\"http\", \"server\", `Server is listening on ${baseUrl}`);\n } catch (error) {\n log.error(\"http\", \"server\", error);\n\n process.exit(1); // stop the process, exit with error\n }\n}\n\nexport async function stopHttpApplication() {\n log.info(\"http\", \"server\", \"Stopping the server\");\n const server = getHttpServer();\n\n await server?.close();\n\n log.success(\"http\", \"server\", \"Server is stopped\");\n}\n"],"mappings":";;;;;;;;;;AAQA,eAAsB,wBAAwB;CAC5C,MAAM,SAAS,gBAAgB;CAE/B,MAAM,oBAAoB,MAAM;CAEhC,OAAO,KAAK,MAAM;CAElB,MAAM,OAAO,WAAW,MAAM;CAE9B,IAAI;EACF,IAAI,KAAK,QAAQ,UAAU,0BAA0B;EAErD,MAAM,OAAO,OAAO;GAClB;GACA,MAAM,WAAW,MAAM;EACzB,CAAC;EAED,MAAM,UAAUA,WAAO,IAAI,aAAa;EAGxC,WAAW,OAAO;EAElB,IAAI,QAAQ,QAAQ,UAAU,0BAA0B,SAAS;CACnE,SAAS,OAAO;EACd,IAAI,MAAM,QAAQ,UAAU,KAAK;EAEjC,QAAQ,KAAK,CAAC;CAChB;AACF;AAEA,eAAsB,sBAAsB;CAC1C,IAAI,KAAK,QAAQ,UAAU,qBAAqB;CAGhD,MAFe,cAEJ,CAAC,EAAE,MAAM;CAEpB,IAAI,QAAQ,QAAQ,UAAU,mBAAmB;AACnD"}
1
+ {"version":3,"file":"createHttpApplication.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/createHttpApplication.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { router } from \"../router\";\nimport { setBaseUrl } from \"../utils/urls\";\nimport { httpConfig } from \"./config\";\nimport { registerHttpPlugins } from \"./plugins\";\nimport { getHttpServer, startHttpServer } from \"./server\";\n\nexport async function createHttpApplication() {\n const server = startHttpServer();\n\n await registerHttpPlugins(server);\n\n router.scan(server);\n\n const port = httpConfig(\"port\");\n\n try {\n log.info(\"http\", \"server\", \"Connecting to the server\");\n // 👇🏻 We can use the url of the server\n await server.listen({\n port,\n host: httpConfig(\"host\"),\n });\n\n const baseUrl = config.get(\"app.baseUrl\");\n\n // update base url\n setBaseUrl(baseUrl);\n\n log.success(\"http\", \"server\", `Server is listening on ${baseUrl}`);\n } catch (error) {\n log.error(\"http\", \"server\", error);\n\n process.exit(1); // stop the process, exit with error\n }\n}\n\nexport async function stopHttpApplication() {\n log.info(\"http\", \"server\", \"Stopping the server\");\n const server = getHttpServer();\n\n await server?.close();\n\n log.success(\"http\", \"server\", \"Server is stopped\");\n}\n"],"mappings":";;;;;;;;;;AAQA,eAAsB,wBAAwB;CAC5C,MAAM,SAAS,gBAAgB;CAE/B,MAAM,oBAAoB,MAAM;CAEhC,OAAO,KAAK,MAAM;CAElB,MAAM,OAAO,WAAW,MAAM;CAE9B,IAAI;EACF,IAAI,KAAK,QAAQ,UAAU,0BAA0B;EAErD,MAAM,OAAO,OAAO;GAClB;GACA,MAAM,WAAW,MAAM;EACzB,CAAC;EAED,MAAM,UAAU,OAAO,IAAI,aAAa;EAGxC,WAAW,OAAO;EAElB,IAAI,QAAQ,QAAQ,UAAU,0BAA0B,SAAS;CACnE,SAAS,OAAO;EACd,IAAI,MAAM,QAAQ,UAAU,KAAK;EAEjC,QAAQ,KAAK,CAAC;CAChB;AACF;AAEA,eAAsB,sBAAsB;CAC1C,IAAI,KAAK,QAAQ,UAAU,qBAAqB;CAGhD,MAFe,cAEJ,CAAC,EAAE,MAAM;CAEpB,IAAI,QAAQ,QAAQ,UAAU,mBAAmB;AACnD"}
@@ -40,6 +40,20 @@ declare class HealthRegistry {
40
40
  * use it to decide whether to ROUTE to the instance.
41
41
  */
42
42
  readiness(): Promise<HealthStatus>;
43
+ /**
44
+ * Register a readiness check asserting the app has at least one route
45
+ * registered. A booted HTTP app that ends up with zero routes almost always
46
+ * means a route module failed to register silently, so the instance should
47
+ * report not-ready (503) rather than 404 every request.
48
+ *
49
+ * The route count is passed in as a getter so this registry stays decoupled
50
+ * from the router (no import cycle). The HTTP connector wires this in after
51
+ * scanning routes; apps with no HTTP surface simply never register it.
52
+ *
53
+ * @param getRouteCount Returns the number of currently-registered routes.
54
+ * @param name Override the check name (defaults to `"routes"`).
55
+ */
56
+ addRoutesRegisteredCheck(getRouteCount: () => number, name?: string): void;
43
57
  /**
44
58
  * Run one check. A thrown error counts as a failed check rather than crashing
45
59
  * the probe. The failure is surfaced in the readiness `checks` map and the
@@ -1 +1 @@
1
- {"version":3,"file":"health.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/health.ts"],"mappings":";;AAMA;;;KAAY,WAAA,mBAA8B,OAAO;AAAA;AAMjD;;;AANiD,KAMrC,YAAA;EACV,MAAA;EACA,MAAA,GAAS,MAAM;AAAA;;AAAA;AACf;;;;;cASI,cAAA;EAAA,iBACa,MAAA;EA6BgB;;;EAxB1B,QAAA,CAAS,IAAA,UAAc,KAAA,EAAO,WAAA;EAArB;;;EAOT,WAAA,CAAY,IAAA;EAAA;;;;EAQZ,QAAA,IAAY,YAAA;EASe;;;AA0BZ;AAYxB;EAtCe,SAAA,IAAa,OAAA,CAAQ,YAAA;;;AAsCM;;;;UAZ1B,QAAA;AAAA;;;;cAYH,MAAA,EAAM,cAAuB"}
1
+ {"version":3,"file":"health.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/health.ts"],"mappings":";;AAMA;;;KAAY,WAAA,mBAA8B,OAAO;AAAA;AAMjD;;;AANiD,KAMrC,YAAA;EACV,MAAA;EACA,MAAA,GAAS,MAAM;AAAA;;AAAA;AACf;;;;;cASI,cAAA;EAAA,iBACa,MAAA;EA6BgB;;;EAxB1B,QAAA,CAAS,IAAA,UAAc,KAAA,EAAO,WAAA;EAArB;;;EAOT,WAAA,CAAY,IAAA;EAAA;;;;EAQZ,QAAA,IAAY,YAAA;EASe;;;;;EAArB,SAAA,IAAa,OAAA,CAAQ,YAAA;EA2CZ;AAYxB;;;;AAA0C;;;;;;;;EAtBjC,wBAAA,CAAyB,aAAA,gBAA6B,IAAA;;;;;;;UAU/C,QAAA;AAAA;;;;cAYH,MAAA,EAAM,cAAuB"}
@@ -52,6 +52,22 @@ var HealthRegistry = class {
52
52
  };
53
53
  }
54
54
  /**
55
+ * Register a readiness check asserting the app has at least one route
56
+ * registered. A booted HTTP app that ends up with zero routes almost always
57
+ * means a route module failed to register silently, so the instance should
58
+ * report not-ready (503) rather than 404 every request.
59
+ *
60
+ * The route count is passed in as a getter so this registry stays decoupled
61
+ * from the router (no import cycle). The HTTP connector wires this in after
62
+ * scanning routes; apps with no HTTP surface simply never register it.
63
+ *
64
+ * @param getRouteCount Returns the number of currently-registered routes.
65
+ * @param name Override the check name (defaults to `"routes"`).
66
+ */
67
+ addRoutesRegisteredCheck(getRouteCount, name = "routes") {
68
+ this.addCheck(name, () => getRouteCount() > 0);
69
+ }
70
+ /**
55
71
  * Run one check. A thrown error counts as a failed check rather than crashing
56
72
  * the probe. The failure is surfaced in the readiness `checks` map and the
57
73
  * 503 status — not logged, since probes poll frequently and a failed check is
@@ -1 +1 @@
1
- {"version":3,"file":"health.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/health.ts"],"sourcesContent":["import { Application } from \"../application\";\n\n/**\n * A readiness probe registered via `health.addCheck(...)`. Returns whether its\n * dependency is currently usable; throwing is treated as a failed check.\n */\nexport type HealthCheck = () => boolean | Promise<boolean>;\n\n/**\n * Result of a liveness/readiness probe. `checks` is present on readiness only,\n * mapping each registered check name to its pass/fail.\n */\nexport type HealthStatus = {\n status: \"ok\" | \"error\";\n checks?: Record<string, boolean>;\n};\n\n/**\n * Registry behind the built-in `/health` (liveness) and `/ready` (readiness)\n * endpoints. Liveness answers \"is the process up?\"; readiness answers \"should\n * traffic be routed here?\" — booted, not shutting down, and every registered\n * dependency check passing. Connectors and app code register checks; the HTTP\n * connector exposes the endpoints.\n */\nclass HealthRegistry {\n private readonly checks = new Map<string, HealthCheck>();\n\n /**\n * Register (or replace) a readiness check by name — e.g. a database ping.\n */\n public addCheck(name: string, check: HealthCheck): void {\n this.checks.set(name, check);\n }\n\n /**\n * Remove a previously-registered readiness check.\n */\n public removeCheck(name: string): void {\n this.checks.delete(name);\n }\n\n /**\n * Liveness — the process is up and not tearing down. Orchestrators use it to\n * decide whether to RESTART the instance, so it ignores dependency checks.\n */\n public liveness(): HealthStatus {\n return { status: Application.isShuttingDown ? \"error\" : \"ok\" };\n }\n\n /**\n * Readiness — should this instance receive traffic? Requires a finished boot,\n * no in-progress shutdown, and every registered check passing. Load balancers\n * use it to decide whether to ROUTE to the instance.\n */\n public async readiness(): Promise<HealthStatus> {\n if (!Application.isBooted || Application.isShuttingDown) {\n return { status: \"error\" };\n }\n\n const checks: Record<string, boolean> = {};\n let allPassed = true;\n\n for (const [name, check] of this.checks) {\n const passed = await this.runCheck(check);\n checks[name] = passed;\n\n if (!passed) {\n allPassed = false;\n }\n }\n\n return { status: allPassed ? \"ok\" : \"error\", checks };\n }\n\n /**\n * Run one check. A thrown error counts as a failed check rather than crashing\n * the probe. The failure is surfaced in the readiness `checks` map and the\n * 503 status — not logged, since probes poll frequently and a failed check is\n * a normal signal, not an error event.\n */\n private async runCheck(check: HealthCheck): Promise<boolean> {\n try {\n return await check();\n } catch {\n return false;\n }\n }\n}\n\n/**\n * The application's health registry singleton.\n */\nexport const health = new HealthRegistry();\n"],"mappings":";;;;;;;;;;;AAwBA,IAAM,iBAAN,MAAqB;;gCACO,IAAI,IAAyB;;;;;CAKvD,AAAO,SAAS,MAAc,OAA0B;EACtD,KAAK,OAAO,IAAI,MAAM,KAAK;CAC7B;;;;CAKA,AAAO,YAAY,MAAoB;EACrC,KAAK,OAAO,OAAO,IAAI;CACzB;;;;;CAMA,AAAO,WAAyB;EAC9B,OAAO,EAAE,QAAQ,YAAY,iBAAiB,UAAU,KAAK;CAC/D;;;;;;CAOA,MAAa,YAAmC;EAC9C,IAAI,CAAC,YAAY,YAAY,YAAY,gBACvC,OAAO,EAAE,QAAQ,QAAQ;EAG3B,MAAM,SAAkC,CAAC;EACzC,IAAI,YAAY;EAEhB,KAAK,MAAM,CAAC,MAAM,UAAU,KAAK,QAAQ;GACvC,MAAM,SAAS,MAAM,KAAK,SAAS,KAAK;GACxC,OAAO,QAAQ;GAEf,IAAI,CAAC,QACH,YAAY;EAEhB;EAEA,OAAO;GAAE,QAAQ,YAAY,OAAO;GAAS;EAAO;CACtD;;;;;;;CAQA,MAAc,SAAS,OAAsC;EAC3D,IAAI;GACF,OAAO,MAAM,MAAM;EACrB,QAAQ;GACN,OAAO;EACT;CACF;AACF;;;;AAKA,MAAa,SAAS,IAAI,eAAe"}
1
+ {"version":3,"file":"health.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/health.ts"],"sourcesContent":["import { Application } from \"../application\";\n\n/**\n * A readiness probe registered via `health.addCheck(...)`. Returns whether its\n * dependency is currently usable; throwing is treated as a failed check.\n */\nexport type HealthCheck = () => boolean | Promise<boolean>;\n\n/**\n * Result of a liveness/readiness probe. `checks` is present on readiness only,\n * mapping each registered check name to its pass/fail.\n */\nexport type HealthStatus = {\n status: \"ok\" | \"error\";\n checks?: Record<string, boolean>;\n};\n\n/**\n * Registry behind the built-in `/health` (liveness) and `/ready` (readiness)\n * endpoints. Liveness answers \"is the process up?\"; readiness answers \"should\n * traffic be routed here?\" — booted, not shutting down, and every registered\n * dependency check passing. Connectors and app code register checks; the HTTP\n * connector exposes the endpoints.\n */\nclass HealthRegistry {\n private readonly checks = new Map<string, HealthCheck>();\n\n /**\n * Register (or replace) a readiness check by name — e.g. a database ping.\n */\n public addCheck(name: string, check: HealthCheck): void {\n this.checks.set(name, check);\n }\n\n /**\n * Remove a previously-registered readiness check.\n */\n public removeCheck(name: string): void {\n this.checks.delete(name);\n }\n\n /**\n * Liveness — the process is up and not tearing down. Orchestrators use it to\n * decide whether to RESTART the instance, so it ignores dependency checks.\n */\n public liveness(): HealthStatus {\n return { status: Application.isShuttingDown ? \"error\" : \"ok\" };\n }\n\n /**\n * Readiness — should this instance receive traffic? Requires a finished boot,\n * no in-progress shutdown, and every registered check passing. Load balancers\n * use it to decide whether to ROUTE to the instance.\n */\n public async readiness(): Promise<HealthStatus> {\n if (!Application.isBooted || Application.isShuttingDown) {\n return { status: \"error\" };\n }\n\n const checks: Record<string, boolean> = {};\n let allPassed = true;\n\n for (const [name, check] of this.checks) {\n const passed = await this.runCheck(check);\n checks[name] = passed;\n\n if (!passed) {\n allPassed = false;\n }\n }\n\n return { status: allPassed ? \"ok\" : \"error\", checks };\n }\n\n /**\n * Register a readiness check asserting the app has at least one route\n * registered. A booted HTTP app that ends up with zero routes almost always\n * means a route module failed to register silently, so the instance should\n * report not-ready (503) rather than 404 every request.\n *\n * The route count is passed in as a getter so this registry stays decoupled\n * from the router (no import cycle). The HTTP connector wires this in after\n * scanning routes; apps with no HTTP surface simply never register it.\n *\n * @param getRouteCount Returns the number of currently-registered routes.\n * @param name Override the check name (defaults to `\"routes\"`).\n */\n public addRoutesRegisteredCheck(getRouteCount: () => number, name = \"routes\"): void {\n this.addCheck(name, () => getRouteCount() > 0);\n }\n\n /**\n * Run one check. A thrown error counts as a failed check rather than crashing\n * the probe. The failure is surfaced in the readiness `checks` map and the\n * 503 status — not logged, since probes poll frequently and a failed check is\n * a normal signal, not an error event.\n */\n private async runCheck(check: HealthCheck): Promise<boolean> {\n try {\n return await check();\n } catch {\n return false;\n }\n }\n}\n\n/**\n * The application's health registry singleton.\n */\nexport const health = new HealthRegistry();\n"],"mappings":";;;;;;;;;;;AAwBA,IAAM,iBAAN,MAAqB;;gCACO,IAAI,IAAyB;;;;;CAKvD,AAAO,SAAS,MAAc,OAA0B;EACtD,KAAK,OAAO,IAAI,MAAM,KAAK;CAC7B;;;;CAKA,AAAO,YAAY,MAAoB;EACrC,KAAK,OAAO,OAAO,IAAI;CACzB;;;;;CAMA,AAAO,WAAyB;EAC9B,OAAO,EAAE,QAAQ,YAAY,iBAAiB,UAAU,KAAK;CAC/D;;;;;;CAOA,MAAa,YAAmC;EAC9C,IAAI,CAAC,YAAY,YAAY,YAAY,gBACvC,OAAO,EAAE,QAAQ,QAAQ;EAG3B,MAAM,SAAkC,CAAC;EACzC,IAAI,YAAY;EAEhB,KAAK,MAAM,CAAC,MAAM,UAAU,KAAK,QAAQ;GACvC,MAAM,SAAS,MAAM,KAAK,SAAS,KAAK;GACxC,OAAO,QAAQ;GAEf,IAAI,CAAC,QACH,YAAY;EAEhB;EAEA,OAAO;GAAE,QAAQ,YAAY,OAAO;GAAS;EAAO;CACtD;;;;;;;;;;;;;;CAeA,AAAO,yBAAyB,eAA6B,OAAO,UAAgB;EAClF,KAAK,SAAS,YAAY,cAAc,IAAI,CAAC;CAC/C;;;;;;;CAQA,MAAc,SAAS,OAAsC;EAC3D,IAAI;GACF,OAAO,MAAM,MAAM;EACrB,QAAQ;GACN,OAAO;EACT;CACF;AACF;;;;AAKA,MAAa,SAAS,IAAI,eAAe"}
@@ -1,7 +1,7 @@
1
1
  import { t } from "./inject-request-context.mjs";
2
2
  import { HttpErrorCodes } from "../error-codes.mjs";
3
3
  import { buildIdempotencyCacheKey, hashBody, isValidIdempotencyKey } from "./utils/idempotency-key.mjs";
4
- import baseConfig from "@mongez/config";
4
+ import config from "@mongez/config";
5
5
  import { log } from "@warlock.js/logger";
6
6
  import { cache } from "@warlock.js/cache";
7
7
 
@@ -44,10 +44,10 @@ const DEFAULT_METHODS = [
44
44
  */
45
45
  function idempotencyMiddleware(options = {}) {
46
46
  return async (request, response) => {
47
- const headerName = options.headerName || baseConfig.get("http.idempotency.headerName", "Idempotency-Key");
48
- const methods = options.methods || baseConfig.get("http.idempotency.methods", DEFAULT_METHODS);
49
- const ttl = options.ttl || baseConfig.get("http.idempotency.ttl", 86400);
50
- const driverName = options.driver || baseConfig.get("http.idempotency.driver");
47
+ const headerName = options.headerName || config.get("http.idempotency.headerName", "Idempotency-Key");
48
+ const methods = options.methods || config.get("http.idempotency.methods", DEFAULT_METHODS);
49
+ const ttl = options.ttl || config.get("http.idempotency.ttl", 86400);
50
+ const driverName = options.driver || config.get("http.idempotency.driver");
51
51
  if (!methods.includes(request.method.toUpperCase())) return;
52
52
  const idempotencyKey = request.header(headerName.toLowerCase());
53
53
  if (!idempotencyKey) return;
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency.middleware.mjs","names":["config"],"sources":["../../../../../../../../@warlock.js/core/src/http/middleware/idempotency.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Response } from \"../response\";\nimport { t } from \"./inject-request-context\";\nimport {\n buildIdempotencyCacheKey,\n hashBody,\n isValidIdempotencyKey,\n} from \"./utils/idempotency-key\";\n\n/**\n * Options for the idempotency middleware.\n */\nexport type IdempotencyOptions = {\n /**\n * Cache TTL in seconds. Falls back to `http.idempotency.ttl`, then `86400` (24h).\n */\n ttl?: number;\n /**\n * Header name carrying the client's key. Falls back to\n * `http.idempotency.headerName`, then `\"Idempotency-Key\"`.\n */\n headerName?: string;\n /**\n * HTTP methods eligible for idempotency. Falls back to\n * `http.idempotency.methods`, then `[\"POST\",\"PUT\",\"PATCH\",\"DELETE\"]`.\n * Safe methods (GET/HEAD) are skipped regardless.\n */\n methods?: string[];\n /**\n * Cache driver name. Falls back to `http.idempotency.driver`, then the\n * default driver of the cache manager.\n */\n driver?: string;\n};\n\ntype CachedResponse = {\n status: number;\n body: unknown;\n bodyHash: string;\n contentType?: string;\n};\n\nconst DEFAULT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/**\n * Dedupe non-idempotent writes by an `Idempotency-Key` header — same key,\n * same body, within TTL → cached replay; same key, different body → 422\n * `IdempotencyKeyConflict`.\n *\n * **Must run after `authMiddleware`** — the cache key is scoped per-user\n * (`idem:{userType}:{userId}:{key}`) so user A can't replay user B's key.\n * Anonymous requests fall back to IP scope.\n *\n * The replay sets `Idempotent-Replay: true` on the response for easy\n * client-side / observability detection.\n *\n * Eligible methods default to POST/PUT/PATCH/DELETE. GET/HEAD pass through\n * even with the header set (RFC: safe methods are already idempotent).\n *\n * @example\n * import { authMiddleware } from \"@warlock.js/auth\";\n * import { middleware } from \"@warlock.js/core\";\n *\n * router.post(\"/orders\", createOrderController, {\n * middleware: [authMiddleware(\"client\"), middleware.idempotency()],\n * });\n *\n * router.post(\"/ai/summarize\", summarizeController, {\n * middleware: [\n * authMiddleware(\"client\"),\n * middleware.idempotency({ ttl: 60 * 60 }), // 1h is enough for client retries\n * ],\n * });\n */\nexport function idempotencyMiddleware(options: IdempotencyOptions = {}): Middleware {\n return async (request, response) => {\n const headerName =\n options.headerName ||\n config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods =\n options.methods || config.get(\"http.idempotency.methods\", DEFAULT_METHODS);\n const ttl = options.ttl || config.get(\"http.idempotency.ttl\", 86400);\n const driverName = options.driver || config.get(\"http.idempotency.driver\");\n\n if (!methods.includes(request.method.toUpperCase())) return;\n\n const idempotencyKey = request.header(headerName.toLowerCase());\n\n if (!idempotencyKey) return;\n\n if (!isValidIdempotencyKey(idempotencyKey)) {\n return response.badRequest({\n error: t(\"http.idempotencyKeyInvalid\"),\n errorCode: HttpErrorCodes.IdempotencyKeyInvalid,\n });\n }\n\n const cacheDriver = driverName ? await cache.use(driverName) : cache;\n const cacheKey = buildIdempotencyCacheKey(request, idempotencyKey);\n const bodyHash = hashBody(request.body);\n\n const cached = (await cacheDriver.get(cacheKey)) as CachedResponse | null;\n\n if (cached) {\n if (cached.bodyHash !== bodyHash) {\n return response.unprocessableEntity({\n error: t(\"http.idempotencyKeyConflict\"),\n errorCode: HttpErrorCodes.IdempotencyKeyConflict,\n });\n }\n\n response.header(\"Idempotent-Replay\", \"true\");\n\n return response.replay({\n status: cached.status,\n body: cached.body,\n contentType: cached.contentType,\n });\n }\n\n response.onSent((sentResponse: Response) => {\n // Don't cache server errors — clients should be able to retry past a 5xx.\n // 4xx are deterministic outcomes of the request, so caching is fine.\n if (sentResponse.statusCode >= 500) return;\n\n const sentContentType = sentResponse.contentType;\n\n // `set` is fire-and-forget inside `onSent`; without a `.catch` a rejected\n // write (e.g. Redis down) would surface as an unhandledRejection.\n cacheDriver\n .set(\n cacheKey,\n {\n status: sentResponse.statusCode,\n body: sentResponse.parsedBody,\n bodyHash,\n contentType: typeof sentContentType === \"string\" ? sentContentType : undefined,\n },\n ttl,\n )\n .catch((error: unknown) => {\n log.error(\"idempotency-middleware\", \"set\", error);\n });\n });\n };\n}\n"],"mappings":";;;;;;;;AA8CA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,SAAS,aAAa;EAClC,MAAM,aACJ,QAAQ,cACRA,WAAO,IAAI,+BAA+B,iBAAiB;EAC7D,MAAM,UACJ,QAAQ,WAAWA,WAAO,IAAI,4BAA4B,eAAe;EAC3E,MAAM,MAAM,QAAQ,OAAOA,WAAO,IAAI,wBAAwB,KAAK;EACnE,MAAM,aAAa,QAAQ,UAAUA,WAAO,IAAI,yBAAyB;EAEzE,IAAI,CAAC,QAAQ,SAAS,QAAQ,OAAO,YAAY,CAAC,GAAG;EAErD,MAAM,iBAAiB,QAAQ,OAAO,WAAW,YAAY,CAAC;EAE9D,IAAI,CAAC,gBAAgB;EAErB,IAAI,CAAC,sBAAsB,cAAc,GACvC,OAAO,SAAS,WAAW;GACzB,OAAO,EAAE,4BAA4B;GACrC;EACF,CAAC;EAGH,MAAM,cAAc,aAAa,MAAM,MAAM,IAAI,UAAU,IAAI;EAC/D,MAAM,WAAW,yBAAyB,SAAS,cAAc;EACjE,MAAM,WAAW,SAAS,QAAQ,IAAI;EAEtC,MAAM,SAAU,MAAM,YAAY,IAAI,QAAQ;EAE9C,IAAI,QAAQ;GACV,IAAI,OAAO,aAAa,UACtB,OAAO,SAAS,oBAAoB;IAClC,OAAO,EAAE,6BAA6B;IACtC;GACF,CAAC;GAGH,SAAS,OAAO,qBAAqB,MAAM;GAE3C,OAAO,SAAS,OAAO;IACrB,QAAQ,OAAO;IACf,MAAM,OAAO;IACb,aAAa,OAAO;GACtB,CAAC;EACH;EAEA,SAAS,QAAQ,iBAA2B;GAG1C,IAAI,aAAa,cAAc,KAAK;GAEpC,MAAM,kBAAkB,aAAa;GAIrC,YACG,IACC,UACA;IACE,QAAQ,aAAa;IACrB,MAAM,aAAa;IACnB;IACA,aAAa,OAAO,oBAAoB,WAAW,kBAAkB;GACvE,GACA,GACF,CAAC,CACA,OAAO,UAAmB;IACzB,IAAI,MAAM,0BAA0B,OAAO,KAAK;GAClD,CAAC;EACL,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"idempotency.middleware.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/http/middleware/idempotency.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Response } from \"../response\";\nimport { t } from \"./inject-request-context\";\nimport {\n buildIdempotencyCacheKey,\n hashBody,\n isValidIdempotencyKey,\n} from \"./utils/idempotency-key\";\n\n/**\n * Options for the idempotency middleware.\n */\nexport type IdempotencyOptions = {\n /**\n * Cache TTL in seconds. Falls back to `http.idempotency.ttl`, then `86400` (24h).\n */\n ttl?: number;\n /**\n * Header name carrying the client's key. Falls back to\n * `http.idempotency.headerName`, then `\"Idempotency-Key\"`.\n */\n headerName?: string;\n /**\n * HTTP methods eligible for idempotency. Falls back to\n * `http.idempotency.methods`, then `[\"POST\",\"PUT\",\"PATCH\",\"DELETE\"]`.\n * Safe methods (GET/HEAD) are skipped regardless.\n */\n methods?: string[];\n /**\n * Cache driver name. Falls back to `http.idempotency.driver`, then the\n * default driver of the cache manager.\n */\n driver?: string;\n};\n\ntype CachedResponse = {\n status: number;\n body: unknown;\n bodyHash: string;\n contentType?: string;\n};\n\nconst DEFAULT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/**\n * Dedupe non-idempotent writes by an `Idempotency-Key` header — same key,\n * same body, within TTL → cached replay; same key, different body → 422\n * `IdempotencyKeyConflict`.\n *\n * **Must run after `authMiddleware`** — the cache key is scoped per-user\n * (`idem:{userType}:{userId}:{key}`) so user A can't replay user B's key.\n * Anonymous requests fall back to IP scope.\n *\n * The replay sets `Idempotent-Replay: true` on the response for easy\n * client-side / observability detection.\n *\n * Eligible methods default to POST/PUT/PATCH/DELETE. GET/HEAD pass through\n * even with the header set (RFC: safe methods are already idempotent).\n *\n * @example\n * import { authMiddleware } from \"@warlock.js/auth\";\n * import { middleware } from \"@warlock.js/core\";\n *\n * router.post(\"/orders\", createOrderController, {\n * middleware: [authMiddleware(\"client\"), middleware.idempotency()],\n * });\n *\n * router.post(\"/ai/summarize\", summarizeController, {\n * middleware: [\n * authMiddleware(\"client\"),\n * middleware.idempotency({ ttl: 60 * 60 }), // 1h is enough for client retries\n * ],\n * });\n */\nexport function idempotencyMiddleware(options: IdempotencyOptions = {}): Middleware {\n return async (request, response) => {\n const headerName =\n options.headerName ||\n config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods =\n options.methods || config.get(\"http.idempotency.methods\", DEFAULT_METHODS);\n const ttl = options.ttl || config.get(\"http.idempotency.ttl\", 86400);\n const driverName = options.driver || config.get(\"http.idempotency.driver\");\n\n if (!methods.includes(request.method.toUpperCase())) return;\n\n const idempotencyKey = request.header(headerName.toLowerCase());\n\n if (!idempotencyKey) return;\n\n if (!isValidIdempotencyKey(idempotencyKey)) {\n return response.badRequest({\n error: t(\"http.idempotencyKeyInvalid\"),\n errorCode: HttpErrorCodes.IdempotencyKeyInvalid,\n });\n }\n\n const cacheDriver = driverName ? await cache.use(driverName) : cache;\n const cacheKey = buildIdempotencyCacheKey(request, idempotencyKey);\n const bodyHash = hashBody(request.body);\n\n const cached = (await cacheDriver.get(cacheKey)) as CachedResponse | null;\n\n if (cached) {\n if (cached.bodyHash !== bodyHash) {\n return response.unprocessableEntity({\n error: t(\"http.idempotencyKeyConflict\"),\n errorCode: HttpErrorCodes.IdempotencyKeyConflict,\n });\n }\n\n response.header(\"Idempotent-Replay\", \"true\");\n\n return response.replay({\n status: cached.status,\n body: cached.body,\n contentType: cached.contentType,\n });\n }\n\n response.onSent((sentResponse: Response) => {\n // Don't cache server errors — clients should be able to retry past a 5xx.\n // 4xx are deterministic outcomes of the request, so caching is fine.\n if (sentResponse.statusCode >= 500) return;\n\n const sentContentType = sentResponse.contentType;\n\n // `set` is fire-and-forget inside `onSent`; without a `.catch` a rejected\n // write (e.g. Redis down) would surface as an unhandledRejection.\n cacheDriver\n .set(\n cacheKey,\n {\n status: sentResponse.statusCode,\n body: sentResponse.parsedBody,\n bodyHash,\n contentType: typeof sentContentType === \"string\" ? sentContentType : undefined,\n },\n ttl,\n )\n .catch((error: unknown) => {\n log.error(\"idempotency-middleware\", \"set\", error);\n });\n });\n };\n}\n"],"mappings":";;;;;;;;AA8CA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,SAAS,aAAa;EAClC,MAAM,aACJ,QAAQ,cACR,OAAO,IAAI,+BAA+B,iBAAiB;EAC7D,MAAM,UACJ,QAAQ,WAAW,OAAO,IAAI,4BAA4B,eAAe;EAC3E,MAAM,MAAM,QAAQ,OAAO,OAAO,IAAI,wBAAwB,KAAK;EACnE,MAAM,aAAa,QAAQ,UAAU,OAAO,IAAI,yBAAyB;EAEzE,IAAI,CAAC,QAAQ,SAAS,QAAQ,OAAO,YAAY,CAAC,GAAG;EAErD,MAAM,iBAAiB,QAAQ,OAAO,WAAW,YAAY,CAAC;EAE9D,IAAI,CAAC,gBAAgB;EAErB,IAAI,CAAC,sBAAsB,cAAc,GACvC,OAAO,SAAS,WAAW;GACzB,OAAO,EAAE,4BAA4B;GACrC;EACF,CAAC;EAGH,MAAM,cAAc,aAAa,MAAM,MAAM,IAAI,UAAU,IAAI;EAC/D,MAAM,WAAW,yBAAyB,SAAS,cAAc;EACjE,MAAM,WAAW,SAAS,QAAQ,IAAI;EAEtC,MAAM,SAAU,MAAM,YAAY,IAAI,QAAQ;EAE9C,IAAI,QAAQ;GACV,IAAI,OAAO,aAAa,UACtB,OAAO,SAAS,oBAAoB;IAClC,OAAO,EAAE,6BAA6B;IACtC;GACF,CAAC;GAGH,SAAS,OAAO,qBAAqB,MAAM;GAE3C,OAAO,SAAS,OAAO;IACrB,QAAQ,OAAO;IACf,MAAM,OAAO;IACb,aAAa,OAAO;GACtB,CAAC;EACH;EAEA,SAAS,QAAQ,iBAA2B;GAG1C,IAAI,aAAa,cAAc,KAAK;GAEpC,MAAM,kBAAkB,aAAa;GAIrC,YACG,IACC,UACA;IACE,QAAQ,aAAa;IACrB,MAAM,aAAa;IACnB;IACA,aAAa,OAAO,oBAAoB,WAAW,kBAAkB;GACvE,GACA,GACF,CAAC,CACA,OAAO,UAAmB;IACzB,IAAI,MAAM,0BAA0B,OAAO,KAAK;GAClD,CAAC;EACL,CAAC;CACH;AACF"}
@@ -2,7 +2,7 @@ import { environment } from "../../utils/environment.mjs";
2
2
  import { requestContext, useRequestStore } from "../context/request-context.mjs";
3
3
  import "../../utils/index.mjs";
4
4
  import { BadRequestError, ForbiddenError, HttpError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "../errors/errors.mjs";
5
- import baseConfig from "@mongez/config";
5
+ import config from "@mongez/config";
6
6
  import { trans } from "@mongez/localization";
7
7
  import { DatabaseWriterValidationError } from "@warlock.js/cascade";
8
8
  import { contextManager } from "@warlock.js/context";
@@ -22,7 +22,7 @@ import { contextManager } from "@warlock.js/context";
22
22
  * Skip when `http.requestId.enabled` is explicitly false.
23
23
  */
24
24
  function stampRequestIdHeader(request, response) {
25
- const requestIdConfig = baseConfig.get("http.requestId", {});
25
+ const requestIdConfig = config.get("http.requestId", {});
26
26
  if (requestIdConfig.enabled === false) return;
27
27
  const headerName = requestIdConfig.header || "X-Request-Id";
28
28
  response.header(headerName, request.id);
@@ -1 +1 @@
1
- {"version":3,"file":"inject-request-context.mjs","names":["config","requestContextInstance"],"sources":["../../../../../../../../@warlock.js/core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\r\n * Request Context Middleware\r\n *\r\n * Creates a unified context for each request using the ContextManager.\r\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\r\n */\r\nimport { trans } from \"@mongez/localization\";\r\nimport { GenericObject } from \"@mongez/reinforcements\";\r\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\r\nimport { contextManager } from \"@warlock.js/context\";\r\nimport config from \"@mongez/config\";\r\nimport { environment } from \"../../utils\";\r\nimport {\r\n requestContext as requestContextInstance,\r\n useRequestStore,\r\n} from \"../context/request-context\";\r\nimport {\r\n BadRequestError,\r\n ForbiddenError,\r\n HttpError,\r\n ResourceNotFoundError,\r\n ServerError,\r\n UnAuthorizedError,\r\n} from \"../errors\";\r\nimport { type Request } from \"../request\";\r\nimport { type Response } from \"../response\";\r\nimport { type ReturnedResponse } from \"./../types\";\r\n\r\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\r\n\r\n/**\r\n * Echo `request.id` back as a response header so the FE / proxies / log\r\n * aggregators can correlate by the same value the server logs against.\r\n *\r\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\r\n * Skip when `http.requestId.enabled` is explicitly false.\r\n */\r\nfunction stampRequestIdHeader(request: Request, response: Response) {\r\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\r\n\r\n if (requestIdConfig.enabled === false) return;\r\n\r\n const headerName = requestIdConfig.header || \"X-Request-Id\";\r\n\r\n response.header(headerName, request.id);\r\n}\r\n\r\n/**\r\n * Create request store and execute middleware + handler\r\n *\r\n * Runs all registered contexts together using ContextManager.\r\n */\r\nexport function createRequestStore(\r\n request: Request<any>,\r\n response: Response,\r\n): Promise<ReturnedResponse> {\r\n stampRequestIdHeader(request, response);\r\n\r\n // Build all context stores using the immutable API\r\n // Each context defines its own store initialization via buildStore()\r\n const httpContextStore = contextManager.buildStores({ request, response });\r\n\r\n // Run all contexts together!\r\n return contextManager.runAll(httpContextStore, async () => {\r\n try {\r\n // Run middleware chain\r\n const result = await request.runMiddleware();\r\n\r\n if (result) {\r\n return result as ReturnedResponse;\r\n }\r\n\r\n // Execute route handler\r\n request.trigger(\"executingAction\", request.route);\r\n\r\n const handler = request.getHandler();\r\n\r\n request.log(\"Executing Handler\", \"info\");\r\n\r\n const output = await handler(request, response);\r\n\r\n request.log(\"Handler Executed Successfully\", \"success\");\r\n\r\n request.trigger(\"executedAction\", request.route);\r\n\r\n return output as ReturnedResponse;\r\n } catch (error: any) {\r\n request.log(`${error.constructor.name}: Request failed: ${error.message}`, \"error\");\r\n return handleRequestError(error, response);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Handle request errors\r\n * @internal\r\n */\r\nfunction handleRequestError(error: any, response: Response): ReturnedResponse {\r\n if (error instanceof HttpError) {\r\n const payload: GenericObject = {\r\n error: error.message,\r\n };\r\n if (error.payload) {\r\n payload.payload = error.payload;\r\n }\r\n\r\n if (environment() === \"development\") {\r\n payload.stack = error.stack;\r\n }\r\n\r\n return response.setStatusCode(error.status).send(payload);\r\n }\r\n\r\n if (error instanceof ResourceNotFoundError) {\r\n return response.notFound({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof UnAuthorizedError) {\r\n return response.unauthorized({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof ForbiddenError) {\r\n return response.forbidden({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof BadRequestError) {\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof DatabaseWriterValidationError) {\r\n return response.badRequest({\r\n errors: error.errors,\r\n });\r\n }\r\n\r\n if (error instanceof ServerError) {\r\n return response.serverError({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n console.log(error);\r\n\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n}\r\n\r\n/**\r\n * Translate a keyword (uses request context for locale)\r\n */\r\nexport function t(keyword: string, placeholders?: any) {\r\n return (\r\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\r\n trans(keyword, placeholders)\r\n );\r\n}\r\n\r\n/**\r\n * Get or compute a value from the request cache\r\n *\r\n * If the value exists in request, return it.\r\n * Otherwise, execute callback, store result in request, and return it.\r\n */\r\nexport async function fromRequest<T>(\r\n key: string,\r\n callback: (request?: Request) => Promise<T>,\r\n): Promise<T> {\r\n const { request } = useRequestStore();\r\n\r\n if (!request) return await callback();\r\n\r\n if (request[key]) return request[key];\r\n\r\n request[key] = await callback(request);\r\n\r\n return request[key];\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkBA,WAAO,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;CAItC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAEF,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,SAAS,MAAM,QAAQ,SAAS,QAAQ;GAE9C,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAY;GACnB,QAAQ,IAAI,GAAG,MAAM,YAAY,KAAK,oBAAoB,MAAM,WAAW,OAAO;GAClF,OAAO,mBAAmB,OAAO,QAAQ;EAC3C;CACF,CAAC;AACH;;;;;AAMA,SAAS,mBAAmB,OAAY,UAAsC;CAC5E,IAAI,iBAAiB,WAAW;EAC9B,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,uBACnB,OAAO,SAAS,SAAS;EACvB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,mBACnB,OAAO,SAAS,aAAa;EAC3B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,gBACnB,OAAO,SAAS,UAAU;EACxB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,iBACnB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAGH,IAAI,iBAAiB,aACnB,OAAO,SAAS,YAAY;EAC1B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,QAAQ,IAAI,KAAK;CAEjB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEC,eAAuB,WAAW,CAAC,EAAE,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B;;;;;;;AAQA,eAAsB,YACpB,KACA,UACY;CACZ,MAAM,EAAE,YAAY,gBAAgB;CAEpC,IAAI,CAAC,SAAS,OAAO,MAAM,SAAS;CAEpC,IAAI,QAAQ,MAAM,OAAO,QAAQ;CAEjC,QAAQ,OAAO,MAAM,SAAS,OAAO;CAErC,OAAO,QAAQ;AACjB"}
1
+ {"version":3,"file":"inject-request-context.mjs","names":["requestContextInstance"],"sources":["../../../../../../../../@warlock.js/core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\r\n * Request Context Middleware\r\n *\r\n * Creates a unified context for each request using the ContextManager.\r\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\r\n */\r\nimport { trans } from \"@mongez/localization\";\r\nimport { GenericObject } from \"@mongez/reinforcements\";\r\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\r\nimport { contextManager } from \"@warlock.js/context\";\r\nimport config from \"@mongez/config\";\r\nimport { environment } from \"../../utils\";\r\nimport {\r\n requestContext as requestContextInstance,\r\n useRequestStore,\r\n} from \"../context/request-context\";\r\nimport {\r\n BadRequestError,\r\n ForbiddenError,\r\n HttpError,\r\n ResourceNotFoundError,\r\n ServerError,\r\n UnAuthorizedError,\r\n} from \"../errors\";\r\nimport { type Request } from \"../request\";\r\nimport { type Response } from \"../response\";\r\nimport { type ReturnedResponse } from \"./../types\";\r\n\r\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\r\n\r\n/**\r\n * Echo `request.id` back as a response header so the FE / proxies / log\r\n * aggregators can correlate by the same value the server logs against.\r\n *\r\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\r\n * Skip when `http.requestId.enabled` is explicitly false.\r\n */\r\nfunction stampRequestIdHeader(request: Request, response: Response) {\r\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\r\n\r\n if (requestIdConfig.enabled === false) return;\r\n\r\n const headerName = requestIdConfig.header || \"X-Request-Id\";\r\n\r\n response.header(headerName, request.id);\r\n}\r\n\r\n/**\r\n * Create request store and execute middleware + handler\r\n *\r\n * Runs all registered contexts together using ContextManager.\r\n */\r\nexport function createRequestStore(\r\n request: Request<any>,\r\n response: Response,\r\n): Promise<ReturnedResponse> {\r\n stampRequestIdHeader(request, response);\r\n\r\n // Build all context stores using the immutable API\r\n // Each context defines its own store initialization via buildStore()\r\n const httpContextStore = contextManager.buildStores({ request, response });\r\n\r\n // Run all contexts together!\r\n return contextManager.runAll(httpContextStore, async () => {\r\n try {\r\n // Run middleware chain\r\n const result = await request.runMiddleware();\r\n\r\n if (result) {\r\n return result as ReturnedResponse;\r\n }\r\n\r\n // Execute route handler\r\n request.trigger(\"executingAction\", request.route);\r\n\r\n const handler = request.getHandler();\r\n\r\n request.log(\"Executing Handler\", \"info\");\r\n\r\n const output = await handler(request, response);\r\n\r\n request.log(\"Handler Executed Successfully\", \"success\");\r\n\r\n request.trigger(\"executedAction\", request.route);\r\n\r\n return output as ReturnedResponse;\r\n } catch (error: any) {\r\n request.log(`${error.constructor.name}: Request failed: ${error.message}`, \"error\");\r\n return handleRequestError(error, response);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Handle request errors\r\n * @internal\r\n */\r\nfunction handleRequestError(error: any, response: Response): ReturnedResponse {\r\n if (error instanceof HttpError) {\r\n const payload: GenericObject = {\r\n error: error.message,\r\n };\r\n if (error.payload) {\r\n payload.payload = error.payload;\r\n }\r\n\r\n if (environment() === \"development\") {\r\n payload.stack = error.stack;\r\n }\r\n\r\n return response.setStatusCode(error.status).send(payload);\r\n }\r\n\r\n if (error instanceof ResourceNotFoundError) {\r\n return response.notFound({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof UnAuthorizedError) {\r\n return response.unauthorized({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof ForbiddenError) {\r\n return response.forbidden({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof BadRequestError) {\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof DatabaseWriterValidationError) {\r\n return response.badRequest({\r\n errors: error.errors,\r\n });\r\n }\r\n\r\n if (error instanceof ServerError) {\r\n return response.serverError({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n console.log(error);\r\n\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n}\r\n\r\n/**\r\n * Translate a keyword (uses request context for locale)\r\n */\r\nexport function t(keyword: string, placeholders?: any) {\r\n return (\r\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\r\n trans(keyword, placeholders)\r\n );\r\n}\r\n\r\n/**\r\n * Get or compute a value from the request cache\r\n *\r\n * If the value exists in request, return it.\r\n * Otherwise, execute callback, store result in request, and return it.\r\n */\r\nexport async function fromRequest<T>(\r\n key: string,\r\n callback: (request?: Request) => Promise<T>,\r\n): Promise<T> {\r\n const { request } = useRequestStore();\r\n\r\n if (!request) return await callback();\r\n\r\n if (request[key]) return request[key];\r\n\r\n request[key] = await callback(request);\r\n\r\n return request[key];\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqCA,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;CAItC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAEF,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,SAAS,MAAM,QAAQ,SAAS,QAAQ;GAE9C,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAY;GACnB,QAAQ,IAAI,GAAG,MAAM,YAAY,KAAK,oBAAoB,MAAM,WAAW,OAAO;GAClF,OAAO,mBAAmB,OAAO,QAAQ;EAC3C;CACF,CAAC;AACH;;;;;AAMA,SAAS,mBAAmB,OAAY,UAAsC;CAC5E,IAAI,iBAAiB,WAAW;EAC9B,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,uBACnB,OAAO,SAAS,SAAS;EACvB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,mBACnB,OAAO,SAAS,aAAa;EAC3B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,gBACnB,OAAO,SAAS,UAAU;EACxB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,iBACnB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAGH,IAAI,iBAAiB,aACnB,OAAO,SAAS,YAAY;EAC1B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,QAAQ,IAAI,KAAK;CAEjB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEA,eAAuB,WAAW,CAAC,EAAE,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B;;;;;;;AAQA,eAAsB,YACpB,KACA,UACY;CACZ,MAAM,EAAE,YAAY,gBAAgB;CAEpC,IAAI,CAAC,SAAS,OAAO,MAAM,SAAS;CAEpC,IAAI,QAAQ,MAAM,OAAO,QAAQ;CAEjC,QAAQ,OAAO,MAAM,SAAS,OAAO;CAErC,OAAO,QAAQ;AACjB"}
@@ -1,6 +1,6 @@
1
1
  import { t } from "./inject-request-context.mjs";
2
2
  import { HttpErrorCodes } from "../error-codes.mjs";
3
- import baseConfig from "@mongez/config";
3
+ import config from "@mongez/config";
4
4
 
5
5
  //#region ../@warlock.js/core/src/http/middleware/maintenance.middleware.ts
6
6
  function isAllowlisted(path, patterns) {
@@ -31,11 +31,11 @@ function isAllowlisted(path, patterns) {
31
31
  */
32
32
  function maintenanceMiddleware(options = {}) {
33
33
  return (request, response) => {
34
- if (!baseConfig.get("http.maintenance.enabled", false)) return;
35
- const allowlist = options.allowlist || baseConfig.get("http.maintenance.allowlist", ["/health"]);
34
+ if (!config.get("http.maintenance.enabled", false)) return;
35
+ const allowlist = options.allowlist || config.get("http.maintenance.allowlist", ["/health"]);
36
36
  const pathname = request.path.split("?")[0];
37
37
  if (isAllowlisted(pathname, allowlist)) return;
38
- const retryAfter = options.retryAfter || baseConfig.get("http.maintenance.retryAfter", 60);
38
+ const retryAfter = options.retryAfter || config.get("http.maintenance.retryAfter", 60);
39
39
  response.header("Retry-After", retryAfter);
40
40
  return response.serviceUnavailable({
41
41
  error: options.errorMessage || t("http.maintenance"),
@@ -1 +1 @@
1
- {"version":3,"file":"maintenance.middleware.mjs","names":["config"],"sources":["../../../../../../../../@warlock.js/core/src/http/middleware/maintenance.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the maintenance middleware.\n */\nexport type MaintenanceOptions = {\n /**\n * Path prefixes (ending in `*`) or exact paths to bypass even when\n * maintenance is on. Falls back to `http.maintenance.allowlist`, then\n * `[\"/health\"]`.\n *\n * @example\n * allowlist: [\"/health\", \"/admin/*\", \"/webhooks/stripe\"]\n */\n allowlist?: string[];\n /**\n * Seconds advertised in the `Retry-After` header. Falls back to\n * `http.maintenance.retryAfter`, then `60`.\n */\n retryAfter?: number;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\nfunction isAllowlisted(path: string, patterns: string[]) {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return path.startsWith(pattern.slice(0, -1));\n }\n\n return path === pattern;\n });\n}\n\n/**\n * Return 503 + `Retry-After` for every request when `http.maintenance.enabled`\n * is true, except for paths matching the allowlist.\n *\n * Designed for app-wide registration via `http.middleware.all`. Toggled via\n * config — flipping the flag requires a restart (no runtime hot-flip yet).\n * Allowlist defaults to `[\"/health\"]` so health checks still pass during\n * planned downtime.\n *\n * @example\n * // src/config/http.ts\n * import { middleware } from \"@warlock.js/core\";\n *\n * export default {\n * maintenance: { enabled: env(\"MAINTENANCE_MODE\") === \"true\" },\n * middleware: {\n * all: [middleware.maintenance({ allowlist: [\"/health\", \"/admin/*\"] })],\n * },\n * };\n */\nexport function maintenanceMiddleware(options: MaintenanceOptions = {}): Middleware {\n return (request, response) => {\n const enabled = config.get(\"http.maintenance.enabled\", false);\n\n if (!enabled) return;\n\n const allowlist =\n options.allowlist || config.get(\"http.maintenance.allowlist\", [\"/health\"]);\n\n // `request.path` includes the query string, but allowlist entries are\n // path-only (\"/webhooks/stripe\"), so strip the query before matching —\n // otherwise \"/webhooks/stripe?sig=...\" never matches its exact entry.\n const pathname = request.path.split(\"?\")[0];\n\n if (isAllowlisted(pathname, allowlist)) return;\n\n const retryAfter =\n options.retryAfter || config.get(\"http.maintenance.retryAfter\", 60);\n\n response.header(\"Retry-After\", retryAfter);\n\n return response.serviceUnavailable({\n error: options.errorMessage || t(\"http.maintenance\"),\n errorCode: HttpErrorCodes.Maintenance,\n });\n };\n}\n"],"mappings":";;;;;AA6BA,SAAS,cAAc,MAAc,UAAoB;CACvD,OAAO,SAAS,MAAM,YAAY;EAChC,IAAI,QAAQ,SAAS,GAAG,GACtB,OAAO,KAAK,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAG7C,OAAO,SAAS;CAClB,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,QAAQ,SAAS,aAAa;EAG5B,IAAI,CAFYA,WAAO,IAAI,4BAA4B,KAE5C,GAAG;EAEd,MAAM,YACJ,QAAQ,aAAaA,WAAO,IAAI,8BAA8B,CAAC,SAAS,CAAC;EAK3E,MAAM,WAAW,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC;EAEzC,IAAI,cAAc,UAAU,SAAS,GAAG;EAExC,MAAM,aACJ,QAAQ,cAAcA,WAAO,IAAI,+BAA+B,EAAE;EAEpE,SAAS,OAAO,eAAe,UAAU;EAEzC,OAAO,SAAS,mBAAmB;GACjC,OAAO,QAAQ,gBAAgB,EAAE,kBAAkB;GACnD;EACF,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"maintenance.middleware.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/http/middleware/maintenance.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the maintenance middleware.\n */\nexport type MaintenanceOptions = {\n /**\n * Path prefixes (ending in `*`) or exact paths to bypass even when\n * maintenance is on. Falls back to `http.maintenance.allowlist`, then\n * `[\"/health\"]`.\n *\n * @example\n * allowlist: [\"/health\", \"/admin/*\", \"/webhooks/stripe\"]\n */\n allowlist?: string[];\n /**\n * Seconds advertised in the `Retry-After` header. Falls back to\n * `http.maintenance.retryAfter`, then `60`.\n */\n retryAfter?: number;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\nfunction isAllowlisted(path: string, patterns: string[]) {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return path.startsWith(pattern.slice(0, -1));\n }\n\n return path === pattern;\n });\n}\n\n/**\n * Return 503 + `Retry-After` for every request when `http.maintenance.enabled`\n * is true, except for paths matching the allowlist.\n *\n * Designed for app-wide registration via `http.middleware.all`. Toggled via\n * config — flipping the flag requires a restart (no runtime hot-flip yet).\n * Allowlist defaults to `[\"/health\"]` so health checks still pass during\n * planned downtime.\n *\n * @example\n * // src/config/http.ts\n * import { middleware } from \"@warlock.js/core\";\n *\n * export default {\n * maintenance: { enabled: env(\"MAINTENANCE_MODE\") === \"true\" },\n * middleware: {\n * all: [middleware.maintenance({ allowlist: [\"/health\", \"/admin/*\"] })],\n * },\n * };\n */\nexport function maintenanceMiddleware(options: MaintenanceOptions = {}): Middleware {\n return (request, response) => {\n const enabled = config.get(\"http.maintenance.enabled\", false);\n\n if (!enabled) return;\n\n const allowlist =\n options.allowlist || config.get(\"http.maintenance.allowlist\", [\"/health\"]);\n\n // `request.path` includes the query string, but allowlist entries are\n // path-only (\"/webhooks/stripe\"), so strip the query before matching —\n // otherwise \"/webhooks/stripe?sig=...\" never matches its exact entry.\n const pathname = request.path.split(\"?\")[0];\n\n if (isAllowlisted(pathname, allowlist)) return;\n\n const retryAfter =\n options.retryAfter || config.get(\"http.maintenance.retryAfter\", 60);\n\n response.header(\"Retry-After\", retryAfter);\n\n return response.serviceUnavailable({\n error: options.errorMessage || t(\"http.maintenance\"),\n errorCode: HttpErrorCodes.Maintenance,\n });\n };\n}\n"],"mappings":";;;;;AA6BA,SAAS,cAAc,MAAc,UAAoB;CACvD,OAAO,SAAS,MAAM,YAAY;EAChC,IAAI,QAAQ,SAAS,GAAG,GACtB,OAAO,KAAK,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAG7C,OAAO,SAAS;CAClB,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,QAAQ,SAAS,aAAa;EAG5B,IAAI,CAFY,OAAO,IAAI,4BAA4B,KAE5C,GAAG;EAEd,MAAM,YACJ,QAAQ,aAAa,OAAO,IAAI,8BAA8B,CAAC,SAAS,CAAC;EAK3E,MAAM,WAAW,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC;EAEzC,IAAI,cAAc,UAAU,SAAS,GAAG;EAExC,MAAM,aACJ,QAAQ,cAAc,OAAO,IAAI,+BAA+B,EAAE;EAEpE,SAAS,OAAO,eAAe,UAAU;EAEzC,OAAO,SAAS,mBAAmB;GACjC,OAAO,QAAQ,gBAAgB,EAAE,kBAAkB;GACnD;EACF,CAAC;CACH;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { rootPath } from "../utils/paths.mjs";
2
2
  import "../utils/index.mjs";
3
- import baseConfig from "@mongez/config";
3
+ import config from "@mongez/config";
4
4
  import fastifyMultipart from "@fastify/multipart";
5
5
 
6
6
  //#region ../@warlock.js/core/src/http/plugins.ts
@@ -10,25 +10,25 @@ const defaultCorsOptions = {
10
10
  };
11
11
  async function registerHttpPlugins(server) {
12
12
  server.register(import("@fastify/rate-limit"), {
13
- max: baseConfig.get("http.rateLimit.max", 60),
14
- timeWindow: baseConfig.get("http.rateLimit.duration", 60 * 1e3)
13
+ max: config.get("http.rateLimit.max", 60),
14
+ timeWindow: config.get("http.rateLimit.duration", 60 * 1e3)
15
15
  });
16
16
  const corsOptions = {
17
- ...baseConfig.get("http.cors", {}),
17
+ ...config.get("http.cors", {}),
18
18
  ...defaultCorsOptions
19
19
  };
20
20
  server.register(import("@fastify/cors"), corsOptions);
21
21
  server.register(fastifyMultipart, {
22
22
  attachFieldsToBody: true,
23
- limits: { fileSize: baseConfig.get("http.fileUploadLimit", 10 * 1024 * 1024) }
23
+ limits: { fileSize: config.get("http.fileUploadLimit", 10 * 1024 * 1024) }
24
24
  });
25
25
  server.register(import("@fastify/static"), {
26
- root: baseConfig.get("storage.publicRoot", rootPath("public")),
27
- prefix: baseConfig.get("storage.publicPrefix", "/public/")
26
+ root: config.get("storage.publicRoot", rootPath("public")),
27
+ prefix: config.get("storage.publicPrefix", "/public/")
28
28
  });
29
29
  server.register(import("@fastify/cookie"), {
30
- secret: baseConfig.get("http.cookies.secret"),
31
- parseOptions: baseConfig.get("http.cookies.options", {})
30
+ secret: config.get("http.cookies.secret"),
31
+ parseOptions: config.get("http.cookies.options", {})
32
32
  });
33
33
  }
34
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.mjs","names":["config"],"sources":["../../../../../../../@warlock.js/core/src/http/plugins.ts"],"sourcesContent":["import type { FastifyCorsOptions } from \"@fastify/cors\";\nimport fastifyMultipart from \"@fastify/multipart\";\nimport config from \"@mongez/config\";\nimport { rootPath } from \"../utils\";\nimport type { FastifyInstance } from \"./server\";\n\nconst defaultCorsOptions: FastifyCorsOptions = {\n origin: \"*\",\n methods: \"*\",\n};\n\nexport async function registerHttpPlugins(server: FastifyInstance) {\n // 👇🏻 register rate-limit plugin\n server.register(import(\"@fastify/rate-limit\"), {\n // max requests per time window\n max: config.get(\"http.rateLimit.max\", 60),\n // maximum time that is will allow max requests\n timeWindow: config.get(\"http.rateLimit.duration\", 60 * 1000),\n });\n\n // 👇🏻 register cors plugin\n const corsOptions: FastifyCorsOptions | undefined = {\n ...config.get(\"http.cors\", {}),\n ...defaultCorsOptions,\n };\n\n server.register(import(\"@fastify/cors\"), corsOptions);\n\n // 👇🏻 import multipart plugin\n server.register(fastifyMultipart, {\n attachFieldsToBody: true,\n limits: {\n // file size could be up to 10MB\n fileSize: config.get(\"http.fileUploadLimit\", 10 * 1024 * 1024),\n },\n });\n\n server.register(import(\"@fastify/static\"), {\n root: config.get(\"storage.publicRoot\", rootPath(\"public\")),\n prefix: config.get(\"storage.publicPrefix\", \"/public/\"),\n });\n\n // 👇🏻 register cookie plugin\n server.register(import(\"@fastify/cookie\"), {\n secret: config.get(\"http.cookies.secret\"), // Optional: allow signed cookies\n parseOptions: config.get(\"http.cookies.options\", {}),\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,qBAAyC;CAC7C,QAAQ;CACR,SAAS;AACX;AAEA,eAAsB,oBAAoB,QAAyB;CAEjE,OAAO,SAAS,OAAO,wBAAwB;EAE7C,KAAKA,WAAO,IAAI,sBAAsB,EAAE;EAExC,YAAYA,WAAO,IAAI,2BAA2B,KAAK,GAAI;CAC7D,CAAC;CAGD,MAAM,cAA8C;EAClD,GAAGA,WAAO,IAAI,aAAa,CAAC,CAAC;EAC7B,GAAG;CACL;CAEA,OAAO,SAAS,OAAO,kBAAkB,WAAW;CAGpD,OAAO,SAAS,kBAAkB;EAChC,oBAAoB;EACpB,QAAQ,EAEN,UAAUA,WAAO,IAAI,wBAAwB,KAAK,OAAO,IAAI,EAC/D;CACF,CAAC;CAED,OAAO,SAAS,OAAO,oBAAoB;EACzC,MAAMA,WAAO,IAAI,sBAAsB,SAAS,QAAQ,CAAC;EACzD,QAAQA,WAAO,IAAI,wBAAwB,UAAU;CACvD,CAAC;CAGD,OAAO,SAAS,OAAO,oBAAoB;EACzC,QAAQA,WAAO,IAAI,qBAAqB;EACxC,cAAcA,WAAO,IAAI,wBAAwB,CAAC,CAAC;CACrD,CAAC;AACH"}
1
+ {"version":3,"file":"plugins.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/plugins.ts"],"sourcesContent":["import type { FastifyCorsOptions } from \"@fastify/cors\";\nimport fastifyMultipart from \"@fastify/multipart\";\nimport config from \"@mongez/config\";\nimport { rootPath } from \"../utils\";\nimport type { FastifyInstance } from \"./server\";\n\nconst defaultCorsOptions: FastifyCorsOptions = {\n origin: \"*\",\n methods: \"*\",\n};\n\nexport async function registerHttpPlugins(server: FastifyInstance) {\n // 👇🏻 register rate-limit plugin\n server.register(import(\"@fastify/rate-limit\"), {\n // max requests per time window\n max: config.get(\"http.rateLimit.max\", 60),\n // maximum time that is will allow max requests\n timeWindow: config.get(\"http.rateLimit.duration\", 60 * 1000),\n });\n\n // 👇🏻 register cors plugin\n const corsOptions: FastifyCorsOptions | undefined = {\n ...config.get(\"http.cors\", {}),\n ...defaultCorsOptions,\n };\n\n server.register(import(\"@fastify/cors\"), corsOptions);\n\n // 👇🏻 import multipart plugin\n server.register(fastifyMultipart, {\n attachFieldsToBody: true,\n limits: {\n // file size could be up to 10MB\n fileSize: config.get(\"http.fileUploadLimit\", 10 * 1024 * 1024),\n },\n });\n\n server.register(import(\"@fastify/static\"), {\n root: config.get(\"storage.publicRoot\", rootPath(\"public\")),\n prefix: config.get(\"storage.publicPrefix\", \"/public/\"),\n });\n\n // 👇🏻 register cookie plugin\n server.register(import(\"@fastify/cookie\"), {\n secret: config.get(\"http.cookies.secret\"), // Optional: allow signed cookies\n parseOptions: config.get(\"http.cookies.options\", {}),\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,qBAAyC;CAC7C,QAAQ;CACR,SAAS;AACX;AAEA,eAAsB,oBAAoB,QAAyB;CAEjE,OAAO,SAAS,OAAO,wBAAwB;EAE7C,KAAK,OAAO,IAAI,sBAAsB,EAAE;EAExC,YAAY,OAAO,IAAI,2BAA2B,KAAK,GAAI;CAC7D,CAAC;CAGD,MAAM,cAA8C;EAClD,GAAG,OAAO,IAAI,aAAa,CAAC,CAAC;EAC7B,GAAG;CACL;CAEA,OAAO,SAAS,OAAO,kBAAkB,WAAW;CAGpD,OAAO,SAAS,kBAAkB;EAChC,oBAAoB;EACpB,QAAQ,EAEN,UAAU,OAAO,IAAI,wBAAwB,KAAK,OAAO,IAAI,EAC/D;CACF,CAAC;CAED,OAAO,SAAS,OAAO,oBAAoB;EACzC,MAAM,OAAO,IAAI,sBAAsB,SAAS,QAAQ,CAAC;EACzD,QAAQ,OAAO,IAAI,wBAAwB,UAAU;CACvD,CAAC;CAGD,OAAO,SAAS,OAAO,oBAAoB;EACzC,QAAQ,OAAO,IAAI,qBAAqB;EACxC,cAAc,OAAO,IAAI,wBAAwB,CAAC,CAAC;CACrD,CAAC;AACH"}
@@ -1,7 +1,7 @@
1
1
  import { StorageFile } from "../storage/storage-file.mjs";
2
2
  import "../storage/index.mjs";
3
3
  import { renderReact } from "../react/index.mjs";
4
- import baseConfig from "@mongez/config";
4
+ import config from "@mongez/config";
5
5
  import { log } from "@warlock.js/logger";
6
6
  import path from "path";
7
7
  import { isIterable, isPlainObject, isScalar } from "@mongez/supportive-is";
@@ -175,7 +175,7 @@ var Response = class Response {
175
175
  * Make a log message
176
176
  */
177
177
  log(message, level = "info") {
178
- if (!baseConfig.get("http.log")) return;
178
+ if (!config.get("http.log")) return;
179
179
  log.log({
180
180
  module: "response",
181
181
  action: this.route.method + " " + this.route.path.replace("/*", "") + `:${this.request.id}`,
@@ -561,7 +561,7 @@ var Response = class Response {
561
561
  */
562
562
  cookie(name, value, options = {}) {
563
563
  const { raw, ...cookieOptions } = options;
564
- const defaultOptions = baseConfig.get("http.cookies.options", {});
564
+ const defaultOptions = config.get("http.cookies.options", {});
565
565
  const serializedValue = raw ? String(value) : JSON.stringify(value);
566
566
  this.baseResponse.setCookie(name, serializedValue, {
567
567
  ...defaultOptions,
@@ -576,7 +576,7 @@ var Response = class Response {
576
576
  * response.clearCookie('token', { path: '/' });
577
577
  */
578
578
  clearCookie(name, options) {
579
- const defaultOptions = baseConfig.get("http.cookies.options", {});
579
+ const defaultOptions = config.get("http.cookies.options", {});
580
580
  this.baseResponse.clearCookie(name, {
581
581
  ...defaultOptions,
582
582
  ...options
@@ -849,7 +849,7 @@ var Response = class Response {
849
849
  * Mark the response as failed
850
850
  */
851
851
  failedSchema(result) {
852
- const { errors, inputKey, inputError, status } = baseConfig.get("validation.response", {
852
+ const { errors, inputKey, inputError, status } = config.get("validation.response", {
853
853
  errors: "errors",
854
854
  inputKey: "input",
855
855
  inputError: "error",