@warlock.js/core 5.19.1 → 5.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/esm/connectors/cache-connector.d.mts.map +1 -1
  3. package/esm/connectors/cache-connector.mjs +5 -0
  4. package/esm/connectors/cache-connector.mjs.map +1 -1
  5. package/esm/connectors/single-server-warnings.mjs +33 -0
  6. package/esm/connectors/single-server-warnings.mjs.map +1 -0
  7. package/esm/connectors/socket-connector.d.mts.map +1 -1
  8. package/esm/connectors/socket-connector.mjs +4 -0
  9. package/esm/connectors/socket-connector.mjs.map +1 -1
  10. package/esm/connectors/storage.connector.d.mts.map +1 -1
  11. package/esm/connectors/storage.connector.mjs +6 -0
  12. package/esm/connectors/storage.connector.mjs.map +1 -1
  13. package/esm/connectors/types.d.mts +3 -1
  14. package/esm/connectors/types.d.mts.map +1 -1
  15. package/esm/connectors/types.mjs.map +1 -1
  16. package/esm/dev-server/development-server.mjs +4 -0
  17. package/esm/dev-server/development-server.mjs.map +1 -1
  18. package/esm/dev-server/route-types-publisher.mjs +60 -0
  19. package/esm/dev-server/route-types-publisher.mjs.map +1 -0
  20. package/esm/http/build-rate-limit-options.d.mts +37 -0
  21. package/esm/http/build-rate-limit-options.d.mts.map +1 -0
  22. package/esm/http/build-rate-limit-options.mjs +37 -0
  23. package/esm/http/build-rate-limit-options.mjs.map +1 -0
  24. package/esm/http/middleware/idempotency.middleware.d.mts +5 -0
  25. package/esm/http/middleware/idempotency.middleware.d.mts.map +1 -1
  26. package/esm/http/middleware/idempotency.middleware.mjs +35 -3
  27. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  28. package/esm/http/middleware/rate-limit.middleware.d.mts +4 -3
  29. package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
  30. package/esm/http/middleware/rate-limit.middleware.mjs +4 -3
  31. package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
  32. package/esm/http/plugins.d.mts.map +1 -1
  33. package/esm/http/plugins.mjs +3 -4
  34. package/esm/http/plugins.mjs.map +1 -1
  35. package/esm/http/types.d.mts +2 -14
  36. package/esm/http/types.d.mts.map +1 -1
  37. package/esm/index.d.mts +3 -2
  38. package/esm/index.mjs +3 -2
  39. package/esm/production/assert-generated-imports.mjs +1 -0
  40. package/esm/production/assert-generated-imports.mjs.map +1 -1
  41. package/esm/production/production-builder.mjs +17 -1
  42. package/esm/production/production-builder.mjs.map +1 -1
  43. package/esm/production/route-registration-child.d.mts +1 -0
  44. package/esm/production/route-registration-child.mjs +65 -0
  45. package/esm/production/route-registration-child.mjs.map +1 -0
  46. package/esm/production/route-registration-snapshot.mjs +166 -0
  47. package/esm/production/route-registration-snapshot.mjs.map +1 -0
  48. package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
  49. package/esm/repositories/adapters/cascade/cascade-adapter.mjs +5 -7
  50. package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
  51. package/esm/socket/index.d.mts +1 -1
  52. package/esm/socket/types.d.mts +16 -1
  53. package/esm/socket/types.d.mts.map +1 -1
  54. package/esm/storage/drivers/cloud-driver.d.mts +9 -0
  55. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  56. package/esm/storage/drivers/cloud-driver.mjs +48 -0
  57. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  58. package/esm/storage/drivers/do-spaces-driver.d.mts +7 -0
  59. package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
  60. package/esm/storage/drivers/do-spaces-driver.mjs +9 -2
  61. package/esm/storage/drivers/do-spaces-driver.mjs.map +1 -1
  62. package/esm/storage/drivers/local-driver.d.mts +9 -0
  63. package/esm/storage/drivers/local-driver.d.mts.map +1 -1
  64. package/esm/storage/drivers/local-driver.mjs +41 -3
  65. package/esm/storage/drivers/local-driver.mjs.map +1 -1
  66. package/esm/storage/index.d.mts +1 -0
  67. package/esm/storage/index.mjs +2 -1
  68. package/esm/storage/scoped-storage.d.mts +14 -0
  69. package/esm/storage/scoped-storage.d.mts.map +1 -1
  70. package/esm/storage/scoped-storage.mjs +22 -0
  71. package/esm/storage/scoped-storage.mjs.map +1 -1
  72. package/esm/storage/storage.d.mts +11 -0
  73. package/esm/storage/storage.d.mts.map +1 -1
  74. package/esm/storage/storage.mjs +32 -0
  75. package/esm/storage/storage.mjs.map +1 -1
  76. package/esm/storage/types.d.mts +21 -0
  77. package/esm/storage/types.d.mts.map +1 -1
  78. package/esm/storage/utils/storage-capability-error.d.mts +25 -0
  79. package/esm/storage/utils/storage-capability-error.d.mts.map +1 -0
  80. package/esm/storage/utils/storage-capability-error.mjs +26 -0
  81. package/esm/storage/utils/storage-capability-error.mjs.map +1 -0
  82. package/llms-full.txt +71 -42
  83. package/llms.txt +41 -41
  84. package/package.json +21 -15
  85. package/skills/add-connector/SKILL.md +5 -1
  86. package/skills/benchmark-code/SKILL.md +1 -1
  87. package/skills/build-restful/SKILL.md +1 -1
  88. package/skills/build-url/SKILL.md +1 -1
  89. package/skills/configure-app/SKILL.md +7 -1
  90. package/skills/create-controller/SKILL.md +1 -1
  91. package/skills/create-module/SKILL.md +1 -1
  92. package/skills/define-resource/SKILL.md +1 -1
  93. package/skills/encrypt-data/SKILL.md +1 -1
  94. package/skills/hash-password/SKILL.md +1 -1
  95. package/skills/health-checks/SKILL.md +1 -1
  96. package/skills/index.md +30 -0
  97. package/skills/lower-stage3-decorators/SKILL.md +1 -1
  98. package/skills/process-image/SKILL.md +1 -1
  99. package/skills/register-route/SKILL.md +1 -1
  100. package/skills/request-memo/SKILL.md +1 -1
  101. package/skills/request-tracing/SKILL.md +1 -1
  102. package/skills/resolve-path/SKILL.md +1 -1
  103. package/skills/retry-operation/SKILL.md +1 -1
  104. package/skills/run-app/SKILL.md +1 -1
  105. package/skills/send-mail/SKILL.md +1 -1
  106. package/skills/send-response/SKILL.md +1 -1
  107. package/skills/store-file/SKILL.md +11 -1
  108. package/skills/test-http/SKILL.md +1 -1
  109. package/skills/test-service/SKILL.md +1 -1
  110. package/skills/update-packages/SKILL.md +1 -1
  111. package/skills/upload-file/SKILL.md +1 -1
  112. package/skills/use-app-context/SKILL.md +1 -1
  113. package/skills/use-localization/SKILL.md +1 -1
  114. package/skills/use-middleware/SKILL.md +7 -2
  115. package/skills/use-model-transformers/SKILL.md +1 -1
  116. package/skills/use-repository/SKILL.md +1 -1
  117. package/skills/use-request-locals/SKILL.md +1 -1
  118. package/skills/validate-input/SKILL.md +1 -1
  119. package/skills/warlock-conventions/SKILL.md +1 -1
  120. package/skills/warlock-doctor/SKILL.md +1 -1
  121. package/skills/warlock-routes/SKILL.md +1 -1
  122. package/skills/wire-socket/SKILL.md +5 -1
  123. package/skills/write-cli-command/SKILL.md +1 -1
  124. package/skills/write-middleware/SKILL.md +1 -1
  125. package/skills/write-seeder/SKILL.md +1 -1
  126. package/skills/write-use-case/SKILL.md +1 -1
@@ -47,6 +47,7 @@ function idempotencyMiddleware(options = {}) {
47
47
  const headerName = options.headerName || config.get("http.idempotency.headerName", "Idempotency-Key");
48
48
  const methods = options.methods || config.get("http.idempotency.methods", DEFAULT_METHODS);
49
49
  const ttl = options.ttl || config.get("http.idempotency.ttl", 86400);
50
+ const reservationTtl = options.reservationTtl || config.get("http.idempotency.reservationTtl", 60);
50
51
  const driverName = options.driver || config.get("http.idempotency.driver");
51
52
  if (!methods.includes(request.method.toUpperCase())) return;
52
53
  const idempotencyKey = request.header(headerName.toLowerCase());
@@ -58,8 +59,15 @@ function idempotencyMiddleware(options = {}) {
58
59
  const cacheDriver = driverName ? await cache.use(driverName) : cache;
59
60
  const cacheKey = buildIdempotencyCacheKey(request, idempotencyKey);
60
61
  const bodyHash = hashBody(request.body);
61
- const cached = await cacheDriver.get(cacheKey);
62
- if (cached) {
62
+ const respondToExisting = (existing) => {
63
+ if (!existing || existing.state === "in-flight") {
64
+ response.header("Retry-After", "1");
65
+ return response.conflict({
66
+ error: "A request with this Idempotency-Key is already in progress",
67
+ errorCode: "EC100"
68
+ });
69
+ }
70
+ const cached = existing;
63
71
  if (cached.bodyHash !== bodyHash) return response.unprocessableEntity({
64
72
  error: t("http.idempotencyKeyConflict"),
65
73
  errorCode: "EC100"
@@ -70,9 +78,33 @@ function idempotencyMiddleware(options = {}) {
70
78
  body: cached.body,
71
79
  contentType: cached.contentType
72
80
  });
81
+ };
82
+ let existing = null;
83
+ try {
84
+ existing = await cacheDriver.get(cacheKey);
85
+ } catch (error) {
86
+ log.error("idempotency-middleware", "get", error);
87
+ }
88
+ if (existing) return respondToExisting(existing);
89
+ try {
90
+ const reservation = await cacheDriver.set(cacheKey, {
91
+ state: "in-flight",
92
+ startedAt: Date.now()
93
+ }, {
94
+ onConflict: "create",
95
+ ttl: reservationTtl
96
+ });
97
+ if (reservation && reservation.wasSet === false) return respondToExisting(reservation.existing);
98
+ } catch (error) {
99
+ log.error("idempotency-middleware", "reserve", error);
73
100
  }
74
101
  response.onSent((sentResponse) => {
75
- if (sentResponse.statusCode >= 500) return;
102
+ if (sentResponse.statusCode >= 500) {
103
+ Promise.resolve().then(() => cacheDriver.remove(cacheKey)).catch((error) => {
104
+ log.error("idempotency-middleware", "remove", error);
105
+ });
106
+ return;
107
+ }
76
108
  const sentContentType = sentResponse.contentType;
77
109
  cacheDriver.set(cacheKey, {
78
110
  status: sentResponse.statusCode,
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency.middleware.mjs","names":[],"sources":["../../../../../../../../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 { buildIdempotencyCacheKey, hashBody, isValidIdempotencyKey } 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 || config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods = 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":";;;;;;;;AA0CA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,EAAE,SAAS,eAAe;EACtC,MAAM,aACJ,QAAQ,cAAc,OAAO,IAAI,+BAA+B,iBAAiB;EACnF,MAAM,UAAU,QAAQ,WAAW,OAAO,IAAI,4BAA4B,eAAe;EACzF,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"}
1
+ {"version":3,"file":"idempotency.middleware.mjs","names":[],"sources":["../../../../../../../../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 { buildIdempotencyCacheKey, hashBody, isValidIdempotencyKey } 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 * TTL in seconds of the in-flight reservation held while the handler runs.\n * Falls back to `http.idempotency.reservationTtl`, then `60`.\n */\n reservationTtl?: 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\ntype InFlightReservation = {\n state: \"in-flight\";\n startedAt: number;\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 || config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods = options.methods || config.get(\"http.idempotency.methods\", DEFAULT_METHODS);\n const ttl = options.ttl || config.get(\"http.idempotency.ttl\", 86400);\n const reservationTtl =\n options.reservationTtl || config.get(\"http.idempotency.reservationTtl\", 60);\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 respondToExisting = (existing: CachedResponse | InFlightReservation | undefined | null) => {\n if (!existing || (existing as InFlightReservation).state === \"in-flight\") {\n response.header(\"Retry-After\", \"1\");\n\n return response.conflict({\n error: \"A request with this Idempotency-Key is already in progress\",\n errorCode: HttpErrorCodes.IdempotencyKeyConflict,\n });\n }\n\n const cached = existing as CachedResponse;\n\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 // Fast path: a finished (or in-flight) request is already recorded.\n let existing: CachedResponse | InFlightReservation | null = null;\n\n try {\n existing = (await cacheDriver.get(cacheKey)) as CachedResponse | InFlightReservation | null;\n } catch (error) {\n // The cache is an optimisation of idempotency, not a gate on writes:\n // when it is down, fail open (the handler runs) and log it.\n log.error(\"idempotency-middleware\", \"get\", error);\n }\n\n if (existing) return respondToExisting(existing);\n\n // Reserve the key atomically before the handler runs, so concurrent\n // requests with the same key can't both execute it.\n try {\n const reservation = (await cacheDriver.set(\n cacheKey,\n { state: \"in-flight\", startedAt: Date.now() } satisfies InFlightReservation,\n { onConflict: \"create\", ttl: reservationTtl },\n )) as { wasSet?: boolean; existing?: CachedResponse | InFlightReservation } | undefined;\n\n if (reservation && reservation.wasSet === false) {\n return respondToExisting(reservation.existing);\n }\n } catch (error) {\n log.error(\"idempotency-middleware\", \"reserve\", error);\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 // Free the reservation so a retry can run.\n if (sentResponse.statusCode >= 500) {\n // Wrapped so a synchronous throw is caught as well as a rejection.\n Promise.resolve()\n .then(() => cacheDriver.remove(cacheKey))\n .catch((error: unknown) => {\n log.error(\"idempotency-middleware\", \"remove\", error);\n });\n\n return;\n }\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":";;;;;;;;AAoDA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,EAAE,SAAS,eAAe;EACtC,MAAM,aACJ,QAAQ,cAAc,OAAO,IAAI,+BAA+B,iBAAiB;EACnF,MAAM,UAAU,QAAQ,WAAW,OAAO,IAAI,4BAA4B,eAAe;EACzF,MAAM,MAAM,QAAQ,OAAO,OAAO,IAAI,wBAAwB,KAAK;EACnE,MAAM,iBACJ,QAAQ,kBAAkB,OAAO,IAAI,mCAAmC,EAAE;EAC5E,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,qBAAqB,aAAsE;GAC/F,IAAI,CAAC,YAAa,SAAiC,UAAU,aAAa;IACxE,SAAS,OAAO,eAAe,GAAG;IAElC,OAAO,SAAS,SAAS;KACvB,OAAO;KACP;IACF,CAAC;GACH;GAEA,MAAM,SAAS;GAEf,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;EAGA,IAAI,WAAwD;EAE5D,IAAI;GACF,WAAY,MAAM,YAAY,IAAI,QAAQ;EAC5C,SAAS,OAAO;GAGd,IAAI,MAAM,0BAA0B,OAAO,KAAK;EAClD;EAEA,IAAI,UAAU,OAAO,kBAAkB,QAAQ;EAI/C,IAAI;GACF,MAAM,cAAe,MAAM,YAAY,IACrC,UACA;IAAE,OAAO;IAAa,WAAW,KAAK,IAAI;GAAE,GAC5C;IAAE,YAAY;IAAU,KAAK;GAAe,CAC9C;GAEA,IAAI,eAAe,YAAY,WAAW,OACxC,OAAO,kBAAkB,YAAY,QAAQ;EAEjD,SAAS,OAAO;GACd,IAAI,MAAM,0BAA0B,WAAW,KAAK;EACtD;EAEA,SAAS,QAAQ,iBAA2B;GAI1C,IAAI,aAAa,cAAc,KAAK;IAElC,QAAQ,QAAQ,CAAC,CACd,WAAW,YAAY,OAAO,QAAQ,CAAC,CAAC,CACxC,OAAO,UAAmB;KACzB,IAAI,MAAM,0BAA0B,UAAU,KAAK;IACrD,CAAC;IAEH;GACF;GAEA,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"}
@@ -32,9 +32,10 @@ type RateLimitOptions = {
32
32
  * 429. Use this for endpoints that need a tighter cap than the global default:
33
33
  * login, OTP request, password reset, expensive search, AI completions.
34
34
  *
35
- * The counter lives in-process. With `N` replicas the effective cap is
36
- * `N × max`. For genuinely shared limits, configure `@fastify/rate-limit`'s
37
- * Redis store via `http.rateLimit` instead.
35
+ * The counter of THIS middleware always lives in-process. With `N` replicas
36
+ * its effective cap is `N × max`. The global limit can be shared across
37
+ * replicas by setting `http.rateLimit.redis` (an ioredis instance); that
38
+ * does not affect this per-route middleware.
38
39
  *
39
40
  * Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on
40
41
  * every response; adds `Retry-After` (seconds) on a 429.
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.middleware.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/rate-limit.middleware.ts"],"mappings":";;;;;;KAQY,gBAAA;EAAgB;;;EAI1B,GAAA;EAIA;;;EAAA,QAAA;EAYA;;AAAY;AA6Cd;;;;EAjDE,YAAA,IAAgB,OAAA,EAAS,OAAO;EAiDE;;;EA7ClC,YAAA;AAAA;;;;;;;;;;;;;;;;;;;;iBA6Cc,mBAAA,CAAoB,OAAA,EAAS,gBAAA,GAAmB,UAAU"}
1
+ {"version":3,"file":"rate-limit.middleware.d.mts","names":[],"sources":["../../../../../../../../core/src/http/middleware/rate-limit.middleware.ts"],"mappings":";;;;;;KAQY,gBAAA;EAAgB;;;EAI1B,GAAA;EAIA;;;EAAA,QAAA;EAYA;;AAAY;AA8Cd;;;;EAlDE,YAAA,IAAgB,OAAA,EAAS,OAAO;EAkDE;;;EA9ClC,YAAA;AAAA;;;;;;;;;;;;;;;;;;;;;iBA8Cc,mBAAA,CAAoB,OAAA,EAAS,gBAAA,GAAmB,UAAU"}
@@ -19,9 +19,10 @@ function pruneExpired(now) {
19
19
  * 429. Use this for endpoints that need a tighter cap than the global default:
20
20
  * login, OTP request, password reset, expensive search, AI completions.
21
21
  *
22
- * The counter lives in-process. With `N` replicas the effective cap is
23
- * `N × max`. For genuinely shared limits, configure `@fastify/rate-limit`'s
24
- * Redis store via `http.rateLimit` instead.
22
+ * The counter of THIS middleware always lives in-process. With `N` replicas
23
+ * its effective cap is `N × max`. The global limit can be shared across
24
+ * replicas by setting `http.rateLimit.redis` (an ioredis instance); that
25
+ * does not affect this per-route middleware.
25
26
  *
26
27
  * Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on
27
28
  * every response; adds `Retry-After` (seconds) on a 429.
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.middleware.mjs","names":[],"sources":["../../../../../../../../core/src/http/middleware/rate-limit.middleware.ts"],"sourcesContent":["import type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Request } from \"../request\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the per-route rate limit middleware.\n */\nexport type RateLimitOptions = {\n /**\n * Max requests allowed within the time window.\n */\n max: number;\n /**\n * Time window in milliseconds.\n */\n duration: number;\n /**\n * Group key generator. Defaults to the client IP. Override to scope per-user,\n * per-organization, per-tenant, etc.\n *\n * @example\n * keyGenerator: (request) => request.locals.user?.id ?? request.ip,\n */\n keyGenerator?: (request: Request) => string;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\ntype Bucket = {\n count: number;\n resetAt: number;\n};\n\nconst buckets = new Map<string, Bucket>();\n\n/**\n * Sweep expired buckets so the Map doesn't grow unbounded with one-shot keys\n * (rare per-user keys, dynamic path parameters in `keyGenerator`, etc.).\n * Called opportunistically, at most once per minute, not on a timer — keeps\n * the module side-effect-free for testing.\n */\nlet lastPruneAt = 0;\n\nfunction pruneExpired(now: number) {\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) {\n buckets.delete(key);\n }\n }\n}\n\n/**\n * Per-route / per-group rate limit. Layers on top of `@fastify/rate-limit`'s\n * global cap (configured via `http.rateLimit`) — both run, and either can\n * 429. Use this for endpoints that need a tighter cap than the global default:\n * login, OTP request, password reset, expensive search, AI completions.\n *\n * The counter lives in-process. With `N` replicas the effective cap is\n * `N × max`. For genuinely shared limits, configure `@fastify/rate-limit`'s\n * Redis store via `http.rateLimit` instead.\n *\n * Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on\n * every response; adds `Retry-After` (seconds) on a 429.\n *\n * @example\n * import { middleware } from \"@warlock.js/core\";\n * router.post(\"/auth/login\", loginController, {\n * middleware: [middleware.rateLimit({ max: 5, duration: 60_000 })],\n * });\n */\nexport function rateLimitMiddleware(options: RateLimitOptions): Middleware {\n return ({ request, response }) => {\n const now = Date.now();\n\n if (now - lastPruneAt > 60_000) {\n pruneExpired(now);\n lastPruneAt = now;\n }\n\n const groupKey = options.keyGenerator?.(request) || request.detectIp() || \"unknown\";\n const cacheKey = `${request.route.path}:${groupKey}`;\n\n let bucket = buckets.get(cacheKey);\n\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + options.duration };\n buckets.set(cacheKey, bucket);\n }\n\n bucket.count++;\n\n const remaining = Math.max(0, options.max - bucket.count);\n const retryAfter = Math.max(1, Math.ceil((bucket.resetAt - now) / 1000));\n\n response.header(\"X-RateLimit-Limit\", options.max);\n response.header(\"X-RateLimit-Remaining\", remaining);\n response.header(\"X-RateLimit-Reset\", Math.ceil(bucket.resetAt / 1000));\n\n if (bucket.count > options.max) {\n response.header(\"Retry-After\", retryAfter);\n\n return response.tooManyRequests({\n error: options.errorMessage || t(\"http.rateLimitExceeded\"),\n errorCode: HttpErrorCodes.RateLimitExceeded,\n });\n }\n };\n}\n"],"mappings":";;;;AAoCA,MAAM,0BAAU,IAAI,IAAoB;;;;;;;AAQxC,IAAI,cAAc;AAElB,SAAS,aAAa,KAAa;CACjC,KAAK,MAAM,CAAC,KAAK,WAAW,SAC1B,IAAI,OAAO,WAAW,KACpB,QAAQ,OAAO,GAAG;AAGxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,oBAAoB,SAAuC;CACzE,QAAQ,EAAE,SAAS,eAAe;EAChC,MAAM,MAAM,KAAK,IAAI;EAErB,IAAI,MAAM,cAAc,KAAQ;GAC9B,aAAa,GAAG;GAChB,cAAc;EAChB;EAEA,MAAM,WAAW,QAAQ,eAAe,OAAO,KAAK,QAAQ,SAAS,KAAK;EAC1E,MAAM,WAAW,GAAG,QAAQ,MAAM,KAAK,GAAG;EAE1C,IAAI,SAAS,QAAQ,IAAI,QAAQ;EAEjC,IAAI,CAAC,UAAU,OAAO,WAAW,KAAK;GACpC,SAAS;IAAE,OAAO;IAAG,SAAS,MAAM,QAAQ;GAAS;GACrD,QAAQ,IAAI,UAAU,MAAM;EAC9B;EAEA,OAAO;EAEP,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,MAAM,OAAO,KAAK;EACxD,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,UAAU,OAAO,GAAI,CAAC;EAEvE,SAAS,OAAO,qBAAqB,QAAQ,GAAG;EAChD,SAAS,OAAO,yBAAyB,SAAS;EAClD,SAAS,OAAO,qBAAqB,KAAK,KAAK,OAAO,UAAU,GAAI,CAAC;EAErE,IAAI,OAAO,QAAQ,QAAQ,KAAK;GAC9B,SAAS,OAAO,eAAe,UAAU;GAEzC,OAAO,SAAS,gBAAgB;IAC9B,OAAO,QAAQ,gBAAgB,EAAE,wBAAwB;IACzD;GACF,CAAC;EACH;CACF;AACF"}
1
+ {"version":3,"file":"rate-limit.middleware.mjs","names":[],"sources":["../../../../../../../../core/src/http/middleware/rate-limit.middleware.ts"],"sourcesContent":["import type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Request } from \"../request\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the per-route rate limit middleware.\n */\nexport type RateLimitOptions = {\n /**\n * Max requests allowed within the time window.\n */\n max: number;\n /**\n * Time window in milliseconds.\n */\n duration: number;\n /**\n * Group key generator. Defaults to the client IP. Override to scope per-user,\n * per-organization, per-tenant, etc.\n *\n * @example\n * keyGenerator: (request) => request.locals.user?.id ?? request.ip,\n */\n keyGenerator?: (request: Request) => string;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\ntype Bucket = {\n count: number;\n resetAt: number;\n};\n\nconst buckets = new Map<string, Bucket>();\n\n/**\n * Sweep expired buckets so the Map doesn't grow unbounded with one-shot keys\n * (rare per-user keys, dynamic path parameters in `keyGenerator`, etc.).\n * Called opportunistically, at most once per minute, not on a timer — keeps\n * the module side-effect-free for testing.\n */\nlet lastPruneAt = 0;\n\nfunction pruneExpired(now: number) {\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) {\n buckets.delete(key);\n }\n }\n}\n\n/**\n * Per-route / per-group rate limit. Layers on top of `@fastify/rate-limit`'s\n * global cap (configured via `http.rateLimit`) — both run, and either can\n * 429. Use this for endpoints that need a tighter cap than the global default:\n * login, OTP request, password reset, expensive search, AI completions.\n *\n * The counter of THIS middleware always lives in-process. With `N` replicas\n * its effective cap is `N × max`. The global limit can be shared across\n * replicas by setting `http.rateLimit.redis` (an ioredis instance); that\n * does not affect this per-route middleware.\n *\n * Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on\n * every response; adds `Retry-After` (seconds) on a 429.\n *\n * @example\n * import { middleware } from \"@warlock.js/core\";\n * router.post(\"/auth/login\", loginController, {\n * middleware: [middleware.rateLimit({ max: 5, duration: 60_000 })],\n * });\n */\nexport function rateLimitMiddleware(options: RateLimitOptions): Middleware {\n return ({ request, response }) => {\n const now = Date.now();\n\n if (now - lastPruneAt > 60_000) {\n pruneExpired(now);\n lastPruneAt = now;\n }\n\n const groupKey = options.keyGenerator?.(request) || request.detectIp() || \"unknown\";\n const cacheKey = `${request.route.path}:${groupKey}`;\n\n let bucket = buckets.get(cacheKey);\n\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + options.duration };\n buckets.set(cacheKey, bucket);\n }\n\n bucket.count++;\n\n const remaining = Math.max(0, options.max - bucket.count);\n const retryAfter = Math.max(1, Math.ceil((bucket.resetAt - now) / 1000));\n\n response.header(\"X-RateLimit-Limit\", options.max);\n response.header(\"X-RateLimit-Remaining\", remaining);\n response.header(\"X-RateLimit-Reset\", Math.ceil(bucket.resetAt / 1000));\n\n if (bucket.count > options.max) {\n response.header(\"Retry-After\", retryAfter);\n\n return response.tooManyRequests({\n error: options.errorMessage || t(\"http.rateLimitExceeded\"),\n errorCode: HttpErrorCodes.RateLimitExceeded,\n });\n }\n };\n}\n"],"mappings":";;;;AAoCA,MAAM,0BAAU,IAAI,IAAoB;;;;;;;AAQxC,IAAI,cAAc;AAElB,SAAS,aAAa,KAAa;CACjC,KAAK,MAAM,CAAC,KAAK,WAAW,SAC1B,IAAI,OAAO,WAAW,KACpB,QAAQ,OAAO,GAAG;AAGxB;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,oBAAoB,SAAuC;CACzE,QAAQ,EAAE,SAAS,eAAe;EAChC,MAAM,MAAM,KAAK,IAAI;EAErB,IAAI,MAAM,cAAc,KAAQ;GAC9B,aAAa,GAAG;GAChB,cAAc;EAChB;EAEA,MAAM,WAAW,QAAQ,eAAe,OAAO,KAAK,QAAQ,SAAS,KAAK;EAC1E,MAAM,WAAW,GAAG,QAAQ,MAAM,KAAK,GAAG;EAE1C,IAAI,SAAS,QAAQ,IAAI,QAAQ;EAEjC,IAAI,CAAC,UAAU,OAAO,WAAW,KAAK;GACpC,SAAS;IAAE,OAAO;IAAG,SAAS,MAAM,QAAQ;GAAS;GACrD,QAAQ,IAAI,UAAU,MAAM;EAC9B;EAEA,OAAO;EAEP,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,MAAM,OAAO,KAAK;EACxD,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,UAAU,OAAO,GAAI,CAAC;EAEvE,SAAS,OAAO,qBAAqB,QAAQ,GAAG;EAChD,SAAS,OAAO,yBAAyB,SAAS;EAClD,SAAS,OAAO,qBAAqB,KAAK,KAAK,OAAO,UAAU,GAAI,CAAC;EAErE,IAAI,OAAO,QAAQ,QAAQ,KAAK;GAC9B,SAAS,OAAO,eAAe,UAAU;GAEzC,OAAO,SAAS,gBAAgB;IAC9B,OAAO,QAAQ,gBAAgB,EAAE,wBAAwB;IACzD;GACF,CAAC;EACH;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.d.mts","names":[],"sources":["../../../../../../../core/src/http/plugins.ts"],"mappings":";;;iBAQsB,mBAAA,CAAoB,MAAA,EAAQ,eAAA,GAAe,OAAA"}
1
+ {"version":3,"file":"plugins.d.mts","names":[],"sources":["../../../../../../../core/src/http/plugins.ts"],"mappings":";;;iBASsB,mBAAA,CAAoB,MAAA,EAAQ,eAAA,GAAe,OAAA"}
@@ -1,16 +1,15 @@
1
1
  import { rootPath } from "../utils/paths.mjs";
2
2
  import "../utils/index.mjs";
3
3
  import { buildCorsOptions } from "./build-cors-options.mjs";
4
+ import { buildRateLimitOptions } from "./build-rate-limit-options.mjs";
4
5
  import { parseUrlencodedBody } from "./parse-urlencoded-body.mjs";
5
6
  import config from "@mongez/config";
6
7
  import fastifyMultipart from "@fastify/multipart";
7
8
 
8
9
  //#region ../core/src/http/plugins.ts
9
10
  async function registerHttpPlugins(server) {
10
- server.register(import("@fastify/rate-limit"), {
11
- max: config.get("http.rateLimit.max", 60),
12
- timeWindow: config.get("http.rateLimit.duration", 60 * 1e3)
13
- });
11
+ const rateLimitOptions = buildRateLimitOptions(config.get("http.rateLimit"));
12
+ if (rateLimitOptions) server.register(import("@fastify/rate-limit"), rateLimitOptions);
14
13
  server.register(import("@fastify/cors"), buildCorsOptions());
15
14
  server.register(fastifyMultipart, {
16
15
  attachFieldsToBody: true,
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.mjs","names":[],"sources":["../../../../../../../core/src/http/plugins.ts"],"sourcesContent":["import fastifyMultipart from \"@fastify/multipart\";\nimport config from \"@mongez/config\";\nimport type { FastifyRequest } from \"fastify\";\nimport { rootPath } from \"../utils\";\nimport { buildCorsOptions } from \"./build-cors-options\";\nimport { parseUrlencodedBody } from \"./parse-urlencoded-body\";\nimport type { FastifyInstance } from \"./server\";\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 server.register(import(\"@fastify/cors\"), buildCorsOptions());\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 // 👇🏻 parse application/x-www-form-urlencoded bodies (Apple's OAuth\n // `form_post` callback, plain HTML forms). Fastify only parses JSON and\n // text natively, so without this every urlencoded POST is rejected with\n // FST_ERR_CTP_INVALID_MEDIA_TYPE before it reaches a route.\n //\n // No `bodyLimit` option here: Fastify falls back to the server's own\n // `bodyLimit` (`server.ts`, from `http.bodyLimit`) whenever a content-type\n // parser doesn't set one — the exact limit JSON bodies are already held to.\n // Reading `http.bodyLimit` again here would duplicate that lookup and risk\n // the two silently drifting.\n server.addContentTypeParser(\n \"application/x-www-form-urlencoded\",\n { parseAs: \"string\" },\n (_request: FastifyRequest, body: string, done: (err: Error | null, body?: any) => void) => {\n try {\n done(null, parseUrlencodedBody(body));\n } catch (error) {\n done(error as Error, undefined);\n }\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":";;;;;;;;AAQA,eAAsB,oBAAoB,QAAyB;CAEjE,OAAO,SAAS,OAAO,wBAAwB;EAE7C,KAAK,OAAO,IAAI,sBAAsB,EAAE;EAExC,YAAY,OAAO,IAAI,2BAA2B,KAAK,GAAI;CAC7D,CAAC;CAGD,OAAO,SAAS,OAAO,kBAAkB,iBAAiB,CAAC;CAG3D,OAAO,SAAS,kBAAkB;EAChC,oBAAoB;EACpB,QAAQ,EAEN,UAAU,OAAO,IAAI,wBAAwB,KAAK,OAAO,IAAI,EAC/D;CACF,CAAC;CAYD,OAAO,qBACL,qCACA,EAAE,SAAS,SAAS,IACnB,UAA0B,MAAc,SAAkD;EACzF,IAAI;GACF,KAAK,MAAM,oBAAoB,IAAI,CAAC;EACtC,SAAS,OAAO;GACd,KAAK,OAAgB,MAAS;EAChC;CACF,CACF;CAEA,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
+ {"version":3,"file":"plugins.mjs","names":[],"sources":["../../../../../../../core/src/http/plugins.ts"],"sourcesContent":["import fastifyMultipart from \"@fastify/multipart\";\nimport config from \"@mongez/config\";\nimport type { FastifyRequest } from \"fastify\";\nimport { rootPath } from \"../utils\";\nimport { buildCorsOptions } from \"./build-cors-options\";\nimport { buildRateLimitOptions } from \"./build-rate-limit-options\";\nimport { parseUrlencodedBody } from \"./parse-urlencoded-body\";\nimport type { FastifyInstance } from \"./server\";\n\nexport async function registerHttpPlugins(server: FastifyInstance) {\n // 👇🏻 register rate-limit plugin\n const rateLimitOptions = buildRateLimitOptions(config.get(\"http.rateLimit\"));\n\n if (rateLimitOptions) {\n server.register(import(\"@fastify/rate-limit\"), rateLimitOptions);\n }\n\n // 👇🏻 register cors plugin\n server.register(import(\"@fastify/cors\"), buildCorsOptions());\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 // 👇🏻 parse application/x-www-form-urlencoded bodies (Apple's OAuth\n // `form_post` callback, plain HTML forms). Fastify only parses JSON and\n // text natively, so without this every urlencoded POST is rejected with\n // FST_ERR_CTP_INVALID_MEDIA_TYPE before it reaches a route.\n //\n // No `bodyLimit` option here: Fastify falls back to the server's own\n // `bodyLimit` (`server.ts`, from `http.bodyLimit`) whenever a content-type\n // parser doesn't set one — the exact limit JSON bodies are already held to.\n // Reading `http.bodyLimit` again here would duplicate that lookup and risk\n // the two silently drifting.\n server.addContentTypeParser(\n \"application/x-www-form-urlencoded\",\n { parseAs: \"string\" },\n (_request: FastifyRequest, body: string, done: (err: Error | null, body?: any) => void) => {\n try {\n done(null, parseUrlencodedBody(body));\n } catch (error) {\n done(error as Error, undefined);\n }\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":";;;;;;;;;AASA,eAAsB,oBAAoB,QAAyB;CAEjE,MAAM,mBAAmB,sBAAsB,OAAO,IAAI,gBAAgB,CAAC;CAE3E,IAAI,kBACF,OAAO,SAAS,OAAO,wBAAwB,gBAAgB;CAIjE,OAAO,SAAS,OAAO,kBAAkB,iBAAiB,CAAC;CAG3D,OAAO,SAAS,kBAAkB;EAChC,oBAAoB;EACpB,QAAQ,EAEN,UAAU,OAAO,IAAI,wBAAwB,KAAK,OAAO,IAAI,EAC/D;CACF,CAAC;CAYD,OAAO,qBACL,qCACA,EAAE,SAAS,SAAS,IACnB,UAA0B,MAAc,SAAkD;EACzF,IAAI;GACF,KAAK,MAAM,oBAAoB,IAAI,CAAC;EACtC,SAAS,OAAO;GACd,KAAK,OAAgB,MAAS;EAChC;CACF,CACF;CAEA,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,3 +1,4 @@
1
+ import { HttpRateLimitConfig } from "./build-rate-limit-options.mjs";
1
2
  import { Response } from "./response.mjs";
2
3
  import { CspConfig } from "./csp.mjs";
3
4
  import { HttpTracingConfig } from "./tracing/tracing.type.mjs";
@@ -249,20 +250,7 @@ interface HttpConfigurations {
249
250
  /**
250
251
  * Rate limit
251
252
  */
252
- rateLimit?: {
253
- /**
254
- * max number of connections during windowMs milliseconds before sending a 429 response
255
- *
256
- * @default 60
257
- */
258
- max?: number;
259
- /**
260
- * how long to keep records of requests in memory
261
- *
262
- * @default 60 * 1000
263
- */
264
- duration?: number;
265
- };
253
+ rateLimit?: HttpRateLimitConfig;
266
254
  /**
267
255
  * Request id (correlation) settings.
268
256
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/http/types.ts"],"mappings":";;;;;;;;;KAQY,YAAA;;;AAAZ;;;;AAAwB;AA0BxB;;;;AAca;AAeb;;;;AACU;AAMV;;;;;;UApCiB,aAAA;EAoDL;;;;;;;;;;;;;EAtCV,WAAW;AAAA;;;;AAmDY;AAiEzB;;;;;;;;KArGY,kBAAA;EACV,QAAQ;AAAA;;;;KAME,gBAAA;;;;AAIR;;;;EAIA,OAAA,CAAQ,QAAA;;;;EAIR,MAAA;;;;EAIA,OAAA,CAAQ,MAAA;;;;;;;;EAQR,OAAA;;;;KAKQ,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiUiB;AAG7B;;AAH6B;;;;;;;;;;;AAmBtB;;;;;;;;;;;;;;;;;;;;;UAnRU,iBAAA;EA0SwB;;;AAIlC;EAzSL,MAAA;;;;;;EAMA,WAAA;;;;EAIA,UAAA,EAAY,UAAU;AAAA;;;;UAMP,kBAAA;;;;EAIf,IAAA;;;;EAIA,GAAA;;;;EAIA,IAAA,GAAO,kBAAA;;;;;;EAMP,eAAA;;;;;;;;;;EAUA,SAAA;;;;;;;;;;;;;;;;;;;;EAoBA,UAAA,mCAA6C,OAAA,UAAiB,GAAA;EAC9D,OAAA;;;;IAIE,MAAA;;;;IAIA,OAAA,GAAU,sBAAA;EAAA;;;;;;;;;;EAWZ,GAAA,GAAM,SAAA;;;;EAIN,SAAA;;;;;;IAME,GAAA;;;;;;IAMA,QAAA;EAAA;;;;;;;;EASF,SAAA;;;;;;IAME,MAAA;;;;IAIA,SAAA;;;;;;;IAOA,OAAA;EAAA;;;;EAKF,WAAA;;;;;;IAME,GAAA;;;;;;IAMA,UAAA;;;;;;;IAOA,OAAA;;;;IAIA,MAAA;EAAA;;;;EAKF,WAAA;;;;;;;IAOE,OAAA;;;;;;IAMA,SAAA;;;;;;IAMA,UAAA;EAAA;;;;EAKF,gBAAA;;;;;;;IAOE,OAAA;;;;;;;;IAQA,qBAAA;EAAA;;;;EAKF,MAAA;;;;;;IAME,OAAA;;;;;;;IAOA,IAAA;;;;;;;IAOA,aAAA;EAAA;;;;EAKF,IAAA;;;;EAIA,UAAA;;;;IAIE,GAAA,GAAM,UAAA;;;;IAIN,IAAA,GAAO,iBAAA;;;;IAIP,MAAA,GAAS,iBAAA;EAAA;;;;;;EAOX,OAAA,GAAU,iBAAA;AAAA;AAAA,KAGA,wBAAA;;;;EAIV,IAAA,GAAO,IAAA;;;;EAIP,MAAA,GAAS,IAAA,EAAM,KAAA,CAAM,SAAS;;;;EAI9B,GAAA;;;;EAIA,KAAA;AAAA;AAAA,KAGU,qBAAA;;;;;;;EAOV,IAAA,GAAO,KAAA,UAAe,IAAA,OAAW,EAAA,cAAgB,qBAAA;;;;EAIjD,OAAA,GAAU,OAAA,aAAoB,qBAAA;;;;EAI9B,GAAA,QAAW,qBAAA;;;;;EAKX,YAAA,GAAe,OAAA,iBAAwB,qBAAA;;;;EAIvC,KAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../core/src/http/types.ts"],"mappings":";;;;;;;;;;KASY,YAAA;;;AAAZ;;;;AAAwB;AA0BxB;;;;AAca;AAeb;;;;AACU;AAMV;;;;;;UApCiB,aAAA;EAoDL;;;;;;;;;;;;;EAtCV,WAAW;AAAA;;;;AAmDY;AAiEzB;;;;;;;;KArGY,kBAAA;EACV,QAAQ;AAAA;;;;KAME,gBAAA;;;;AAIR;;;;EAIA,OAAA,CAAQ,QAAA;;;;EAIR,MAAA;;;;EAIA,OAAA,CAAQ,MAAA;;;;;;;;EAQR,OAAA;;;;KAKQ,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoTiB;AAG7B;;AAH6B;;;;;;;;;;;AAmBtB;;;;;;;;;;;;;;;;;;;;;UAtQU,iBAAA;EA6RwB;;;AAIlC;EA5RL,MAAA;;;;;;EAMA,WAAA;;;;EAIA,UAAA,EAAY,UAAU;AAAA;;;;UAMP,kBAAA;;;;EAIf,IAAA;;;;EAIA,GAAA;;;;EAIA,IAAA,GAAO,kBAAA;;;;;;EAMP,eAAA;;;;;;;;;;EAUA,SAAA;;;;;;;;;;;;;;;;;;;;EAoBA,UAAA,mCAA6C,OAAA,UAAiB,GAAA;EAC9D,OAAA;;;;IAIE,MAAA;;;;IAIA,OAAA,GAAU,sBAAA;EAAA;;;;;;;;;;EAWZ,GAAA,GAAM,SAAA;;;;EAIN,SAAA,GAAY,mBAAA;;;;;;;;EAQZ,SAAA;;;;;;IAME,MAAA;;;;IAIA,SAAA;;;;;;;IAOA,OAAA;EAAA;;;;EAKF,WAAA;;;;;;IAME,GAAA;;;;;;IAMA,UAAA;;;;;;;IAOA,OAAA;;;;IAIA,MAAA;EAAA;;;;EAKF,WAAA;;;;;;;IAOE,OAAA;;;;;;IAMA,SAAA;;;;;;IAMA,UAAA;EAAA;;;;EAKF,gBAAA;;;;;;;IAOE,OAAA;;;;;;;;IAQA,qBAAA;EAAA;;;;EAKF,MAAA;;;;;;IAME,OAAA;;;;;;;IAOA,IAAA;;;;;;;IAOA,aAAA;EAAA;;;;EAKF,IAAA;;;;EAIA,UAAA;;;;IAIE,GAAA,GAAM,UAAA;;;;IAIN,IAAA,GAAO,iBAAA;;;;IAIP,MAAA,GAAS,iBAAA;EAAA;;;;;;EAOX,OAAA,GAAU,iBAAA;AAAA;AAAA,KAGA,wBAAA;;;;EAIV,IAAA,GAAO,IAAA;;;;EAIP,MAAA,GAAS,IAAA,EAAM,KAAA,CAAM,SAAS;;;;EAI9B,GAAA;;;;EAIA,KAAA;AAAA;AAAA,KAGU,qBAAA;;;;;;;EAOV,IAAA,GAAO,KAAA,UAAe,IAAA,OAAW,EAAA,cAAgB,qBAAA;;;;EAIjD,OAAA,GAAU,OAAA,aAAoB,qBAAA;;;;EAI9B,GAAA,QAAW,qBAAA;;;;;EAKX,YAAA,GAAe,OAAA,iBAAwB,qBAAA;;;;EAIvC,KAAA;AAAA"}
package/esm/index.d.mts CHANGED
@@ -10,6 +10,7 @@ import { resolveWithinRoot } from "./storage/utils/contain-path.mjs";
10
10
  import { MimeTypes, getMimeType } from "./storage/utils/mime.mjs";
11
11
  import { SafeFetchOptions, SafeFetchResult, isPrivateOrReservedIp, safeFetchToBuffer } from "./storage/utils/safe-fetch.mjs";
12
12
  import { StorageError, StorageErrorOptions } from "./storage/utils/storage-error.mjs";
13
+ import { StorageCapabilityError } from "./storage/utils/storage-capability-error.mjs";
13
14
  import { StorageNotInitializedError } from "./storage/utils/storage-not-initialized-error.mjs";
14
15
  import { CloudDriver, loadS3 } from "./storage/drivers/cloud-driver.mjs";
15
16
  import { DOSpacesDriver } from "./storage/drivers/do-spaces-driver.mjs";
@@ -142,7 +143,7 @@ import { CascadeQueryBuilder } from "./repositories/adapters/cascade/cascade-que
142
143
  import { DefineResourceOptions, defineResource } from "./resource/define-resource.mjs";
143
144
  import { RegisterResource } from "./resource/register-resource.mjs";
144
145
  import { Restful } from "./restful/restful.mjs";
145
- import { SocketOptions } from "./socket/types.mjs";
146
+ import { SocketAdapterFactory, SocketOptions } from "./socket/types.mjs";
146
147
  import { getSocketServer } from "./socket/utils.mjs";
147
148
  import { RegisteredUseCase, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema } from "./use-cases/types.mjs";
148
149
  import { useCase } from "./use-cases/use-case.mjs";
@@ -176,7 +177,7 @@ import { WarlockConfigManager, isUnknownTsExtensionError, warlockConfigManager }
176
177
  import { env } from "@mongez/dotenv";
177
178
  import { colors } from "@mongez/copper";
178
179
  export * from "@mongez/localization";
179
- export { $registerUseCase, $unregisterUseCase, AccessConnector, AggregateExpressionInput, AggregateExpressions, AiConnector, AllRepositoryOptions, AppConfigurations, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, BootContext, BootListener, BootSignal, BootSignalType, BootValidator, CLICommand, type CLICommandAction, type CLICommandOption, type CLICommandOptions, type CLICommandPreload, type CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, ClosableServer, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, type CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorBuildContext, ConnectorBuildContribution, ConnectorBuildGenerateResult, ConnectorEsbuildPatch, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieJarUnavailableError, CookieOptions, CspConfig, CsrfOriginCheckReason, CsrfOriginVerdict, CursorPaginationOptions, CursorPaginationResult, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecodedAccessToken, DefineResourceOptions, DeleteManyResult, DetectedImageFormat, type DevelopmentModelModuleEntry, type DevelopmentModelModuleState, type DevelopmentModelModules, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GenerateImageVariantsOptions, GeneratedImageDescriptor, GeneratedImageVariant, GroupByFields, GroupedRoutesOptions, HealthCheck, HealthStatus, HeraldConnector, HttpConfigurations, HttpConnector, HttpContext, HttpError, HttpErrorCodes, HttpReadyReport, HttpTracingConfig, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, ImageVariantDefinition, ImageVariantFit, ImageVariantOutputFormat, ImageVariantsConfigError, InvalidCspDirectiveError, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NamedApiRoute, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, Path, PipeableReactStream, PipelineOptions, PortInUseError, PositionalHandlerSuspect, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutFromUrlOptions, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterConfiguredConnectorsOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLocals, RequestLog, RequestMethod, RequestUserMovedError, type ResolvedCLICommandOption, ResolvedImageVariantsConfig, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteRegistry, RouteResource, Router, RouterGroupCallback, RouterStacks, RuntimeStrategy, S3Driver, type SESConfigurations, type SMTPConfigurations, SafeFetchOptions, SafeFetchResult, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedClock, SeedContext, SeedRecordRef, SeedResult, Seeder, SeederDependencyCycleError, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, ShutdownListener, SocketConnector, SocketOptions, Storage, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageError, StorageErrorOptions, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StorageNotInitializedError, StoragePutEventPayload, StreamReactResponseOptions, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, TracingContext, TracingContextSource, TracingHooks, TracingPhaseInfo, TracingRequestEndInfo, Track, TrackableModel, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UnknownSeederDependencyError, UploadedFile, UploadedFileImageOptions, UploadedFileJson, UploadedFileQuery, UploadsConfigurations, UploadsImagesConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, VariantOutputFormat, VariantSourceFormat, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, XMLable, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateImageVariants, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getPublicUrl, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isAllowedCsrfOrigin, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeOrigin, normalizeRequestPath, normalizeRoutePath, originOf, ownOrigin, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, requiresDefaultCsrfGuard, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveCsrfOriginVerdict, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runDefaultCsrfGuard, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileController, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
180
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, AggregateExpressionInput, AggregateExpressions, AiConnector, AllRepositoryOptions, AppConfigurations, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, BootContext, BootListener, BootSignal, BootSignalType, BootValidator, CLICommand, type CLICommandAction, type CLICommandOption, type CLICommandOptions, type CLICommandPreload, type CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, ClosableServer, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, type CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorBuildContext, ConnectorBuildContribution, ConnectorBuildGenerateResult, ConnectorEsbuildPatch, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieJarUnavailableError, CookieOptions, CspConfig, CsrfOriginCheckReason, CsrfOriginVerdict, CursorPaginationOptions, CursorPaginationResult, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecodedAccessToken, DefineResourceOptions, DeleteManyResult, DetectedImageFormat, type DevelopmentModelModuleEntry, type DevelopmentModelModuleState, type DevelopmentModelModules, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GenerateImageVariantsOptions, GeneratedImageDescriptor, GeneratedImageVariant, GroupByFields, GroupedRoutesOptions, HealthCheck, HealthStatus, HeraldConnector, HttpConfigurations, HttpConnector, HttpContext, HttpError, HttpErrorCodes, HttpReadyReport, HttpTracingConfig, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, ImageVariantDefinition, ImageVariantFit, ImageVariantOutputFormat, ImageVariantsConfigError, InvalidCspDirectiveError, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NamedApiRoute, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, Path, PipeableReactStream, PipelineOptions, PortInUseError, PositionalHandlerSuspect, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutFromUrlOptions, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterConfiguredConnectorsOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLocals, RequestLog, RequestMethod, RequestUserMovedError, type ResolvedCLICommandOption, ResolvedImageVariantsConfig, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteRegistry, RouteResource, Router, RouterGroupCallback, RouterStacks, RuntimeStrategy, S3Driver, type SESConfigurations, type SMTPConfigurations, SafeFetchOptions, SafeFetchResult, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedClock, SeedContext, SeedRecordRef, SeedResult, Seeder, SeederDependencyCycleError, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, ShutdownListener, SocketAdapterFactory, SocketConnector, SocketOptions, Storage, StorageCapabilityError, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageError, StorageErrorOptions, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StorageNotInitializedError, StoragePutEventPayload, StreamReactResponseOptions, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, TracingContext, TracingContextSource, TracingHooks, TracingPhaseInfo, TracingRequestEndInfo, Track, TrackableModel, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UnknownSeederDependencyError, UploadedFile, UploadedFileImageOptions, UploadedFileJson, UploadedFileQuery, UploadsConfigurations, UploadsImagesConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, VariantOutputFormat, VariantSourceFormat, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, XMLable, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateImageVariants, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getPublicUrl, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isAllowedCsrfOrigin, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeOrigin, normalizeRequestPath, normalizeRoutePath, originOf, ownOrigin, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, requiresDefaultCsrfGuard, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveCsrfOriginVerdict, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runDefaultCsrfGuard, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileController, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
180
181
  import "./config/types.mjs";
181
182
  import "./http/request.mjs";
182
183
  import "./http/types.mjs";
package/esm/index.mjs CHANGED
@@ -39,8 +39,9 @@ import { BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, isBootSignal, sendBootSignal
39
39
  import { Application } from "./application/application.mjs";
40
40
  import { storageConfig, storageConfigurations } from "./storage/config.mjs";
41
41
  import { StorageFile } from "./storage/storage-file.mjs";
42
- import { StorageNotInitializedError } from "./storage/utils/storage-not-initialized-error.mjs";
43
42
  import { StorageError } from "./storage/utils/storage-error.mjs";
43
+ import { StorageCapabilityError } from "./storage/utils/storage-capability-error.mjs";
44
+ import { StorageNotInitializedError } from "./storage/utils/storage-not-initialized-error.mjs";
44
45
  import { isPrivateOrReservedIp, safeFetchToBuffer } from "./storage/utils/safe-fetch.mjs";
45
46
  import { ScopedStorage } from "./storage/scoped-storage.mjs";
46
47
  import { storageDriverContext } from "./storage/context/storage-driver-context.mjs";
@@ -170,4 +171,4 @@ import { colors } from "@mongez/copper";
170
171
 
171
172
  export * from "@mongez/localization"
172
173
 
173
- export { $registerUseCase, $unregisterUseCase, AccessConnector, AiConnector, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, CookieJarUnavailableError, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, FileValidator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, ImageVariantsConfigError, InvalidCspDirectiveError, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Path, PortInUseError, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, RequestUserMovedError, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, RouteRegistry, Router, S3Driver, ScopedStorage, SeederDependencyCycleError, ServerError, SocketConnector, Storage, StorageConnector, StorageError, StorageFile, StorageNotInitializedError, UPLOADS_DEFAULTS, UnAuthorizedError, UnknownSeederDependencyError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateImageVariants, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getPublicUrl, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isAllowedCsrfOrigin, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeOrigin, normalizeRequestPath, normalizeRoutePath, originOf, ownOrigin, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, requiresDefaultCsrfGuard, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveCsrfOriginVerdict, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runDefaultCsrfGuard, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileController, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
174
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, AiConnector, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, CookieJarUnavailableError, DEFAULT_CSP_DIRECTIVES, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, FileValidator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, ImageVariantsConfigError, InvalidCspDirectiveError, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Path, PortInUseError, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, RequestUserMovedError, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, RouteRegistry, Router, S3Driver, ScopedStorage, SeederDependencyCycleError, ServerError, SocketConnector, Storage, StorageCapabilityError, StorageConnector, StorageError, StorageFile, StorageNotInitializedError, UPLOADS_DEFAULTS, UnAuthorizedError, UnknownSeederDependencyError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, applyCspHeader, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildCspHeaderValue, buildIdempotencyCacheKey, buildTracingContext, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, deriveTraceId, describePositionalHandlerSuspect, dispatchPhase, dispatchRequestEnd, dispatchRequestStart, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateImageVariants, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getPublicUrl, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isAllowedCsrfOrigin, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isTracingEnabled, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, mergeCspDirectives, middleware, normalizeOrigin, normalizeRequestPath, normalizeRoutePath, originOf, ownOrigin, parseSize, parseTraceparentTraceId, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, requiresDefaultCsrfGuard, resetHttpReadyReport, resetMailConfig, resetTracingConfigForTests, resolveCspConfig, resolveCsrfOriginVerdict, resolveMailConfig, resolveTracingConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runDefaultCsrfGuard, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, serializeCspDirectives, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, streamReactResponse, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileController, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, validateCspConfigAtBoot, validateCspDirectives, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
@@ -49,6 +49,7 @@ function resolvePackageName(specifier) {
49
49
  if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("node:") || specifier.includes("://")) return;
50
50
  const segments = specifier.split("/");
51
51
  const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
52
+ if (!packageName) return;
52
53
  if (builtinModules.includes(packageName)) return;
53
54
  return packageName;
54
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"assert-generated-imports.mjs","names":[],"sources":["../../../../../../../core/src/production/assert-generated-imports.ts"],"sourcesContent":["import { builtinModules } from \"node:module\";\n\n/**\n * A specifier the builder wrote into generated code that the consuming app does\n * not declare.\n */\nexport type UndeclaredImport = {\n /** File the import was written into, relative to the generation directory. */\n file: string;\n /** The specifier exactly as emitted. */\n specifier: string;\n /** The package the specifier resolves to — `@scope/name` for a deep import. */\n packageName: string;\n};\n\nexport type GeneratedFile = {\n file: string;\n content: string;\n};\n\n/**\n * Raised when generated code would ship an import the app cannot resolve.\n *\n * Carries every violation rather than the first, so a build reports the whole\n * problem in one pass instead of one specifier per re-run.\n */\nexport class UndeclaredGeneratedImportError extends Error {\n public constructor(public readonly violations: UndeclaredImport[]) {\n super(\n [\n `Generated code imports ${violations.length} package(s) the application does not declare:`,\n \"\",\n ...violations.map(\n ({ file, specifier, packageName }) =>\n ` ${file} → \"${specifier}\" (requires \"${packageName}\" in the app's dependencies)`,\n ),\n \"\",\n \"Generated code may only import packages the app itself declares. A framework-internal\",\n \"dependency must be reached through a re-export from @warlock.js/core — a bare specifier\",\n \"resolves by accident under npm/yarn hoisting and fails under pnpm's strict layout.\",\n ].join(\"\\n\"),\n );\n\n this.name = \"UndeclaredGeneratedImportError\";\n }\n}\n\n// The clause between `import` and `from` excludes `;` and both quote\n// characters so the lazy match cannot run past the end of its own statement.\n// Allowing it to would let a side-effect import (`import \"./bootstrap\";`) bind\n// to the `from` of the NEXT statement and disappear from the results.\nconst IMPORT_PATTERN =\n /(?:import|export)\\s+(?:[^\"';]*?\\sfrom\\s*)?[\"']([^\"']+)[\"']|import\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)/g;\n\n/**\n * Every module specifier a source file references — static, side-effect,\n * re-export and dynamic `import()` forms alike.\n *\n * Regex is sufficient and deliberate here: the input is code this builder wrote\n * itself, so it has no comments, no strings that look like imports, and no\n * syntax the pattern could misread. Parsing it with a full AST would buy\n * nothing and cost a dependency in the build path.\n */\nexport function extractSpecifiers(content: string): string[] {\n const specifiers: string[] = [];\n\n for (const match of content.matchAll(IMPORT_PATTERN)) {\n const specifier = match[1] ?? match[2];\n\n if (specifier) {\n specifiers.push(specifier);\n }\n }\n\n return specifiers;\n}\n\n/**\n * The package a specifier resolves to — `@scope/name` or `name`, ignoring any\n * deep path after it. Returns undefined for anything that is not a package:\n * relative paths, absolute paths, URLs, and Node builtins.\n */\nexport function resolvePackageName(specifier: string): string | undefined {\n if (\n specifier.startsWith(\".\") ||\n specifier.startsWith(\"/\") ||\n specifier.startsWith(\"node:\") ||\n specifier.includes(\"://\")\n ) {\n return undefined;\n }\n\n const segments = specifier.split(\"/\");\n const packageName = specifier.startsWith(\"@\") ? segments.slice(0, 2).join(\"/\") : segments[0];\n\n if (builtinModules.includes(packageName)) {\n return undefined;\n }\n\n return packageName;\n}\n\n/**\n * Find every import in generated code that the app does not declare.\n *\n * `dependencies` only, deliberately: the artifact this checks is a production\n * bundle, so a package the app lists as a dev dependency is not guaranteed to\n * be installed where it runs.\n */\nexport function findUndeclaredImports(\n files: GeneratedFile[],\n declaredDependencies: string[],\n): UndeclaredImport[] {\n const declared = new Set(declaredDependencies);\n const violations: UndeclaredImport[] = [];\n\n for (const { file, content } of files) {\n for (const specifier of extractSpecifiers(content)) {\n const packageName = resolvePackageName(specifier);\n\n if (!packageName || declared.has(packageName)) {\n continue;\n }\n\n violations.push({ file, specifier, packageName });\n }\n }\n\n return violations;\n}\n\n/**\n * Fail the build when generated code would import something the app cannot\n * resolve.\n *\n * This is the part that stops the defect class from returning. Rewriting the\n * one bad specifier fixes today's bundle; only the assertion stops the next\n * change to the generator from reintroducing it — under npm or yarn the mistake\n * is invisible, because hoisting resolves it by accident.\n *\n * @throws {UndeclaredGeneratedImportError} listing every violation found.\n */\nexport function assertGeneratedImportsAreDeclared(\n files: GeneratedFile[],\n declaredDependencies: string[],\n): void {\n const violations = findUndeclaredImports(files, declaredDependencies);\n\n if (violations.length > 0) {\n throw new UndeclaredGeneratedImportError(violations);\n }\n}\n"],"mappings":";;;;;;;;;AA0BA,IAAa,iCAAb,cAAoD,MAAM;CACxD,AAAO,YAAY,AAAgB,YAAgC;EACjE,MACE;GACE,0BAA0B,WAAW,OAAO;GAC5C;GACA,GAAG,WAAW,KACX,EAAE,MAAM,WAAW,kBAClB,KAAK,KAAK,MAAM,UAAU,eAAe,YAAY,6BACzD;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,IAAI,CACb;EAdiC;EAgBjC,KAAK,OAAO;CACd;AACF;AAMA,MAAM,iBACJ;;;;;;;;;;AAWF,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,aAAuB,CAAC;CAE9B,KAAK,MAAM,SAAS,QAAQ,SAAS,cAAc,GAAG;EACpD,MAAM,YAAY,MAAM,MAAM,MAAM;EAEpC,IAAI,WACF,WAAW,KAAK,SAAS;CAE7B;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,mBAAmB,WAAuC;CACxE,IACE,UAAU,WAAW,GAAG,KACxB,UAAU,WAAW,GAAG,KACxB,UAAU,WAAW,OAAO,KAC5B,UAAU,SAAS,KAAK,GAExB;CAGF,MAAM,WAAW,UAAU,MAAM,GAAG;CACpC,MAAM,cAAc,UAAU,WAAW,GAAG,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,SAAS;CAE1F,IAAI,eAAe,SAAS,WAAW,GACrC;CAGF,OAAO;AACT;;;;;;;;AASA,SAAgB,sBACd,OACA,sBACoB;CACpB,MAAM,WAAW,IAAI,IAAI,oBAAoB;CAC7C,MAAM,aAAiC,CAAC;CAExC,KAAK,MAAM,EAAE,MAAM,aAAa,OAC9B,KAAK,MAAM,aAAa,kBAAkB,OAAO,GAAG;EAClD,MAAM,cAAc,mBAAmB,SAAS;EAEhD,IAAI,CAAC,eAAe,SAAS,IAAI,WAAW,GAC1C;EAGF,WAAW,KAAK;GAAE;GAAM;GAAW;EAAY,CAAC;CAClD;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,kCACd,OACA,sBACM;CACN,MAAM,aAAa,sBAAsB,OAAO,oBAAoB;CAEpE,IAAI,WAAW,SAAS,GACtB,MAAM,IAAI,+BAA+B,UAAU;AAEvD"}
1
+ {"version":3,"file":"assert-generated-imports.mjs","names":[],"sources":["../../../../../../../core/src/production/assert-generated-imports.ts"],"sourcesContent":["import { builtinModules } from \"node:module\";\n\n/**\n * A specifier the builder wrote into generated code that the consuming app does\n * not declare.\n */\nexport type UndeclaredImport = {\n /** File the import was written into, relative to the generation directory. */\n file: string;\n /** The specifier exactly as emitted. */\n specifier: string;\n /** The package the specifier resolves to — `@scope/name` for a deep import. */\n packageName: string;\n};\n\nexport type GeneratedFile = {\n file: string;\n content: string;\n};\n\n/**\n * Raised when generated code would ship an import the app cannot resolve.\n *\n * Carries every violation rather than the first, so a build reports the whole\n * problem in one pass instead of one specifier per re-run.\n */\nexport class UndeclaredGeneratedImportError extends Error {\n public constructor(public readonly violations: UndeclaredImport[]) {\n super(\n [\n `Generated code imports ${violations.length} package(s) the application does not declare:`,\n \"\",\n ...violations.map(\n ({ file, specifier, packageName }) =>\n ` ${file} → \"${specifier}\" (requires \"${packageName}\" in the app's dependencies)`,\n ),\n \"\",\n \"Generated code may only import packages the app itself declares. A framework-internal\",\n \"dependency must be reached through a re-export from @warlock.js/core — a bare specifier\",\n \"resolves by accident under npm/yarn hoisting and fails under pnpm's strict layout.\",\n ].join(\"\\n\"),\n );\n\n this.name = \"UndeclaredGeneratedImportError\";\n }\n}\n\n// The clause between `import` and `from` excludes `;` and both quote\n// characters so the lazy match cannot run past the end of its own statement.\n// Allowing it to would let a side-effect import (`import \"./bootstrap\";`) bind\n// to the `from` of the NEXT statement and disappear from the results.\nconst IMPORT_PATTERN =\n /(?:import|export)\\s+(?:[^\"';]*?\\sfrom\\s*)?[\"']([^\"']+)[\"']|import\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)/g;\n\n/**\n * Every module specifier a source file references — static, side-effect,\n * re-export and dynamic `import()` forms alike.\n *\n * Regex is sufficient and deliberate here: the input is code this builder wrote\n * itself, so it has no comments, no strings that look like imports, and no\n * syntax the pattern could misread. Parsing it with a full AST would buy\n * nothing and cost a dependency in the build path.\n */\nexport function extractSpecifiers(content: string): string[] {\n const specifiers: string[] = [];\n\n for (const match of content.matchAll(IMPORT_PATTERN)) {\n const specifier = match[1] ?? match[2];\n\n if (specifier) {\n specifiers.push(specifier);\n }\n }\n\n return specifiers;\n}\n\n/**\n * The package a specifier resolves to — `@scope/name` or `name`, ignoring any\n * deep path after it. Returns undefined for anything that is not a package:\n * relative paths, absolute paths, URLs, and Node builtins.\n */\nexport function resolvePackageName(specifier: string): string | undefined {\n if (\n specifier.startsWith(\".\") ||\n specifier.startsWith(\"/\") ||\n specifier.startsWith(\"node:\") ||\n specifier.includes(\"://\")\n ) {\n return undefined;\n }\n\n const segments = specifier.split(\"/\");\n const packageName = specifier.startsWith(\"@\") ? segments.slice(0, 2).join(\"/\") : segments[0];\n\n if (!packageName) {\n return undefined;\n }\n\n if (builtinModules.includes(packageName)) {\n return undefined;\n }\n\n return packageName;\n}\n\n/**\n * Find every import in generated code that the app does not declare.\n *\n * `dependencies` only, deliberately: the artifact this checks is a production\n * bundle, so a package the app lists as a dev dependency is not guaranteed to\n * be installed where it runs.\n */\nexport function findUndeclaredImports(\n files: GeneratedFile[],\n declaredDependencies: string[],\n): UndeclaredImport[] {\n const declared = new Set(declaredDependencies);\n const violations: UndeclaredImport[] = [];\n\n for (const { file, content } of files) {\n for (const specifier of extractSpecifiers(content)) {\n const packageName = resolvePackageName(specifier);\n\n if (!packageName || declared.has(packageName)) {\n continue;\n }\n\n violations.push({ file, specifier, packageName });\n }\n }\n\n return violations;\n}\n\n/**\n * Fail the build when generated code would import something the app cannot\n * resolve.\n *\n * This is the part that stops the defect class from returning. Rewriting the\n * one bad specifier fixes today's bundle; only the assertion stops the next\n * change to the generator from reintroducing it — under npm or yarn the mistake\n * is invisible, because hoisting resolves it by accident.\n *\n * @throws {UndeclaredGeneratedImportError} listing every violation found.\n */\nexport function assertGeneratedImportsAreDeclared(\n files: GeneratedFile[],\n declaredDependencies: string[],\n): void {\n const violations = findUndeclaredImports(files, declaredDependencies);\n\n if (violations.length > 0) {\n throw new UndeclaredGeneratedImportError(violations);\n }\n}\n"],"mappings":";;;;;;;;;AA0BA,IAAa,iCAAb,cAAoD,MAAM;CACxD,AAAO,YAAY,AAAgB,YAAgC;EACjE,MACE;GACE,0BAA0B,WAAW,OAAO;GAC5C;GACA,GAAG,WAAW,KACX,EAAE,MAAM,WAAW,kBAClB,KAAK,KAAK,MAAM,UAAU,eAAe,YAAY,6BACzD;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,IAAI,CACb;EAdiC;EAgBjC,KAAK,OAAO;CACd;AACF;AAMA,MAAM,iBACJ;;;;;;;;;;AAWF,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,aAAuB,CAAC;CAE9B,KAAK,MAAM,SAAS,QAAQ,SAAS,cAAc,GAAG;EACpD,MAAM,YAAY,MAAM,MAAM,MAAM;EAEpC,IAAI,WACF,WAAW,KAAK,SAAS;CAE7B;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,mBAAmB,WAAuC;CACxE,IACE,UAAU,WAAW,GAAG,KACxB,UAAU,WAAW,GAAG,KACxB,UAAU,WAAW,OAAO,KAC5B,UAAU,SAAS,KAAK,GAExB;CAGF,MAAM,WAAW,UAAU,MAAM,GAAG;CACpC,MAAM,cAAc,UAAU,WAAW,GAAG,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,SAAS;CAE1F,IAAI,CAAC,aACH;CAGF,IAAI,eAAe,SAAS,WAAW,GACrC;CAGF,OAAO;AACT;;;;;;;;AASA,SAAgB,sBACd,OACA,sBACoB;CACpB,MAAM,WAAW,IAAI,IAAI,oBAAoB;CAC7C,MAAM,aAAiC,CAAC;CAExC,KAAK,MAAM,EAAE,MAAM,aAAa,OAC9B,KAAK,MAAM,aAAa,kBAAkB,OAAO,GAAG;EAClD,MAAM,cAAc,mBAAmB,SAAS;EAEhD,IAAI,CAAC,eAAe,SAAS,IAAI,WAAW,GAC1C;EAGF,WAAW,KAAK;GAAE;GAAM;GAAW;EAAY,CAAC;CAClD;CAGF,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,kCACd,OACA,sBACM;CACN,MAAM,aAAa,sBAAsB,OAAO,oBAAoB;CAEpE,IAAI,WAAW,SAAS,GACtB,MAAM,IAAI,+BAA+B,UAAU;AAEvD"}
@@ -10,6 +10,8 @@ import { dedupe, runEmitContributions, runGenerateContributions } from "./build-
10
10
  import { writeDistBuildManifestAsync } from "./dist-build-manifest.mjs";
11
11
  import { nativeNodeModulesPlugin } from "./esbuild-plugins.mjs";
12
12
  import { assertEsbuildBinaryIsLinked } from "./esbuild-preflight.mjs";
13
+ import { collectRouteRegistrationSnapshot } from "./route-registration-snapshot.mjs";
14
+ import { clearOwnedRouteTypes } from "../dev-server/route-types-publisher.mjs";
13
15
  import { commitDistAsync, createTempOutputDir, rollbackDistAsync, stageDistAsync } from "./promote-dist.mjs";
14
16
  import { resolveBuildConfig } from "./resolve-build-config.mjs";
15
17
  import { tsconfigPathAliases } from "./tsconfig-path-aliases.mjs";
@@ -126,6 +128,7 @@ var ProductionBuilder = class {
126
128
  await this.generateCombinedFiles();
127
129
  await this.generateEntryPoint();
128
130
  await this.assertGeneratedImports();
131
+ await this.captureNamedApiRoutesForWeb();
129
132
  if (await this.runGenerateContributions()) await this.assertGeneratedImports();
130
133
  await this.bundle(tempOutDir);
131
134
  staged = await stageDistAsync(tempOutDir, finalOutDir);
@@ -159,10 +162,23 @@ var ProductionBuilder = class {
159
162
  return {
160
163
  productionDir: this.productionDir,
161
164
  appRoot: rootPath(),
162
- options: this.options
165
+ options: this.options,
166
+ namedApiRoutes: this.namedApiRoutes
163
167
  };
164
168
  }
165
169
  /**
170
+ * Web route declarations need application API registration, which cannot be
171
+ * inferred from generated files. Capture it once in a disposable child before
172
+ * contributors write build artifacts; no connector lifecycle runs there.
173
+ */
174
+ async captureNamedApiRoutesForWeb() {
175
+ if (!this.connectors.some((connector) => connector.name === "web")) {
176
+ await clearOwnedRouteTypes(rootPath());
177
+ return;
178
+ }
179
+ this.namedApiRoutes = (await collectRouteRegistrationSnapshot({ cwd: rootPath() })).routes;
180
+ }
181
+ /**
166
182
  * Drain the `generate` hooks and fold what they returned back into the
167
183
  * build.
168
184
  *