@warlock.js/core 4.9.2 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/esm/application/index.mjs +1 -1
- package/esm/config/config-getter.mjs +5 -5
- package/esm/config/config-getter.mjs.map +1 -1
- package/esm/config/config-loader.mjs +2 -2
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/access-connector.mjs +2 -2
- package/esm/connectors/access-connector.mjs.map +1 -1
- package/esm/connectors/ai-connector.mjs +2 -2
- package/esm/connectors/ai-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.mjs +2 -2
- package/esm/connectors/cache-connector.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs +2 -2
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs +2 -2
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.mjs +8 -8
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs +2 -2
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs +2 -2
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/notifications-connector.mjs +2 -2
- package/esm/connectors/notifications-connector.mjs.map +1 -1
- package/esm/connectors/socket-connector.mjs +3 -3
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/http/config.mjs +2 -2
- package/esm/http/config.mjs.map +1 -1
- package/esm/http/createHttpApplication.mjs +2 -2
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs +5 -5
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +2 -2
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs +4 -4
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/plugins.mjs +9 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/response.d.mts +17 -2
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +41 -7
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.mjs +4 -4
- package/esm/http/server.mjs.map +1 -1
- package/esm/index.mjs +1 -1
- package/esm/utils/paths.mjs +2 -2
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +2 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/llms-full.txt +23 -4
- package/package.json +12 -12
- package/skills/send-response/SKILL.md +23 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createHttpApplication.mjs","names":[],"sources":["../../../../../../../core/src/http/createHttpApplication.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { router } from \"../router\";\nimport { setBaseUrl } from \"../utils/urls\";\nimport { httpConfig } from \"./config\";\nimport { registerHttpPlugins } from \"./plugins\";\nimport { getHttpServer, startHttpServer } from \"./server\";\n\nexport async function createHttpApplication() {\n const server = startHttpServer();\n\n await registerHttpPlugins(server);\n\n router.scan(server);\n\n const port = httpConfig(\"port\");\n\n try {\n log.info(\"http\", \"server\", \"Connecting to the server\");\n // 👇🏻 We can use the url of the server\n await server.listen({\n port,\n host: httpConfig(\"host\"),\n });\n\n const baseUrl = config.get(\"app.baseUrl\");\n\n // update base url\n setBaseUrl(baseUrl);\n\n log.success(\"http\", \"server\", `Server is listening on ${baseUrl}`);\n } catch (error) {\n log.error(\"http\", \"server\", error);\n\n process.exit(1); // stop the process, exit with error\n }\n}\n\nexport async function stopHttpApplication() {\n log.info(\"http\", \"server\", \"Stopping the server\");\n const server = getHttpServer();\n\n await server?.close();\n\n log.success(\"http\", \"server\", \"Server is stopped\");\n}\n"],"mappings":";;;;;;;;;;AAQA,eAAsB,wBAAwB;CAC5C,MAAM,SAAS,gBAAgB;CAE/B,MAAM,oBAAoB,MAAM;CAEhC,OAAO,KAAK,MAAM;CAElB,MAAM,OAAO,WAAW,MAAM;CAE9B,IAAI;EACF,IAAI,KAAK,QAAQ,UAAU,0BAA0B;EAErD,MAAM,OAAO,OAAO;GAClB;GACA,MAAM,WAAW,MAAM;EACzB,CAAC;EAED,MAAM,
|
|
1
|
+
{"version":3,"file":"createHttpApplication.mjs","names":["config"],"sources":["../../../../../../../core/src/http/createHttpApplication.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { router } from \"../router\";\nimport { setBaseUrl } from \"../utils/urls\";\nimport { httpConfig } from \"./config\";\nimport { registerHttpPlugins } from \"./plugins\";\nimport { getHttpServer, startHttpServer } from \"./server\";\n\nexport async function createHttpApplication() {\n const server = startHttpServer();\n\n await registerHttpPlugins(server);\n\n router.scan(server);\n\n const port = httpConfig(\"port\");\n\n try {\n log.info(\"http\", \"server\", \"Connecting to the server\");\n // 👇🏻 We can use the url of the server\n await server.listen({\n port,\n host: httpConfig(\"host\"),\n });\n\n const baseUrl = config.get(\"app.baseUrl\");\n\n // update base url\n setBaseUrl(baseUrl);\n\n log.success(\"http\", \"server\", `Server is listening on ${baseUrl}`);\n } catch (error) {\n log.error(\"http\", \"server\", error);\n\n process.exit(1); // stop the process, exit with error\n }\n}\n\nexport async function stopHttpApplication() {\n log.info(\"http\", \"server\", \"Stopping the server\");\n const server = getHttpServer();\n\n await server?.close();\n\n log.success(\"http\", \"server\", \"Server is stopped\");\n}\n"],"mappings":";;;;;;;;;;AAQA,eAAsB,wBAAwB;CAC5C,MAAM,SAAS,gBAAgB;CAE/B,MAAM,oBAAoB,MAAM;CAEhC,OAAO,KAAK,MAAM;CAElB,MAAM,OAAO,WAAW,MAAM;CAE9B,IAAI;EACF,IAAI,KAAK,QAAQ,UAAU,0BAA0B;EAErD,MAAM,OAAO,OAAO;GAClB;GACA,MAAM,WAAW,MAAM;EACzB,CAAC;EAED,MAAM,UAAUA,WAAO,IAAI,aAAa;EAGxC,WAAW,OAAO;EAElB,IAAI,QAAQ,QAAQ,UAAU,0BAA0B,SAAS;CACnE,SAAS,OAAO;EACd,IAAI,MAAM,QAAQ,UAAU,KAAK;EAEjC,QAAQ,KAAK,CAAC;CAChB;AACF;AAEA,eAAsB,sBAAsB;CAC1C,IAAI,KAAK,QAAQ,UAAU,qBAAqB;CAGhD,MAFe,cAEJ,CAAC,EAAE,MAAM;CAEpB,IAAI,QAAQ,QAAQ,UAAU,mBAAmB;AACnD"}
|
|
@@ -1,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
|
|
4
|
+
import baseConfig from "@mongez/config";
|
|
5
5
|
import { log } from "@warlock.js/logger";
|
|
6
6
|
import { cache } from "@warlock.js/cache";
|
|
7
7
|
|
|
@@ -44,10 +44,10 @@ const DEFAULT_METHODS = [
|
|
|
44
44
|
*/
|
|
45
45
|
function idempotencyMiddleware(options = {}) {
|
|
46
46
|
return async (request, response) => {
|
|
47
|
-
const headerName = options.headerName ||
|
|
48
|
-
const methods = options.methods ||
|
|
49
|
-
const ttl = options.ttl ||
|
|
50
|
-
const driverName = options.driver ||
|
|
47
|
+
const headerName = options.headerName || baseConfig.get("http.idempotency.headerName", "Idempotency-Key");
|
|
48
|
+
const methods = options.methods || baseConfig.get("http.idempotency.methods", DEFAULT_METHODS);
|
|
49
|
+
const ttl = options.ttl || baseConfig.get("http.idempotency.ttl", 86400);
|
|
50
|
+
const driverName = options.driver || baseConfig.get("http.idempotency.driver");
|
|
51
51
|
if (!methods.includes(request.method.toUpperCase())) return;
|
|
52
52
|
const idempotencyKey = request.header(headerName.toLowerCase());
|
|
53
53
|
if (!idempotencyKey) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency.middleware.mjs","names":[],"sources":["../../../../../../../../core/src/http/middleware/idempotency.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Response } from \"../response\";\nimport { t } from \"./inject-request-context\";\nimport {\n buildIdempotencyCacheKey,\n hashBody,\n isValidIdempotencyKey,\n} from \"./utils/idempotency-key\";\n\n/**\n * Options for the idempotency middleware.\n */\nexport type IdempotencyOptions = {\n /**\n * Cache TTL in seconds. Falls back to `http.idempotency.ttl`, then `86400` (24h).\n */\n ttl?: number;\n /**\n * Header name carrying the client's key. Falls back to\n * `http.idempotency.headerName`, then `\"Idempotency-Key\"`.\n */\n headerName?: string;\n /**\n * HTTP methods eligible for idempotency. Falls back to\n * `http.idempotency.methods`, then `[\"POST\",\"PUT\",\"PATCH\",\"DELETE\"]`.\n * Safe methods (GET/HEAD) are skipped regardless.\n */\n methods?: string[];\n /**\n * Cache driver name. Falls back to `http.idempotency.driver`, then the\n * default driver of the cache manager.\n */\n driver?: string;\n};\n\ntype CachedResponse = {\n status: number;\n body: unknown;\n bodyHash: string;\n contentType?: string;\n};\n\nconst DEFAULT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/**\n * Dedupe non-idempotent writes by an `Idempotency-Key` header — same key,\n * same body, within TTL → cached replay; same key, different body → 422\n * `IdempotencyKeyConflict`.\n *\n * **Must run after `authMiddleware`** — the cache key is scoped per-user\n * (`idem:{userType}:{userId}:{key}`) so user A can't replay user B's key.\n * Anonymous requests fall back to IP scope.\n *\n * The replay sets `Idempotent-Replay: true` on the response for easy\n * client-side / observability detection.\n *\n * Eligible methods default to POST/PUT/PATCH/DELETE. GET/HEAD pass through\n * even with the header set (RFC: safe methods are already idempotent).\n *\n * @example\n * import { authMiddleware } from \"@warlock.js/auth\";\n * import { middleware } from \"@warlock.js/core\";\n *\n * router.post(\"/orders\", createOrderController, {\n * middleware: [authMiddleware(\"client\"), middleware.idempotency()],\n * });\n *\n * router.post(\"/ai/summarize\", summarizeController, {\n * middleware: [\n * authMiddleware(\"client\"),\n * middleware.idempotency({ ttl: 60 * 60 }), // 1h is enough for client retries\n * ],\n * });\n */\nexport function idempotencyMiddleware(options: IdempotencyOptions = {}): Middleware {\n return async (request, response) => {\n const headerName =\n options.headerName ||\n config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods =\n options.methods || config.get(\"http.idempotency.methods\", DEFAULT_METHODS);\n const ttl = options.ttl || config.get(\"http.idempotency.ttl\", 86400);\n const driverName = options.driver || config.get(\"http.idempotency.driver\");\n\n if (!methods.includes(request.method.toUpperCase())) return;\n\n const idempotencyKey = request.header(headerName.toLowerCase());\n\n if (!idempotencyKey) return;\n\n if (!isValidIdempotencyKey(idempotencyKey)) {\n return response.badRequest({\n error: t(\"http.idempotencyKeyInvalid\"),\n errorCode: HttpErrorCodes.IdempotencyKeyInvalid,\n });\n }\n\n const cacheDriver = driverName ? await cache.use(driverName) : cache;\n const cacheKey = buildIdempotencyCacheKey(request, idempotencyKey);\n const bodyHash = hashBody(request.body);\n\n const cached = (await cacheDriver.get(cacheKey)) as CachedResponse | null;\n\n if (cached) {\n if (cached.bodyHash !== bodyHash) {\n return response.unprocessableEntity({\n error: t(\"http.idempotencyKeyConflict\"),\n errorCode: HttpErrorCodes.IdempotencyKeyConflict,\n });\n }\n\n response.header(\"Idempotent-Replay\", \"true\");\n\n return response.replay({\n status: cached.status,\n body: cached.body,\n contentType: cached.contentType,\n });\n }\n\n response.onSent((sentResponse: Response) => {\n // Don't cache server errors — clients should be able to retry past a 5xx.\n // 4xx are deterministic outcomes of the request, so caching is fine.\n if (sentResponse.statusCode >= 500) return;\n\n const sentContentType = sentResponse.contentType;\n\n // `set` is fire-and-forget inside `onSent`; without a `.catch` a rejected\n // write (e.g. Redis down) would surface as an unhandledRejection.\n cacheDriver\n .set(\n cacheKey,\n {\n status: sentResponse.statusCode,\n body: sentResponse.parsedBody,\n bodyHash,\n contentType: typeof sentContentType === \"string\" ? sentContentType : undefined,\n },\n ttl,\n )\n .catch((error: unknown) => {\n log.error(\"idempotency-middleware\", \"set\", error);\n });\n });\n };\n}\n"],"mappings":";;;;;;;;AA8CA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,SAAS,aAAa;EAClC,MAAM,aACJ,QAAQ,
|
|
1
|
+
{"version":3,"file":"idempotency.middleware.mjs","names":["config"],"sources":["../../../../../../../../core/src/http/middleware/idempotency.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { cache } from \"@warlock.js/cache\";\nimport { log } from \"@warlock.js/logger\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Response } from \"../response\";\nimport { t } from \"./inject-request-context\";\nimport {\n buildIdempotencyCacheKey,\n hashBody,\n isValidIdempotencyKey,\n} from \"./utils/idempotency-key\";\n\n/**\n * Options for the idempotency middleware.\n */\nexport type IdempotencyOptions = {\n /**\n * Cache TTL in seconds. Falls back to `http.idempotency.ttl`, then `86400` (24h).\n */\n ttl?: number;\n /**\n * Header name carrying the client's key. Falls back to\n * `http.idempotency.headerName`, then `\"Idempotency-Key\"`.\n */\n headerName?: string;\n /**\n * HTTP methods eligible for idempotency. Falls back to\n * `http.idempotency.methods`, then `[\"POST\",\"PUT\",\"PATCH\",\"DELETE\"]`.\n * Safe methods (GET/HEAD) are skipped regardless.\n */\n methods?: string[];\n /**\n * Cache driver name. Falls back to `http.idempotency.driver`, then the\n * default driver of the cache manager.\n */\n driver?: string;\n};\n\ntype CachedResponse = {\n status: number;\n body: unknown;\n bodyHash: string;\n contentType?: string;\n};\n\nconst DEFAULT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\n\n/**\n * Dedupe non-idempotent writes by an `Idempotency-Key` header — same key,\n * same body, within TTL → cached replay; same key, different body → 422\n * `IdempotencyKeyConflict`.\n *\n * **Must run after `authMiddleware`** — the cache key is scoped per-user\n * (`idem:{userType}:{userId}:{key}`) so user A can't replay user B's key.\n * Anonymous requests fall back to IP scope.\n *\n * The replay sets `Idempotent-Replay: true` on the response for easy\n * client-side / observability detection.\n *\n * Eligible methods default to POST/PUT/PATCH/DELETE. GET/HEAD pass through\n * even with the header set (RFC: safe methods are already idempotent).\n *\n * @example\n * import { authMiddleware } from \"@warlock.js/auth\";\n * import { middleware } from \"@warlock.js/core\";\n *\n * router.post(\"/orders\", createOrderController, {\n * middleware: [authMiddleware(\"client\"), middleware.idempotency()],\n * });\n *\n * router.post(\"/ai/summarize\", summarizeController, {\n * middleware: [\n * authMiddleware(\"client\"),\n * middleware.idempotency({ ttl: 60 * 60 }), // 1h is enough for client retries\n * ],\n * });\n */\nexport function idempotencyMiddleware(options: IdempotencyOptions = {}): Middleware {\n return async (request, response) => {\n const headerName =\n options.headerName ||\n config.get(\"http.idempotency.headerName\", \"Idempotency-Key\");\n const methods =\n options.methods || config.get(\"http.idempotency.methods\", DEFAULT_METHODS);\n const ttl = options.ttl || config.get(\"http.idempotency.ttl\", 86400);\n const driverName = options.driver || config.get(\"http.idempotency.driver\");\n\n if (!methods.includes(request.method.toUpperCase())) return;\n\n const idempotencyKey = request.header(headerName.toLowerCase());\n\n if (!idempotencyKey) return;\n\n if (!isValidIdempotencyKey(idempotencyKey)) {\n return response.badRequest({\n error: t(\"http.idempotencyKeyInvalid\"),\n errorCode: HttpErrorCodes.IdempotencyKeyInvalid,\n });\n }\n\n const cacheDriver = driverName ? await cache.use(driverName) : cache;\n const cacheKey = buildIdempotencyCacheKey(request, idempotencyKey);\n const bodyHash = hashBody(request.body);\n\n const cached = (await cacheDriver.get(cacheKey)) as CachedResponse | null;\n\n if (cached) {\n if (cached.bodyHash !== bodyHash) {\n return response.unprocessableEntity({\n error: t(\"http.idempotencyKeyConflict\"),\n errorCode: HttpErrorCodes.IdempotencyKeyConflict,\n });\n }\n\n response.header(\"Idempotent-Replay\", \"true\");\n\n return response.replay({\n status: cached.status,\n body: cached.body,\n contentType: cached.contentType,\n });\n }\n\n response.onSent((sentResponse: Response) => {\n // Don't cache server errors — clients should be able to retry past a 5xx.\n // 4xx are deterministic outcomes of the request, so caching is fine.\n if (sentResponse.statusCode >= 500) return;\n\n const sentContentType = sentResponse.contentType;\n\n // `set` is fire-and-forget inside `onSent`; without a `.catch` a rejected\n // write (e.g. Redis down) would surface as an unhandledRejection.\n cacheDriver\n .set(\n cacheKey,\n {\n status: sentResponse.statusCode,\n body: sentResponse.parsedBody,\n bodyHash,\n contentType: typeof sentContentType === \"string\" ? sentContentType : undefined,\n },\n ttl,\n )\n .catch((error: unknown) => {\n log.error(\"idempotency-middleware\", \"set\", error);\n });\n });\n };\n}\n"],"mappings":";;;;;;;;AA8CA,MAAM,kBAAkB;CAAC;CAAQ;CAAO;CAAS;AAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzD,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,OAAO,OAAO,SAAS,aAAa;EAClC,MAAM,aACJ,QAAQ,cACRA,WAAO,IAAI,+BAA+B,iBAAiB;EAC7D,MAAM,UACJ,QAAQ,WAAWA,WAAO,IAAI,4BAA4B,eAAe;EAC3E,MAAM,MAAM,QAAQ,OAAOA,WAAO,IAAI,wBAAwB,KAAK;EACnE,MAAM,aAAa,QAAQ,UAAUA,WAAO,IAAI,yBAAyB;EAEzE,IAAI,CAAC,QAAQ,SAAS,QAAQ,OAAO,YAAY,CAAC,GAAG;EAErD,MAAM,iBAAiB,QAAQ,OAAO,WAAW,YAAY,CAAC;EAE9D,IAAI,CAAC,gBAAgB;EAErB,IAAI,CAAC,sBAAsB,cAAc,GACvC,OAAO,SAAS,WAAW;GACzB,OAAO,EAAE,4BAA4B;GACrC;EACF,CAAC;EAGH,MAAM,cAAc,aAAa,MAAM,MAAM,IAAI,UAAU,IAAI;EAC/D,MAAM,WAAW,yBAAyB,SAAS,cAAc;EACjE,MAAM,WAAW,SAAS,QAAQ,IAAI;EAEtC,MAAM,SAAU,MAAM,YAAY,IAAI,QAAQ;EAE9C,IAAI,QAAQ;GACV,IAAI,OAAO,aAAa,UACtB,OAAO,SAAS,oBAAoB;IAClC,OAAO,EAAE,6BAA6B;IACtC;GACF,CAAC;GAGH,SAAS,OAAO,qBAAqB,MAAM;GAE3C,OAAO,SAAS,OAAO;IACrB,QAAQ,OAAO;IACf,MAAM,OAAO;IACb,aAAa,OAAO;GACtB,CAAC;EACH;EAEA,SAAS,QAAQ,iBAA2B;GAG1C,IAAI,aAAa,cAAc,KAAK;GAEpC,MAAM,kBAAkB,aAAa;GAIrC,YACG,IACC,UACA;IACE,QAAQ,aAAa;IACrB,MAAM,aAAa;IACnB;IACA,aAAa,OAAO,oBAAoB,WAAW,kBAAkB;GACvE,GACA,GACF,CAAC,CACA,OAAO,UAAmB;IACzB,IAAI,MAAM,0BAA0B,OAAO,KAAK;GAClD,CAAC;EACL,CAAC;CACH;AACF"}
|
|
@@ -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
|
|
5
|
+
import baseConfig 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 =
|
|
25
|
+
const requestIdConfig = baseConfig.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":["requestContextInstance"],"sources":["../../../../../../../../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,
|
|
1
|
+
{"version":3,"file":"inject-request-context.mjs","names":["config","requestContextInstance"],"sources":["../../../../../../../../core/src/http/middleware/inject-request-context.ts"],"sourcesContent":["/**\r\n * Request Context Middleware\r\n *\r\n * Creates a unified context for each request using the ContextManager.\r\n * All framework contexts (request, storage, database) are available throughout the request lifecycle.\r\n */\r\nimport { trans } from \"@mongez/localization\";\r\nimport { GenericObject } from \"@mongez/reinforcements\";\r\nimport { DatabaseWriterValidationError } from \"@warlock.js/cascade\";\r\nimport { contextManager } from \"@warlock.js/context\";\r\nimport config from \"@mongez/config\";\r\nimport { environment } from \"../../utils\";\r\nimport {\r\n requestContext as requestContextInstance,\r\n useRequestStore,\r\n} from \"../context/request-context\";\r\nimport {\r\n BadRequestError,\r\n ForbiddenError,\r\n HttpError,\r\n ResourceNotFoundError,\r\n ServerError,\r\n UnAuthorizedError,\r\n} from \"../errors\";\r\nimport { type Request } from \"../request\";\r\nimport { type Response } from \"../response\";\r\nimport { type ReturnedResponse } from \"./../types\";\r\n\r\n// Contexts are now registered in core/context/init-contexts.ts via initializeContexts()\r\n\r\n/**\r\n * Echo `request.id` back as a response header so the FE / proxies / log\r\n * aggregators can correlate by the same value the server logs against.\r\n *\r\n * Reads the header name from `http.requestId.header` (default `X-Request-Id`).\r\n * Skip when `http.requestId.enabled` is explicitly false.\r\n */\r\nfunction stampRequestIdHeader(request: Request, response: Response) {\r\n const requestIdConfig = config.get(\"http.requestId\", {} as Record<string, any>);\r\n\r\n if (requestIdConfig.enabled === false) return;\r\n\r\n const headerName = requestIdConfig.header || \"X-Request-Id\";\r\n\r\n response.header(headerName, request.id);\r\n}\r\n\r\n/**\r\n * Create request store and execute middleware + handler\r\n *\r\n * Runs all registered contexts together using ContextManager.\r\n */\r\nexport function createRequestStore(\r\n request: Request<any>,\r\n response: Response,\r\n): Promise<ReturnedResponse> {\r\n stampRequestIdHeader(request, response);\r\n\r\n // Build all context stores using the immutable API\r\n // Each context defines its own store initialization via buildStore()\r\n const httpContextStore = contextManager.buildStores({ request, response });\r\n\r\n // Run all contexts together!\r\n return contextManager.runAll(httpContextStore, async () => {\r\n try {\r\n // Run middleware chain\r\n const result = await request.runMiddleware();\r\n\r\n if (result) {\r\n return result as ReturnedResponse;\r\n }\r\n\r\n // Execute route handler\r\n request.trigger(\"executingAction\", request.route);\r\n\r\n const handler = request.getHandler();\r\n\r\n request.log(\"Executing Handler\", \"info\");\r\n\r\n const output = await handler(request, response);\r\n\r\n request.log(\"Handler Executed Successfully\", \"success\");\r\n\r\n request.trigger(\"executedAction\", request.route);\r\n\r\n return output as ReturnedResponse;\r\n } catch (error: any) {\r\n request.log(`${error.constructor.name}: Request failed: ${error.message}`, \"error\");\r\n return handleRequestError(error, response);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Handle request errors\r\n * @internal\r\n */\r\nfunction handleRequestError(error: any, response: Response): ReturnedResponse {\r\n if (error instanceof HttpError) {\r\n const payload: GenericObject = {\r\n error: error.message,\r\n };\r\n if (error.payload) {\r\n payload.payload = error.payload;\r\n }\r\n\r\n if (environment() === \"development\") {\r\n payload.stack = error.stack;\r\n }\r\n\r\n return response.setStatusCode(error.status).send(payload);\r\n }\r\n\r\n if (error instanceof ResourceNotFoundError) {\r\n return response.notFound({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof UnAuthorizedError) {\r\n return response.unauthorized({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof ForbiddenError) {\r\n return response.forbidden({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof BadRequestError) {\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n if (error instanceof DatabaseWriterValidationError) {\r\n return response.badRequest({\r\n errors: error.errors,\r\n });\r\n }\r\n\r\n if (error instanceof ServerError) {\r\n return response.serverError({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n }\r\n\r\n console.log(error);\r\n\r\n return response.badRequest({\r\n error: error.message,\r\n ...error.payload,\r\n });\r\n}\r\n\r\n/**\r\n * Translate a keyword (uses request context for locale)\r\n */\r\nexport function t(keyword: string, placeholders?: any) {\r\n return (\r\n requestContextInstance.getRequest()?.trans(keyword, placeholders) ||\r\n trans(keyword, placeholders)\r\n );\r\n}\r\n\r\n/**\r\n * Get or compute a value from the request cache\r\n *\r\n * If the value exists in request, return it.\r\n * Otherwise, execute callback, store result in request, and return it.\r\n */\r\nexport async function fromRequest<T>(\r\n key: string,\r\n callback: (request?: Request) => Promise<T>,\r\n): Promise<T> {\r\n const { request } = useRequestStore();\r\n\r\n if (!request) return await callback();\r\n\r\n if (request[key]) return request[key];\r\n\r\n request[key] = await callback(request);\r\n\r\n return request[key];\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAS,qBAAqB,SAAkB,UAAoB;CAClE,MAAM,kBAAkBA,WAAO,IAAI,kBAAkB,CAAC,CAAwB;CAE9E,IAAI,gBAAgB,YAAY,OAAO;CAEvC,MAAM,aAAa,gBAAgB,UAAU;CAE7C,SAAS,OAAO,YAAY,QAAQ,EAAE;AACxC;;;;;;AAOA,SAAgB,mBACd,SACA,UAC2B;CAC3B,qBAAqB,SAAS,QAAQ;CAItC,MAAM,mBAAmB,eAAe,YAAY;EAAE;EAAS;CAAS,CAAC;CAGzE,OAAO,eAAe,OAAO,kBAAkB,YAAY;EACzD,IAAI;GAEF,MAAM,SAAS,MAAM,QAAQ,cAAc;GAE3C,IAAI,QACF,OAAO;GAIT,QAAQ,QAAQ,mBAAmB,QAAQ,KAAK;GAEhD,MAAM,UAAU,QAAQ,WAAW;GAEnC,QAAQ,IAAI,qBAAqB,MAAM;GAEvC,MAAM,SAAS,MAAM,QAAQ,SAAS,QAAQ;GAE9C,QAAQ,IAAI,iCAAiC,SAAS;GAEtD,QAAQ,QAAQ,kBAAkB,QAAQ,KAAK;GAE/C,OAAO;EACT,SAAS,OAAY;GACnB,QAAQ,IAAI,GAAG,MAAM,YAAY,KAAK,oBAAoB,MAAM,WAAW,OAAO;GAClF,OAAO,mBAAmB,OAAO,QAAQ;EAC3C;CACF,CAAC;AACH;;;;;AAMA,SAAS,mBAAmB,OAAY,UAAsC;CAC5E,IAAI,iBAAiB,WAAW;EAC9B,MAAM,UAAyB,EAC7B,OAAO,MAAM,QACf;EACA,IAAI,MAAM,SACR,QAAQ,UAAU,MAAM;EAG1B,IAAI,YAAY,MAAM,eACpB,QAAQ,QAAQ,MAAM;EAGxB,OAAO,SAAS,cAAc,MAAM,MAAM,CAAC,CAAC,KAAK,OAAO;CAC1D;CAEA,IAAI,iBAAiB,uBACnB,OAAO,SAAS,SAAS;EACvB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,mBACnB,OAAO,SAAS,aAAa;EAC3B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,gBACnB,OAAO,SAAS,UAAU;EACxB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,iBACnB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,IAAI,iBAAiB,+BACnB,OAAO,SAAS,WAAW,EACzB,QAAQ,MAAM,OAChB,CAAC;CAGH,IAAI,iBAAiB,aACnB,OAAO,SAAS,YAAY;EAC1B,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;CAGH,QAAQ,IAAI,KAAK;CAEjB,OAAO,SAAS,WAAW;EACzB,OAAO,MAAM;EACb,GAAG,MAAM;CACX,CAAC;AACH;;;;AAKA,SAAgB,EAAE,SAAiB,cAAoB;CACrD,OACEC,eAAuB,WAAW,CAAC,EAAE,MAAM,SAAS,YAAY,KAChE,MAAM,SAAS,YAAY;AAE/B;;;;;;;AAQA,eAAsB,YACpB,KACA,UACY;CACZ,MAAM,EAAE,YAAY,gBAAgB;CAEpC,IAAI,CAAC,SAAS,OAAO,MAAM,SAAS;CAEpC,IAAI,QAAQ,MAAM,OAAO,QAAQ;CAEjC,QAAQ,OAAO,MAAM,SAAS,OAAO;CAErC,OAAO,QAAQ;AACjB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t } from "./inject-request-context.mjs";
|
|
2
2
|
import { HttpErrorCodes } from "../error-codes.mjs";
|
|
3
|
-
import
|
|
3
|
+
import baseConfig from "@mongez/config";
|
|
4
4
|
|
|
5
5
|
//#region ../core/src/http/middleware/maintenance.middleware.ts
|
|
6
6
|
function isAllowlisted(path, patterns) {
|
|
@@ -31,11 +31,11 @@ function isAllowlisted(path, patterns) {
|
|
|
31
31
|
*/
|
|
32
32
|
function maintenanceMiddleware(options = {}) {
|
|
33
33
|
return (request, response) => {
|
|
34
|
-
if (!
|
|
35
|
-
const allowlist = options.allowlist ||
|
|
34
|
+
if (!baseConfig.get("http.maintenance.enabled", false)) return;
|
|
35
|
+
const allowlist = options.allowlist || baseConfig.get("http.maintenance.allowlist", ["/health"]);
|
|
36
36
|
const pathname = request.path.split("?")[0];
|
|
37
37
|
if (isAllowlisted(pathname, allowlist)) return;
|
|
38
|
-
const retryAfter = options.retryAfter ||
|
|
38
|
+
const retryAfter = options.retryAfter || baseConfig.get("http.maintenance.retryAfter", 60);
|
|
39
39
|
response.header("Retry-After", retryAfter);
|
|
40
40
|
return response.serviceUnavailable({
|
|
41
41
|
error: options.errorMessage || t("http.maintenance"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maintenance.middleware.mjs","names":[],"sources":["../../../../../../../../core/src/http/middleware/maintenance.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the maintenance middleware.\n */\nexport type MaintenanceOptions = {\n /**\n * Path prefixes (ending in `*`) or exact paths to bypass even when\n * maintenance is on. Falls back to `http.maintenance.allowlist`, then\n * `[\"/health\"]`.\n *\n * @example\n * allowlist: [\"/health\", \"/admin/*\", \"/webhooks/stripe\"]\n */\n allowlist?: string[];\n /**\n * Seconds advertised in the `Retry-After` header. Falls back to\n * `http.maintenance.retryAfter`, then `60`.\n */\n retryAfter?: number;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\nfunction isAllowlisted(path: string, patterns: string[]) {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return path.startsWith(pattern.slice(0, -1));\n }\n\n return path === pattern;\n });\n}\n\n/**\n * Return 503 + `Retry-After` for every request when `http.maintenance.enabled`\n * is true, except for paths matching the allowlist.\n *\n * Designed for app-wide registration via `http.middleware.all`. Toggled via\n * config — flipping the flag requires a restart (no runtime hot-flip yet).\n * Allowlist defaults to `[\"/health\"]` so health checks still pass during\n * planned downtime.\n *\n * @example\n * // src/config/http.ts\n * import { middleware } from \"@warlock.js/core\";\n *\n * export default {\n * maintenance: { enabled: env(\"MAINTENANCE_MODE\") === \"true\" },\n * middleware: {\n * all: [middleware.maintenance({ allowlist: [\"/health\", \"/admin/*\"] })],\n * },\n * };\n */\nexport function maintenanceMiddleware(options: MaintenanceOptions = {}): Middleware {\n return (request, response) => {\n const enabled = config.get(\"http.maintenance.enabled\", false);\n\n if (!enabled) return;\n\n const allowlist =\n options.allowlist || config.get(\"http.maintenance.allowlist\", [\"/health\"]);\n\n // `request.path` includes the query string, but allowlist entries are\n // path-only (\"/webhooks/stripe\"), so strip the query before matching —\n // otherwise \"/webhooks/stripe?sig=...\" never matches its exact entry.\n const pathname = request.path.split(\"?\")[0];\n\n if (isAllowlisted(pathname, allowlist)) return;\n\n const retryAfter =\n options.retryAfter || config.get(\"http.maintenance.retryAfter\", 60);\n\n response.header(\"Retry-After\", retryAfter);\n\n return response.serviceUnavailable({\n error: options.errorMessage || t(\"http.maintenance\"),\n errorCode: HttpErrorCodes.Maintenance,\n });\n };\n}\n"],"mappings":";;;;;AA6BA,SAAS,cAAc,MAAc,UAAoB;CACvD,OAAO,SAAS,MAAM,YAAY;EAChC,IAAI,QAAQ,SAAS,GAAG,GACtB,OAAO,KAAK,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAG7C,OAAO,SAAS;CAClB,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,QAAQ,SAAS,aAAa;EAG5B,IAAI,
|
|
1
|
+
{"version":3,"file":"maintenance.middleware.mjs","names":["config"],"sources":["../../../../../../../../core/src/http/middleware/maintenance.middleware.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the maintenance middleware.\n */\nexport type MaintenanceOptions = {\n /**\n * Path prefixes (ending in `*`) or exact paths to bypass even when\n * maintenance is on. Falls back to `http.maintenance.allowlist`, then\n * `[\"/health\"]`.\n *\n * @example\n * allowlist: [\"/health\", \"/admin/*\", \"/webhooks/stripe\"]\n */\n allowlist?: string[];\n /**\n * Seconds advertised in the `Retry-After` header. Falls back to\n * `http.maintenance.retryAfter`, then `60`.\n */\n retryAfter?: number;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\nfunction isAllowlisted(path: string, patterns: string[]) {\n return patterns.some((pattern) => {\n if (pattern.endsWith(\"*\")) {\n return path.startsWith(pattern.slice(0, -1));\n }\n\n return path === pattern;\n });\n}\n\n/**\n * Return 503 + `Retry-After` for every request when `http.maintenance.enabled`\n * is true, except for paths matching the allowlist.\n *\n * Designed for app-wide registration via `http.middleware.all`. Toggled via\n * config — flipping the flag requires a restart (no runtime hot-flip yet).\n * Allowlist defaults to `[\"/health\"]` so health checks still pass during\n * planned downtime.\n *\n * @example\n * // src/config/http.ts\n * import { middleware } from \"@warlock.js/core\";\n *\n * export default {\n * maintenance: { enabled: env(\"MAINTENANCE_MODE\") === \"true\" },\n * middleware: {\n * all: [middleware.maintenance({ allowlist: [\"/health\", \"/admin/*\"] })],\n * },\n * };\n */\nexport function maintenanceMiddleware(options: MaintenanceOptions = {}): Middleware {\n return (request, response) => {\n const enabled = config.get(\"http.maintenance.enabled\", false);\n\n if (!enabled) return;\n\n const allowlist =\n options.allowlist || config.get(\"http.maintenance.allowlist\", [\"/health\"]);\n\n // `request.path` includes the query string, but allowlist entries are\n // path-only (\"/webhooks/stripe\"), so strip the query before matching —\n // otherwise \"/webhooks/stripe?sig=...\" never matches its exact entry.\n const pathname = request.path.split(\"?\")[0];\n\n if (isAllowlisted(pathname, allowlist)) return;\n\n const retryAfter =\n options.retryAfter || config.get(\"http.maintenance.retryAfter\", 60);\n\n response.header(\"Retry-After\", retryAfter);\n\n return response.serviceUnavailable({\n error: options.errorMessage || t(\"http.maintenance\"),\n errorCode: HttpErrorCodes.Maintenance,\n });\n };\n}\n"],"mappings":";;;;;AA6BA,SAAS,cAAc,MAAc,UAAoB;CACvD,OAAO,SAAS,MAAM,YAAY;EAChC,IAAI,QAAQ,SAAS,GAAG,GACtB,OAAO,KAAK,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EAG7C,OAAO,SAAS;CAClB,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,UAA8B,CAAC,GAAe;CAClF,QAAQ,SAAS,aAAa;EAG5B,IAAI,CAFYA,WAAO,IAAI,4BAA4B,KAE5C,GAAG;EAEd,MAAM,YACJ,QAAQ,aAAaA,WAAO,IAAI,8BAA8B,CAAC,SAAS,CAAC;EAK3E,MAAM,WAAW,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC;EAEzC,IAAI,cAAc,UAAU,SAAS,GAAG;EAExC,MAAM,aACJ,QAAQ,cAAcA,WAAO,IAAI,+BAA+B,EAAE;EAEpE,SAAS,OAAO,eAAe,UAAU;EAEzC,OAAO,SAAS,mBAAmB;GACjC,OAAO,QAAQ,gBAAgB,EAAE,kBAAkB;GACnD;EACF,CAAC;CACH;AACF"}
|
package/esm/http/plugins.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { rootPath } from "../utils/paths.mjs";
|
|
2
2
|
import "../utils/index.mjs";
|
|
3
|
-
import
|
|
3
|
+
import baseConfig from "@mongez/config";
|
|
4
4
|
import fastifyMultipart from "@fastify/multipart";
|
|
5
5
|
|
|
6
6
|
//#region ../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:
|
|
14
|
-
timeWindow:
|
|
13
|
+
max: baseConfig.get("http.rateLimit.max", 60),
|
|
14
|
+
timeWindow: baseConfig.get("http.rateLimit.duration", 60 * 1e3)
|
|
15
15
|
});
|
|
16
16
|
const corsOptions = {
|
|
17
|
-
...
|
|
17
|
+
...baseConfig.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:
|
|
23
|
+
limits: { fileSize: baseConfig.get("http.fileUploadLimit", 10 * 1024 * 1024) }
|
|
24
24
|
});
|
|
25
25
|
server.register(import("@fastify/static"), {
|
|
26
|
-
root:
|
|
27
|
-
prefix:
|
|
26
|
+
root: baseConfig.get("storage.publicRoot", rootPath("public")),
|
|
27
|
+
prefix: baseConfig.get("storage.publicPrefix", "/public/")
|
|
28
28
|
});
|
|
29
29
|
server.register(import("@fastify/cookie"), {
|
|
30
|
-
secret:
|
|
31
|
-
parseOptions:
|
|
30
|
+
secret: baseConfig.get("http.cookies.secret"),
|
|
31
|
+
parseOptions: baseConfig.get("http.cookies.options", {})
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
package/esm/http/plugins.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.mjs","names":[],"sources":["../../../../../../../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,
|
|
1
|
+
{"version":3,"file":"plugins.mjs","names":["config"],"sources":["../../../../../../../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"}
|
package/esm/http/response.d.mts
CHANGED
|
@@ -324,13 +324,28 @@ declare class Response {
|
|
|
324
324
|
* cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the
|
|
325
325
|
* JSON wrapping for plain-string cookies (session tokens, opaque IDs).
|
|
326
326
|
*
|
|
327
|
+
* **Secure by default.** `httpOnly: true`, `sameSite: "lax"`, and — outside
|
|
328
|
+
* development — `secure: true` are applied unless you override them. These
|
|
329
|
+
* are the flags whose absence never fails a test and is fatal in production:
|
|
330
|
+
* without `httpOnly` any injected script can read the cookie, without
|
|
331
|
+
* `secure` it travels in cleartext, without `sameSite` it rides along on
|
|
332
|
+
* cross-site requests. Opting out is explicit, per call or via
|
|
333
|
+
* `http.cookies.options`.
|
|
334
|
+
*
|
|
335
|
+
* Precedence, lowest to highest: framework defaults → `http.cookies.options`
|
|
336
|
+
* → the per-call `options` argument.
|
|
337
|
+
*
|
|
327
338
|
* @example
|
|
328
339
|
* // JSON-wrapped (default) — round-trips with request.cookie()
|
|
329
|
-
* response.cookie("prefs", { theme: "dark" }, { maxAge: 3600
|
|
340
|
+
* response.cookie("prefs", { theme: "dark" }, { maxAge: 3600 });
|
|
330
341
|
*
|
|
331
342
|
* @example
|
|
332
343
|
* // Raw string — no JSON quoting; useful for tokens / opaque IDs
|
|
333
|
-
* response.cookie("session", "abc.def.ghi", { raw: true
|
|
344
|
+
* response.cookie("session", "abc.def.ghi", { raw: true });
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* // Deliberately readable by client-side JS
|
|
348
|
+
* response.cookie("theme", "dark", { httpOnly: false });
|
|
334
349
|
*/
|
|
335
350
|
cookie(name: string, value: CookieValue, options?: CookieOptions): this;
|
|
336
351
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.d.mts","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"response.d.mts","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"mappings":"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"}
|
package/esm/http/response.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { Application } from "../application/application.mjs";
|
|
1
2
|
import { StorageFile } from "../storage/storage-file.mjs";
|
|
2
3
|
import "../storage/index.mjs";
|
|
3
4
|
import { renderReact } from "../react/index.mjs";
|
|
4
|
-
import
|
|
5
|
+
import baseConfig from "@mongez/config";
|
|
5
6
|
import { log } from "@warlock.js/logger";
|
|
6
7
|
import path from "path";
|
|
7
8
|
import { isIterable, isPlainObject, isScalar } from "@mongez/supportive-is";
|
|
@@ -33,6 +34,23 @@ let ResponseStatus = /* @__PURE__ */ function(ResponseStatus) {
|
|
|
33
34
|
ResponseStatus[ResponseStatus["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
|
|
34
35
|
return ResponseStatus;
|
|
35
36
|
}({});
|
|
37
|
+
/**
|
|
38
|
+
* The cookie flags every response cookie gets unless something overrides them.
|
|
39
|
+
*
|
|
40
|
+
* Computed per call rather than hoisted to a constant because `secure` depends
|
|
41
|
+
* on the environment, which is not known at module-evaluation time.
|
|
42
|
+
*
|
|
43
|
+
* `secure` is relaxed in development only: a `Secure` cookie is dropped by the
|
|
44
|
+
* browser over plain http, which would silently break every local login. It
|
|
45
|
+
* stays on everywhere else, including test and staging.
|
|
46
|
+
*/
|
|
47
|
+
function secureCookieDefaults() {
|
|
48
|
+
return {
|
|
49
|
+
httpOnly: true,
|
|
50
|
+
sameSite: "lax",
|
|
51
|
+
secure: !Application.isDevelopment
|
|
52
|
+
};
|
|
53
|
+
}
|
|
36
54
|
var Response = class Response {
|
|
37
55
|
constructor() {
|
|
38
56
|
this.currentStatusCode = 200;
|
|
@@ -175,7 +193,7 @@ var Response = class Response {
|
|
|
175
193
|
* Make a log message
|
|
176
194
|
*/
|
|
177
195
|
log(message, level = "info") {
|
|
178
|
-
if (!
|
|
196
|
+
if (!baseConfig.get("http.log")) return;
|
|
179
197
|
log.log({
|
|
180
198
|
module: "response",
|
|
181
199
|
action: this.route.method + " " + this.route.path.replace("/*", "") + `:${this.request.id}`,
|
|
@@ -551,19 +569,35 @@ var Response = class Response {
|
|
|
551
569
|
* cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the
|
|
552
570
|
* JSON wrapping for plain-string cookies (session tokens, opaque IDs).
|
|
553
571
|
*
|
|
572
|
+
* **Secure by default.** `httpOnly: true`, `sameSite: "lax"`, and — outside
|
|
573
|
+
* development — `secure: true` are applied unless you override them. These
|
|
574
|
+
* are the flags whose absence never fails a test and is fatal in production:
|
|
575
|
+
* without `httpOnly` any injected script can read the cookie, without
|
|
576
|
+
* `secure` it travels in cleartext, without `sameSite` it rides along on
|
|
577
|
+
* cross-site requests. Opting out is explicit, per call or via
|
|
578
|
+
* `http.cookies.options`.
|
|
579
|
+
*
|
|
580
|
+
* Precedence, lowest to highest: framework defaults → `http.cookies.options`
|
|
581
|
+
* → the per-call `options` argument.
|
|
582
|
+
*
|
|
554
583
|
* @example
|
|
555
584
|
* // JSON-wrapped (default) — round-trips with request.cookie()
|
|
556
|
-
* response.cookie("prefs", { theme: "dark" }, { maxAge: 3600
|
|
585
|
+
* response.cookie("prefs", { theme: "dark" }, { maxAge: 3600 });
|
|
557
586
|
*
|
|
558
587
|
* @example
|
|
559
588
|
* // Raw string — no JSON quoting; useful for tokens / opaque IDs
|
|
560
|
-
* response.cookie("session", "abc.def.ghi", { raw: true
|
|
589
|
+
* response.cookie("session", "abc.def.ghi", { raw: true });
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* // Deliberately readable by client-side JS
|
|
593
|
+
* response.cookie("theme", "dark", { httpOnly: false });
|
|
561
594
|
*/
|
|
562
595
|
cookie(name, value, options = {}) {
|
|
563
596
|
const { raw, ...cookieOptions } = options;
|
|
564
|
-
const defaultOptions =
|
|
597
|
+
const defaultOptions = baseConfig.get("http.cookies.options", {});
|
|
565
598
|
const serializedValue = raw ? String(value) : JSON.stringify(value);
|
|
566
599
|
this.baseResponse.setCookie(name, serializedValue, {
|
|
600
|
+
...secureCookieDefaults(),
|
|
567
601
|
...defaultOptions,
|
|
568
602
|
...cookieOptions
|
|
569
603
|
});
|
|
@@ -576,7 +610,7 @@ var Response = class Response {
|
|
|
576
610
|
* response.clearCookie('token', { path: '/' });
|
|
577
611
|
*/
|
|
578
612
|
clearCookie(name, options) {
|
|
579
|
-
const defaultOptions =
|
|
613
|
+
const defaultOptions = baseConfig.get("http.cookies.options", {});
|
|
580
614
|
this.baseResponse.clearCookie(name, {
|
|
581
615
|
...defaultOptions,
|
|
582
616
|
...options
|
|
@@ -849,7 +883,7 @@ var Response = class Response {
|
|
|
849
883
|
* Mark the response as failed
|
|
850
884
|
*/
|
|
851
885
|
failedSchema(result) {
|
|
852
|
-
const { errors, inputKey, inputError, status } =
|
|
886
|
+
const { errors, inputKey, inputError, status } = baseConfig.get("validation.response", {
|
|
853
887
|
errors: "errors",
|
|
854
888
|
inputKey: "input",
|
|
855
889
|
inputError: "error",
|