@warlock.js/core 4.1.10 → 4.1.12

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 (41) hide show
  1. package/esm/config/config-getter.mjs +5 -5
  2. package/esm/config/config-getter.mjs.map +1 -1
  3. package/esm/config/config-loader.mjs +2 -2
  4. package/esm/config/config-loader.mjs.map +1 -1
  5. package/esm/connectors/cache-connector.mjs +2 -2
  6. package/esm/connectors/cache-connector.mjs.map +1 -1
  7. package/esm/connectors/database-connector.mjs +2 -2
  8. package/esm/connectors/database-connector.mjs.map +1 -1
  9. package/esm/connectors/herald-connector.mjs +2 -2
  10. package/esm/connectors/herald-connector.mjs.map +1 -1
  11. package/esm/connectors/http-connector.mjs +5 -5
  12. package/esm/connectors/http-connector.mjs.map +1 -1
  13. package/esm/connectors/logger-connector.mjs +2 -2
  14. package/esm/connectors/logger-connector.mjs.map +1 -1
  15. package/esm/connectors/mail-connector.mjs +2 -2
  16. package/esm/connectors/mail-connector.mjs.map +1 -1
  17. package/esm/connectors/socket-connector.mjs +3 -3
  18. package/esm/connectors/socket-connector.mjs.map +1 -1
  19. package/esm/generations/add-command.action.mjs +0 -8
  20. package/esm/generations/add-command.action.mjs.map +1 -1
  21. package/esm/http/config.mjs +2 -2
  22. package/esm/http/config.mjs.map +1 -1
  23. package/esm/http/createHttpApplication.mjs +2 -2
  24. package/esm/http/createHttpApplication.mjs.map +1 -1
  25. package/esm/http/middleware/idempotency.middleware.mjs +5 -5
  26. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  27. package/esm/http/middleware/inject-request-context.mjs +2 -2
  28. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  29. package/esm/http/middleware/maintenance.middleware.mjs +4 -4
  30. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  31. package/esm/http/plugins.mjs +9 -9
  32. package/esm/http/plugins.mjs.map +1 -1
  33. package/esm/http/response.mjs +5 -5
  34. package/esm/http/response.mjs.map +1 -1
  35. package/esm/http/server.mjs +2 -2
  36. package/esm/http/server.mjs.map +1 -1
  37. package/esm/utils/paths.mjs +2 -2
  38. package/esm/utils/paths.mjs.map +1 -1
  39. package/esm/validation/validateAll.mjs +2 -2
  40. package/esm/validation/validateAll.mjs.map +1 -1
  41. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { t } from "./inject-request-context.mjs";
2
2
  import { HttpErrorCodes } from "../error-codes.mjs";
3
3
  import { buildIdempotencyCacheKey, hashBody, isValidIdempotencyKey } from "./utils/idempotency-key.mjs";
4
- import baseConfig from "@mongez/config";
4
+ import config from "@mongez/config";
5
5
  import { cache } from "@warlock.js/cache";
6
6
 
7
7
  //#region ../@warlock.js/core/src/http/middleware/idempotency.middleware.ts
@@ -43,10 +43,10 @@ const DEFAULT_METHODS = [
43
43
  */
44
44
  function idempotencyMiddleware(options = {}) {
45
45
  return async (request, response) => {
46
- const headerName = options.headerName || baseConfig.get("http.idempotency.headerName", "Idempotency-Key");
47
- const methods = options.methods || baseConfig.get("http.idempotency.methods", DEFAULT_METHODS);
48
- const ttl = options.ttl || baseConfig.get("http.idempotency.ttl", 86400);
49
- const driverName = options.driver || baseConfig.get("http.idempotency.driver");
46
+ const headerName = options.headerName || config.get("http.idempotency.headerName", "Idempotency-Key");
47
+ const methods = options.methods || config.get("http.idempotency.methods", DEFAULT_METHODS);
48
+ const ttl = options.ttl || config.get("http.idempotency.ttl", 86400);
49
+ const driverName = options.driver || config.get("http.idempotency.driver");
50
50
  if (!methods.includes(request.method.toUpperCase())) return;
51
51
  const idempotencyKey = request.header(headerName.toLowerCase());
52
52
  if (!idempotencyKey) return;
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency.middleware.mjs","names":["config"],"sources":["../../../../../../../@warlock.js/core/src/http/middleware/idempotency.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Response } from \"../response\";\nimport { t } from \"./inject-request-context\";\nimport {\n buildIdempotencyCacheKey,\n hashBody,\n isValidIdempotencyKey,\n} from \"./utils/idempotency-key\";\n\n/**\n * Options for the idempotency middleware.\n */\nexport type IdempotencyOptions = {\n /**\n * Cache TTL in seconds. Falls back to `http.idempotency.ttl`, then `86400` (24h).\n */\n ttl?: number;\n /**\n * Header name carrying the client's key. Falls back to\n * `http.idempotency.headerName`, then `\"Idempotency-Key\"`.\n */\n headerName?: string;\n /**\n * HTTP methods eligible for idempotency. Falls back to\n * `http.idempotency.methods`, then `[\"POST\",\"PUT\",\"PATCH\",\"DELETE\"]`.\n * Safe methods (GET/HEAD) are skipped regardless.\n */\n methods?: string[];\n /**\n * Cache driver name. Falls back to `http.idempotency.driver`, then the\n * default driver of the cache manager.\n */\n driver?: string;\n};\n\ntype CachedResponse = {\n status: number;\n body: unknown;\n bodyHash: string;\n contentType?: string;\n};\n\nconst DEFAULT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/**\n * Dedupe non-idempotent writes by an `Idempotency-Key` header — same key,\n * same body, within TTL → cached replay; same key, different body → 422\n * `IdempotencyKeyConflict`.\n *\n * **Must run after `authMiddleware`** — the cache key is scoped per-user\n * (`idem:{userType}:{userId}:{key}`) so user A can't replay user B's key.\n * Anonymous requests fall back to IP scope.\n *\n * The replay sets `Idempotent-Replay: true` on the response for easy\n * client-side / observability detection.\n *\n * Eligible methods default to POST/PUT/PATCH/DELETE. GET/HEAD pass through\n * even with the header set (RFC: safe methods are already idempotent).\n *\n * @example\n * import { authMiddleware } from \"@warlock.js/auth\";\n * import { middleware } from \"@warlock.js/core\";\n *\n * router.post(\"/orders\", createOrderController, {\n * middleware: [authMiddleware(\"client\"), middleware.idempotency()],\n * });\n *\n * router.post(\"/ai/summarize\", summarizeController, {\n * middleware: [\n * authMiddleware(\"client\"),\n * middleware.idempotency({ ttl: 60 * 60 }), // 1h is enough for client retries\n * ],\n * });\n */\nexport function idempotencyMiddleware(options: IdempotencyOptions = {}): Middleware {\n return async (request, response) => {\n const headerName =\n options.headerName ||\n config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods =\n options.methods || config.get(\"http.idempotency.methods\", DEFAULT_METHODS);\n const ttl = options.ttl || config.get(\"http.idempotency.ttl\", 86400);\n const driverName = options.driver || config.get(\"http.idempotency.driver\");\n\n if (!methods.includes(request.method.toUpperCase())) return;\n\n const idempotencyKey = request.header(headerName.toLowerCase());\n\n if (!idempotencyKey) return;\n\n if (!isValidIdempotencyKey(idempotencyKey)) {\n return response.badRequest({\n error: t(\"http.idempotencyKeyInvalid\"),\n errorCode: HttpErrorCodes.IdempotencyKeyInvalid,\n });\n }\n\n const cacheDriver = driverName ? await cache.use(driverName) : cache;\n const cacheKey = buildIdempotencyCacheKey(request, idempotencyKey);\n const bodyHash = hashBody(request.body);\n\n const cached = (await cacheDriver.get(cacheKey)) as CachedResponse | null;\n\n if (cached) {\n if (cached.bodyHash !== bodyHash) {\n return response.unprocessableEntity({\n error: t(\"http.idempotencyKeyConflict\"),\n errorCode: HttpErrorCodes.IdempotencyKeyConflict,\n });\n }\n\n response.header(\"Idempotent-Replay\", \"true\");\n\n return response.replay({\n status: cached.status,\n body: cached.body,\n contentType: cached.contentType,\n });\n }\n\n response.onSent((sentResponse: Response) => {\n // Don't cache server errors — clients should be able to retry past a 5xx.\n // 4xx are deterministic outcomes of the request, so caching is fine.\n if (sentResponse.statusCode >= 500) return;\n\n const sentContentType = sentResponse.contentType;\n\n cacheDriver.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 });\n };\n}\n"],"mappings":";;;;;;;AA6CA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,SAAS,aAAa;EAClC,MAAM,aACJ,QAAQ,cACRA,WAAO,IAAI,+BAA+B,iBAAiB;EAC7D,MAAM,UACJ,QAAQ,WAAWA,WAAO,IAAI,4BAA4B,eAAe;EAC3E,MAAM,MAAM,QAAQ,OAAOA,WAAO,IAAI,wBAAwB,KAAK;EACnE,MAAM,aAAa,QAAQ,UAAUA,WAAO,IAAI,yBAAyB;EAEzE,IAAI,CAAC,QAAQ,SAAS,QAAQ,OAAO,YAAY,CAAC,GAAG;EAErD,MAAM,iBAAiB,QAAQ,OAAO,WAAW,YAAY,CAAC;EAE9D,IAAI,CAAC,gBAAgB;EAErB,IAAI,CAAC,sBAAsB,cAAc,GACvC,OAAO,SAAS,WAAW;GACzB,OAAO,EAAE,4BAA4B;GACrC;EACF,CAAC;EAGH,MAAM,cAAc,aAAa,MAAM,MAAM,IAAI,UAAU,IAAI;EAC/D,MAAM,WAAW,yBAAyB,SAAS,cAAc;EACjE,MAAM,WAAW,SAAS,QAAQ,IAAI;EAEtC,MAAM,SAAU,MAAM,YAAY,IAAI,QAAQ;EAE9C,IAAI,QAAQ;GACV,IAAI,OAAO,aAAa,UACtB,OAAO,SAAS,oBAAoB;IAClC,OAAO,EAAE,6BAA6B;IACtC;GACF,CAAC;GAGH,SAAS,OAAO,qBAAqB,MAAM;GAE3C,OAAO,SAAS,OAAO;IACrB,QAAQ,OAAO;IACf,MAAM,OAAO;IACb,aAAa,OAAO;GACtB,CAAC;EACH;EAEA,SAAS,QAAQ,iBAA2B;GAG1C,IAAI,aAAa,cAAc,KAAK;GAEpC,MAAM,kBAAkB,aAAa;GAErC,YAAY,IACV,UACA;IACE,QAAQ,aAAa;IACrB,MAAM,aAAa;IACnB;IACA,aAAa,OAAO,oBAAoB,WAAW,kBAAkB;GACvE,GACA,GACF;EACF,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"idempotency.middleware.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/middleware/idempotency.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Response } from \"../response\";\nimport { t } from \"./inject-request-context\";\nimport {\n buildIdempotencyCacheKey,\n hashBody,\n isValidIdempotencyKey,\n} from \"./utils/idempotency-key\";\n\n/**\n * Options for the idempotency middleware.\n */\nexport type IdempotencyOptions = {\n /**\n * Cache TTL in seconds. Falls back to `http.idempotency.ttl`, then `86400` (24h).\n */\n ttl?: number;\n /**\n * Header name carrying the client's key. Falls back to\n * `http.idempotency.headerName`, then `\"Idempotency-Key\"`.\n */\n headerName?: string;\n /**\n * HTTP methods eligible for idempotency. Falls back to\n * `http.idempotency.methods`, then `[\"POST\",\"PUT\",\"PATCH\",\"DELETE\"]`.\n * Safe methods (GET/HEAD) are skipped regardless.\n */\n methods?: string[];\n /**\n * Cache driver name. Falls back to `http.idempotency.driver`, then the\n * default driver of the cache manager.\n */\n driver?: string;\n};\n\ntype CachedResponse = {\n status: number;\n body: unknown;\n bodyHash: string;\n contentType?: string;\n};\n\nconst DEFAULT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/**\n * Dedupe non-idempotent writes by an `Idempotency-Key` header — same key,\n * same body, within TTL → cached replay; same key, different body → 422\n * `IdempotencyKeyConflict`.\n *\n * **Must run after `authMiddleware`** — the cache key is scoped per-user\n * (`idem:{userType}:{userId}:{key}`) so user A can't replay user B's key.\n * Anonymous requests fall back to IP scope.\n *\n * The replay sets `Idempotent-Replay: true` on the response for easy\n * client-side / observability detection.\n *\n * Eligible methods default to POST/PUT/PATCH/DELETE. GET/HEAD pass through\n * even with the header set (RFC: safe methods are already idempotent).\n *\n * @example\n * import { authMiddleware } from \"@warlock.js/auth\";\n * import { middleware } from \"@warlock.js/core\";\n *\n * router.post(\"/orders\", createOrderController, {\n * middleware: [authMiddleware(\"client\"), middleware.idempotency()],\n * });\n *\n * router.post(\"/ai/summarize\", summarizeController, {\n * middleware: [\n * authMiddleware(\"client\"),\n * middleware.idempotency({ ttl: 60 * 60 }), // 1h is enough for client retries\n * ],\n * });\n */\nexport function idempotencyMiddleware(options: IdempotencyOptions = {}): Middleware {\n return async (request, response) => {\n const headerName =\n options.headerName ||\n config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods =\n options.methods || config.get(\"http.idempotency.methods\", DEFAULT_METHODS);\n const ttl = options.ttl || config.get(\"http.idempotency.ttl\", 86400);\n const driverName = options.driver || config.get(\"http.idempotency.driver\");\n\n if (!methods.includes(request.method.toUpperCase())) return;\n\n const idempotencyKey = request.header(headerName.toLowerCase());\n\n if (!idempotencyKey) return;\n\n if (!isValidIdempotencyKey(idempotencyKey)) {\n return response.badRequest({\n error: t(\"http.idempotencyKeyInvalid\"),\n errorCode: HttpErrorCodes.IdempotencyKeyInvalid,\n });\n }\n\n const cacheDriver = driverName ? await cache.use(driverName) : cache;\n const cacheKey = buildIdempotencyCacheKey(request, idempotencyKey);\n const bodyHash = hashBody(request.body);\n\n const cached = (await cacheDriver.get(cacheKey)) as CachedResponse | null;\n\n if (cached) {\n if (cached.bodyHash !== bodyHash) {\n return response.unprocessableEntity({\n error: t(\"http.idempotencyKeyConflict\"),\n errorCode: HttpErrorCodes.IdempotencyKeyConflict,\n });\n }\n\n response.header(\"Idempotent-Replay\", \"true\");\n\n return response.replay({\n status: cached.status,\n body: cached.body,\n contentType: cached.contentType,\n });\n }\n\n response.onSent((sentResponse: Response) => {\n // Don't cache server errors — clients should be able to retry past a 5xx.\n // 4xx are deterministic outcomes of the request, so caching is fine.\n if (sentResponse.statusCode >= 500) return;\n\n const sentContentType = sentResponse.contentType;\n\n cacheDriver.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 });\n };\n}\n"],"mappings":";;;;;;;AA6CA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,SAAS,aAAa;EAClC,MAAM,aACJ,QAAQ,cACR,OAAO,IAAI,+BAA+B,iBAAiB;EAC7D,MAAM,UACJ,QAAQ,WAAW,OAAO,IAAI,4BAA4B,eAAe;EAC3E,MAAM,MAAM,QAAQ,OAAO,OAAO,IAAI,wBAAwB,KAAK;EACnE,MAAM,aAAa,QAAQ,UAAU,OAAO,IAAI,yBAAyB;EAEzE,IAAI,CAAC,QAAQ,SAAS,QAAQ,OAAO,YAAY,CAAC,GAAG;EAErD,MAAM,iBAAiB,QAAQ,OAAO,WAAW,YAAY,CAAC;EAE9D,IAAI,CAAC,gBAAgB;EAErB,IAAI,CAAC,sBAAsB,cAAc,GACvC,OAAO,SAAS,WAAW;GACzB,OAAO,EAAE,4BAA4B;GACrC;EACF,CAAC;EAGH,MAAM,cAAc,aAAa,MAAM,MAAM,IAAI,UAAU,IAAI;EAC/D,MAAM,WAAW,yBAAyB,SAAS,cAAc;EACjE,MAAM,WAAW,SAAS,QAAQ,IAAI;EAEtC,MAAM,SAAU,MAAM,YAAY,IAAI,QAAQ;EAE9C,IAAI,QAAQ;GACV,IAAI,OAAO,aAAa,UACtB,OAAO,SAAS,oBAAoB;IAClC,OAAO,EAAE,6BAA6B;IACtC;GACF,CAAC;GAGH,SAAS,OAAO,qBAAqB,MAAM;GAE3C,OAAO,SAAS,OAAO;IACrB,QAAQ,OAAO;IACf,MAAM,OAAO;IACb,aAAa,OAAO;GACtB,CAAC;EACH;EAEA,SAAS,QAAQ,iBAA2B;GAG1C,IAAI,aAAa,cAAc,KAAK;GAEpC,MAAM,kBAAkB,aAAa;GAErC,YAAY,IACV,UACA;IACE,QAAQ,aAAa;IACrB,MAAM,aAAa;IACnB;IACA,aAAa,OAAO,oBAAoB,WAAW,kBAAkB;GACvE,GACA,GACF;EACF,CAAC;CACH;AACF"}
@@ -2,7 +2,7 @@ import { environment } from "../../utils/environment.mjs";
2
2
  import { requestContext, useRequestStore } from "../context/request-context.mjs";
3
3
  import "../../utils/index.mjs";
4
4
  import { BadRequestError, ForbiddenError, HttpError, ResourceNotFoundError, ServerError, UnAuthorizedError } from "../errors/errors.mjs";
5
- import baseConfig from "@mongez/config";
5
+ import config from "@mongez/config";
6
6
  import { trans } from "@mongez/localization";
7
7
  import { DatabaseWriterValidationError } from "@warlock.js/cascade";
8
8
  import { contextManager } from "@warlock.js/context";
@@ -22,7 +22,7 @@ import { contextManager } from "@warlock.js/context";
22
22
  * Skip when `http.requestId.enabled` is explicitly false.
23
23
  */
24
24
  function stampRequestIdHeader(request, response) {
25
- const requestIdConfig = baseConfig.get("http.requestId", {});
25
+ const requestIdConfig = config.get("http.requestId", {});
26
26
  if (requestIdConfig.enabled === false) return;
27
27
  const headerName = requestIdConfig.header || "X-Request-Id";
28
28
  response.header(headerName, request.id);
@@ -1 +1 @@
1
- {"version":3,"file":"inject-request-context.mjs","names":["config","requestContextInstance"],"sources":["../../../../../../../@warlock.js/core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\r\n * Request Context Middleware\r\n *\r\n * Creates a unified context for each request using the ContextManager.\r\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\r\n */\r\nimport { trans } from \"@mongez/localization\";\r\nimport { GenericObject } from \"@mongez/reinforcements\";\r\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\r\nimport { contextManager } from \"@warlock.js/context\";\r\nimport config from \"@mongez/config\";\r\nimport { environment } from \"../../utils\";\r\nimport {\r\n requestContext as requestContextInstance,\r\n useRequestStore,\r\n} from \"../context/request-context\";\r\nimport {\r\n BadRequestError,\r\n ForbiddenError,\r\n HttpError,\r\n ResourceNotFoundError,\r\n ServerError,\r\n UnAuthorizedError,\r\n} from \"../errors\";\r\nimport { type Request } from \"../request\";\r\nimport { type Response } from \"../response\";\r\nimport { type ReturnedResponse } from \"./../types\";\r\n\r\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\r\n\r\n/**\r\n * Echo `request.id` back as a response header so the FE / proxies / log\r\n * aggregators can correlate by the same value the server logs against.\r\n *\r\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\r\n * Skip when `http.requestId.enabled` is explicitly false.\r\n */\r\nfunction stampRequestIdHeader(request: Request, response: Response) {\r\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\r\n\r\n if (requestIdConfig.enabled === false) return;\r\n\r\n const headerName = requestIdConfig.header || \"X-Request-Id\";\r\n\r\n response.header(headerName, request.id);\r\n}\r\n\r\n/**\r\n * Create request store and execute middleware + handler\r\n *\r\n * Runs all registered contexts together using ContextManager.\r\n */\r\nexport function createRequestStore(\r\n request: Request<any>,\r\n response: Response,\r\n): Promise<ReturnedResponse> {\r\n stampRequestIdHeader(request, response);\r\n\r\n // Build all context stores using the immutable API\r\n // Each context defines its own store initialization via buildStore()\r\n const httpContextStore = contextManager.buildStores({ request, response });\r\n\r\n // Run all contexts together!\r\n return contextManager.runAll(httpContextStore, async () => {\r\n try {\r\n // Run middleware chain\r\n const result = await request.runMiddleware();\r\n\r\n if (result) {\r\n return result as ReturnedResponse;\r\n }\r\n\r\n // Execute route handler\r\n request.trigger(\"executingAction\", request.route);\r\n\r\n const handler = request.getHandler();\r\n\r\n request.log(\"Executing Handler\", \"info\");\r\n\r\n const output = await handler(request, response);\r\n\r\n request.log(\"Handler Executed Successfully\", \"success\");\r\n\r\n request.trigger(\"executedAction\", request.route);\r\n\r\n return output as ReturnedResponse;\r\n } catch (error: any) {\r\n request.log(`${error.constructor.name}: Request failed: ${error.message}`, \"error\");\r\n return handleRequestError(error, response);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Handle request errors\r\n * @internal\r\n */\r\nfunction handleRequestError(error: any, response: Response): ReturnedResponse {\r\n if (error instanceof HttpError) {\r\n const payload: GenericObject = {\r\n error: error.message,\r\n };\r\n if (error.payload) {\r\n payload.payload = error.payload;\r\n }\r\n\r\n if (environment() === \"development\") {\r\n payload.stack = error.stack;\r\n }\r\n\r\n return response.setStatusCode(error.status).send(payload);\r\n }\r\n\r\n if (error instanceof ResourceNotFoundError) {\r\n return response.notFound({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof UnAuthorizedError) {\r\n return response.unauthorized({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof ForbiddenError) {\r\n return response.forbidden({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof BadRequestError) {\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof DatabaseWriterValidationError) {\r\n return response.badRequest({\r\n errors: error.errors,\r\n });\r\n }\r\n\r\n if (error instanceof ServerError) {\r\n return response.serverError({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n console.log(error);\r\n\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n}\r\n\r\n/**\r\n * Translate a keyword (uses request context for locale)\r\n */\r\nexport function t(keyword: string, placeholders?: any) {\r\n return (\r\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\r\n trans(keyword, placeholders)\r\n );\r\n}\r\n\r\n/**\r\n * Get or compute a value from the request cache\r\n *\r\n * If the value exists in request, return it.\r\n * Otherwise, execute callback, store result in request, and return it.\r\n */\r\nexport async function fromRequest<T>(\r\n key: string,\r\n callback: (request?: Request) => Promise<T>,\r\n): Promise<T> {\r\n const { request } = useRequestStore();\r\n\r\n if (!request) return await callback();\r\n\r\n if (request[key]) return request[key];\r\n\r\n request[key] = await callback(request);\r\n\r\n return request[key];\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkBA,WAAO,IAAI,kBAAkB,CAAC,CAAwB;CAE9E,IAAI,gBAAgB,YAAY,OAAO;CAEvC,MAAM,aAAa,gBAAgB,UAAU;CAE7C,SAAS,OAAO,YAAY,QAAQ,EAAE;AACxC;;;;;;AAOA,SAAgB,mBACd,SACA,UAC2B;CAC3B,qBAAqB,SAAS,QAAQ;CAItC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAEF,MAAM,SAAS,MAAM,QAAQ,cAAc;GAE3C,IAAI,QACF,OAAO;GAIT,QAAQ,QAAQ,mBAAmB,QAAQ,KAAK;GAEhD,MAAM,UAAU,QAAQ,WAAW;GAEnC,QAAQ,IAAI,qBAAqB,MAAM;GAEvC,MAAM,SAAS,MAAM,QAAQ,SAAS,QAAQ;GAE9C,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAY;GACnB,QAAQ,IAAI,GAAG,MAAM,YAAY,KAAK,oBAAoB,MAAM,WAAW,OAAO;GAClF,OAAO,mBAAmB,OAAO,QAAQ;EAC3C;CACF,CAAC;AACH;;;;;AAMA,SAAS,mBAAmB,OAAY,UAAsC;CAC5E,IAAI,iBAAiB,WAAW;EAC9B,MAAM,UAAyB,EAC7B,OAAO,MAAM,QACf;EACA,IAAI,MAAM,SACR,QAAQ,UAAU,MAAM;EAG1B,IAAI,YAAY,MAAM,eACpB,QAAQ,QAAQ,MAAM;EAGxB,OAAO,SAAS,cAAc,MAAM,MAAM,EAAE,KAAK,OAAO;CAC1D;CAEA,IAAI,iBAAiB,uBACnB,OAAO,SAAS,SAAS;EACvB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,mBACnB,OAAO,SAAS,aAAa;EAC3B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,gBACnB,OAAO,SAAS,UAAU;EACxB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,iBACnB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAGH,IAAI,iBAAiB,aACnB,OAAO,SAAS,YAAY;EAC1B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,QAAQ,IAAI,KAAK;CAEjB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEC,eAAuB,WAAW,GAAG,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B;;;;;;;AAQA,eAAsB,YACpB,KACA,UACY;CACZ,MAAM,EAAE,YAAY,gBAAgB;CAEpC,IAAI,CAAC,SAAS,OAAO,MAAM,SAAS;CAEpC,IAAI,QAAQ,MAAM,OAAO,QAAQ;CAEjC,QAAQ,OAAO,MAAM,SAAS,OAAO;CAErC,OAAO,QAAQ;AACjB"}
1
+ {"version":3,"file":"inject-request-context.mjs","names":["requestContextInstance"],"sources":["../../../../../../../@warlock.js/core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\r\n * Request Context Middleware\r\n *\r\n * Creates a unified context for each request using the ContextManager.\r\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\r\n */\r\nimport { trans } from \"@mongez/localization\";\r\nimport { GenericObject } from \"@mongez/reinforcements\";\r\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\r\nimport { contextManager } from \"@warlock.js/context\";\r\nimport config from \"@mongez/config\";\r\nimport { environment } from \"../../utils\";\r\nimport {\r\n requestContext as requestContextInstance,\r\n useRequestStore,\r\n} from \"../context/request-context\";\r\nimport {\r\n BadRequestError,\r\n ForbiddenError,\r\n HttpError,\r\n ResourceNotFoundError,\r\n ServerError,\r\n UnAuthorizedError,\r\n} from \"../errors\";\r\nimport { type Request } from \"../request\";\r\nimport { type Response } from \"../response\";\r\nimport { type ReturnedResponse } from \"./../types\";\r\n\r\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\r\n\r\n/**\r\n * Echo `request.id` back as a response header so the FE / proxies / log\r\n * aggregators can correlate by the same value the server logs against.\r\n *\r\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\r\n * Skip when `http.requestId.enabled` is explicitly false.\r\n */\r\nfunction stampRequestIdHeader(request: Request, response: Response) {\r\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\r\n\r\n if (requestIdConfig.enabled === false) return;\r\n\r\n const headerName = requestIdConfig.header || \"X-Request-Id\";\r\n\r\n response.header(headerName, request.id);\r\n}\r\n\r\n/**\r\n * Create request store and execute middleware + handler\r\n *\r\n * Runs all registered contexts together using ContextManager.\r\n */\r\nexport function createRequestStore(\r\n request: Request<any>,\r\n response: Response,\r\n): Promise<ReturnedResponse> {\r\n stampRequestIdHeader(request, response);\r\n\r\n // Build all context stores using the immutable API\r\n // Each context defines its own store initialization via buildStore()\r\n const httpContextStore = contextManager.buildStores({ request, response });\r\n\r\n // Run all contexts together!\r\n return contextManager.runAll(httpContextStore, async () => {\r\n try {\r\n // Run middleware chain\r\n const result = await request.runMiddleware();\r\n\r\n if (result) {\r\n return result as ReturnedResponse;\r\n }\r\n\r\n // Execute route handler\r\n request.trigger(\"executingAction\", request.route);\r\n\r\n const handler = request.getHandler();\r\n\r\n request.log(\"Executing Handler\", \"info\");\r\n\r\n const output = await handler(request, response);\r\n\r\n request.log(\"Handler Executed Successfully\", \"success\");\r\n\r\n request.trigger(\"executedAction\", request.route);\r\n\r\n return output as ReturnedResponse;\r\n } catch (error: any) {\r\n request.log(`${error.constructor.name}: Request failed: ${error.message}`, \"error\");\r\n return handleRequestError(error, response);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Handle request errors\r\n * @internal\r\n */\r\nfunction handleRequestError(error: any, response: Response): ReturnedResponse {\r\n if (error instanceof HttpError) {\r\n const payload: GenericObject = {\r\n error: error.message,\r\n };\r\n if (error.payload) {\r\n payload.payload = error.payload;\r\n }\r\n\r\n if (environment() === \"development\") {\r\n payload.stack = error.stack;\r\n }\r\n\r\n return response.setStatusCode(error.status).send(payload);\r\n }\r\n\r\n if (error instanceof ResourceNotFoundError) {\r\n return response.notFound({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof UnAuthorizedError) {\r\n return response.unauthorized({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof ForbiddenError) {\r\n return response.forbidden({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof BadRequestError) {\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof DatabaseWriterValidationError) {\r\n return response.badRequest({\r\n errors: error.errors,\r\n });\r\n }\r\n\r\n if (error instanceof ServerError) {\r\n return response.serverError({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n console.log(error);\r\n\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n}\r\n\r\n/**\r\n * Translate a keyword (uses request context for locale)\r\n */\r\nexport function t(keyword: string, placeholders?: any) {\r\n return (\r\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\r\n trans(keyword, placeholders)\r\n );\r\n}\r\n\r\n/**\r\n * Get or compute a value from the request cache\r\n *\r\n * If the value exists in request, return it.\r\n * Otherwise, execute callback, store result in request, and return it.\r\n */\r\nexport async function fromRequest<T>(\r\n key: string,\r\n callback: (request?: Request) => Promise<T>,\r\n): Promise<T> {\r\n const { request } = useRequestStore();\r\n\r\n if (!request) return await callback();\r\n\r\n if (request[key]) return request[key];\r\n\r\n request[key] = await callback(request);\r\n\r\n return request[key];\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkB,OAAO,IAAI,kBAAkB,CAAC,CAAwB;CAE9E,IAAI,gBAAgB,YAAY,OAAO;CAEvC,MAAM,aAAa,gBAAgB,UAAU;CAE7C,SAAS,OAAO,YAAY,QAAQ,EAAE;AACxC;;;;;;AAOA,SAAgB,mBACd,SACA,UAC2B;CAC3B,qBAAqB,SAAS,QAAQ;CAItC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAEF,MAAM,SAAS,MAAM,QAAQ,cAAc;GAE3C,IAAI,QACF,OAAO;GAIT,QAAQ,QAAQ,mBAAmB,QAAQ,KAAK;GAEhD,MAAM,UAAU,QAAQ,WAAW;GAEnC,QAAQ,IAAI,qBAAqB,MAAM;GAEvC,MAAM,SAAS,MAAM,QAAQ,SAAS,QAAQ;GAE9C,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAY;GACnB,QAAQ,IAAI,GAAG,MAAM,YAAY,KAAK,oBAAoB,MAAM,WAAW,OAAO;GAClF,OAAO,mBAAmB,OAAO,QAAQ;EAC3C;CACF,CAAC;AACH;;;;;AAMA,SAAS,mBAAmB,OAAY,UAAsC;CAC5E,IAAI,iBAAiB,WAAW;EAC9B,MAAM,UAAyB,EAC7B,OAAO,MAAM,QACf;EACA,IAAI,MAAM,SACR,QAAQ,UAAU,MAAM;EAG1B,IAAI,YAAY,MAAM,eACpB,QAAQ,QAAQ,MAAM;EAGxB,OAAO,SAAS,cAAc,MAAM,MAAM,EAAE,KAAK,OAAO;CAC1D;CAEA,IAAI,iBAAiB,uBACnB,OAAO,SAAS,SAAS;EACvB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,mBACnB,OAAO,SAAS,aAAa;EAC3B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,gBACnB,OAAO,SAAS,UAAU;EACxB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,iBACnB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAGH,IAAI,iBAAiB,aACnB,OAAO,SAAS,YAAY;EAC1B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,QAAQ,IAAI,KAAK;CAEjB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEA,eAAuB,WAAW,GAAG,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B;;;;;;;AAQA,eAAsB,YACpB,KACA,UACY;CACZ,MAAM,EAAE,YAAY,gBAAgB;CAEpC,IAAI,CAAC,SAAS,OAAO,MAAM,SAAS;CAEpC,IAAI,QAAQ,MAAM,OAAO,QAAQ;CAEjC,QAAQ,OAAO,MAAM,SAAS,OAAO;CAErC,OAAO,QAAQ;AACjB"}
@@ -1,6 +1,6 @@
1
1
  import { t } from "./inject-request-context.mjs";
2
2
  import { HttpErrorCodes } from "../error-codes.mjs";
3
- import baseConfig from "@mongez/config";
3
+ import config from "@mongez/config";
4
4
 
5
5
  //#region ../@warlock.js/core/src/http/middleware/maintenance.middleware.ts
6
6
  function isAllowlisted(path, patterns) {
@@ -31,10 +31,10 @@ function isAllowlisted(path, patterns) {
31
31
  */
32
32
  function maintenanceMiddleware(options = {}) {
33
33
  return (request, response) => {
34
- if (!baseConfig.get("http.maintenance.enabled", false)) return;
35
- const allowlist = options.allowlist || baseConfig.get("http.maintenance.allowlist", ["/health"]);
34
+ if (!config.get("http.maintenance.enabled", false)) return;
35
+ const allowlist = options.allowlist || config.get("http.maintenance.allowlist", ["/health"]);
36
36
  if (isAllowlisted(request.path, allowlist)) return;
37
- const retryAfter = options.retryAfter || baseConfig.get("http.maintenance.retryAfter", 60);
37
+ const retryAfter = options.retryAfter || config.get("http.maintenance.retryAfter", 60);
38
38
  response.header("Retry-After", retryAfter);
39
39
  return response.serviceUnavailable({
40
40
  error: options.errorMessage || t("http.maintenance"),
@@ -1 +1 @@
1
- {"version":3,"file":"maintenance.middleware.mjs","names":["config"],"sources":["../../../../../../../@warlock.js/core/src/http/middleware/maintenance.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the maintenance middleware.\n */\nexport type MaintenanceOptions = {\n /**\n * Path prefixes (ending in `*`) or exact paths to bypass even when\n * maintenance is on. Falls back to `http.maintenance.allowlist`, then\n * `[\"/health\"]`.\n *\n * @example\n * allowlist: [\"/health\", \"/admin/*\", \"/webhooks/stripe\"]\n */\n allowlist?: string[];\n /**\n * Seconds advertised in the `Retry-After` header. Falls back to\n * `http.maintenance.retryAfter`, then `60`.\n */\n retryAfter?: number;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\nfunction isAllowlisted(path: string, patterns: string[]) {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return path.startsWith(pattern.slice(0, -1));\n }\n\n return path === pattern;\n });\n}\n\n/**\n * Return 503 + `Retry-After` for every request when `http.maintenance.enabled`\n * is true, except for paths matching the allowlist.\n *\n * Designed for app-wide registration via `http.middleware.all`. Toggled via\n * config — flipping the flag requires a restart (no runtime hot-flip yet).\n * Allowlist defaults to `[\"/health\"]` so health checks still pass during\n * planned downtime.\n *\n * @example\n * // src/config/http.ts\n * import { middleware } from \"@warlock.js/core\";\n *\n * export default {\n * maintenance: { enabled: env(\"MAINTENANCE_MODE\") === \"true\" },\n * middleware: {\n * all: [middleware.maintenance({ allowlist: [\"/health\", \"/admin/*\"] })],\n * },\n * };\n */\nexport function maintenanceMiddleware(options: MaintenanceOptions = {}): Middleware {\n return (request, response) => {\n const enabled = config.get(\"http.maintenance.enabled\", false);\n\n if (!enabled) return;\n\n const allowlist =\n options.allowlist || config.get(\"http.maintenance.allowlist\", [\"/health\"]);\n\n if (isAllowlisted(request.path, allowlist)) return;\n\n const retryAfter =\n options.retryAfter || config.get(\"http.maintenance.retryAfter\", 60);\n\n response.header(\"Retry-After\", retryAfter);\n\n return response.serviceUnavailable({\n error: options.errorMessage || t(\"http.maintenance\"),\n errorCode: HttpErrorCodes.Maintenance,\n });\n };\n}\n"],"mappings":";;;;;AA6BA,SAAS,cAAc,MAAc,UAAoB;CACvD,OAAO,SAAS,MAAM,YAAY;EAChC,IAAI,QAAQ,SAAS,GAAG,GACtB,OAAO,KAAK,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAG7C,OAAO,SAAS;CAClB,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,QAAQ,SAAS,aAAa;EAG5B,IAAI,CAFYA,WAAO,IAAI,4BAA4B,KAE5C,GAAG;EAEd,MAAM,YACJ,QAAQ,aAAaA,WAAO,IAAI,8BAA8B,CAAC,SAAS,CAAC;EAE3E,IAAI,cAAc,QAAQ,MAAM,SAAS,GAAG;EAE5C,MAAM,aACJ,QAAQ,cAAcA,WAAO,IAAI,+BAA+B,EAAE;EAEpE,SAAS,OAAO,eAAe,UAAU;EAEzC,OAAO,SAAS,mBAAmB;GACjC,OAAO,QAAQ,gBAAgB,EAAE,kBAAkB;GACnD;EACF,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"maintenance.middleware.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/middleware/maintenance.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the maintenance middleware.\n */\nexport type MaintenanceOptions = {\n /**\n * Path prefixes (ending in `*`) or exact paths to bypass even when\n * maintenance is on. Falls back to `http.maintenance.allowlist`, then\n * `[\"/health\"]`.\n *\n * @example\n * allowlist: [\"/health\", \"/admin/*\", \"/webhooks/stripe\"]\n */\n allowlist?: string[];\n /**\n * Seconds advertised in the `Retry-After` header. Falls back to\n * `http.maintenance.retryAfter`, then `60`.\n */\n retryAfter?: number;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\nfunction isAllowlisted(path: string, patterns: string[]) {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return path.startsWith(pattern.slice(0, -1));\n }\n\n return path === pattern;\n });\n}\n\n/**\n * Return 503 + `Retry-After` for every request when `http.maintenance.enabled`\n * is true, except for paths matching the allowlist.\n *\n * Designed for app-wide registration via `http.middleware.all`. Toggled via\n * config — flipping the flag requires a restart (no runtime hot-flip yet).\n * Allowlist defaults to `[\"/health\"]` so health checks still pass during\n * planned downtime.\n *\n * @example\n * // src/config/http.ts\n * import { middleware } from \"@warlock.js/core\";\n *\n * export default {\n * maintenance: { enabled: env(\"MAINTENANCE_MODE\") === \"true\" },\n * middleware: {\n * all: [middleware.maintenance({ allowlist: [\"/health\", \"/admin/*\"] })],\n * },\n * };\n */\nexport function maintenanceMiddleware(options: MaintenanceOptions = {}): Middleware {\n return (request, response) => {\n const enabled = config.get(\"http.maintenance.enabled\", false);\n\n if (!enabled) return;\n\n const allowlist =\n options.allowlist || config.get(\"http.maintenance.allowlist\", [\"/health\"]);\n\n if (isAllowlisted(request.path, allowlist)) return;\n\n const retryAfter =\n options.retryAfter || config.get(\"http.maintenance.retryAfter\", 60);\n\n response.header(\"Retry-After\", retryAfter);\n\n return response.serviceUnavailable({\n error: options.errorMessage || t(\"http.maintenance\"),\n errorCode: HttpErrorCodes.Maintenance,\n });\n };\n}\n"],"mappings":";;;;;AA6BA,SAAS,cAAc,MAAc,UAAoB;CACvD,OAAO,SAAS,MAAM,YAAY;EAChC,IAAI,QAAQ,SAAS,GAAG,GACtB,OAAO,KAAK,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAG7C,OAAO,SAAS;CAClB,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,QAAQ,SAAS,aAAa;EAG5B,IAAI,CAFY,OAAO,IAAI,4BAA4B,KAE5C,GAAG;EAEd,MAAM,YACJ,QAAQ,aAAa,OAAO,IAAI,8BAA8B,CAAC,SAAS,CAAC;EAE3E,IAAI,cAAc,QAAQ,MAAM,SAAS,GAAG;EAE5C,MAAM,aACJ,QAAQ,cAAc,OAAO,IAAI,+BAA+B,EAAE;EAEpE,SAAS,OAAO,eAAe,UAAU;EAEzC,OAAO,SAAS,mBAAmB;GACjC,OAAO,QAAQ,gBAAgB,EAAE,kBAAkB;GACnD;EACF,CAAC;CACH;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { rootPath } from "../utils/paths.mjs";
2
2
  import "../utils/index.mjs";
3
- import baseConfig from "@mongez/config";
3
+ import config from "@mongez/config";
4
4
  import fastifyMultipart from "@fastify/multipart";
5
5
 
6
6
  //#region ../@warlock.js/core/src/http/plugins.ts
@@ -10,25 +10,25 @@ const defaultCorsOptions = {
10
10
  };
11
11
  async function registerHttpPlugins(server) {
12
12
  server.register(import("@fastify/rate-limit"), {
13
- max: baseConfig.get("http.rateLimit.max", 60),
14
- timeWindow: baseConfig.get("http.rateLimit.duration", 60 * 1e3)
13
+ max: config.get("http.rateLimit.max", 60),
14
+ timeWindow: config.get("http.rateLimit.duration", 60 * 1e3)
15
15
  });
16
16
  const corsOptions = {
17
- ...baseConfig.get("http.cors", {}),
17
+ ...config.get("http.cors", {}),
18
18
  ...defaultCorsOptions
19
19
  };
20
20
  server.register(import("@fastify/cors"), corsOptions);
21
21
  server.register(fastifyMultipart, {
22
22
  attachFieldsToBody: true,
23
- limits: { fileSize: baseConfig.get("http.fileUploadLimit", 10 * 1024 * 1024) }
23
+ limits: { fileSize: config.get("http.fileUploadLimit", 10 * 1024 * 1024) }
24
24
  });
25
25
  server.register(import("@fastify/static"), {
26
- root: baseConfig.get("storage.publicRoot", rootPath("public")),
27
- prefix: baseConfig.get("storage.publicPrefix", "/public/")
26
+ root: config.get("storage.publicRoot", rootPath("public")),
27
+ prefix: config.get("storage.publicPrefix", "/public/")
28
28
  });
29
29
  server.register(import("@fastify/cookie"), {
30
- secret: baseConfig.get("http.cookies.secret"),
31
- parseOptions: baseConfig.get("http.cookies.options", {})
30
+ secret: config.get("http.cookies.secret"),
31
+ parseOptions: config.get("http.cookies.options", {})
32
32
  });
33
33
  }
34
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"plugins.mjs","names":["config"],"sources":["../../../../../../@warlock.js/core/src/http/plugins.ts"],"sourcesContent":["import type { FastifyCorsOptions } from \"@fastify/cors\";\nimport fastifyMultipart from \"@fastify/multipart\";\nimport config from \"@mongez/config\";\nimport { rootPath } from \"../utils\";\nimport type { FastifyInstance } from \"./server\";\n\nconst defaultCorsOptions: FastifyCorsOptions = {\n origin: \"*\",\n methods: \"*\",\n};\n\nexport async function registerHttpPlugins(server: FastifyInstance) {\n // 👇🏻 register rate-limit plugin\n server.register(import(\"@fastify/rate-limit\"), {\n // max requests per time window\n max: config.get(\"http.rateLimit.max\", 60),\n // maximum time that is will allow max requests\n timeWindow: config.get(\"http.rateLimit.duration\", 60 * 1000),\n });\n\n // 👇🏻 register cors plugin\n const corsOptions: FastifyCorsOptions | undefined = {\n ...config.get(\"http.cors\", {}),\n ...defaultCorsOptions,\n };\n\n server.register(import(\"@fastify/cors\"), corsOptions);\n\n // 👇🏻 import multipart plugin\n server.register(fastifyMultipart, {\n attachFieldsToBody: true,\n limits: {\n // file size could be up to 10MB\n fileSize: config.get(\"http.fileUploadLimit\", 10 * 1024 * 1024),\n },\n });\n\n server.register(import(\"@fastify/static\"), {\n root: config.get(\"storage.publicRoot\", rootPath(\"public\")),\n prefix: config.get(\"storage.publicPrefix\", \"/public/\"),\n });\n\n // 👇🏻 register cookie plugin\n server.register(import(\"@fastify/cookie\"), {\n secret: config.get(\"http.cookies.secret\"), // Optional: allow signed cookies\n parseOptions: config.get(\"http.cookies.options\", {}),\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,qBAAyC;CAC7C,QAAQ;CACR,SAAS;AACX;AAEA,eAAsB,oBAAoB,QAAyB;CAEjE,OAAO,SAAS,OAAO,wBAAwB;EAE7C,KAAKA,WAAO,IAAI,sBAAsB,EAAE;EAExC,YAAYA,WAAO,IAAI,2BAA2B,KAAK,GAAI;CAC7D,CAAC;CAGD,MAAM,cAA8C;EAClD,GAAGA,WAAO,IAAI,aAAa,CAAC,CAAC;EAC7B,GAAG;CACL;CAEA,OAAO,SAAS,OAAO,kBAAkB,WAAW;CAGpD,OAAO,SAAS,kBAAkB;EAChC,oBAAoB;EACpB,QAAQ,EAEN,UAAUA,WAAO,IAAI,wBAAwB,KAAK,OAAO,IAAI,EAC/D;CACF,CAAC;CAED,OAAO,SAAS,OAAO,oBAAoB;EACzC,MAAMA,WAAO,IAAI,sBAAsB,SAAS,QAAQ,CAAC;EACzD,QAAQA,WAAO,IAAI,wBAAwB,UAAU;CACvD,CAAC;CAGD,OAAO,SAAS,OAAO,oBAAoB;EACzC,QAAQA,WAAO,IAAI,qBAAqB;EACxC,cAAcA,WAAO,IAAI,wBAAwB,CAAC,CAAC;CACrD,CAAC;AACH"}
1
+ {"version":3,"file":"plugins.mjs","names":[],"sources":["../../../../../../@warlock.js/core/src/http/plugins.ts"],"sourcesContent":["import type { FastifyCorsOptions } from \"@fastify/cors\";\nimport fastifyMultipart from \"@fastify/multipart\";\nimport config from \"@mongez/config\";\nimport { rootPath } from \"../utils\";\nimport type { FastifyInstance } from \"./server\";\n\nconst defaultCorsOptions: FastifyCorsOptions = {\n origin: \"*\",\n methods: \"*\",\n};\n\nexport async function registerHttpPlugins(server: FastifyInstance) {\n // 👇🏻 register rate-limit plugin\n server.register(import(\"@fastify/rate-limit\"), {\n // max requests per time window\n max: config.get(\"http.rateLimit.max\", 60),\n // maximum time that is will allow max requests\n timeWindow: config.get(\"http.rateLimit.duration\", 60 * 1000),\n });\n\n // 👇🏻 register cors plugin\n const corsOptions: FastifyCorsOptions | undefined = {\n ...config.get(\"http.cors\", {}),\n ...defaultCorsOptions,\n };\n\n server.register(import(\"@fastify/cors\"), corsOptions);\n\n // 👇🏻 import multipart plugin\n server.register(fastifyMultipart, {\n attachFieldsToBody: true,\n limits: {\n // file size could be up to 10MB\n fileSize: config.get(\"http.fileUploadLimit\", 10 * 1024 * 1024),\n },\n });\n\n server.register(import(\"@fastify/static\"), {\n root: config.get(\"storage.publicRoot\", rootPath(\"public\")),\n prefix: config.get(\"storage.publicPrefix\", \"/public/\"),\n });\n\n // 👇🏻 register cookie plugin\n server.register(import(\"@fastify/cookie\"), {\n secret: config.get(\"http.cookies.secret\"), // Optional: allow signed cookies\n parseOptions: config.get(\"http.cookies.options\", {}),\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,qBAAyC;CAC7C,QAAQ;CACR,SAAS;AACX;AAEA,eAAsB,oBAAoB,QAAyB;CAEjE,OAAO,SAAS,OAAO,wBAAwB;EAE7C,KAAK,OAAO,IAAI,sBAAsB,EAAE;EAExC,YAAY,OAAO,IAAI,2BAA2B,KAAK,GAAI;CAC7D,CAAC;CAGD,MAAM,cAA8C;EAClD,GAAG,OAAO,IAAI,aAAa,CAAC,CAAC;EAC7B,GAAG;CACL;CAEA,OAAO,SAAS,OAAO,kBAAkB,WAAW;CAGpD,OAAO,SAAS,kBAAkB;EAChC,oBAAoB;EACpB,QAAQ,EAEN,UAAU,OAAO,IAAI,wBAAwB,KAAK,OAAO,IAAI,EAC/D;CACF,CAAC;CAED,OAAO,SAAS,OAAO,oBAAoB;EACzC,MAAM,OAAO,IAAI,sBAAsB,SAAS,QAAQ,CAAC;EACzD,QAAQ,OAAO,IAAI,wBAAwB,UAAU;CACvD,CAAC;CAGD,OAAO,SAAS,OAAO,oBAAoB;EACzC,QAAQ,OAAO,IAAI,qBAAqB;EACxC,cAAc,OAAO,IAAI,wBAAwB,CAAC,CAAC;CACrD,CAAC;AACH"}
@@ -1,7 +1,7 @@
1
1
  import { StorageFile } from "../storage/storage-file.mjs";
2
2
  import "../storage/index.mjs";
3
3
  import { renderReact } from "../react/index.mjs";
4
- import baseConfig from "@mongez/config";
4
+ import config from "@mongez/config";
5
5
  import { log } from "@warlock.js/logger";
6
6
  import path from "path";
7
7
  import { isIterable, isPlainObject, isScalar } from "@mongez/supportive-is";
@@ -175,7 +175,7 @@ var Response = class Response {
175
175
  * Make a log message
176
176
  */
177
177
  log(message, level = "info") {
178
- if (!baseConfig.get("http.log")) return;
178
+ if (!config.get("http.log")) return;
179
179
  log.log({
180
180
  module: "response",
181
181
  action: this.route.method + " " + this.route.path.replace("/*", "") + `:${this.request.id}`,
@@ -561,7 +561,7 @@ var Response = class Response {
561
561
  */
562
562
  cookie(name, value, options = {}) {
563
563
  const { raw, ...cookieOptions } = options;
564
- const defaultOptions = baseConfig.get("http.cookies.options", {});
564
+ const defaultOptions = config.get("http.cookies.options", {});
565
565
  const serializedValue = raw ? String(value) : JSON.stringify(value);
566
566
  this.baseResponse.setCookie(name, serializedValue, {
567
567
  ...defaultOptions,
@@ -576,7 +576,7 @@ var Response = class Response {
576
576
  * response.clearCookie('token', { path: '/' });
577
577
  */
578
578
  clearCookie(name, options) {
579
- const defaultOptions = baseConfig.get("http.cookies.options", {});
579
+ const defaultOptions = config.get("http.cookies.options", {});
580
580
  this.baseResponse.clearCookie(name, {
581
581
  ...defaultOptions,
582
582
  ...options
@@ -832,7 +832,7 @@ var Response = class Response {
832
832
  * Mark the response as failed
833
833
  */
834
834
  failedSchema(result) {
835
- const { errors, inputKey, inputError, status } = baseConfig.get("validation.response", {
835
+ const { errors, inputKey, inputError, status } = config.get("validation.response", {
836
836
  errors: "errors",
837
837
  inputKey: "input",
838
838
  inputError: "error",
@@ -1 +1 @@
1
- {"version":3,"file":"response.mjs","names":["config"],"sources":["../../../../../../@warlock.js/core/src/http/response.ts"],"sourcesContent":["import type { CookieSerializeOptions } from \"@fastify/cookie\";\r\nimport config from \"@mongez/config\";\r\nimport type { EventSubscription } from \"@mongez/events\";\r\nimport events from \"@mongez/events\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { isIterable, isPlainObject, isScalar } from \"@mongez/supportive-is\";\r\nimport type { LogLevel } from \"@warlock.js/logger\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { ValidationResult } from \"@warlock.js/seal\";\r\nimport type { FastifyReply } from \"fastify\";\r\nimport fs from \"fs\";\r\nimport mime from \"mime\";\r\nimport path from \"path\";\r\nimport type React from \"react\";\r\nimport { type ReactNode } from \"react\";\r\nimport type { Route } from \"../router\";\r\nimport { StorageFile } from \"../storage\";\r\nimport { renderReact } from \"./../react\";\r\nimport type { Request } from \"./request\";\r\nimport type { ResponseEvent, ResponseSSEController, ResponseStreamController } from \"./types\";\r\n\r\ntype CookieValue = string | number | boolean | Record<string, any> | Array<any>;\r\n\r\n/**\r\n * Cookie options accepted by `response.cookie()`.\r\n *\r\n * Extends Fastify's `CookieSerializeOptions` with `raw` — set to `true` to\r\n * skip the default `JSON.stringify` of the value and write it as-is. Use for\r\n * plain-string cookies (session tokens, opaque IDs) that shouldn't be JSON-quoted.\r\n *\r\n * When `raw: true`, non-string values are coerced via `String(value)`. The\r\n * read side (`request.cookie(name)`) tries `JSON.parse` first and falls back\r\n * to the raw string on parse failure, so round-tripping a raw string cookie\r\n * Just Works.\r\n */\r\nexport type CookieOptions = CookieSerializeOptions & {\r\n /**\r\n * Skip JSON.stringify and write the value as-is.\r\n *\r\n * @default false\r\n */\r\n raw?: boolean;\r\n};\r\n\r\nexport enum ResponseStatus {\r\n OK = 200,\r\n CREATED = 201,\r\n ACCEPTED = 202,\r\n MOVED_PERMANENTLY = 301,\r\n FOUND = 302,\r\n SEE_OTHER = 303,\r\n NOT_MODIFIED = 304,\r\n TEMPORARY_REDIRECT = 307,\r\n PERMANENT_REDIRECT = 308,\r\n NO_CONTENT = 204,\r\n BAD_REQUEST = 400,\r\n UNAUTHORIZED = 401,\r\n FORBIDDEN = 403,\r\n NOT_FOUND = 404,\r\n METHOD_NOT_ALLOWED = 405,\r\n CONFLICT = 409,\r\n TOO_MANY_REQUESTS = 429,\r\n INTERNAL_SERVER_ERROR = 500,\r\n SERVICE_UNAVAILABLE = 503,\r\n}\r\n\r\n/**\r\n * Options for sending files\r\n */\r\nexport type SendFileOptions = {\r\n cacheTime?: number;\r\n immutable?: boolean;\r\n inline?: boolean;\r\n filename?: string;\r\n};\r\n\r\n/**\r\n * Options for sending buffers\r\n */\r\nexport type SendBufferOptions = SendFileOptions & {\r\n contentType?: string;\r\n etag?: string;\r\n};\r\n\r\nexport class Response {\r\n /**\r\n * Current route\r\n */\r\n protected route!: Route;\r\n\r\n /**\r\n * Underlying Fastify reply — a public escape hatch to capabilities the\r\n * framework's high-level helpers don't yet cover.\r\n *\r\n * **Prefer framework methods first**: `response.send()`, `response.header()`,\r\n * `response.cookie()`, `response.sendFile()`, `response.stream()`, etc.\r\n * They wire status codes, content-type detection, the event lifecycle, and\r\n * the cache-pattern replay path correctly.\r\n *\r\n * **Reach for `baseResponse` only** when the framework genuinely lacks a\r\n * helper for what you need — and when you do, file an issue so we can add\r\n * it. Streaming and SSE are the precedent here: they bypass `send()`\r\n * deliberately because the framework didn't ship chunked-write support\r\n * natively at the time. Reaching here for non-streaming work means a\r\n * missing helper, not an answer.\r\n */\r\n public baseResponse!: FastifyReply;\r\n\r\n /**\r\n * Current status code\r\n */\r\n protected currentStatusCode = 200;\r\n\r\n /**\r\n * Current response body\r\n */\r\n protected currentBody: any;\r\n\r\n /**\r\n * Request object\r\n */\r\n public request!: Request;\r\n\r\n /**\r\n * Internal events related to this particular response object\r\n */\r\n protected events = new Map<string, any[]>();\r\n\r\n /**\r\n * Parsed body\r\n * This will return the parsed body of the response\r\n * Please note that if this property is called before the response is sent, it will return undefined\r\n */\r\n public parsedBody: any;\r\n\r\n /**\r\n * Get raw response\r\n */\r\n public get raw() {\r\n return this.baseResponse.raw;\r\n }\r\n\r\n /**\r\n * Get Current response body\r\n */\r\n public get body() {\r\n return this.currentBody;\r\n }\r\n\r\n /**\r\n * Set response body\r\n */\r\n public set body(body: any) {\r\n this.currentBody = body;\r\n }\r\n\r\n /**\r\n * Add event on sending response\r\n */\r\n public onSending(callback: any) {\r\n this.events.set(\"sending\", [...(this.events.get(\"sending\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Add event on sent response\r\n */\r\n public onSent(callback: any) {\r\n this.events.set(\"sent\", [...(this.events.get(\"sent\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the Fastify response object\r\n */\r\n public setResponse(response: FastifyReply) {\r\n this.baseResponse = response;\r\n\r\n // Listen to the 'finish' event to track when response is fully sent\r\n // This works for all response types: JSON, streams, buffers, files, etc.\r\n this.baseResponse.raw.once(\"finish\", () => {\r\n this.request.endTime = Date.now();\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Reset the response state\r\n */\r\n public reset() {\r\n this.route = {} as Route;\r\n this.currentBody = null;\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n /**\r\n * Set current route\r\n */\r\n public setRoute(route: Route) {\r\n this.route = route;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the content type\r\n */\r\n public get contentType() {\r\n return this.baseResponse.getHeader(\"Content-Type\");\r\n }\r\n\r\n /**\r\n * Set the content type\r\n */\r\n public setContentType(contentType: string) {\r\n this.baseResponse.header(\"Content-Type\", contentType);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the status code\r\n */\r\n public get statusCode(): number {\r\n return this.currentStatusCode ?? this.baseResponse.statusCode;\r\n }\r\n\r\n /**\r\n * Check if response status is ok\r\n */\r\n public get isOk() {\r\n return this.currentStatusCode >= 200 && this.currentStatusCode < 300;\r\n }\r\n\r\n /**\r\n * Check if the response has been sent\r\n */\r\n public get sent() {\r\n return this.baseResponse.sent;\r\n }\r\n\r\n /**\r\n * Add a listener to the response event\r\n */\r\n public static on(\r\n event: ResponseEvent,\r\n listener: (response: Response) => void,\r\n ): EventSubscription {\r\n return events.subscribe(`response.${event}`, listener);\r\n }\r\n\r\n /**\r\n * Trigger the response event\r\n */\r\n protected static async trigger(event: ResponseEvent, ...args: any[]) {\r\n // make a timeout to make sure the request events is executed first\r\n return new Promise((resolve) => {\r\n setTimeout(async () => {\r\n await events.triggerAllAsync(`response.${event}`, ...args);\r\n resolve(true);\r\n }, 0);\r\n });\r\n }\r\n\r\n /**\r\n * Parse body\r\n */\r\n protected async parseBody() {\r\n return await this.parse(this.currentBody);\r\n }\r\n\r\n /**\r\n * Parse the given value\r\n */\r\n public async parse(value: any): Promise<any> {\r\n // if it is a falsy value, return it\r\n if (!value || isScalar(value)) return value;\r\n\r\n // if it has a `toJSON` method, call it and await the result then return it\r\n if (value.toJSON) {\r\n value.request = this.request;\r\n return await value.toJSON();\r\n }\r\n\r\n // if it is iterable, an array or array-like object then parse each item\r\n if (isIterable(value)) {\r\n const values = Array.from(value);\r\n\r\n return Promise.all(\r\n values.map(async (item: any) => {\r\n return await this.parse(item);\r\n }),\r\n );\r\n }\r\n\r\n // if not plain object, then return it\r\n if (!isPlainObject(value)) {\r\n return value;\r\n }\r\n\r\n // loop over the object and check if the value and call `parse` on it\r\n for (const key in value) {\r\n const subValue = value[key];\r\n\r\n value[key] = await this.parse(subValue);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * Make a log message\r\n */\r\n public log(message: string, level: LogLevel = \"info\") {\r\n if (!config.get(\"http.log\")) return;\r\n\r\n log.log({\r\n module: \"response\",\r\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.request.id}`,\r\n message,\r\n type: level,\r\n context: {\r\n request: this.request,\r\n response: this,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Check if returning response is json\r\n */\r\n public get isJson() {\r\n return this.getHeader(\"Content-Type\") === \"application/json\";\r\n }\r\n\r\n /**\r\n * Send the response\r\n * @param data - Response data\r\n * @param statusCode - HTTP status code\r\n * @param triggerEvents - Whether to trigger response events (default: true)\r\n */\r\n public async send(data?: any, statusCode?: number, triggerEvents = true): Promise<Response> {\r\n // Defensive guard against double-send. The underlying Fastify reply silently\r\n // ignores subsequent sends once `sent === true`, which has historically hidden\r\n // middleware bugs (cache-pattern replay paths that returned `baseResponse.send`\r\n // ended up re-entering `Response.send` with the FastifyReply as the body).\r\n // Surfacing the misuse via `error`-level log makes the bug loud without\r\n // crashing production traffic.\r\n if (this.baseResponse.sent) {\r\n log.error(\r\n \"response\",\r\n \"send\",\r\n `send() called on already-sent response (request:${this.request?.id ?? \"unknown\"}) — likely a middleware bug`,\r\n );\r\n\r\n return this;\r\n }\r\n\r\n if (statusCode) {\r\n this.currentStatusCode = statusCode;\r\n }\r\n\r\n if (data === this) return this;\r\n\r\n if (data) {\r\n this.currentBody = data;\r\n }\r\n\r\n if (!this.currentStatusCode) {\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n this.log(\"Sending response\");\r\n // Auto-pick `application/json` only when no content-type was set by the caller.\r\n // This preserves explicit overrides (e.g. `application/vnd.api+json` from a\r\n // cache replay, `application/problem+json` from an RFC 7807 error response)\r\n // while keeping the convenience default for the common object-body path.\r\n if (Array.isArray(this.currentBody) || isPlainObject(this.currentBody)) {\r\n if (!this.baseResponse.getHeader(\"Content-Type\")) {\r\n this.setContentType(\"application/json\");\r\n }\r\n }\r\n\r\n if (triggerEvents) {\r\n await Response.trigger(\"sending\", this);\r\n\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isJson) {\r\n await Response.trigger(\"sendingJson\", this);\r\n for (const callback of this.events.get(\"sendingJson\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isOk) {\r\n await Response.trigger(\"sendingSuccessJson\", this);\r\n for (const callback of this.events.get(\"sendingSuccessJson\") || []) {\r\n await callback(this);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // parse the body and make sure it is transformed to sync data instead of async data\r\n if (typeof this.currentBody !== \"string\") {\r\n this.parsedBody = await this.parseBody();\r\n } else {\r\n this.parsedBody = data;\r\n }\r\n\r\n // Set the status first\r\n this.baseResponse.status(this.currentStatusCode);\r\n\r\n // Then send the response with the parsed body\r\n await this.baseResponse.send(this.parsedBody);\r\n\r\n this.log(\"Response sent\");\r\n\r\n if (triggerEvents) {\r\n // trigger the sent event\r\n Response.trigger(\"sent\", this);\r\n\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // trigger the success event if the status code is 2xx\r\n if (this.currentStatusCode >= 200 && this.currentStatusCode < 300) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // trigger the successCreate event if the status code is 201\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n // trigger the badRequest event if the status code is 400\r\n if (this.currentStatusCode === 400) {\r\n Response.trigger(\"badRequest\", this);\r\n }\r\n\r\n // trigger the unauthorized event if the status code is 401\r\n if (this.currentStatusCode === 401) {\r\n Response.trigger(\"unauthorized\", this);\r\n }\r\n\r\n // trigger the forbidden event if the status code is 403\r\n if (this.currentStatusCode === 403) {\r\n Response.trigger(\"forbidden\", this);\r\n }\r\n\r\n // trigger the notFound event if the status code is 404\r\n if (this.currentStatusCode === 404) {\r\n Response.trigger(\"notFound\", this);\r\n }\r\n\r\n // trigger the content too large event if the status code is 413\r\n if (this.currentStatusCode === 413) {\r\n Response.trigger(\"contentTooLarge\", this);\r\n }\r\n\r\n // trigger the throttled event if the status code is 429\r\n if (this.currentStatusCode === 429) {\r\n Response.trigger(\"throttled\", this);\r\n }\r\n\r\n // trigger the serverError event if the status code is 500\r\n if (this.currentStatusCode === 500) {\r\n Response.trigger(\"serverError\", this);\r\n }\r\n\r\n // trigger the error event if the status code is 4xx or 5xx\r\n if (this.currentStatusCode >= 400) {\r\n Response.trigger(\"error\", this);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Replay a previously-captured response shape — used by cache-pattern\r\n * middlewares (idempotency, response cache) to send a cached response\r\n * without re-running the controller.\r\n *\r\n * Preserves the cached status code, content-type, and any extra headers,\r\n * then sends the body through the standard `send()` pipeline so the full\r\n * event lifecycle still fires (`sent`, `success`, status-specific events).\r\n * That keeps cross-cutting observers (logger, metrics, audit) consistent\r\n * between fresh and replayed responses.\r\n *\r\n * @example\r\n * // Inside a cache-pattern middleware on HIT:\r\n * return response.header(\"X-Cache\", \"HIT\").replay({\r\n * status: cached.status,\r\n * body: cached.body,\r\n * contentType: cached.contentType,\r\n * });\r\n */\r\n public replay(cached: {\r\n status: number;\r\n body: unknown;\r\n contentType?: string;\r\n headers?: Record<string, string>;\r\n }): Promise<Response> {\r\n this.setStatusCode(cached.status);\r\n\r\n if (cached.contentType) {\r\n this.setContentType(cached.contentType);\r\n }\r\n\r\n if (cached.headers) {\r\n for (const [name, value] of Object.entries(cached.headers)) {\r\n this.header(name, value);\r\n }\r\n }\r\n\r\n return this.send(cached.body);\r\n }\r\n\r\n /**\r\n * Send html response\r\n */\r\n public html(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/html\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Render the given react component\r\n */\r\n public render(element: React.ReactElement | React.ComponentType, status = 200) {\r\n return this.setStatusCode(status).html(renderReact(element));\r\n }\r\n\r\n /**\r\n * Send xml response\r\n */\r\n public xml(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/xml\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Send plain text response\r\n */\r\n public text(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/plain\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Create a streaming response for progressive/chunked data sending\r\n *\r\n * This method allows you to send data in chunks and control when the response ends.\r\n * Perfect for Server-Sent Events (SSE), progressive rendering, or streaming large responses.\r\n *\r\n * @example\r\n * ```ts\r\n * const stream = response.stream(\"text/html\");\r\n * stream.send(\"<html><body>\");\r\n * stream.send(\"<h1>Hello</h1>\");\r\n * stream.render(<MyComponent />);\r\n * stream.send(\"</body></html>\");\r\n * stream.end();\r\n * ```\r\n *\r\n * @param contentType - The content type for the stream (default: \"text/plain\")\r\n * @returns Stream controller with send(), render(), and end() methods\r\n */\r\n public stream(contentType = \"text/plain\"): ResponseStreamController {\r\n // Set headers using the response API\r\n this.setContentType(contentType);\r\n this.header(\"Transfer-Encoding\", \"chunked\");\r\n this.header(\"Cache-Control\", \"no-cache\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Content-Type-Options\", \"nosniff\");\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const chunks: any[] = [];\r\n\r\n // Write headers to start the stream\r\n // Note: We use raw here because we need chunked encoding control\r\n // This is the only valid use case for bypassing Fastify's abstraction\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n return {\r\n /**\r\n * Send a chunk of data to the client\r\n * @param data - Data to send (string, Buffer, or any serializable data)\r\n */\r\n send: (data: any) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot send data: stream has already ended\");\r\n }\r\n\r\n this.baseResponse.raw.write(data);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Render a React component and send it as a chunk\r\n * @param element - React element or component to render\r\n */\r\n render: (element: ReactNode) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot render: stream has already ended\");\r\n }\r\n\r\n const html = renderReact(element);\r\n chunks.push(html);\r\n this.baseResponse.raw.write(html);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * End the stream and trigger completion events\r\n */\r\n end: () => {\r\n if (isEnded) {\r\n return this;\r\n }\r\n\r\n isEnded = true;\r\n\r\n // Store the streamed content for logging/debugging\r\n this.currentBody = chunks;\r\n this.parsedBody = chunks;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"Stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // Trigger status-specific events\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Create a Server-Sent Events (SSE) stream\r\n *\r\n * SSE is a standard for pushing real-time updates from server to client.\r\n * Perfect for live notifications, progress updates, or real-time data feeds.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n *\r\n * // Send events\r\n * sse.send(\"message\", { text: \"Hello!\" });\r\n * sse.send(\"notification\", { type: \"info\", message: \"Update available\" }, \"msg-123\");\r\n *\r\n * // Keep connection alive\r\n * const keepAlive = setInterval(() => sse.comment(\"ping\"), 30000);\r\n *\r\n * // Clean up when done\r\n * clearInterval(keepAlive);\r\n * sse.end();\r\n * ```\r\n *\r\n * @returns SSE controller with send(), comment(), and end() methods\r\n */\r\n public sse(): ResponseSSEController {\r\n // Set SSE-specific headers\r\n this.setContentType(\"text/event-stream\");\r\n this.header(\"Cache-Control\", \"no-cache, no-store, must-revalidate\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Accel-Buffering\", \"no\"); // Disable nginx buffering\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting SSE stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const events: any[] = [];\r\n const disconnectHandlers: Array<() => void> = [];\r\n\r\n // Write headers to start the stream\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n // Detect client disconnect — set isEnded silently and invoke cleanup handlers.\r\n // Without this, background jobs keep writing to a dead socket after the client drops.\r\n this.baseResponse.raw.on(\"close\", () => {\r\n if (!isEnded) {\r\n isEnded = true;\r\n this.log(\"SSE client disconnected\");\r\n for (const handler of disconnectHandlers) {\r\n handler();\r\n }\r\n }\r\n });\r\n\r\n const controller: ResponseSSEController = {\r\n /**\r\n * Send an SSE event\r\n * @param event - Event name (e.g., \"message\", \"chunk\", \"done\")\r\n * @param data - Event data (will be JSON stringified)\r\n * @param id - Optional event ID for client-side Last-Event-ID tracking (reconnect support)\r\n */\r\n send: (event: string, data: any, id?: string): ResponseSSEController => {\r\n // Silent no-op after disconnect — background jobs should not crash when\r\n // the client drops mid-stream. The onDisconnect handler handles cleanup.\r\n if (isEnded) return controller;\r\n\r\n let message = \"\";\r\n if (id) message += `id: ${id}\\n`;\r\n message += `event: ${event}\\n`;\r\n message += `data: ${JSON.stringify(data)}\\n\\n`;\r\n\r\n events.push({ event, data, id });\r\n this.baseResponse.raw.write(message);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Send a comment (keeps connection alive, invisible to client)\r\n * Useful for preventing timeout on long-lived connections\r\n * @param text - Comment text\r\n */\r\n comment: (text: string): ResponseSSEController => {\r\n // Silent no-op after disconnect\r\n if (isEnded) return controller;\r\n\r\n this.baseResponse.raw.write(`: ${text}\\n\\n`);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * End the SSE stream and trigger completion events\r\n */\r\n end: (): ResponseSSEController => {\r\n if (isEnded) return controller;\r\n\r\n isEnded = true;\r\n\r\n // Store the events for logging/debugging\r\n this.currentBody = events;\r\n this.parsedBody = events;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"SSE stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Register a handler to be called when the client disconnects.\r\n * Use this to clean up EventEmitter listeners, cancel background jobs, etc.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n * const listener = (chunk) => sse.send(\"chunk\", { chunk });\r\n * eventBus.on(aiMessageId, listener);\r\n * sse.onDisconnect(() => eventBus.off(aiMessageId, listener));\r\n * ```\r\n */\r\n onDisconnect: (handler: () => void): ResponseSSEController => {\r\n disconnectHandlers.push(handler);\r\n return controller;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended (either via end() or client disconnect)\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n\r\n return controller;\r\n }\r\n\r\n /**\r\n * Set the status code\r\n */\r\n public setStatusCode(statusCode: number) {\r\n this.currentStatusCode = statusCode;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Redirect the user to another route\r\n */\r\n public redirect(url: string, statusCode = 302) {\r\n this.baseResponse.redirect(url, statusCode);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Permanent redirect\r\n */\r\n public permanentRedirect(url: string) {\r\n this.baseResponse.redirect(url, 301);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the response time\r\n */\r\n public getResponseTime() {\r\n return this.baseResponse.elapsedTime;\r\n }\r\n\r\n /**\r\n * Remove a specific header\r\n */\r\n public removeHeader(key: string) {\r\n this.baseResponse.removeHeader(key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get a specific header\r\n */\r\n public getHeader(key: string) {\r\n return this.baseResponse.getHeader(key);\r\n }\r\n\r\n /**\r\n * Get the response headers\r\n */\r\n public getHeaders() {\r\n return this.baseResponse.getHeaders();\r\n }\r\n\r\n /**\r\n * Set multiple headers\r\n */\r\n public headers(headers: Record<string, string>) {\r\n this.baseResponse.headers(headers);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the response header\r\n */\r\n public header(key: string, value: any) {\r\n this.baseResponse.header(key, value);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set a cookie on the response.\r\n *\r\n * Values are JSON-stringified by default so structured cookies round-trip\r\n * cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the\r\n * JSON wrapping for plain-string cookies (session tokens, opaque IDs).\r\n *\r\n * @example\r\n * // JSON-wrapped (default) — round-trips with request.cookie()\r\n * response.cookie(\"prefs\", { theme: \"dark\" }, { maxAge: 3600, httpOnly: true });\r\n *\r\n * @example\r\n * // Raw string — no JSON quoting; useful for tokens / opaque IDs\r\n * response.cookie(\"session\", \"abc.def.ghi\", { raw: true, httpOnly: true });\r\n */\r\n public cookie(name: string, value: CookieValue, options: CookieOptions = {}) {\r\n const { raw, ...cookieOptions } = options;\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n const serializedValue = raw ? String(value) : JSON.stringify(value);\r\n\r\n this.baseResponse.setCookie(name, serializedValue, {\r\n ...defaultOptions,\r\n ...cookieOptions,\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Clear a cookie from the response\r\n *\r\n * @example\r\n * response.clearCookie('token', { path: '/' });\r\n */\r\n public clearCookie(name: string, options?: CookieSerializeOptions) {\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n this.baseResponse.clearCookie(name, { ...defaultOptions, ...options });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Alias to header method\r\n */\r\n public setHeader(key: string, value: any) {\r\n return this.header(key, value);\r\n }\r\n\r\n /**\r\n * Send an error response with status code 500\r\n */\r\n public serverError(data: any) {\r\n return this.send(data, 500);\r\n }\r\n\r\n /**\r\n * Send a forbidden response with status code 403\r\n */\r\n public forbidden(\r\n data: any = {\r\n error: \"You are not allowed to access this resource, FORBIDDEN\",\r\n },\r\n ) {\r\n return this.send(data, 403);\r\n }\r\n\r\n /**\r\n * Send a service unavailable response with status code 503\r\n */\r\n public serviceUnavailable(data: any) {\r\n return this.send(data, 503);\r\n }\r\n\r\n /**\r\n * Send an unauthorized response with status code 401\r\n */\r\n public unauthorized(\r\n data: any = {\r\n error: \"unauthorized\",\r\n },\r\n ) {\r\n return this.send(data, 401);\r\n }\r\n\r\n /**\r\n * Send a not found response with status code 404\r\n */\r\n public notFound(\r\n data: any = {\r\n error: \"notFound\",\r\n },\r\n ) {\r\n return this.send(data, 404);\r\n }\r\n\r\n /**\r\n * Send a bad request response with status code 400\r\n */\r\n public badRequest(data: any) {\r\n return this.send(data, 400);\r\n }\r\n\r\n /**\r\n * Send a content too large response with status code 413\r\n */\r\n public contentTooLarge(data: any) {\r\n return this.send(data, 413);\r\n }\r\n\r\n /**\r\n * Send a success response with status code 201\r\n */\r\n public successCreate(data: any) {\r\n return this.send(data, 201);\r\n }\r\n\r\n /**\r\n * Send a success response\r\n */\r\n public success(data: any = { success: true }) {\r\n return this.send(data);\r\n }\r\n\r\n /**\r\n * Send a no content response with status code 204\r\n */\r\n public noContent() {\r\n return this.baseResponse.status(204).send();\r\n }\r\n\r\n /**\r\n * Send an accepted response with status code 202\r\n * Used for async operations that have been accepted but not yet processed\r\n */\r\n public accepted(data: any = { message: \"Request accepted for processing\" }) {\r\n return this.send(data, 202);\r\n }\r\n\r\n /**\r\n * Send a conflict response with status code 409\r\n */\r\n public conflict(data: any = { error: \"Resource conflict\" }) {\r\n return this.send(data, 409);\r\n }\r\n\r\n /**\r\n * Send a too many requests response with status code 429\r\n */\r\n public tooManyRequests(data: any) {\r\n return this.send(data, 429);\r\n }\r\n\r\n /**\r\n * Send an unprocessable entity response with status code 422\r\n * Used for semantic validation errors\r\n */\r\n public unprocessableEntity(data: any) {\r\n return this.send(data, 422);\r\n }\r\n\r\n /**\r\n * Apply response options (cache, disposition, etag)\r\n * Shared helper for sendFile and sendBuffer\r\n */\r\n private applyResponseOptions(options: SendBufferOptions, defaultFilename?: string): boolean {\r\n // Set content type if provided\r\n if (options.contentType) {\r\n this.baseResponse.type(options.contentType);\r\n }\r\n\r\n // Set cache headers if specified\r\n if (options.cacheTime) {\r\n const cacheControl = options.immutable\r\n ? `public, max-age=${options.cacheTime}, immutable`\r\n : `public, max-age=${options.cacheTime}`;\r\n this.header(\"Cache-Control\", cacheControl);\r\n this.header(\"Expires\", new Date(Date.now() + options.cacheTime * 1000).toUTCString());\r\n }\r\n\r\n // Set ETag if provided (for conditional requests)\r\n if (options.etag) {\r\n this.header(\"ETag\", options.etag);\r\n\r\n // Check If-None-Match for conditional request\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n if (ifNoneMatch && ifNoneMatch === options.etag) {\r\n this.log(\"Content not modified (ETag match), sending 304\");\r\n this.baseResponse.status(304).send();\r\n return true; // Indicates 304 was sent\r\n }\r\n }\r\n\r\n // Set Content-Disposition if inline or filename is specified\r\n if (options.inline !== undefined || options.filename) {\r\n const disposition = options.inline ? \"inline\" : \"attachment\";\r\n const filename = options.filename || defaultFilename || \"file\";\r\n this.header(\"Content-Disposition\", `${disposition}; filename=\\\"${filename}\\\"`);\r\n }\r\n\r\n return false; // No 304 sent\r\n }\r\n\r\n /**\r\n * Send a file as a response\r\n */\r\n public async sendFile(filePath: string | StorageFile, options?: number | SendFileOptions) {\r\n if (filePath instanceof StorageFile) {\r\n filePath = filePath.absolutePath!;\r\n }\r\n\r\n this.log(`Sending file: ${filePath}`);\r\n\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get file stats for ETag and Last-Modified\r\n const stats = await fs.promises.stat(filePath);\r\n const lastModified = stats.mtime;\r\n\r\n // Generate ETag based on file size and modification time\r\n const etag = `\"${stats.size}-${stats.mtime.getTime()}\"`;\r\n\r\n // Set Last-Modified header\r\n this.header(\"Last-Modified\", lastModified.toUTCString());\r\n this.header(\"ETag\", etag);\r\n\r\n // Set content type\r\n const contentType = this.getFileContentType(filePath);\r\n this.baseResponse.type(contentType);\r\n\r\n // Apply common response options (cache, disposition)\r\n const defaultFilename = path.basename(filePath);\r\n const sent304 = this.applyResponseOptions({ ...opts, etag, contentType }, defaultFilename);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Check conditional request headers\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n const ifModifiedSince = this.request.header(\"if-modified-since\");\r\n\r\n // Handle If-None-Match (ETag validation)\r\n if (ifNoneMatch && ifNoneMatch === etag) {\r\n this.log(\"File not modified (ETag match), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n\r\n // Handle If-Modified-Since (Last-Modified validation)\r\n if (ifModifiedSince) {\r\n const modifiedSinceDate = new Date(ifModifiedSince);\r\n if (lastModified.getTime() <= modifiedSinceDate.getTime()) {\r\n this.log(\"File not modified (Last-Modified check), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n }\r\n\r\n // Use streaming for efficient file sending\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error sending file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error sending file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Send buffer as a response\r\n * Useful for dynamically generated content (e.g., resized images, generated PDFs)\r\n */\r\n public sendBuffer(buffer: Buffer, options?: number | SendBufferOptions) {\r\n this.log(\"Sending buffer\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Apply common response options (cache, disposition, etag)\r\n const sent304 = this.applyResponseOptions(opts);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send an Image instance as a response\r\n * Automatically detects image format and sets content type\r\n */\r\n public async sendImage(\r\n image: any, // Type as 'any' to avoid circular dependency with Image class\r\n options?: number | (Omit<SendBufferOptions, \"contentType\"> & { contentType?: string }),\r\n ) {\r\n this.log(\"Sending image\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get image metadata to determine format\r\n const metadata = await image.metadata();\r\n const format = metadata.format || \"jpeg\";\r\n\r\n // Convert image to buffer\r\n const buffer = await image.toBuffer();\r\n\r\n // Auto-set content type if not provided\r\n const contentType = opts.contentType || `image/${format}`;\r\n\r\n // Auto-generate ETag if not provided\r\n // Format: \"format-widthxheight-size\" (e.g., \"jpeg-800x600-45231\")\r\n // This catches changes in dimensions, quality, filters, and format\r\n if (!opts.etag) {\r\n const width = metadata.width || 0;\r\n const height = metadata.height || 0;\r\n opts.etag = `\"${format}-${width}x${height}-${buffer.length}\"`;\r\n }\r\n\r\n // Apply common response options with auto-detected content type\r\n const sent304 = this.applyResponseOptions({ ...opts, contentType });\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send file and cache it\r\n * Cache time in seconds\r\n * Cache time will be one year\r\n */\r\n public sendCachedFile(path: string | StorageFile, cacheTime = 31536000) {\r\n return this.sendFile(path, cacheTime);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public download(path: string, filename?: string) {\r\n return this.downloadFile(path, filename);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public async downloadFile(filePath: string, filename?: string) {\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n if (!filename) {\r\n filename = path.basename(filePath);\r\n }\r\n\r\n this.baseResponse.header(\"Content-Disposition\", `attachment; filename=\"${filename}\"`);\r\n\r\n // this.baseResponse.header(\"Content-Type\", this.getFileContentType(filePath));\r\n this.baseResponse.header(\"Content-Type\", \"application/octet-stream\");\r\n\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file for download: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error downloading file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error downloading file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Get content type of the given path\r\n */\r\n public getFileContentType(filePath: string) {\r\n const type = mime.getType(filePath) || \"application/octet-stream\";\r\n return type;\r\n }\r\n\r\n /**\r\n * Mark the response as failed\r\n */\r\n public failedSchema(result: ValidationResult) {\r\n const { errors, inputKey, inputError, status } = config.get(\"validation.response\", {\r\n errors: \"errors\",\r\n inputKey: \"input\",\r\n inputError: \"error\",\r\n status: 400,\r\n });\r\n\r\n log.error(\"request\", \"validation\", `${this.request.id} - Validation failed`);\r\n\r\n return this.send(\r\n {\r\n [errors]: result.errors.map((error) => ({\r\n [inputKey]: error.input,\r\n [inputError]: error.error,\r\n })),\r\n },\r\n status,\r\n );\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"response.mjs","names":[],"sources":["../../../../../../@warlock.js/core/src/http/response.ts"],"sourcesContent":["import type { CookieSerializeOptions } from \"@fastify/cookie\";\r\nimport config from \"@mongez/config\";\r\nimport type { EventSubscription } from \"@mongez/events\";\r\nimport events from \"@mongez/events\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { isIterable, isPlainObject, isScalar } from \"@mongez/supportive-is\";\r\nimport type { LogLevel } from \"@warlock.js/logger\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { ValidationResult } from \"@warlock.js/seal\";\r\nimport type { FastifyReply } from \"fastify\";\r\nimport fs from \"fs\";\r\nimport mime from \"mime\";\r\nimport path from \"path\";\r\nimport type React from \"react\";\r\nimport { type ReactNode } from \"react\";\r\nimport type { Route } from \"../router\";\r\nimport { StorageFile } from \"../storage\";\r\nimport { renderReact } from \"./../react\";\r\nimport type { Request } from \"./request\";\r\nimport type { ResponseEvent, ResponseSSEController, ResponseStreamController } from \"./types\";\r\n\r\ntype CookieValue = string | number | boolean | Record<string, any> | Array<any>;\r\n\r\n/**\r\n * Cookie options accepted by `response.cookie()`.\r\n *\r\n * Extends Fastify's `CookieSerializeOptions` with `raw` — set to `true` to\r\n * skip the default `JSON.stringify` of the value and write it as-is. Use for\r\n * plain-string cookies (session tokens, opaque IDs) that shouldn't be JSON-quoted.\r\n *\r\n * When `raw: true`, non-string values are coerced via `String(value)`. The\r\n * read side (`request.cookie(name)`) tries `JSON.parse` first and falls back\r\n * to the raw string on parse failure, so round-tripping a raw string cookie\r\n * Just Works.\r\n */\r\nexport type CookieOptions = CookieSerializeOptions & {\r\n /**\r\n * Skip JSON.stringify and write the value as-is.\r\n *\r\n * @default false\r\n */\r\n raw?: boolean;\r\n};\r\n\r\nexport enum ResponseStatus {\r\n OK = 200,\r\n CREATED = 201,\r\n ACCEPTED = 202,\r\n MOVED_PERMANENTLY = 301,\r\n FOUND = 302,\r\n SEE_OTHER = 303,\r\n NOT_MODIFIED = 304,\r\n TEMPORARY_REDIRECT = 307,\r\n PERMANENT_REDIRECT = 308,\r\n NO_CONTENT = 204,\r\n BAD_REQUEST = 400,\r\n UNAUTHORIZED = 401,\r\n FORBIDDEN = 403,\r\n NOT_FOUND = 404,\r\n METHOD_NOT_ALLOWED = 405,\r\n CONFLICT = 409,\r\n TOO_MANY_REQUESTS = 429,\r\n INTERNAL_SERVER_ERROR = 500,\r\n SERVICE_UNAVAILABLE = 503,\r\n}\r\n\r\n/**\r\n * Options for sending files\r\n */\r\nexport type SendFileOptions = {\r\n cacheTime?: number;\r\n immutable?: boolean;\r\n inline?: boolean;\r\n filename?: string;\r\n};\r\n\r\n/**\r\n * Options for sending buffers\r\n */\r\nexport type SendBufferOptions = SendFileOptions & {\r\n contentType?: string;\r\n etag?: string;\r\n};\r\n\r\nexport class Response {\r\n /**\r\n * Current route\r\n */\r\n protected route!: Route;\r\n\r\n /**\r\n * Underlying Fastify reply — a public escape hatch to capabilities the\r\n * framework's high-level helpers don't yet cover.\r\n *\r\n * **Prefer framework methods first**: `response.send()`, `response.header()`,\r\n * `response.cookie()`, `response.sendFile()`, `response.stream()`, etc.\r\n * They wire status codes, content-type detection, the event lifecycle, and\r\n * the cache-pattern replay path correctly.\r\n *\r\n * **Reach for `baseResponse` only** when the framework genuinely lacks a\r\n * helper for what you need — and when you do, file an issue so we can add\r\n * it. Streaming and SSE are the precedent here: they bypass `send()`\r\n * deliberately because the framework didn't ship chunked-write support\r\n * natively at the time. Reaching here for non-streaming work means a\r\n * missing helper, not an answer.\r\n */\r\n public baseResponse!: FastifyReply;\r\n\r\n /**\r\n * Current status code\r\n */\r\n protected currentStatusCode = 200;\r\n\r\n /**\r\n * Current response body\r\n */\r\n protected currentBody: any;\r\n\r\n /**\r\n * Request object\r\n */\r\n public request!: Request;\r\n\r\n /**\r\n * Internal events related to this particular response object\r\n */\r\n protected events = new Map<string, any[]>();\r\n\r\n /**\r\n * Parsed body\r\n * This will return the parsed body of the response\r\n * Please note that if this property is called before the response is sent, it will return undefined\r\n */\r\n public parsedBody: any;\r\n\r\n /**\r\n * Get raw response\r\n */\r\n public get raw() {\r\n return this.baseResponse.raw;\r\n }\r\n\r\n /**\r\n * Get Current response body\r\n */\r\n public get body() {\r\n return this.currentBody;\r\n }\r\n\r\n /**\r\n * Set response body\r\n */\r\n public set body(body: any) {\r\n this.currentBody = body;\r\n }\r\n\r\n /**\r\n * Add event on sending response\r\n */\r\n public onSending(callback: any) {\r\n this.events.set(\"sending\", [...(this.events.get(\"sending\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Add event on sent response\r\n */\r\n public onSent(callback: any) {\r\n this.events.set(\"sent\", [...(this.events.get(\"sent\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the Fastify response object\r\n */\r\n public setResponse(response: FastifyReply) {\r\n this.baseResponse = response;\r\n\r\n // Listen to the 'finish' event to track when response is fully sent\r\n // This works for all response types: JSON, streams, buffers, files, etc.\r\n this.baseResponse.raw.once(\"finish\", () => {\r\n this.request.endTime = Date.now();\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Reset the response state\r\n */\r\n public reset() {\r\n this.route = {} as Route;\r\n this.currentBody = null;\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n /**\r\n * Set current route\r\n */\r\n public setRoute(route: Route) {\r\n this.route = route;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the content type\r\n */\r\n public get contentType() {\r\n return this.baseResponse.getHeader(\"Content-Type\");\r\n }\r\n\r\n /**\r\n * Set the content type\r\n */\r\n public setContentType(contentType: string) {\r\n this.baseResponse.header(\"Content-Type\", contentType);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the status code\r\n */\r\n public get statusCode(): number {\r\n return this.currentStatusCode ?? this.baseResponse.statusCode;\r\n }\r\n\r\n /**\r\n * Check if response status is ok\r\n */\r\n public get isOk() {\r\n return this.currentStatusCode >= 200 && this.currentStatusCode < 300;\r\n }\r\n\r\n /**\r\n * Check if the response has been sent\r\n */\r\n public get sent() {\r\n return this.baseResponse.sent;\r\n }\r\n\r\n /**\r\n * Add a listener to the response event\r\n */\r\n public static on(\r\n event: ResponseEvent,\r\n listener: (response: Response) => void,\r\n ): EventSubscription {\r\n return events.subscribe(`response.${event}`, listener);\r\n }\r\n\r\n /**\r\n * Trigger the response event\r\n */\r\n protected static async trigger(event: ResponseEvent, ...args: any[]) {\r\n // make a timeout to make sure the request events is executed first\r\n return new Promise((resolve) => {\r\n setTimeout(async () => {\r\n await events.triggerAllAsync(`response.${event}`, ...args);\r\n resolve(true);\r\n }, 0);\r\n });\r\n }\r\n\r\n /**\r\n * Parse body\r\n */\r\n protected async parseBody() {\r\n return await this.parse(this.currentBody);\r\n }\r\n\r\n /**\r\n * Parse the given value\r\n */\r\n public async parse(value: any): Promise<any> {\r\n // if it is a falsy value, return it\r\n if (!value || isScalar(value)) return value;\r\n\r\n // if it has a `toJSON` method, call it and await the result then return it\r\n if (value.toJSON) {\r\n value.request = this.request;\r\n return await value.toJSON();\r\n }\r\n\r\n // if it is iterable, an array or array-like object then parse each item\r\n if (isIterable(value)) {\r\n const values = Array.from(value);\r\n\r\n return Promise.all(\r\n values.map(async (item: any) => {\r\n return await this.parse(item);\r\n }),\r\n );\r\n }\r\n\r\n // if not plain object, then return it\r\n if (!isPlainObject(value)) {\r\n return value;\r\n }\r\n\r\n // loop over the object and check if the value and call `parse` on it\r\n for (const key in value) {\r\n const subValue = value[key];\r\n\r\n value[key] = await this.parse(subValue);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * Make a log message\r\n */\r\n public log(message: string, level: LogLevel = \"info\") {\r\n if (!config.get(\"http.log\")) return;\r\n\r\n log.log({\r\n module: \"response\",\r\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.request.id}`,\r\n message,\r\n type: level,\r\n context: {\r\n request: this.request,\r\n response: this,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Check if returning response is json\r\n */\r\n public get isJson() {\r\n return this.getHeader(\"Content-Type\") === \"application/json\";\r\n }\r\n\r\n /**\r\n * Send the response\r\n * @param data - Response data\r\n * @param statusCode - HTTP status code\r\n * @param triggerEvents - Whether to trigger response events (default: true)\r\n */\r\n public async send(data?: any, statusCode?: number, triggerEvents = true): Promise<Response> {\r\n // Defensive guard against double-send. The underlying Fastify reply silently\r\n // ignores subsequent sends once `sent === true`, which has historically hidden\r\n // middleware bugs (cache-pattern replay paths that returned `baseResponse.send`\r\n // ended up re-entering `Response.send` with the FastifyReply as the body).\r\n // Surfacing the misuse via `error`-level log makes the bug loud without\r\n // crashing production traffic.\r\n if (this.baseResponse.sent) {\r\n log.error(\r\n \"response\",\r\n \"send\",\r\n `send() called on already-sent response (request:${this.request?.id ?? \"unknown\"}) — likely a middleware bug`,\r\n );\r\n\r\n return this;\r\n }\r\n\r\n if (statusCode) {\r\n this.currentStatusCode = statusCode;\r\n }\r\n\r\n if (data === this) return this;\r\n\r\n if (data) {\r\n this.currentBody = data;\r\n }\r\n\r\n if (!this.currentStatusCode) {\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n this.log(\"Sending response\");\r\n // Auto-pick `application/json` only when no content-type was set by the caller.\r\n // This preserves explicit overrides (e.g. `application/vnd.api+json` from a\r\n // cache replay, `application/problem+json` from an RFC 7807 error response)\r\n // while keeping the convenience default for the common object-body path.\r\n if (Array.isArray(this.currentBody) || isPlainObject(this.currentBody)) {\r\n if (!this.baseResponse.getHeader(\"Content-Type\")) {\r\n this.setContentType(\"application/json\");\r\n }\r\n }\r\n\r\n if (triggerEvents) {\r\n await Response.trigger(\"sending\", this);\r\n\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isJson) {\r\n await Response.trigger(\"sendingJson\", this);\r\n for (const callback of this.events.get(\"sendingJson\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isOk) {\r\n await Response.trigger(\"sendingSuccessJson\", this);\r\n for (const callback of this.events.get(\"sendingSuccessJson\") || []) {\r\n await callback(this);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // parse the body and make sure it is transformed to sync data instead of async data\r\n if (typeof this.currentBody !== \"string\") {\r\n this.parsedBody = await this.parseBody();\r\n } else {\r\n this.parsedBody = data;\r\n }\r\n\r\n // Set the status first\r\n this.baseResponse.status(this.currentStatusCode);\r\n\r\n // Then send the response with the parsed body\r\n await this.baseResponse.send(this.parsedBody);\r\n\r\n this.log(\"Response sent\");\r\n\r\n if (triggerEvents) {\r\n // trigger the sent event\r\n Response.trigger(\"sent\", this);\r\n\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // trigger the success event if the status code is 2xx\r\n if (this.currentStatusCode >= 200 && this.currentStatusCode < 300) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // trigger the successCreate event if the status code is 201\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n // trigger the badRequest event if the status code is 400\r\n if (this.currentStatusCode === 400) {\r\n Response.trigger(\"badRequest\", this);\r\n }\r\n\r\n // trigger the unauthorized event if the status code is 401\r\n if (this.currentStatusCode === 401) {\r\n Response.trigger(\"unauthorized\", this);\r\n }\r\n\r\n // trigger the forbidden event if the status code is 403\r\n if (this.currentStatusCode === 403) {\r\n Response.trigger(\"forbidden\", this);\r\n }\r\n\r\n // trigger the notFound event if the status code is 404\r\n if (this.currentStatusCode === 404) {\r\n Response.trigger(\"notFound\", this);\r\n }\r\n\r\n // trigger the content too large event if the status code is 413\r\n if (this.currentStatusCode === 413) {\r\n Response.trigger(\"contentTooLarge\", this);\r\n }\r\n\r\n // trigger the throttled event if the status code is 429\r\n if (this.currentStatusCode === 429) {\r\n Response.trigger(\"throttled\", this);\r\n }\r\n\r\n // trigger the serverError event if the status code is 500\r\n if (this.currentStatusCode === 500) {\r\n Response.trigger(\"serverError\", this);\r\n }\r\n\r\n // trigger the error event if the status code is 4xx or 5xx\r\n if (this.currentStatusCode >= 400) {\r\n Response.trigger(\"error\", this);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Replay a previously-captured response shape — used by cache-pattern\r\n * middlewares (idempotency, response cache) to send a cached response\r\n * without re-running the controller.\r\n *\r\n * Preserves the cached status code, content-type, and any extra headers,\r\n * then sends the body through the standard `send()` pipeline so the full\r\n * event lifecycle still fires (`sent`, `success`, status-specific events).\r\n * That keeps cross-cutting observers (logger, metrics, audit) consistent\r\n * between fresh and replayed responses.\r\n *\r\n * @example\r\n * // Inside a cache-pattern middleware on HIT:\r\n * return response.header(\"X-Cache\", \"HIT\").replay({\r\n * status: cached.status,\r\n * body: cached.body,\r\n * contentType: cached.contentType,\r\n * });\r\n */\r\n public replay(cached: {\r\n status: number;\r\n body: unknown;\r\n contentType?: string;\r\n headers?: Record<string, string>;\r\n }): Promise<Response> {\r\n this.setStatusCode(cached.status);\r\n\r\n if (cached.contentType) {\r\n this.setContentType(cached.contentType);\r\n }\r\n\r\n if (cached.headers) {\r\n for (const [name, value] of Object.entries(cached.headers)) {\r\n this.header(name, value);\r\n }\r\n }\r\n\r\n return this.send(cached.body);\r\n }\r\n\r\n /**\r\n * Send html response\r\n */\r\n public html(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/html\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Render the given react component\r\n */\r\n public render(element: React.ReactElement | React.ComponentType, status = 200) {\r\n return this.setStatusCode(status).html(renderReact(element));\r\n }\r\n\r\n /**\r\n * Send xml response\r\n */\r\n public xml(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/xml\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Send plain text response\r\n */\r\n public text(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/plain\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Create a streaming response for progressive/chunked data sending\r\n *\r\n * This method allows you to send data in chunks and control when the response ends.\r\n * Perfect for Server-Sent Events (SSE), progressive rendering, or streaming large responses.\r\n *\r\n * @example\r\n * ```ts\r\n * const stream = response.stream(\"text/html\");\r\n * stream.send(\"<html><body>\");\r\n * stream.send(\"<h1>Hello</h1>\");\r\n * stream.render(<MyComponent />);\r\n * stream.send(\"</body></html>\");\r\n * stream.end();\r\n * ```\r\n *\r\n * @param contentType - The content type for the stream (default: \"text/plain\")\r\n * @returns Stream controller with send(), render(), and end() methods\r\n */\r\n public stream(contentType = \"text/plain\"): ResponseStreamController {\r\n // Set headers using the response API\r\n this.setContentType(contentType);\r\n this.header(\"Transfer-Encoding\", \"chunked\");\r\n this.header(\"Cache-Control\", \"no-cache\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Content-Type-Options\", \"nosniff\");\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const chunks: any[] = [];\r\n\r\n // Write headers to start the stream\r\n // Note: We use raw here because we need chunked encoding control\r\n // This is the only valid use case for bypassing Fastify's abstraction\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n return {\r\n /**\r\n * Send a chunk of data to the client\r\n * @param data - Data to send (string, Buffer, or any serializable data)\r\n */\r\n send: (data: any) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot send data: stream has already ended\");\r\n }\r\n\r\n this.baseResponse.raw.write(data);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Render a React component and send it as a chunk\r\n * @param element - React element or component to render\r\n */\r\n render: (element: ReactNode) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot render: stream has already ended\");\r\n }\r\n\r\n const html = renderReact(element);\r\n chunks.push(html);\r\n this.baseResponse.raw.write(html);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * End the stream and trigger completion events\r\n */\r\n end: () => {\r\n if (isEnded) {\r\n return this;\r\n }\r\n\r\n isEnded = true;\r\n\r\n // Store the streamed content for logging/debugging\r\n this.currentBody = chunks;\r\n this.parsedBody = chunks;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"Stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // Trigger status-specific events\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Create a Server-Sent Events (SSE) stream\r\n *\r\n * SSE is a standard for pushing real-time updates from server to client.\r\n * Perfect for live notifications, progress updates, or real-time data feeds.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n *\r\n * // Send events\r\n * sse.send(\"message\", { text: \"Hello!\" });\r\n * sse.send(\"notification\", { type: \"info\", message: \"Update available\" }, \"msg-123\");\r\n *\r\n * // Keep connection alive\r\n * const keepAlive = setInterval(() => sse.comment(\"ping\"), 30000);\r\n *\r\n * // Clean up when done\r\n * clearInterval(keepAlive);\r\n * sse.end();\r\n * ```\r\n *\r\n * @returns SSE controller with send(), comment(), and end() methods\r\n */\r\n public sse(): ResponseSSEController {\r\n // Set SSE-specific headers\r\n this.setContentType(\"text/event-stream\");\r\n this.header(\"Cache-Control\", \"no-cache, no-store, must-revalidate\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Accel-Buffering\", \"no\"); // Disable nginx buffering\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting SSE stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const events: any[] = [];\r\n const disconnectHandlers: Array<() => void> = [];\r\n\r\n // Write headers to start the stream\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n // Detect client disconnect — set isEnded silently and invoke cleanup handlers.\r\n // Without this, background jobs keep writing to a dead socket after the client drops.\r\n this.baseResponse.raw.on(\"close\", () => {\r\n if (!isEnded) {\r\n isEnded = true;\r\n this.log(\"SSE client disconnected\");\r\n for (const handler of disconnectHandlers) {\r\n handler();\r\n }\r\n }\r\n });\r\n\r\n const controller: ResponseSSEController = {\r\n /**\r\n * Send an SSE event\r\n * @param event - Event name (e.g., \"message\", \"chunk\", \"done\")\r\n * @param data - Event data (will be JSON stringified)\r\n * @param id - Optional event ID for client-side Last-Event-ID tracking (reconnect support)\r\n */\r\n send: (event: string, data: any, id?: string): ResponseSSEController => {\r\n // Silent no-op after disconnect — background jobs should not crash when\r\n // the client drops mid-stream. The onDisconnect handler handles cleanup.\r\n if (isEnded) return controller;\r\n\r\n let message = \"\";\r\n if (id) message += `id: ${id}\\n`;\r\n message += `event: ${event}\\n`;\r\n message += `data: ${JSON.stringify(data)}\\n\\n`;\r\n\r\n events.push({ event, data, id });\r\n this.baseResponse.raw.write(message);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Send a comment (keeps connection alive, invisible to client)\r\n * Useful for preventing timeout on long-lived connections\r\n * @param text - Comment text\r\n */\r\n comment: (text: string): ResponseSSEController => {\r\n // Silent no-op after disconnect\r\n if (isEnded) return controller;\r\n\r\n this.baseResponse.raw.write(`: ${text}\\n\\n`);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * End the SSE stream and trigger completion events\r\n */\r\n end: (): ResponseSSEController => {\r\n if (isEnded) return controller;\r\n\r\n isEnded = true;\r\n\r\n // Store the events for logging/debugging\r\n this.currentBody = events;\r\n this.parsedBody = events;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"SSE stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Register a handler to be called when the client disconnects.\r\n * Use this to clean up EventEmitter listeners, cancel background jobs, etc.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n * const listener = (chunk) => sse.send(\"chunk\", { chunk });\r\n * eventBus.on(aiMessageId, listener);\r\n * sse.onDisconnect(() => eventBus.off(aiMessageId, listener));\r\n * ```\r\n */\r\n onDisconnect: (handler: () => void): ResponseSSEController => {\r\n disconnectHandlers.push(handler);\r\n return controller;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended (either via end() or client disconnect)\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n\r\n return controller;\r\n }\r\n\r\n /**\r\n * Set the status code\r\n */\r\n public setStatusCode(statusCode: number) {\r\n this.currentStatusCode = statusCode;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Redirect the user to another route\r\n */\r\n public redirect(url: string, statusCode = 302) {\r\n this.baseResponse.redirect(url, statusCode);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Permanent redirect\r\n */\r\n public permanentRedirect(url: string) {\r\n this.baseResponse.redirect(url, 301);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the response time\r\n */\r\n public getResponseTime() {\r\n return this.baseResponse.elapsedTime;\r\n }\r\n\r\n /**\r\n * Remove a specific header\r\n */\r\n public removeHeader(key: string) {\r\n this.baseResponse.removeHeader(key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get a specific header\r\n */\r\n public getHeader(key: string) {\r\n return this.baseResponse.getHeader(key);\r\n }\r\n\r\n /**\r\n * Get the response headers\r\n */\r\n public getHeaders() {\r\n return this.baseResponse.getHeaders();\r\n }\r\n\r\n /**\r\n * Set multiple headers\r\n */\r\n public headers(headers: Record<string, string>) {\r\n this.baseResponse.headers(headers);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the response header\r\n */\r\n public header(key: string, value: any) {\r\n this.baseResponse.header(key, value);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set a cookie on the response.\r\n *\r\n * Values are JSON-stringified by default so structured cookies round-trip\r\n * cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the\r\n * JSON wrapping for plain-string cookies (session tokens, opaque IDs).\r\n *\r\n * @example\r\n * // JSON-wrapped (default) — round-trips with request.cookie()\r\n * response.cookie(\"prefs\", { theme: \"dark\" }, { maxAge: 3600, httpOnly: true });\r\n *\r\n * @example\r\n * // Raw string — no JSON quoting; useful for tokens / opaque IDs\r\n * response.cookie(\"session\", \"abc.def.ghi\", { raw: true, httpOnly: true });\r\n */\r\n public cookie(name: string, value: CookieValue, options: CookieOptions = {}) {\r\n const { raw, ...cookieOptions } = options;\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n const serializedValue = raw ? String(value) : JSON.stringify(value);\r\n\r\n this.baseResponse.setCookie(name, serializedValue, {\r\n ...defaultOptions,\r\n ...cookieOptions,\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Clear a cookie from the response\r\n *\r\n * @example\r\n * response.clearCookie('token', { path: '/' });\r\n */\r\n public clearCookie(name: string, options?: CookieSerializeOptions) {\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n this.baseResponse.clearCookie(name, { ...defaultOptions, ...options });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Alias to header method\r\n */\r\n public setHeader(key: string, value: any) {\r\n return this.header(key, value);\r\n }\r\n\r\n /**\r\n * Send an error response with status code 500\r\n */\r\n public serverError(data: any) {\r\n return this.send(data, 500);\r\n }\r\n\r\n /**\r\n * Send a forbidden response with status code 403\r\n */\r\n public forbidden(\r\n data: any = {\r\n error: \"You are not allowed to access this resource, FORBIDDEN\",\r\n },\r\n ) {\r\n return this.send(data, 403);\r\n }\r\n\r\n /**\r\n * Send a service unavailable response with status code 503\r\n */\r\n public serviceUnavailable(data: any) {\r\n return this.send(data, 503);\r\n }\r\n\r\n /**\r\n * Send an unauthorized response with status code 401\r\n */\r\n public unauthorized(\r\n data: any = {\r\n error: \"unauthorized\",\r\n },\r\n ) {\r\n return this.send(data, 401);\r\n }\r\n\r\n /**\r\n * Send a not found response with status code 404\r\n */\r\n public notFound(\r\n data: any = {\r\n error: \"notFound\",\r\n },\r\n ) {\r\n return this.send(data, 404);\r\n }\r\n\r\n /**\r\n * Send a bad request response with status code 400\r\n */\r\n public badRequest(data: any) {\r\n return this.send(data, 400);\r\n }\r\n\r\n /**\r\n * Send a content too large response with status code 413\r\n */\r\n public contentTooLarge(data: any) {\r\n return this.send(data, 413);\r\n }\r\n\r\n /**\r\n * Send a success response with status code 201\r\n */\r\n public successCreate(data: any) {\r\n return this.send(data, 201);\r\n }\r\n\r\n /**\r\n * Send a success response\r\n */\r\n public success(data: any = { success: true }) {\r\n return this.send(data);\r\n }\r\n\r\n /**\r\n * Send a no content response with status code 204\r\n */\r\n public noContent() {\r\n return this.baseResponse.status(204).send();\r\n }\r\n\r\n /**\r\n * Send an accepted response with status code 202\r\n * Used for async operations that have been accepted but not yet processed\r\n */\r\n public accepted(data: any = { message: \"Request accepted for processing\" }) {\r\n return this.send(data, 202);\r\n }\r\n\r\n /**\r\n * Send a conflict response with status code 409\r\n */\r\n public conflict(data: any = { error: \"Resource conflict\" }) {\r\n return this.send(data, 409);\r\n }\r\n\r\n /**\r\n * Send a too many requests response with status code 429\r\n */\r\n public tooManyRequests(data: any) {\r\n return this.send(data, 429);\r\n }\r\n\r\n /**\r\n * Send an unprocessable entity response with status code 422\r\n * Used for semantic validation errors\r\n */\r\n public unprocessableEntity(data: any) {\r\n return this.send(data, 422);\r\n }\r\n\r\n /**\r\n * Apply response options (cache, disposition, etag)\r\n * Shared helper for sendFile and sendBuffer\r\n */\r\n private applyResponseOptions(options: SendBufferOptions, defaultFilename?: string): boolean {\r\n // Set content type if provided\r\n if (options.contentType) {\r\n this.baseResponse.type(options.contentType);\r\n }\r\n\r\n // Set cache headers if specified\r\n if (options.cacheTime) {\r\n const cacheControl = options.immutable\r\n ? `public, max-age=${options.cacheTime}, immutable`\r\n : `public, max-age=${options.cacheTime}`;\r\n this.header(\"Cache-Control\", cacheControl);\r\n this.header(\"Expires\", new Date(Date.now() + options.cacheTime * 1000).toUTCString());\r\n }\r\n\r\n // Set ETag if provided (for conditional requests)\r\n if (options.etag) {\r\n this.header(\"ETag\", options.etag);\r\n\r\n // Check If-None-Match for conditional request\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n if (ifNoneMatch && ifNoneMatch === options.etag) {\r\n this.log(\"Content not modified (ETag match), sending 304\");\r\n this.baseResponse.status(304).send();\r\n return true; // Indicates 304 was sent\r\n }\r\n }\r\n\r\n // Set Content-Disposition if inline or filename is specified\r\n if (options.inline !== undefined || options.filename) {\r\n const disposition = options.inline ? \"inline\" : \"attachment\";\r\n const filename = options.filename || defaultFilename || \"file\";\r\n this.header(\"Content-Disposition\", `${disposition}; filename=\\\"${filename}\\\"`);\r\n }\r\n\r\n return false; // No 304 sent\r\n }\r\n\r\n /**\r\n * Send a file as a response\r\n */\r\n public async sendFile(filePath: string | StorageFile, options?: number | SendFileOptions) {\r\n if (filePath instanceof StorageFile) {\r\n filePath = filePath.absolutePath!;\r\n }\r\n\r\n this.log(`Sending file: ${filePath}`);\r\n\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get file stats for ETag and Last-Modified\r\n const stats = await fs.promises.stat(filePath);\r\n const lastModified = stats.mtime;\r\n\r\n // Generate ETag based on file size and modification time\r\n const etag = `\"${stats.size}-${stats.mtime.getTime()}\"`;\r\n\r\n // Set Last-Modified header\r\n this.header(\"Last-Modified\", lastModified.toUTCString());\r\n this.header(\"ETag\", etag);\r\n\r\n // Set content type\r\n const contentType = this.getFileContentType(filePath);\r\n this.baseResponse.type(contentType);\r\n\r\n // Apply common response options (cache, disposition)\r\n const defaultFilename = path.basename(filePath);\r\n const sent304 = this.applyResponseOptions({ ...opts, etag, contentType }, defaultFilename);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Check conditional request headers\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n const ifModifiedSince = this.request.header(\"if-modified-since\");\r\n\r\n // Handle If-None-Match (ETag validation)\r\n if (ifNoneMatch && ifNoneMatch === etag) {\r\n this.log(\"File not modified (ETag match), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n\r\n // Handle If-Modified-Since (Last-Modified validation)\r\n if (ifModifiedSince) {\r\n const modifiedSinceDate = new Date(ifModifiedSince);\r\n if (lastModified.getTime() <= modifiedSinceDate.getTime()) {\r\n this.log(\"File not modified (Last-Modified check), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n }\r\n\r\n // Use streaming for efficient file sending\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error sending file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error sending file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Send buffer as a response\r\n * Useful for dynamically generated content (e.g., resized images, generated PDFs)\r\n */\r\n public sendBuffer(buffer: Buffer, options?: number | SendBufferOptions) {\r\n this.log(\"Sending buffer\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Apply common response options (cache, disposition, etag)\r\n const sent304 = this.applyResponseOptions(opts);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send an Image instance as a response\r\n * Automatically detects image format and sets content type\r\n */\r\n public async sendImage(\r\n image: any, // Type as 'any' to avoid circular dependency with Image class\r\n options?: number | (Omit<SendBufferOptions, \"contentType\"> & { contentType?: string }),\r\n ) {\r\n this.log(\"Sending image\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get image metadata to determine format\r\n const metadata = await image.metadata();\r\n const format = metadata.format || \"jpeg\";\r\n\r\n // Convert image to buffer\r\n const buffer = await image.toBuffer();\r\n\r\n // Auto-set content type if not provided\r\n const contentType = opts.contentType || `image/${format}`;\r\n\r\n // Auto-generate ETag if not provided\r\n // Format: \"format-widthxheight-size\" (e.g., \"jpeg-800x600-45231\")\r\n // This catches changes in dimensions, quality, filters, and format\r\n if (!opts.etag) {\r\n const width = metadata.width || 0;\r\n const height = metadata.height || 0;\r\n opts.etag = `\"${format}-${width}x${height}-${buffer.length}\"`;\r\n }\r\n\r\n // Apply common response options with auto-detected content type\r\n const sent304 = this.applyResponseOptions({ ...opts, contentType });\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send file and cache it\r\n * Cache time in seconds\r\n * Cache time will be one year\r\n */\r\n public sendCachedFile(path: string | StorageFile, cacheTime = 31536000) {\r\n return this.sendFile(path, cacheTime);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public download(path: string, filename?: string) {\r\n return this.downloadFile(path, filename);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public async downloadFile(filePath: string, filename?: string) {\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n if (!filename) {\r\n filename = path.basename(filePath);\r\n }\r\n\r\n this.baseResponse.header(\"Content-Disposition\", `attachment; filename=\"${filename}\"`);\r\n\r\n // this.baseResponse.header(\"Content-Type\", this.getFileContentType(filePath));\r\n this.baseResponse.header(\"Content-Type\", \"application/octet-stream\");\r\n\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file for download: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error downloading file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error downloading file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Get content type of the given path\r\n */\r\n public getFileContentType(filePath: string) {\r\n const type = mime.getType(filePath) || \"application/octet-stream\";\r\n return type;\r\n }\r\n\r\n /**\r\n * Mark the response as failed\r\n */\r\n public failedSchema(result: ValidationResult) {\r\n const { errors, inputKey, inputError, status } = config.get(\"validation.response\", {\r\n errors: \"errors\",\r\n inputKey: \"input\",\r\n inputError: \"error\",\r\n status: 400,\r\n });\r\n\r\n log.error(\"request\", \"validation\", `${this.request.id} - Validation failed`);\r\n\r\n return this.send(\r\n {\r\n [errors]: result.errors.map((error) => ({\r\n [inputKey]: error.input,\r\n [inputError]: error.error,\r\n })),\r\n },\r\n status,\r\n );\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import baseConfig from "@mongez/config";
1
+ import config from "@mongez/config";
2
2
  import Fastify from "fastify";
3
3
 
4
4
  //#region ../@warlock.js/core/src/http/server.ts
@@ -12,7 +12,7 @@ let server = void 0;
12
12
  function startHttpServer(options) {
13
13
  return server = Fastify({
14
14
  trustProxy: true,
15
- bodyLimit: baseConfig.get("http.bodyLimit", DEFAULT_BODY_LIMIT),
15
+ bodyLimit: config.get("http.bodyLimit", DEFAULT_BODY_LIMIT),
16
16
  ...options
17
17
  });
18
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":["config"],"sources":["../../../../../../@warlock.js/core/src/http/server.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport Fastify, { FastifyServerOptions } from \"fastify\";\r\n\r\nexport type FastifyInstance = ReturnType<typeof Fastify>;\r\n\r\n/**\r\n * Default Fastify body limit. Kept at the historical 200GB so existing apps\r\n * don't regress on upgrade. Override via `http.bodyLimit` in config; for\r\n * per-route caps use the `maxBodySize()` middleware.\r\n */\r\nconst DEFAULT_BODY_LIMIT = 200 * 1024 * 1024 * 1024;\r\n\r\n// Instantiate Fastify server\r\nlet server: FastifyInstance | undefined = undefined;\r\n\r\nexport function startHttpServer(options?: FastifyServerOptions): FastifyInstance {\r\n return (server = Fastify({\r\n trustProxy: true,\r\n bodyLimit: config.get(\"http.bodyLimit\", DEFAULT_BODY_LIMIT),\r\n ...options,\r\n }));\r\n}\r\n\r\n/**\r\n * Expose the server to be publicly accessible\r\n */\r\nexport function getHttpServer(): FastifyInstance {\r\n return server;\r\n}\r\n"],"mappings":";;;;;;;;;AAUA,MAAM,qBAAqB,MAAM,OAAO,OAAO;AAG/C,IAAI,SAAsC;AAE1C,SAAgB,gBAAgB,SAAiD;CAC/E,OAAQ,SAAS,QAAQ;EACvB,YAAY;EACZ,WAAWA,WAAO,IAAI,kBAAkB,kBAAkB;EAC1D,GAAG;CACL,CAAC;AACH;;;;AAKA,SAAgB,gBAAiC;CAC/C,OAAO;AACT"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../../../../../@warlock.js/core/src/http/server.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport Fastify, { FastifyServerOptions } from \"fastify\";\r\n\r\nexport type FastifyInstance = ReturnType<typeof Fastify>;\r\n\r\n/**\r\n * Default Fastify body limit. Kept at the historical 200GB so existing apps\r\n * don't regress on upgrade. Override via `http.bodyLimit` in config; for\r\n * per-route caps use the `maxBodySize()` middleware.\r\n */\r\nconst DEFAULT_BODY_LIMIT = 200 * 1024 * 1024 * 1024;\r\n\r\n// Instantiate Fastify server\r\nlet server: FastifyInstance | undefined = undefined;\r\n\r\nexport function startHttpServer(options?: FastifyServerOptions): FastifyInstance {\r\n return (server = Fastify({\r\n trustProxy: true,\r\n bodyLimit: config.get(\"http.bodyLimit\", DEFAULT_BODY_LIMIT),\r\n ...options,\r\n }));\r\n}\r\n\r\n/**\r\n * Expose the server to be publicly accessible\r\n */\r\nexport function getHttpServer(): FastifyInstance {\r\n return server;\r\n}\r\n"],"mappings":";;;;;;;;;AAUA,MAAM,qBAAqB,MAAM,OAAO,OAAO;AAG/C,IAAI,SAAsC;AAE1C,SAAgB,gBAAgB,SAAiD;CAC/E,OAAQ,SAAS,QAAQ;EACvB,YAAY;EACZ,WAAW,OAAO,IAAI,kBAAkB,kBAAkB;EAC1D,GAAG;CACL,CAAC;AACH;;;;AAKA,SAAgB,gBAAiC;CAC/C,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import baseConfig from "@mongez/config";
1
+ import config from "@mongez/config";
2
2
  import path from "path";
3
3
 
4
4
  //#region ../@warlock.js/core/src/utils/paths.ts
@@ -28,7 +28,7 @@ function storagePath(relativePath = "") {
28
28
  * If no path is given, it will return the absolute path to the uploads folder
29
29
  */
30
30
  function uploadsPath(relativePath = "") {
31
- const configPath = baseConfig.get("uploads.root");
31
+ const configPath = config.get("uploads.root");
32
32
  if (!configPath) return rootPath("storage", "uploads", relativePath);
33
33
  return typeof configPath === "function" ? configPath(relativePath) : path.resolve(configPath, relativePath);
34
34
  }