@warlock.js/core 5.10.0 → 5.12.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 +33 -0
- package/esm/application/app.d.mts +3 -3
- package/esm/application/app.mjs +6 -6
- package/esm/application/app.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs +7 -3
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
- package/esm/connectors/socket-connector.mjs +13 -16
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/container/index.d.mts +20 -7
- package/esm/container/index.d.mts.map +1 -1
- package/esm/container/index.mjs +8 -5
- package/esm/container/index.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs +15 -3
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +1 -1
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs +50 -9
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.d.mts +8 -0
- package/esm/dev-server/file-manager.d.mts.map +1 -1
- package/esm/dev-server/file-manager.mjs +26 -4
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +3 -3
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +16 -2
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/flags.mjs +16 -1
- package/esm/dev-server/flags.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs +27 -3
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/errors/container-key-missing-error.mjs +18 -6
- package/esm/errors/container-key-missing-error.mjs.map +1 -1
- package/esm/generations/stubs.mjs +10 -10
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/context/request-context.d.mts +14 -11
- package/esm/http/context/request-context.d.mts.map +1 -1
- package/esm/http/context/request-context.mjs +13 -9
- package/esm/http/context/request-context.mjs.map +1 -1
- package/esm/http/createHttpApplication.d.mts.map +1 -1
- package/esm/http/createHttpApplication.mjs +2 -0
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/csp.d.mts +139 -0
- package/esm/http/csp.d.mts.map +1 -0
- package/esm/http/csp.mjs +134 -0
- package/esm/http/csp.mjs.map +1 -0
- package/esm/http/errors/errors.d.mts +17 -1
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +20 -1
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/index.d.mts +8 -3
- package/esm/http/index.mjs +6 -1
- package/esm/http/middleware/concurrency-limit.middleware.d.mts +2 -2
- package/esm/http/middleware/concurrency-limit.middleware.mjs +1 -1
- package/esm/http/middleware/concurrency-limit.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.d.mts +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +10 -0
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.d.mts +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.d.mts.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs +13 -13
- package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
- package/esm/http/request-controller.d.mts +1 -1
- package/esm/http/request.d.mts +32 -44
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +52 -44
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +18 -0
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +31 -0
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/stream-react-response.d.mts +65 -0
- package/esm/http/stream-react-response.d.mts.map +1 -0
- package/esm/http/stream-react-response.mjs +46 -0
- package/esm/http/stream-react-response.mjs.map +1 -0
- package/esm/http/tracing/index.mjs +4 -0
- package/esm/http/tracing/trace-id.d.mts +14 -0
- package/esm/http/tracing/trace-id.d.mts.map +1 -0
- package/esm/http/tracing/trace-id.mjs +37 -0
- package/esm/http/tracing/trace-id.mjs.map +1 -0
- package/esm/http/tracing/tracing-dispatcher.d.mts +42 -0
- package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -0
- package/esm/http/tracing/tracing-dispatcher.mjs +94 -0
- package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -0
- package/esm/http/tracing/tracing.type.d.mts +66 -0
- package/esm/http/tracing/tracing.type.d.mts.map +1 -0
- package/esm/http/types.d.mts +27 -21
- package/esm/http/types.d.mts.map +1 -1
- package/esm/index.d.mts +9 -4
- package/esm/index.mjs +6 -2
- package/esm/production/esbuild-preflight.mjs +47 -0
- package/esm/production/esbuild-preflight.mjs.map +1 -0
- package/esm/production/production-builder.mjs +19 -15
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/restful/restful.d.mts +1 -1
- package/esm/router/log-request-lifecycle.mjs +12 -2
- package/esm/router/log-request-lifecycle.mjs.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +10 -0
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +1 -1
- package/esm/socket/utils.mjs +1 -2
- package/esm/socket/utils.mjs.map +1 -1
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs +2 -5
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +12 -0
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/llms-full.txt +315 -25
- package/llms.txt +1 -0
- package/package.json +12 -12
- package/skills/add-connector/SKILL.md +1 -1
- package/skills/build-restful/SKILL.md +2 -2
- package/skills/configure-app/SKILL.md +31 -0
- package/skills/create-controller/SKILL.md +4 -4
- package/skills/request-tracing/SKILL.md +208 -0
- package/skills/send-response/SKILL.md +16 -0
- package/skills/store-file/SKILL.md +1 -1
- package/skills/upload-file/SKILL.md +14 -2
- package/skills/use-app-context/SKILL.md +2 -2
- package/skills/use-middleware/SKILL.md +2 -2
- package/skills/use-repository/SKILL.md +1 -1
- package/skills/use-request-locals/SKILL.md +1 -1
- package/skills/validate-input/SKILL.md +13 -0
- package/skills/wire-socket/SKILL.md +3 -3
- package/skills/write-middleware/SKILL.md +11 -5
- package/skills/write-use-case/SKILL.md +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.middleware.mjs","names":[],"sources":["../../../../../../../../core/src/http/middleware/rate-limit.middleware.ts"],"sourcesContent":["import type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Request } from \"../request\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the per-route rate limit middleware.\n */\nexport type RateLimitOptions = {\n /**\n * Max requests allowed within the time window.\n */\n max: number;\n /**\n * Time window in milliseconds.\n */\n duration: number;\n /**\n * Group key generator. Defaults to the client IP. Override to scope per-user,\n * per-organization, per-tenant, etc.\n *\n * @example\n * keyGenerator: (request) => request.user?.id ?? request.ip,\n */\n keyGenerator?: (request: Request) => string;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\ntype Bucket = {\n count: number;\n resetAt: number;\n};\n\nconst buckets = new Map<string, Bucket>();\n\n/**\n * Sweep expired buckets so the Map doesn't grow unbounded with one-shot keys\n * (rare per-user keys, dynamic path parameters in `keyGenerator`, etc.).\n * Called opportunistically, at most once per minute, not on a timer — keeps\n * the module side-effect-free for testing.\n */\nlet lastPruneAt = 0;\n\nfunction pruneExpired(now: number) {\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) {\n buckets.delete(key);\n }\n }\n}\n\n/**\n * Per-route / per-group rate limit. Layers on top of `@fastify/rate-limit`'s\n * global cap (configured via `http.rateLimit`) — both run, and either can\n * 429. Use this for endpoints that need a tighter cap than the global default:\n * login, OTP request, password reset, expensive search, AI completions.\n *\n * The counter lives in-process. With `N` replicas the effective cap is\n * `N × max`. For genuinely shared limits, configure `@fastify/rate-limit`'s\n * Redis store via `http.rateLimit` instead.\n *\n * Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on\n * every response; adds `Retry-After` (seconds) on a 429.\n *\n * @example\n * import { middleware } from \"@warlock.js/core\";\n * router.post(\"/auth/login\", loginController, {\n * middleware: [middleware.rateLimit({ max: 5, duration: 60_000 })],\n * });\n */\nexport function rateLimitMiddleware(options: RateLimitOptions): Middleware {\n return ({ request, response }) => {\n const now = Date.now();\n\n if (now - lastPruneAt > 60_000) {\n pruneExpired(now);\n lastPruneAt = now;\n }\n\n const groupKey = options.keyGenerator?.(request) || request.detectIp() || \"unknown\";\n const cacheKey = `${request.route.path}:${groupKey}`;\n\n let bucket = buckets.get(cacheKey);\n\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + options.duration };\n buckets.set(cacheKey, bucket);\n }\n\n bucket.count++;\n\n const remaining = Math.max(0, options.max - bucket.count);\n const retryAfter = Math.max(1, Math.ceil((bucket.resetAt - now) / 1000));\n\n response.header(\"X-RateLimit-Limit\", options.max);\n response.header(\"X-RateLimit-Remaining\", remaining);\n response.header(\"X-RateLimit-Reset\", Math.ceil(bucket.resetAt / 1000));\n\n if (bucket.count > options.max) {\n response.header(\"Retry-After\", retryAfter);\n\n return response.tooManyRequests({\n error: options.errorMessage || t(\"http.rateLimitExceeded\"),\n errorCode: HttpErrorCodes.RateLimitExceeded,\n });\n }\n };\n}\n"],"mappings":";;;;AAoCA,MAAM,0BAAU,IAAI,IAAoB;;;;;;;AAQxC,IAAI,cAAc;AAElB,SAAS,aAAa,KAAa;CACjC,KAAK,MAAM,CAAC,KAAK,WAAW,SAC1B,IAAI,OAAO,WAAW,KACpB,QAAQ,OAAO,GAAG;AAGxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,oBAAoB,SAAuC;CACzE,QAAQ,EAAE,SAAS,eAAe;EAChC,MAAM,MAAM,KAAK,IAAI;EAErB,IAAI,MAAM,cAAc,KAAQ;GAC9B,aAAa,GAAG;GAChB,cAAc;EAChB;EAEA,MAAM,WAAW,QAAQ,eAAe,OAAO,KAAK,QAAQ,SAAS,KAAK;EAC1E,MAAM,WAAW,GAAG,QAAQ,MAAM,KAAK,GAAG;EAE1C,IAAI,SAAS,QAAQ,IAAI,QAAQ;EAEjC,IAAI,CAAC,UAAU,OAAO,WAAW,KAAK;GACpC,SAAS;IAAE,OAAO;IAAG,SAAS,MAAM,QAAQ;GAAS;GACrD,QAAQ,IAAI,UAAU,MAAM;EAC9B;EAEA,OAAO;EAEP,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,MAAM,OAAO,KAAK;EACxD,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,UAAU,OAAO,GAAI,CAAC;EAEvE,SAAS,OAAO,qBAAqB,QAAQ,GAAG;EAChD,SAAS,OAAO,yBAAyB,SAAS;EAClD,SAAS,OAAO,qBAAqB,KAAK,KAAK,OAAO,UAAU,GAAI,CAAC;EAErE,IAAI,OAAO,QAAQ,QAAQ,KAAK;GAC9B,SAAS,OAAO,eAAe,UAAU;GAEzC,OAAO,SAAS,gBAAgB;IAC9B,OAAO,QAAQ,gBAAgB,EAAE,wBAAwB;IACzD;GACF,CAAC;EACH;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"rate-limit.middleware.mjs","names":[],"sources":["../../../../../../../../core/src/http/middleware/rate-limit.middleware.ts"],"sourcesContent":["import type { Middleware } from \"../../router\";\nimport { HttpErrorCodes } from \"../error-codes\";\nimport type { Request } from \"../request\";\nimport { t } from \"./inject-request-context\";\n\n/**\n * Options for the per-route rate limit middleware.\n */\nexport type RateLimitOptions = {\n /**\n * Max requests allowed within the time window.\n */\n max: number;\n /**\n * Time window in milliseconds.\n */\n duration: number;\n /**\n * Group key generator. Defaults to the client IP. Override to scope per-user,\n * per-organization, per-tenant, etc.\n *\n * @example\n * keyGenerator: (request) => request.locals.user?.id ?? request.ip,\n */\n keyGenerator?: (request: Request) => string;\n /**\n * Override the default error message.\n */\n errorMessage?: string;\n};\n\ntype Bucket = {\n count: number;\n resetAt: number;\n};\n\nconst buckets = new Map<string, Bucket>();\n\n/**\n * Sweep expired buckets so the Map doesn't grow unbounded with one-shot keys\n * (rare per-user keys, dynamic path parameters in `keyGenerator`, etc.).\n * Called opportunistically, at most once per minute, not on a timer — keeps\n * the module side-effect-free for testing.\n */\nlet lastPruneAt = 0;\n\nfunction pruneExpired(now: number) {\n for (const [key, bucket] of buckets) {\n if (bucket.resetAt <= now) {\n buckets.delete(key);\n }\n }\n}\n\n/**\n * Per-route / per-group rate limit. Layers on top of `@fastify/rate-limit`'s\n * global cap (configured via `http.rateLimit`) — both run, and either can\n * 429. Use this for endpoints that need a tighter cap than the global default:\n * login, OTP request, password reset, expensive search, AI completions.\n *\n * The counter lives in-process. With `N` replicas the effective cap is\n * `N × max`. For genuinely shared limits, configure `@fastify/rate-limit`'s\n * Redis store via `http.rateLimit` instead.\n *\n * Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` on\n * every response; adds `Retry-After` (seconds) on a 429.\n *\n * @example\n * import { middleware } from \"@warlock.js/core\";\n * router.post(\"/auth/login\", loginController, {\n * middleware: [middleware.rateLimit({ max: 5, duration: 60_000 })],\n * });\n */\nexport function rateLimitMiddleware(options: RateLimitOptions): Middleware {\n return ({ request, response }) => {\n const now = Date.now();\n\n if (now - lastPruneAt > 60_000) {\n pruneExpired(now);\n lastPruneAt = now;\n }\n\n const groupKey = options.keyGenerator?.(request) || request.detectIp() || \"unknown\";\n const cacheKey = `${request.route.path}:${groupKey}`;\n\n let bucket = buckets.get(cacheKey);\n\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + options.duration };\n buckets.set(cacheKey, bucket);\n }\n\n bucket.count++;\n\n const remaining = Math.max(0, options.max - bucket.count);\n const retryAfter = Math.max(1, Math.ceil((bucket.resetAt - now) / 1000));\n\n response.header(\"X-RateLimit-Limit\", options.max);\n response.header(\"X-RateLimit-Remaining\", remaining);\n response.header(\"X-RateLimit-Reset\", Math.ceil(bucket.resetAt / 1000));\n\n if (bucket.count > options.max) {\n response.header(\"Retry-After\", retryAfter);\n\n return response.tooManyRequests({\n error: options.errorMessage || t(\"http.rateLimitExceeded\"),\n errorCode: HttpErrorCodes.RateLimitExceeded,\n });\n }\n };\n}\n"],"mappings":";;;;AAoCA,MAAM,0BAAU,IAAI,IAAoB;;;;;;;AAQxC,IAAI,cAAc;AAElB,SAAS,aAAa,KAAa;CACjC,KAAK,MAAM,CAAC,KAAK,WAAW,SAC1B,IAAI,OAAO,WAAW,KACpB,QAAQ,OAAO,GAAG;AAGxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,oBAAoB,SAAuC;CACzE,QAAQ,EAAE,SAAS,eAAe;EAChC,MAAM,MAAM,KAAK,IAAI;EAErB,IAAI,MAAM,cAAc,KAAQ;GAC9B,aAAa,GAAG;GAChB,cAAc;EAChB;EAEA,MAAM,WAAW,QAAQ,eAAe,OAAO,KAAK,QAAQ,SAAS,KAAK;EAC1E,MAAM,WAAW,GAAG,QAAQ,MAAM,KAAK,GAAG;EAE1C,IAAI,SAAS,QAAQ,IAAI,QAAQ;EAEjC,IAAI,CAAC,UAAU,OAAO,WAAW,KAAK;GACpC,SAAS;IAAE,OAAO;IAAG,SAAS,MAAM,QAAQ;GAAS;GACrD,QAAQ,IAAI,UAAU,MAAM;EAC9B;EAEA,OAAO;EAEP,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,MAAM,OAAO,KAAK;EACxD,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,UAAU,OAAO,GAAI,CAAC;EAEvE,SAAS,OAAO,qBAAqB,QAAQ,GAAG;EAChD,SAAS,OAAO,yBAAyB,SAAS;EAClD,SAAS,OAAO,qBAAqB,KAAK,KAAK,OAAO,UAAU,GAAI,CAAC;EAErE,IAAI,OAAO,QAAQ,QAAQ,KAAK;GAC9B,SAAS,OAAO,eAAe,UAAU;GAEzC,OAAO,SAAS,gBAAgB;IAC9B,OAAO,QAAQ,gBAAgB,EAAE,wBAAwB;IACzD;GACF,CAAC;EACH;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency-key.d.mts","names":[],"sources":["../../../../../../../../../core/src/http/middleware/utils/idempotency-key.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"idempotency-key.d.mts","names":[],"sources":["../../../../../../../../../core/src/http/middleware/utils/idempotency-key.ts"],"mappings":";;;iBAcgB,qBAAA,CAAsB,KAAA,YAAiB,KAAK;;AAA5D;;;;AAA4D;AAmB5D;;;;iBAAgB,QAAA,CAAS,IAAa;AAAA,iBAuCtB,wBAAA,CAAyB,OAAA,EAAS,OAAO,EAAE,cAAA"}
|
|
@@ -36,24 +36,22 @@ function hashBody(body) {
|
|
|
36
36
|
* user B used a guessable value — while still letting the primitive work on
|
|
37
37
|
* public endpoints.
|
|
38
38
|
*
|
|
39
|
-
* Idempotency middleware must run **after** `authMiddleware` so
|
|
40
|
-
*
|
|
39
|
+
* Idempotency middleware must run **after** `authMiddleware` so
|
|
40
|
+
* `request.locals.user` (written by `@warlock.js/auth`) and
|
|
41
|
+
* `request.decodedAccessToken` are populated.
|
|
41
42
|
*
|
|
42
43
|
* @example
|
|
43
44
|
* buildIdempotencyCacheKey(request, "01J9XZQ-ABC"); // "idem:client:user_123:01J9XZQ-ABC"
|
|
44
45
|
*/
|
|
45
46
|
/**
|
|
46
|
-
* Read `id` off `request.user` without assuming
|
|
47
|
-
* augmentation
|
|
47
|
+
* Read `id` off `request.locals.user` without assuming any app's
|
|
48
|
+
* `RequestUser` augmentation (declared by `@warlock.js/auth`, which core
|
|
49
|
+
* cannot import) declares it, and without assuming `@warlock.js/auth` is
|
|
50
|
+
* even installed — hence the untyped `unknown` read here rather than a
|
|
51
|
+
* typed `RequestLocals["user"]` access.
|
|
48
52
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* would force that exact field (and type) onto every app: TypeScript
|
|
52
|
-
* interface merging requires all declarations of a shared member to have an
|
|
53
|
-
* identical type, so an app augmenting `RequestUser` with, say, `id: string`
|
|
54
|
-
* only would conflict with a core-declared `id?: string | number`. A local,
|
|
55
|
-
* narrow read survives any augmentation shape (eed20184 step (b) —
|
|
56
|
-
* `implementation/2026-08-20-A2-request-locals.md` §6.2) — no `as any`.
|
|
53
|
+
* A local, narrow runtime read survives any augmentation shape — no
|
|
54
|
+
* `as any`.
|
|
57
55
|
*/
|
|
58
56
|
function readUserId(user) {
|
|
59
57
|
if (!user || typeof user !== "object" || !("id" in user)) return void 0;
|
|
@@ -61,7 +59,9 @@ function readUserId(user) {
|
|
|
61
59
|
return typeof id === "string" || typeof id === "number" ? id : void 0;
|
|
62
60
|
}
|
|
63
61
|
function buildIdempotencyCacheKey(request, idempotencyKey) {
|
|
64
|
-
|
|
62
|
+
const userType = request.decodedAccessToken?.userType || "anonymous";
|
|
63
|
+
const locals = request.locals;
|
|
64
|
+
return `idem:${userType}:${readUserId(locals.user) || request.detectIp() || "unknown"}:${idempotencyKey}`;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency-key.mjs","names":[],"sources":["../../../../../../../../../core/src/http/middleware/utils/idempotency-key.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Request } from \"../../request\";\
|
|
1
|
+
{"version":3,"file":"idempotency-key.mjs","names":[],"sources":["../../../../../../../../../core/src/http/middleware/utils/idempotency-key.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { Request } from \"../../request\";\n\n/**\n * Idempotency-key validation rules.\n *\n * RFC draft-ietf-httpapi-idempotency-key suggests UUIDs or ULIDs but doesn't\n * mandate a format. We accept any printable ASCII string up to 255 chars —\n * tight enough to reject log-injection (no control characters / newlines),\n * loose enough to accept whatever ID scheme the client picks.\n */\nconst MAX_KEY_LENGTH = 255;\nconst PRINTABLE_ASCII = /^[\\x21-\\x7e]+$/;\n\nexport function isValidIdempotencyKey(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= MAX_KEY_LENGTH &&\n PRINTABLE_ASCII.test(value)\n );\n}\n\n/**\n * Hash a request body to a stable hex digest.\n *\n * Used to detect \"same key, different body\" — that's a client bug, not a retry.\n * sha256 is overkill for collision resistance here, but it's the dependency-free\n * choice and avoids importing a faster hasher.\n *\n * @example\n * hashBody({ amount: 100, currency: \"USD\" }); // \"a1b2c3...\"\n */\nexport function hashBody(body: unknown): string {\n const serialized = typeof body === \"string\" ? body : JSON.stringify(body ?? {});\n\n return createHash(\"sha256\").update(serialized).digest(\"hex\");\n}\n\n/**\n * Build the cache key for an idempotency record.\n *\n * Scope is `userType:userId` when authenticated, falling back to the client IP\n * when anonymous. This prevents user A from replaying user B's key — even if\n * user B used a guessable value — while still letting the primitive work on\n * public endpoints.\n *\n * Idempotency middleware must run **after** `authMiddleware` so\n * `request.locals.user` (written by `@warlock.js/auth`) and\n * `request.decodedAccessToken` are populated.\n *\n * @example\n * buildIdempotencyCacheKey(request, \"01J9XZQ-ABC\"); // \"idem:client:user_123:01J9XZQ-ABC\"\n */\n/**\n * Read `id` off `request.locals.user` without assuming any app's\n * `RequestUser` augmentation (declared by `@warlock.js/auth`, which core\n * cannot import) declares it, and without assuming `@warlock.js/auth` is\n * even installed — hence the untyped `unknown` read here rather than a\n * typed `RequestLocals[\"user\"]` access.\n *\n * A local, narrow runtime read survives any augmentation shape — no\n * `as any`.\n */\nfunction readUserId(user: unknown): string | number | undefined {\n if (!user || typeof user !== \"object\" || !(\"id\" in user)) return undefined;\n\n const id = (user as { id?: unknown }).id;\n\n return typeof id === \"string\" || typeof id === \"number\" ? id : undefined;\n}\n\nexport function buildIdempotencyCacheKey(request: Request, idempotencyKey: string): string {\n const userType = request.decodedAccessToken?.userType || \"anonymous\";\n const locals = request.locals as Record<string, unknown>;\n const userId = readUserId(locals.user) || request.detectIp() || \"unknown\";\n\n return `idem:${userType}:${userId}:${idempotencyKey}`;\n}\n"],"mappings":";;;;;;;;;;;AAWA,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;AAExB,SAAgB,sBAAsB,OAAiC;CACrE,OACE,OAAO,UAAU,YACjB,MAAM,SAAS,KACf,MAAM,UAAU,kBAChB,gBAAgB,KAAK,KAAK;AAE9B;;;;;;;;;;;AAYA,SAAgB,SAAS,MAAuB;CAC9C,MAAM,aAAa,OAAO,SAAS,WAAW,OAAO,KAAK,UAAU,QAAQ,CAAC,CAAC;CAE9E,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,OAAO,KAAK;AAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAS,WAAW,MAA4C;CAC9D,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,EAAE,QAAQ,OAAO,OAAO;CAEjE,MAAM,KAAM,KAA0B;CAEtC,OAAO,OAAO,OAAO,YAAY,OAAO,OAAO,WAAW,KAAK;AACjE;AAEA,SAAgB,yBAAyB,SAAkB,gBAAgC;CACzF,MAAM,WAAW,QAAQ,oBAAoB,YAAY;CACzD,MAAM,SAAS,QAAQ;CAGvB,OAAO,QAAQ,SAAS,GAFT,WAAW,OAAO,IAAI,KAAK,QAAQ,SAAS,KAAK,UAE9B,GAAG;AACvC"}
|
package/esm/http/request.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { UploadedFile } from "./uploaded-file.mjs";
|
|
2
1
|
import { Response } from "./response.mjs";
|
|
3
|
-
import {
|
|
2
|
+
import { UploadedFile } from "./uploaded-file.mjs";
|
|
3
|
+
import { DecodedAccessToken, RequestEvent, RequestLocals } from "./types.mjs";
|
|
4
4
|
import { Middleware, RequestHandler, Route } from "../router/types.mjs";
|
|
5
5
|
import { BaseValidator } from "@warlock.js/seal";
|
|
6
6
|
import { trans } from "@mongez/localization";
|
|
@@ -18,7 +18,8 @@ declare class Request<RequestValidation = any> {
|
|
|
18
18
|
*
|
|
19
19
|
* **Prefer framework methods first**: `request.input()`, `request.header()`,
|
|
20
20
|
* `request.body`, `request.query`, `request.params`, `request.file()`,
|
|
21
|
-
* `request.user
|
|
21
|
+
* `request.locals.user` (set by `@warlock.js/auth`), `request.detectIp()`,
|
|
22
|
+
* etc. They handle locale, parsing,
|
|
22
23
|
* trust-proxy, and validation pipeline integration correctly.
|
|
23
24
|
*
|
|
24
25
|
* **Reach for `baseRequest` only** when the framework genuinely lacks a
|
|
@@ -54,38 +55,23 @@ declare class Request<RequestValidation = any> {
|
|
|
54
55
|
get decodedAccessToken(): DecodedAccessToken | undefined;
|
|
55
56
|
set decodedAccessToken(value: DecodedAccessToken | undefined);
|
|
56
57
|
/**
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* `RequestUser` is empty by default, so ANY shape is assignable here at the
|
|
64
|
-
* declaration site — the app or auth package declares its real fields via
|
|
65
|
-
* module augmentation:
|
|
58
|
+
* REMOVED in 5.12.0 — the authenticated user now lives at
|
|
59
|
+
* `request.locals.user`, a key `@warlock.js/auth` declares via module
|
|
60
|
+
* augmentation on `RequestLocals` and writes from its middleware after a
|
|
61
|
+
* successful token resolution. `RequestUser` moved out of core to
|
|
62
|
+
* `@warlock.js/auth` alongside it.
|
|
66
63
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* ```
|
|
64
|
+
* This getter is a development-time diagnostic only, kept for one release
|
|
65
|
+
* so a call site that still reads `request.user` fails loudly at runtime
|
|
66
|
+
* instead of silently reading `undefined`. It is typed `never` so it
|
|
67
|
+
* cannot reintroduce an auth-shaped type into core, and it throws
|
|
68
|
+
* unconditionally outside production so the failure is impossible to miss
|
|
69
|
+
* in local dev — see `RequestUserMovedError`.
|
|
74
70
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* an intersection type hand-declared per app) with a property core itself
|
|
78
|
-
* declares and types. `clearCurrentUser()` below is the one place core
|
|
79
|
-
* writes it directly; auth middleware writes it after a successful token
|
|
80
|
-
* resolution.
|
|
81
|
-
*
|
|
82
|
-
* A prototype accessor, not a plain field: the setter also marks the
|
|
83
|
-
* request `authDerived` (see below), so `clearCurrentUser()`'s
|
|
84
|
-
* `this.user = undefined` still counts as touching auth state rather than
|
|
85
|
-
* un-marking it.
|
|
71
|
+
* There is no setter: nothing in core or downstream packages should ever
|
|
72
|
+
* assign to `request.user` again.
|
|
86
73
|
*/
|
|
87
|
-
get user():
|
|
88
|
-
set user(value: RequestUser | undefined);
|
|
74
|
+
get user(): never;
|
|
89
75
|
/**
|
|
90
76
|
* Private, server-only, per-request data bag.
|
|
91
77
|
*
|
|
@@ -115,18 +101,6 @@ declare class Request<RequestValidation = any> {
|
|
|
115
101
|
* a prior one.
|
|
116
102
|
*/
|
|
117
103
|
locals: RequestLocals;
|
|
118
|
-
/**
|
|
119
|
-
* Forget the authenticated user for this request.
|
|
120
|
-
*
|
|
121
|
-
* The `user` documentation above already names this as "the one place core
|
|
122
|
-
* writes it directly", and `@warlock.js/auth`'s middleware already calls it when
|
|
123
|
-
* a token is forged, malformed, expired, or of the wrong type. It was designed,
|
|
124
|
-
* documented and called — just never written, so `auth` could not typecheck.
|
|
125
|
-
*
|
|
126
|
-
* Clearing rather than leaving a stale value matters: a request that failed
|
|
127
|
-
* authentication must not carry the identity of whoever this object last held.
|
|
128
|
-
*/
|
|
129
|
-
clearCurrentUser(): void;
|
|
130
104
|
/**
|
|
131
105
|
* Backing field for the lazily-generated CSP nonce. Left `undefined` until
|
|
132
106
|
* the first `request.nonce` read; see the `nonce` getter below.
|
|
@@ -180,6 +154,12 @@ declare class Request<RequestValidation = any> {
|
|
|
180
154
|
* Request id
|
|
181
155
|
*/
|
|
182
156
|
id: string;
|
|
157
|
+
/**
|
|
158
|
+
* Trace id. The inbound `traceparent` header's trace id
|
|
159
|
+
* when valid, otherwise `id`. Resolved once in `setRequest`, alongside
|
|
160
|
+
* `id` itself — see `resolveTraceId`.
|
|
161
|
+
*/
|
|
162
|
+
traceId: string;
|
|
183
163
|
/**
|
|
184
164
|
* Start Time
|
|
185
165
|
*/
|
|
@@ -202,6 +182,14 @@ declare class Request<RequestValidation = any> {
|
|
|
202
182
|
* default is used regardless of any incoming header.
|
|
203
183
|
*/
|
|
204
184
|
protected resolveRequestId(): void;
|
|
185
|
+
/**
|
|
186
|
+
* Derive `traceId`: the inbound
|
|
187
|
+
* `traceparent` header's trace id when it is a valid W3C traceparent,
|
|
188
|
+
* otherwise `id`. Always runs — unlike request-id inheritance this has no
|
|
189
|
+
* `enabled: false` escape hatch, since `traceId` is only ever read when
|
|
190
|
+
* tracing hooks are enabled (see `./tracing`).
|
|
191
|
+
*/
|
|
192
|
+
protected resolveTraceId(): void;
|
|
205
193
|
/**
|
|
206
194
|
* Validate a candidate request-id value. Accepts non-empty printable ASCII
|
|
207
195
|
* up to 128 characters — tight enough to reject newline / control-character
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.mts","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"request.d.mts","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"mappings":"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"}
|
package/esm/http/request.mjs
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { config } from "../config/config-getter.mjs";
|
|
2
2
|
import { LOCALE_COOKIE_NAME, resolveLocaleConfiguration } from "../config/locale-configuration.mjs";
|
|
3
|
+
import { RequestUserMovedError } from "./errors/errors.mjs";
|
|
4
|
+
import { deriveTraceId } from "./tracing/trace-id.mjs";
|
|
5
|
+
import { buildTracingContext, dispatchPhase, isTracingEnabled } from "./tracing/tracing-dispatcher.mjs";
|
|
6
|
+
import "./tracing/index.mjs";
|
|
3
7
|
import { createRequestStore } from "./middleware/inject-request-context.mjs";
|
|
8
|
+
import { Application } from "../application/application.mjs";
|
|
4
9
|
import { validateAll } from "../validation/validateAll.mjs";
|
|
5
10
|
import { Response } from "./response.mjs";
|
|
6
11
|
import { UploadedFile } from "./uploaded-file.mjs";
|
|
@@ -22,6 +27,7 @@ var Request = class Request {
|
|
|
22
27
|
this.t = trans;
|
|
23
28
|
this._locale = "";
|
|
24
29
|
this.id = Random.string(32);
|
|
30
|
+
this.traceId = "";
|
|
25
31
|
this.startTime = Date.now();
|
|
26
32
|
}
|
|
27
33
|
/**
|
|
@@ -39,52 +45,24 @@ var Request = class Request {
|
|
|
39
45
|
this.locals.authDerived = true;
|
|
40
46
|
}
|
|
41
47
|
/**
|
|
42
|
-
*
|
|
48
|
+
* REMOVED in 5.12.0 — the authenticated user now lives at
|
|
49
|
+
* `request.locals.user`, a key `@warlock.js/auth` declares via module
|
|
50
|
+
* augmentation on `RequestLocals` and writes from its middleware after a
|
|
51
|
+
* successful token resolution. `RequestUser` moved out of core to
|
|
52
|
+
* `@warlock.js/auth` alongside it.
|
|
43
53
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
54
|
+
* This getter is a development-time diagnostic only, kept for one release
|
|
55
|
+
* so a call site that still reads `request.user` fails loudly at runtime
|
|
56
|
+
* instead of silently reading `undefined`. It is typed `never` so it
|
|
57
|
+
* cannot reintroduce an auth-shaped type into core, and it throws
|
|
58
|
+
* unconditionally outside production so the failure is impossible to miss
|
|
59
|
+
* in local dev — see `RequestUserMovedError`.
|
|
47
60
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* interface RequestUser {
|
|
51
|
-
* id: string | number;
|
|
52
|
-
* }
|
|
53
|
-
* }
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* Replaces the v4 `GuardedRequest` convention
|
|
57
|
-
* (`create-warlock/.../guarded.request.ts` — `Request<T> & { user: User }`,
|
|
58
|
-
* an intersection type hand-declared per app) with a property core itself
|
|
59
|
-
* declares and types. `clearCurrentUser()` below is the one place core
|
|
60
|
-
* writes it directly; auth middleware writes it after a successful token
|
|
61
|
-
* resolution.
|
|
62
|
-
*
|
|
63
|
-
* A prototype accessor, not a plain field: the setter also marks the
|
|
64
|
-
* request `authDerived` (see below), so `clearCurrentUser()`'s
|
|
65
|
-
* `this.user = undefined` still counts as touching auth state rather than
|
|
66
|
-
* un-marking it.
|
|
61
|
+
* There is no setter: nothing in core or downstream packages should ever
|
|
62
|
+
* assign to `request.user` again.
|
|
67
63
|
*/
|
|
68
64
|
get user() {
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
set user(value) {
|
|
72
|
-
this._user = value;
|
|
73
|
-
this.locals.authDerived = true;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Forget the authenticated user for this request.
|
|
77
|
-
*
|
|
78
|
-
* The `user` documentation above already names this as "the one place core
|
|
79
|
-
* writes it directly", and `@warlock.js/auth`'s middleware already calls it when
|
|
80
|
-
* a token is forged, malformed, expired, or of the wrong type. It was designed,
|
|
81
|
-
* documented and called — just never written, so `auth` could not typecheck.
|
|
82
|
-
*
|
|
83
|
-
* Clearing rather than leaving a stale value matters: a request that failed
|
|
84
|
-
* authentication must not carry the identity of whoever this object last held.
|
|
85
|
-
*/
|
|
86
|
-
clearCurrentUser() {
|
|
87
|
-
this.user = void 0;
|
|
65
|
+
if (Application.isDevelopment) throw new RequestUserMovedError();
|
|
88
66
|
}
|
|
89
67
|
/**
|
|
90
68
|
* Per-request Content-Security-Policy nonce — a fresh, unguessable value
|
|
@@ -118,6 +96,7 @@ var Request = class Request {
|
|
|
118
96
|
setRequest(request) {
|
|
119
97
|
this.baseRequest = request;
|
|
120
98
|
this.resolveRequestId();
|
|
99
|
+
this.resolveTraceId();
|
|
121
100
|
this.parsePayload();
|
|
122
101
|
this.trans = this.t = (keyword, placeholders) => transFrom(this.getLocaleCode(), keyword, placeholders);
|
|
123
102
|
return this;
|
|
@@ -143,6 +122,18 @@ var Request = class Request {
|
|
|
143
122
|
if (typeof requestIdConfig.generator === "function") this.id = requestIdConfig.generator();
|
|
144
123
|
}
|
|
145
124
|
/**
|
|
125
|
+
* Derive `traceId`: the inbound
|
|
126
|
+
* `traceparent` header's trace id when it is a valid W3C traceparent,
|
|
127
|
+
* otherwise `id`. Always runs — unlike request-id inheritance this has no
|
|
128
|
+
* `enabled: false` escape hatch, since `traceId` is only ever read when
|
|
129
|
+
* tracing hooks are enabled (see `./tracing`).
|
|
130
|
+
*/
|
|
131
|
+
resolveTraceId() {
|
|
132
|
+
const header = this.baseRequest.headers.traceparent;
|
|
133
|
+
const traceparent = Array.isArray(header) ? header[0] : header;
|
|
134
|
+
this.traceId = deriveTraceId(traceparent, this.id);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
146
137
|
* Validate a candidate request-id value. Accepts non-empty printable ASCII
|
|
147
138
|
* up to 128 characters — tight enough to reject newline / control-character
|
|
148
139
|
* log-injection, loose enough to accept UUIDs, ULIDs, snowflakes, etc.
|
|
@@ -483,7 +474,14 @@ var Request = class Request {
|
|
|
483
474
|
}
|
|
484
475
|
const handler = this.route.handler;
|
|
485
476
|
if (!handler.validation) return;
|
|
486
|
-
|
|
477
|
+
const tracingEnabled = isTracingEnabled();
|
|
478
|
+
const validationStartedAt = tracingEnabled ? performance.now() : 0;
|
|
479
|
+
const validationOutput = await validateAll(handler.validation, this, this.response);
|
|
480
|
+
if (tracingEnabled) dispatchPhase(buildTracingContext(this), {
|
|
481
|
+
name: "validation",
|
|
482
|
+
durationMs: performance.now() - validationStartedAt
|
|
483
|
+
});
|
|
484
|
+
return validationOutput;
|
|
487
485
|
}
|
|
488
486
|
/**
|
|
489
487
|
* Return the request handler attached to the current route.
|
|
@@ -540,12 +538,22 @@ var Request = class Request {
|
|
|
540
538
|
if (middlewares.length === 0) return;
|
|
541
539
|
this.log("About to execute request middlewares");
|
|
542
540
|
this.trigger("executingMiddleware", middlewares, this.route);
|
|
543
|
-
|
|
541
|
+
const tracingEnabled = isTracingEnabled();
|
|
542
|
+
for (const [index, middleware] of middlewares.entries()) {
|
|
544
543
|
this.log("Executing middleware " + colors.yellowBright(middleware.name));
|
|
544
|
+
const middlewareStartedAt = tracingEnabled ? performance.now() : 0;
|
|
545
545
|
const output = await middleware({
|
|
546
546
|
request: this,
|
|
547
547
|
response: this.response
|
|
548
548
|
});
|
|
549
|
+
if (tracingEnabled) dispatchPhase(buildTracingContext(this), {
|
|
550
|
+
name: "middleware",
|
|
551
|
+
durationMs: performance.now() - middlewareStartedAt,
|
|
552
|
+
attrs: {
|
|
553
|
+
name: middleware.name,
|
|
554
|
+
index
|
|
555
|
+
}
|
|
556
|
+
});
|
|
549
557
|
this.log("Executed middleware " + colors.yellowBright(middleware.name), "success");
|
|
550
558
|
if (output !== void 0) {
|
|
551
559
|
this.log(colors.yellow("request intercepted by middleware ") + colors.cyanBright(middleware.name), "warn");
|