@daloyjs/core 1.0.0-beta.6 → 1.0.0-beta.7
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/README.md +2 -2
- package/dist/adapters/bun.d.ts +13 -1
- package/dist/adapters/bun.js +8 -1
- package/dist/adapters/cloudflare.d.ts +7 -1
- package/dist/adapters/cloudflare.js +6 -1
- package/dist/adapters/deno.d.ts +11 -1
- package/dist/adapters/deno.js +8 -1
- package/dist/adapters/fastly.d.ts +12 -2
- package/dist/adapters/fastly.js +12 -2
- package/dist/adapters/lambda.d.ts +37 -1
- package/dist/adapters/lambda.js +6 -1
- package/dist/adapters/node.d.ts +12 -1
- package/dist/adapters/node.js +7 -1
- package/dist/adapters/vercel.d.ts +13 -1
- package/dist/adapters/vercel.js +12 -1
- package/dist/app.d.ts +67 -17
- package/dist/app.js +97 -31
- package/dist/banner.d.ts +6 -0
- package/dist/banner.js +6 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.js +23 -1
- package/dist/combine.d.ts +8 -0
- package/dist/combine.js +8 -0
- package/dist/compression.d.ts +3 -0
- package/dist/compression.js +3 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/conn-info.d.ts +35 -2
- package/dist/conn-info.js +35 -2
- package/dist/contract.d.ts +2 -0
- package/dist/contract.js +2 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.js +12 -0
- package/dist/dependency.d.ts +4 -0
- package/dist/dependency.js +3 -0
- package/dist/discriminator.d.ts +13 -0
- package/dist/discriminator.js +23 -1
- package/dist/docs.d.ts +78 -0
- package/dist/docs.js +21 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +14 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.js +2 -0
- package/dist/fetch-guard.d.ts +7 -0
- package/dist/fetch-guard.js +7 -0
- package/dist/fetch-resilience.d.ts +4 -0
- package/dist/fetch-resilience.js +4 -0
- package/dist/http-signatures.d.ts +32 -0
- package/dist/http-signatures.js +30 -0
- package/dist/index.d.ts +1 -1
- package/dist/ip-restriction.d.ts +40 -3
- package/dist/ip-restriction.js +35 -3
- package/dist/jwk.d.ts +12 -1
- package/dist/jwk.js +6 -0
- package/dist/jwt.d.ts +14 -0
- package/dist/jwt.js +10 -0
- package/dist/load-shedding.d.ts +3 -0
- package/dist/load-shedding.js +3 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +3 -0
- package/dist/mcp.d.ts +152 -10
- package/dist/mcp.js +223 -19
- package/dist/middleware.d.ts +68 -0
- package/dist/middleware.js +17 -0
- package/dist/mtls.d.ts +19 -2
- package/dist/mtls.js +12 -2
- package/dist/multipart.d.ts +42 -5
- package/dist/multipart.js +41 -5
- package/dist/openapi.d.ts +15 -9
- package/dist/openapi.js +6 -9
- package/dist/rate-limit-redis.d.ts +21 -2
- package/dist/rate-limit-redis.js +17 -2
- package/dist/safe-redirect.d.ts +6 -0
- package/dist/safe-redirect.js +6 -0
- package/dist/sbom.cdx.json +9 -9
- package/dist/sbom.spdx.json +5 -5
- package/dist/scheduler.d.ts +4 -0
- package/dist/schema.d.ts +25 -0
- package/dist/security-schemes.d.ts +50 -0
- package/dist/security-schemes.js +6 -0
- package/dist/security.d.ts +33 -0
- package/dist/security.js +28 -10
- package/dist/session.d.ts +34 -5
- package/dist/session.js +31 -5
- package/dist/streaming.d.ts +19 -0
- package/dist/streaming.js +16 -0
- package/dist/subdomains.d.ts +4 -0
- package/dist/subdomains.js +4 -0
- package/dist/time-claims.d.ts +22 -2
- package/dist/time-claims.js +6 -0
- package/dist/tracing.d.ts +12 -0
- package/dist/tracing.js +6 -0
- package/dist/types.d.ts +70 -1
- package/dist/waf.js +21 -1
- package/dist/webhook-delivery.d.ts +6 -0
- package/dist/webhook-delivery.js +5 -0
- package/dist/websocket.d.ts +137 -4
- package/dist/websocket.js +105 -4
- package/package.json +1 -1
package/dist/session.js
CHANGED
|
@@ -161,6 +161,7 @@ function makeSessionContext(id, data, internal, regenerate) {
|
|
|
161
161
|
*/
|
|
162
162
|
export class MemorySessionStore {
|
|
163
163
|
map = new Map();
|
|
164
|
+
/** Load a record; expired records are deleted and reported as `null`. */
|
|
164
165
|
get(sid) {
|
|
165
166
|
const rec = this.map.get(sid);
|
|
166
167
|
if (!rec)
|
|
@@ -171,12 +172,15 @@ export class MemorySessionStore {
|
|
|
171
172
|
}
|
|
172
173
|
return rec;
|
|
173
174
|
}
|
|
175
|
+
/** Store (or overwrite) a record for a session id. */
|
|
174
176
|
set(sid, record) {
|
|
175
177
|
this.map.set(sid, record);
|
|
176
178
|
}
|
|
179
|
+
/** Remove the record for a session id, if present. */
|
|
177
180
|
destroy(sid) {
|
|
178
181
|
this.map.delete(sid);
|
|
179
182
|
}
|
|
183
|
+
/** Extend an existing record's expiry (ms since epoch) without rewriting data. */
|
|
180
184
|
touch(sid, expiresAt) {
|
|
181
185
|
const rec = this.map.get(sid);
|
|
182
186
|
if (rec)
|
|
@@ -210,6 +214,14 @@ export class MemorySessionStore {
|
|
|
210
214
|
* const app = new App();
|
|
211
215
|
* app.use(session({ secret: process.env.SESSION_SECRET! }));
|
|
212
216
|
* ```
|
|
217
|
+
*
|
|
218
|
+
* @param opts Secrets, cookie attributes, store, TTL, and rolling behavior;
|
|
219
|
+
* see {@link SessionOptions}. Cookies default to `__Host-` prefixed,
|
|
220
|
+
* `HttpOnly`, `Secure`, `SameSite=Lax`.
|
|
221
|
+
* @returns A {@link Hooks} object that loads/verifies the session before the
|
|
222
|
+
* handler and persists mutations plus the `Set-Cookie` header afterwards.
|
|
223
|
+
* @throws Error at setup time on missing/short secrets, invalid cookie
|
|
224
|
+
* attribute combinations, or a non-positive `ttlSeconds`.
|
|
213
225
|
*/
|
|
214
226
|
export function session(opts) {
|
|
215
227
|
const cookieName = opts.cookieName ?? DEFAULT_COOKIE_NAME;
|
|
@@ -432,6 +444,12 @@ function stableSnapshot(value) {
|
|
|
432
444
|
* session key-rotation arrays: old cookies verify with any configured secret
|
|
433
445
|
* and rotated cookies are re-signed with the first/current secret.
|
|
434
446
|
*
|
|
447
|
+
* @param opts Which session keys (or computed value) to watch, and whether
|
|
448
|
+
* payload data survives rotation (`keepData`, default `true`); see
|
|
449
|
+
* {@link RotateSessionOptions}.
|
|
450
|
+
* @returns A {@link Hooks} object that snapshots the watched value before the
|
|
451
|
+
* handler and regenerates the session id when it changed afterwards
|
|
452
|
+
* (session-fixation defense).
|
|
435
453
|
* @since 0.23.0
|
|
436
454
|
*/
|
|
437
455
|
export function rotateSession(opts = {}) {
|
|
@@ -462,9 +480,13 @@ export function rotateSession(opts = {}) {
|
|
|
462
480
|
}
|
|
463
481
|
// ---------- Low-level signing helpers (re-exported for advanced use) ----------
|
|
464
482
|
/**
|
|
465
|
-
* Sign an arbitrary string with HMAC-SHA256.
|
|
466
|
-
*
|
|
467
|
-
*
|
|
483
|
+
* Sign an arbitrary string with HMAC-SHA256. Useful for building custom
|
|
484
|
+
* signed cookies or tokens that do not need a session store.
|
|
485
|
+
*
|
|
486
|
+
* @param value String to sign; must not contain `.` (the separator).
|
|
487
|
+
* @param secret HMAC key, at least 16 characters.
|
|
488
|
+
* @returns `${value}.${sig}` where `sig` is URL-safe base64.
|
|
489
|
+
* @throws Error when `value` contains `.` or the secret is too short.
|
|
468
490
|
*/
|
|
469
491
|
export async function signValue(value, secret) {
|
|
470
492
|
if (value.includes(".")) {
|
|
@@ -475,8 +497,12 @@ export async function signValue(value, secret) {
|
|
|
475
497
|
return `${value}.${sig}`;
|
|
476
498
|
}
|
|
477
499
|
/**
|
|
478
|
-
* Verify a `signValue()`-produced string.
|
|
479
|
-
*
|
|
500
|
+
* Verify a `signValue()`-produced string. Constant-time on the signature
|
|
501
|
+
* comparison.
|
|
502
|
+
*
|
|
503
|
+
* @param signed The `${value}.${sig}` string to verify.
|
|
504
|
+
* @param secret HMAC key(s); an array lets rotated old secrets still verify.
|
|
505
|
+
* @returns The original value when any secret's signature matches, else `null`.
|
|
480
506
|
*/
|
|
481
507
|
export async function verifySignedValue(signed, secret) {
|
|
482
508
|
const dot = signed.lastIndexOf(".");
|
package/dist/streaming.d.ts
CHANGED
|
@@ -36,8 +36,11 @@
|
|
|
36
36
|
* and reconnection control without sending an event payload.
|
|
37
37
|
*/
|
|
38
38
|
export interface SSEMessage {
|
|
39
|
+
/** Event payload. Strings are sent verbatim; other values are `JSON.stringify`-ed. */
|
|
39
40
|
data?: unknown;
|
|
41
|
+
/** Event name (`event:` field). Newlines are replaced with spaces to prevent frame injection. */
|
|
40
42
|
event?: string;
|
|
43
|
+
/** Last-event ID (`id:` field). Newlines are replaced with spaces to prevent frame injection. */
|
|
41
44
|
id?: string;
|
|
42
45
|
/** Reconnection delay in milliseconds. */
|
|
43
46
|
retry?: number;
|
|
@@ -76,11 +79,19 @@ type IterableSource<T> = AsyncIterable<T> | Iterable<T> | (() => AsyncIterable<T
|
|
|
76
79
|
* Build a backpressure-safe `ReadableStream` from an async iterable of SSE
|
|
77
80
|
* messages. The iterator is only advanced when the consumer pulls the next
|
|
78
81
|
* chunk, so a slow client cannot cause unbounded buffering.
|
|
82
|
+
*
|
|
83
|
+
* @param source Async/sync iterable (or factory) yielding {@link SSEMessage}s or plain strings.
|
|
84
|
+
* @param opts Abort signal and optional keep-alive interval; see {@link SSEStreamOptions}.
|
|
85
|
+
* @returns A `ReadableStream<Uint8Array>` of encoded `text/event-stream` frames.
|
|
79
86
|
*/
|
|
80
87
|
export declare function sseStream(source: IterableSource<SSEMessage | string>, opts?: SSEStreamOptions): ReadableStream<Uint8Array>;
|
|
81
88
|
/**
|
|
82
89
|
* Wrap `sseStream` in a `Response` with the proper SSE headers
|
|
83
90
|
* (`text/event-stream`, no caching, keep-alive). Caller-supplied headers win.
|
|
91
|
+
*
|
|
92
|
+
* @param source Async/sync iterable (or factory) yielding {@link SSEMessage}s or plain strings.
|
|
93
|
+
* @param opts Status, headers, abort signal, and keep-alive; see {@link SSEResponseOptions}.
|
|
94
|
+
* @returns A streaming `Response` (default status `200`) with SSE headers applied.
|
|
84
95
|
*/
|
|
85
96
|
export declare function sseResponse(source: IterableSource<SSEMessage | string>, opts?: SSEResponseOptions): Response;
|
|
86
97
|
/**
|
|
@@ -88,11 +99,19 @@ export declare function sseResponse(source: IterableSource<SSEMessage | string>,
|
|
|
88
99
|
* JSON) records from an async iterable. Each yielded value is encoded with
|
|
89
100
|
* `JSON.stringify` and terminated with `\n`. Values that stringify to
|
|
90
101
|
* `undefined` throw because they cannot be represented as valid NDJSON.
|
|
102
|
+
*
|
|
103
|
+
* @param source Async/sync iterable (or factory) yielding JSON-serializable values.
|
|
104
|
+
* @param opts Optional abort signal; see {@link StreamOptions}.
|
|
105
|
+
* @returns A `ReadableStream<Uint8Array>` of newline-terminated JSON records.
|
|
91
106
|
*/
|
|
92
107
|
export declare function ndjsonStream<T>(source: IterableSource<T>, opts?: StreamOptions): ReadableStream<Uint8Array>;
|
|
93
108
|
/**
|
|
94
109
|
* Wrap `ndjsonStream` in a `Response` with `application/x-ndjson` and
|
|
95
110
|
* cache-busting headers. Caller-supplied headers win.
|
|
111
|
+
*
|
|
112
|
+
* @param source Async/sync iterable (or factory) yielding JSON-serializable values.
|
|
113
|
+
* @param opts Status, headers, and abort signal; see {@link NDJSONResponseOptions}.
|
|
114
|
+
* @returns A streaming `Response` (default status `200`) with NDJSON headers applied.
|
|
96
115
|
*/
|
|
97
116
|
export declare function ndjsonResponse<T>(source: IterableSource<T>, opts?: NDJSONResponseOptions): Response;
|
|
98
117
|
export {};
|
package/dist/streaming.js
CHANGED
|
@@ -87,6 +87,10 @@ function encodeNDJSON(value) {
|
|
|
87
87
|
* Build a backpressure-safe `ReadableStream` from an async iterable of SSE
|
|
88
88
|
* messages. The iterator is only advanced when the consumer pulls the next
|
|
89
89
|
* chunk, so a slow client cannot cause unbounded buffering.
|
|
90
|
+
*
|
|
91
|
+
* @param source Async/sync iterable (or factory) yielding {@link SSEMessage}s or plain strings.
|
|
92
|
+
* @param opts Abort signal and optional keep-alive interval; see {@link SSEStreamOptions}.
|
|
93
|
+
* @returns A `ReadableStream<Uint8Array>` of encoded `text/event-stream` frames.
|
|
90
94
|
*/
|
|
91
95
|
export function sseStream(source, opts = {}) {
|
|
92
96
|
const iterator = getAsyncIterator(source);
|
|
@@ -165,6 +169,10 @@ export function sseStream(source, opts = {}) {
|
|
|
165
169
|
/**
|
|
166
170
|
* Wrap `sseStream` in a `Response` with the proper SSE headers
|
|
167
171
|
* (`text/event-stream`, no caching, keep-alive). Caller-supplied headers win.
|
|
172
|
+
*
|
|
173
|
+
* @param source Async/sync iterable (or factory) yielding {@link SSEMessage}s or plain strings.
|
|
174
|
+
* @param opts Status, headers, abort signal, and keep-alive; see {@link SSEResponseOptions}.
|
|
175
|
+
* @returns A streaming `Response` (default status `200`) with SSE headers applied.
|
|
168
176
|
*/
|
|
169
177
|
export function sseResponse(source, opts = {}) {
|
|
170
178
|
const stream = sseStream(source, opts);
|
|
@@ -185,6 +193,10 @@ export function sseResponse(source, opts = {}) {
|
|
|
185
193
|
* JSON) records from an async iterable. Each yielded value is encoded with
|
|
186
194
|
* `JSON.stringify` and terminated with `\n`. Values that stringify to
|
|
187
195
|
* `undefined` throw because they cannot be represented as valid NDJSON.
|
|
196
|
+
*
|
|
197
|
+
* @param source Async/sync iterable (or factory) yielding JSON-serializable values.
|
|
198
|
+
* @param opts Optional abort signal; see {@link StreamOptions}.
|
|
199
|
+
* @returns A `ReadableStream<Uint8Array>` of newline-terminated JSON records.
|
|
188
200
|
*/
|
|
189
201
|
export function ndjsonStream(source, opts = {}) {
|
|
190
202
|
const iterator = getAsyncIterator(source);
|
|
@@ -246,6 +258,10 @@ export function ndjsonStream(source, opts = {}) {
|
|
|
246
258
|
/**
|
|
247
259
|
* Wrap `ndjsonStream` in a `Response` with `application/x-ndjson` and
|
|
248
260
|
* cache-busting headers. Caller-supplied headers win.
|
|
261
|
+
*
|
|
262
|
+
* @param source Async/sync iterable (or factory) yielding JSON-serializable values.
|
|
263
|
+
* @param opts Status, headers, and abort signal; see {@link NDJSONResponseOptions}.
|
|
264
|
+
* @returns A streaming `Response` (default status `200`) with NDJSON headers applied.
|
|
249
265
|
*/
|
|
250
266
|
export function ndjsonResponse(source, opts = {}) {
|
|
251
267
|
const stream = ndjsonStream(source, opts);
|
package/dist/subdomains.d.ts
CHANGED
|
@@ -92,6 +92,10 @@ export interface SubdomainsResult {
|
|
|
92
92
|
* // => { baseDomain: "bar.s3.amazonaws.com", subdomain: "foo", labels: ["foo"] }
|
|
93
93
|
* ```
|
|
94
94
|
*
|
|
95
|
+
* @param hostname - Hostname to split (lowercased; a trailing FQDN dot is stripped).
|
|
96
|
+
* @param opts - Optional pinned `baseDomain`, `extraSuffixes`, and the `production` staleness gate.
|
|
97
|
+
* @returns The registrable {@link SubdomainsResult} (`baseDomain`, `subdomain`, `labels`).
|
|
98
|
+
* @throws {Error} On an empty hostname, a host outside a declared `baseDomain`, or a stale PSL snapshot in production.
|
|
95
99
|
* @since 0.24.0
|
|
96
100
|
*/
|
|
97
101
|
export declare function subdomains(hostname: string, opts?: SubdomainsOptions): SubdomainsResult;
|
package/dist/subdomains.js
CHANGED
|
@@ -93,6 +93,10 @@ export const MAX_SNAPSHOT_AGE_DAYS = 90;
|
|
|
93
93
|
* // => { baseDomain: "bar.s3.amazonaws.com", subdomain: "foo", labels: ["foo"] }
|
|
94
94
|
* ```
|
|
95
95
|
*
|
|
96
|
+
* @param hostname - Hostname to split (lowercased; a trailing FQDN dot is stripped).
|
|
97
|
+
* @param opts - Optional pinned `baseDomain`, `extraSuffixes`, and the `production` staleness gate.
|
|
98
|
+
* @returns The registrable {@link SubdomainsResult} (`baseDomain`, `subdomain`, `labels`).
|
|
99
|
+
* @throws {Error} On an empty hostname, a host outside a declared `baseDomain`, or a stale PSL snapshot in production.
|
|
96
100
|
* @since 0.24.0
|
|
97
101
|
*/
|
|
98
102
|
export function subdomains(hostname, opts = {}) {
|
package/dist/time-claims.d.ts
CHANGED
|
@@ -26,10 +26,17 @@
|
|
|
26
26
|
* @since 0.27.0
|
|
27
27
|
*/
|
|
28
28
|
export declare class TemporalClaimError extends Error {
|
|
29
|
+
/** Machine-readable failure code identifying which claim check failed. */
|
|
29
30
|
readonly code: TemporalClaimErrorCode;
|
|
30
31
|
constructor(code: TemporalClaimErrorCode, message: string);
|
|
31
32
|
}
|
|
32
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Failure codes for {@link TemporalClaimError}. `invalid_*` codes mean the
|
|
35
|
+
* claim (or option) was present but not a finite number; the remaining codes
|
|
36
|
+
* mean a well-formed claim failed its RFC 7519 time comparison.
|
|
37
|
+
*
|
|
38
|
+
* @since 0.27.0
|
|
39
|
+
*/
|
|
33
40
|
export type TemporalClaimErrorCode = "invalid_exp" | "token_expired" | "invalid_nbf" | "token_not_yet_valid" | "invalid_iat" | "iat_in_future" | "invalid_clock_skew";
|
|
34
41
|
/**
|
|
35
42
|
* Subset of a JWT-style payload that participates in temporal validation.
|
|
@@ -39,11 +46,19 @@ export type TemporalClaimErrorCode = "invalid_exp" | "token_expired" | "invalid_
|
|
|
39
46
|
* @since 0.27.0
|
|
40
47
|
*/
|
|
41
48
|
export interface TemporalClaims {
|
|
49
|
+
/** Expiration time (RFC 7519 `exp`), unix seconds. Validated when present. */
|
|
42
50
|
readonly exp?: unknown;
|
|
51
|
+
/** Not-before time (RFC 7519 `nbf`), unix seconds. Validated when present. */
|
|
43
52
|
readonly nbf?: unknown;
|
|
53
|
+
/** Issued-at time (RFC 7519 `iat`), unix seconds. Rejected if in the future. */
|
|
44
54
|
readonly iat?: unknown;
|
|
45
55
|
}
|
|
46
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Options for {@link assertTemporalClaims}: the reference clock and the
|
|
58
|
+
* symmetric clock-skew tolerance.
|
|
59
|
+
*
|
|
60
|
+
* @since 0.27.0
|
|
61
|
+
*/
|
|
47
62
|
export interface AssertTemporalClaimsOptions {
|
|
48
63
|
/** Current unix-seconds timestamp. Injectable for tests. */
|
|
49
64
|
readonly now: number;
|
|
@@ -65,6 +80,11 @@ export interface AssertTemporalClaimsOptions {
|
|
|
65
80
|
* - `iat` rejected when `iat - skew > now` (issued in the future — the
|
|
66
81
|
* issuer's clock is wrong, or someone pre-issued a token).
|
|
67
82
|
*
|
|
83
|
+
* @param claims Decoded payload; only the claims that are present are checked.
|
|
84
|
+
* @param opts Reference clock (`now`, unix seconds) and optional
|
|
85
|
+
* `clockSkewSeconds` tolerance (defaults to `0`).
|
|
86
|
+
* @throws TemporalClaimError on the first failing check, including malformed
|
|
87
|
+
* (non-finite) claim values or an invalid `now` / negative skew.
|
|
68
88
|
* @since 0.27.0
|
|
69
89
|
*/
|
|
70
90
|
export declare function assertTemporalClaims(claims: TemporalClaims, opts: AssertTemporalClaimsOptions): void;
|
package/dist/time-claims.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* @since 0.27.0
|
|
27
27
|
*/
|
|
28
28
|
export class TemporalClaimError extends Error {
|
|
29
|
+
/** Machine-readable failure code identifying which claim check failed. */
|
|
29
30
|
code;
|
|
30
31
|
constructor(code, message) {
|
|
31
32
|
super(message);
|
|
@@ -48,6 +49,11 @@ function isFiniteNumber(v) {
|
|
|
48
49
|
* - `iat` rejected when `iat - skew > now` (issued in the future — the
|
|
49
50
|
* issuer's clock is wrong, or someone pre-issued a token).
|
|
50
51
|
*
|
|
52
|
+
* @param claims Decoded payload; only the claims that are present are checked.
|
|
53
|
+
* @param opts Reference clock (`now`, unix seconds) and optional
|
|
54
|
+
* `clockSkewSeconds` tolerance (defaults to `0`).
|
|
55
|
+
* @throws TemporalClaimError on the first failing check, including malformed
|
|
56
|
+
* (non-finite) claim values or an invalid `now` / negative skew.
|
|
51
57
|
* @since 0.27.0
|
|
52
58
|
*/
|
|
53
59
|
export function assertTemporalClaims(claims, opts) {
|
package/dist/tracing.d.ts
CHANGED
|
@@ -47,13 +47,18 @@ export type TracingAttributes = Record<string, TracingAttributeValue>;
|
|
|
47
47
|
* `Span` (extra OTel methods are ignored).
|
|
48
48
|
*/
|
|
49
49
|
export interface TracingSpan {
|
|
50
|
+
/** Set a single attribute on the span. */
|
|
50
51
|
setAttribute(key: string, value: TracingAttributeValue): void;
|
|
52
|
+
/** Optional bulk attribute setter; DaloyJS falls back to `setAttribute` per key. */
|
|
51
53
|
setAttributes?(attrs: TracingAttributes): void;
|
|
54
|
+
/** Set the span status (e.g. {@link TRACING_SPAN_STATUS_ERROR} on failure). */
|
|
52
55
|
setStatus(status: {
|
|
53
56
|
code: number;
|
|
54
57
|
message?: string;
|
|
55
58
|
}): void;
|
|
59
|
+
/** Optional exception recorder; called with the thrown error on failures. */
|
|
56
60
|
recordException?(err: unknown): void;
|
|
61
|
+
/** End the span. DaloyJS calls this exactly once per request. */
|
|
57
62
|
end(endTime?: number): void;
|
|
58
63
|
}
|
|
59
64
|
/** Options passed to {@link TracingTracer.startSpan}. */
|
|
@@ -65,6 +70,7 @@ export interface TracingStartSpanOptions {
|
|
|
65
70
|
}
|
|
66
71
|
/** Minimum tracer surface DaloyJS needs. Compatible with `@opentelemetry/api`'s `Tracer`. */
|
|
67
72
|
export interface TracingTracer {
|
|
73
|
+
/** Create a span; `context` carries the extracted upstream parent context, if any. */
|
|
68
74
|
startSpan(name: string, options?: TracingStartSpanOptions, context?: unknown): TracingSpan;
|
|
69
75
|
}
|
|
70
76
|
/** Options for {@link otelTracing}. */
|
|
@@ -101,5 +107,11 @@ export interface OtelTracingOptions {
|
|
|
101
107
|
* Middleware that wraps every request in an OpenTelemetry-compatible span.
|
|
102
108
|
* Pass any tracer that matches the {@link TracingTracer} surface; DaloyJS
|
|
103
109
|
* does not import `@opentelemetry/api` itself.
|
|
110
|
+
*
|
|
111
|
+
* @param opts Tracer plus optional span naming, attribute, and context
|
|
112
|
+
* extraction hooks; see {@link OtelTracingOptions}.
|
|
113
|
+
* @returns A {@link Hooks} object that starts a SERVER span per request,
|
|
114
|
+
* exposes it on `ctx.state[stateKey]`, records exceptions, marks 5xx
|
|
115
|
+
* responses as errors, and ends the span exactly once on send.
|
|
104
116
|
*/
|
|
105
117
|
export declare function otelTracing(opts: OtelTracingOptions): Hooks;
|
package/dist/tracing.js
CHANGED
|
@@ -72,6 +72,12 @@ function endOnce(entry, attrs) {
|
|
|
72
72
|
* Middleware that wraps every request in an OpenTelemetry-compatible span.
|
|
73
73
|
* Pass any tracer that matches the {@link TracingTracer} surface; DaloyJS
|
|
74
74
|
* does not import `@opentelemetry/api` itself.
|
|
75
|
+
*
|
|
76
|
+
* @param opts Tracer plus optional span naming, attribute, and context
|
|
77
|
+
* extraction hooks; see {@link OtelTracingOptions}.
|
|
78
|
+
* @returns A {@link Hooks} object that starts a SERVER span per request,
|
|
79
|
+
* exposes it on `ctx.state[stateKey]`, records exceptions, marks 5xx
|
|
80
|
+
* responses as errors, and ends the span exactly once on send.
|
|
75
81
|
*/
|
|
76
82
|
export function otelTracing(opts) {
|
|
77
83
|
const stateKey = opts.stateKey ?? "otelSpan";
|
package/dist/types.d.ts
CHANGED
|
@@ -63,9 +63,13 @@ export type PathParams<P extends string> = {
|
|
|
63
63
|
* @since 0.1.0
|
|
64
64
|
*/
|
|
65
65
|
export interface RequestSchemas {
|
|
66
|
+
/** Validator for path parameters. Without it, `ctx.params` is raw {@link PathParams} strings. */
|
|
66
67
|
params?: StandardSchemaV1;
|
|
68
|
+
/** Validator for the parsed query string. Without it, `ctx.query` is a raw string record. */
|
|
67
69
|
query?: StandardSchemaV1;
|
|
70
|
+
/** Validator for request headers. Without it, `ctx.headers` is a raw string record. */
|
|
68
71
|
headers?: StandardSchemaV1;
|
|
72
|
+
/** Validator for the parsed request body. Without it, `ctx.body` is `unknown`. Prefer `.strict()` object schemas so unexpected keys are rejected. */
|
|
69
73
|
body?: StandardSchemaV1;
|
|
70
74
|
}
|
|
71
75
|
/** Infer the validated output of a Standard Schema validator, or `undefined` when no schema is present. */
|
|
@@ -110,12 +114,16 @@ export type InferRequest<R extends RequestSchemas | undefined, P extends string>
|
|
|
110
114
|
* @since 0.1.0
|
|
111
115
|
*/
|
|
112
116
|
export interface ResponseSpec {
|
|
117
|
+
/** Human-readable description emitted into the OpenAPI response object. Required. */
|
|
113
118
|
description: string;
|
|
119
|
+
/** Response-body validator; handler return values are checked against it when `AppOptions.validateResponses` is on. */
|
|
114
120
|
body?: StandardSchemaV1;
|
|
121
|
+
/** Documented response headers keyed by header name; surfaced in the OpenAPI document. */
|
|
115
122
|
headers?: Record<string, {
|
|
116
123
|
description?: string;
|
|
117
124
|
schema?: StandardSchemaV1;
|
|
118
125
|
}>;
|
|
126
|
+
/** Named example payloads emitted into OpenAPI and served when `AppOptions.mockMode` is enabled. */
|
|
119
127
|
examples?: Record<string, unknown>;
|
|
120
128
|
}
|
|
121
129
|
/**
|
|
@@ -216,9 +224,18 @@ export interface AppState {
|
|
|
216
224
|
* @since 0.24.0
|
|
217
225
|
*/
|
|
218
226
|
export type AuthScheme = "bearer" | "basic" | "jwt" | "jwk" | "webhook" | "session" | "apiKey";
|
|
219
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* Verified-identity envelope written to `ctx.state.auth` by the first-party
|
|
229
|
+
* auth helpers. The `scheme` discriminant keeps per-scheme logic (revocation
|
|
230
|
+
* lists, audit logs) from being applied to credentials issued by a different
|
|
231
|
+
* scheme (see {@link AuthScheme}).
|
|
232
|
+
*
|
|
233
|
+
* @since 0.24.0
|
|
234
|
+
*/
|
|
220
235
|
export interface AuthContext<TCredentials = unknown> {
|
|
236
|
+
/** Discriminant naming the auth helper that verified the request (e.g. `"jwt"`, `"session"`). */
|
|
221
237
|
readonly scheme: AuthScheme;
|
|
238
|
+
/** The verified credential payload (decoded JWT claims, session record, ...); shape depends on the scheme. */
|
|
222
239
|
readonly credentials: TCredentials;
|
|
223
240
|
}
|
|
224
241
|
/**
|
|
@@ -235,11 +252,15 @@ export interface AuthContext<TCredentials = unknown> {
|
|
|
235
252
|
* @since 0.1.0
|
|
236
253
|
*/
|
|
237
254
|
export interface BaseContext<P extends string, R extends RequestSchemas | undefined> {
|
|
255
|
+
/** The original web-standard `Request`. Its body stream may already be consumed when a `body` schema triggered parsing. */
|
|
238
256
|
request: Request;
|
|
239
257
|
/** Validated request data (or raw fallbacks if no schema). */
|
|
240
258
|
params: InferRequest<R, P>["params"];
|
|
259
|
+
/** Validated query params; raw `Record<string, string | string[] | undefined>` without a schema. */
|
|
241
260
|
query: InferRequest<R, P>["query"];
|
|
261
|
+
/** Validated request headers; raw `Record<string, string | undefined>` without a schema. */
|
|
242
262
|
headers: InferRequest<R, P>["headers"];
|
|
263
|
+
/** Validated request body; `unknown` without a schema. Parsed prototype-pollution-safe (forbidden keys rejected). */
|
|
243
264
|
body: InferRequest<R, P>["body"];
|
|
244
265
|
/** Mutable per-request state. Plugin-augmented context lives here. */
|
|
245
266
|
state: AppState & Record<string, unknown>;
|
|
@@ -269,9 +290,13 @@ export interface BaseContext<P extends string, R extends RequestSchemas | undefi
|
|
|
269
290
|
* @since 0.1.0
|
|
270
291
|
*/
|
|
271
292
|
export interface Hooks {
|
|
293
|
+
/** Runs first, before validation or context building. Receives the raw web-standard `Request`. */
|
|
272
294
|
onRequest?: (req: Request) => void | Promise<void>;
|
|
295
|
+
/** Runs with the validated {@link BaseContext} before the handler. Returning a `Response` short-circuits the handler entirely (useful for auth guards). */
|
|
273
296
|
beforeHandle?: (ctx: BaseContext<any, any>) => void | Response | Promise<void | Response>;
|
|
297
|
+
/** Runs after the handler with its raw return value. Return a non-`undefined` value to replace the result before serialization and response-schema validation. */
|
|
274
298
|
afterHandle?: (ctx: BaseContext<any, any>, result: unknown) => void | unknown | Promise<void | unknown>;
|
|
299
|
+
/** Runs on the error path before serialization. `ctx` is `undefined` if the error occurred before context was built. Return a `Response` to replace the default RFC 9457 problem+json error response. */
|
|
275
300
|
onError?: (err: unknown, ctx: BaseContext<any, any> | undefined) => void | Response | Promise<void | Response>;
|
|
276
301
|
/**
|
|
277
302
|
* Symmetric to `beforeHandle`, but for outgoing responses. Runs after the Response
|
|
@@ -281,6 +306,7 @@ export interface Hooks {
|
|
|
281
306
|
* the existing response. Multiple `onSend` hooks compose pipeline-style.
|
|
282
307
|
*/
|
|
283
308
|
onSend?: (res: Response, ctx: BaseContext<any, any> | undefined) => void | Response | Promise<void | Response>;
|
|
309
|
+
/** Fire-and-forget observer of the final outgoing `Response` (logging, metrics). Runs last; cannot alter the response. */
|
|
284
310
|
onResponse?: (res: Response) => void | Promise<void>;
|
|
285
311
|
}
|
|
286
312
|
/**
|
|
@@ -312,14 +338,35 @@ export interface Hooks {
|
|
|
312
338
|
* @since 0.1.0
|
|
313
339
|
*/
|
|
314
340
|
export interface RouteDefinition<P extends PathString = PathString, M extends HttpMethod = HttpMethod, Req extends RequestSchemas | undefined = undefined, Res extends ResponsesMap = ResponsesMap> {
|
|
341
|
+
/** HTTP method to match (uppercase, e.g. `"GET"`). See {@link HttpMethod}. */
|
|
315
342
|
method: M;
|
|
343
|
+
/** URL path pattern starting with `/`; `:name` segments become typed path params (e.g. `"/books/:id"`). */
|
|
316
344
|
path: P;
|
|
345
|
+
/** Stable unique operation id for OpenAPI and the generated client (drives SDK method names). Omitted from the spec when unset. */
|
|
317
346
|
operationId?: string;
|
|
347
|
+
/** One-line summary shown in OpenAPI docs UIs. */
|
|
318
348
|
summary?: string;
|
|
349
|
+
/** Longer free-form description for the OpenAPI operation (CommonMark allowed). */
|
|
319
350
|
description?: string;
|
|
351
|
+
/** OpenAPI tags used to group the operation in docs UIs; merged with {@link RouteMeta.tags}. */
|
|
320
352
|
tags?: string[];
|
|
353
|
+
/** Emits `deprecated: true` on the OpenAPI operation. Set implicitly when {@link RouteDefinition.sunset} is present. */
|
|
321
354
|
deprecated?: boolean;
|
|
355
|
+
/** Optional per-route API version label. Informational metadata only; not emitted into the OpenAPI document. */
|
|
322
356
|
version?: string;
|
|
357
|
+
/**
|
|
358
|
+
* Acknowledge that this route's `2xx` responses intentionally carry no
|
|
359
|
+
* response body schema — an opaque, framework-controlled, or non-JSON body
|
|
360
|
+
* (a raw `Response`, an HTML page, a spec document, a proxied payload).
|
|
361
|
+
*
|
|
362
|
+
* Setting this suppresses the `security.response.bodySchemaMissing` boot
|
|
363
|
+
* warning and the `audit.response.bodySchema` `daloy doctor` finding for
|
|
364
|
+
* this route only. It documents intent; it does not add protection —
|
|
365
|
+
* response field-level stripping (OWASP API3) still does not run for a
|
|
366
|
+
* `2xx` response without a body schema, so never set this on a route whose
|
|
367
|
+
* handler builds JSON from domain objects.
|
|
368
|
+
*/
|
|
369
|
+
acknowledgeNoResponseBodySchema?: boolean;
|
|
323
370
|
/**
|
|
324
371
|
* Mark the endpoint as scheduled for removal at a specific date (RFC 8594
|
|
325
372
|
* "The Sunset HTTP Header Field"). Accepts an ISO-8601 string, any string
|
|
@@ -353,8 +400,11 @@ export interface RouteDefinition<P extends PathString = PathString, M extends Ht
|
|
|
353
400
|
* @since 0.37.0
|
|
354
401
|
*/
|
|
355
402
|
sunset?: string | Date;
|
|
403
|
+
/** Standard Schemas ({@link RequestSchemas}) validating `params`/`query`/`headers`/`body`. Parts without a schema arrive untyped; validation failures return 400/422 before the handler runs. */
|
|
356
404
|
request?: Req;
|
|
405
|
+
/** Map of status code to {@link ResponseSpec}. Drives response-body validation, the handler's allowed return types, OpenAPI responses, and the typed client. */
|
|
357
406
|
responses: Res;
|
|
407
|
+
/** Declarative auth requirement ({@link AuthSpec}); surfaces as the OpenAPI `security` requirement for this operation. */
|
|
358
408
|
auth?: AuthSpec;
|
|
359
409
|
/**
|
|
360
410
|
* Per-route Content-Type allowlist. When the route declares a `body`
|
|
@@ -421,6 +471,7 @@ export interface RouteDefinition<P extends PathString = PathString, M extends Ht
|
|
|
421
471
|
* @since 0.14.0
|
|
422
472
|
*/
|
|
423
473
|
meta?: RouteMeta;
|
|
474
|
+
/** Per-route lifecycle {@link Hooks}. Run after global and group hooks in the pipeline. */
|
|
424
475
|
hooks?: Hooks;
|
|
425
476
|
/**
|
|
426
477
|
* The route handler. Receives the typed, validated {@link BaseContext} and
|
|
@@ -455,14 +506,23 @@ export interface RouteDefinition<P extends PathString = PathString, M extends Ht
|
|
|
455
506
|
* `handler` (no execution path on the producer side).
|
|
456
507
|
*/
|
|
457
508
|
export interface CallbackOperation {
|
|
509
|
+
/** HTTP method the producer uses when invoking the callback URL. */
|
|
458
510
|
method: HttpMethod;
|
|
511
|
+
/** Stable unique operation id for the callback operation in the OpenAPI document. */
|
|
459
512
|
operationId?: string;
|
|
513
|
+
/** One-line summary shown in OpenAPI docs UIs. */
|
|
460
514
|
summary?: string;
|
|
515
|
+
/** Longer free-form description for the callback operation. */
|
|
461
516
|
description?: string;
|
|
517
|
+
/** OpenAPI tags grouping the callback operation in docs UIs. */
|
|
462
518
|
tags?: string[];
|
|
519
|
+
/** Emits `deprecated: true` on the callback operation. */
|
|
463
520
|
deprecated?: boolean;
|
|
521
|
+
/** Schemas describing the outgoing callback request (documentation only; never executed by the framework). */
|
|
464
522
|
request?: RequestSchemas;
|
|
523
|
+
/** Responses the producer expects back from the consumer, keyed by status code. */
|
|
465
524
|
responses: ResponsesMap;
|
|
525
|
+
/** Auth requirement the callback request is documented to carry ({@link AuthSpec}). */
|
|
466
526
|
auth?: AuthSpec;
|
|
467
527
|
}
|
|
468
528
|
/**
|
|
@@ -495,14 +555,18 @@ export interface CallbackMap {
|
|
|
495
555
|
* @since 0.14.0
|
|
496
556
|
*/
|
|
497
557
|
export interface RouteExample {
|
|
558
|
+
/** One-line label for the example, surfaced as the OpenAPI example `summary`. */
|
|
498
559
|
summary?: string;
|
|
560
|
+
/** Longer explanation of what the example demonstrates. */
|
|
499
561
|
description?: string;
|
|
562
|
+
/** Sample inbound request. `body` is validated against the route's request body schema by `runContractTests()`. */
|
|
500
563
|
request?: {
|
|
501
564
|
params?: Record<string, string>;
|
|
502
565
|
query?: Record<string, unknown>;
|
|
503
566
|
headers?: Record<string, string>;
|
|
504
567
|
body?: unknown;
|
|
505
568
|
};
|
|
569
|
+
/** Sample response. `status` must be declared in the route's `responses`; `body` is validated against that status's schema. */
|
|
506
570
|
response?: {
|
|
507
571
|
status: number;
|
|
508
572
|
body?: unknown;
|
|
@@ -526,9 +590,14 @@ export interface RouteExample {
|
|
|
526
590
|
* @since 0.14.0
|
|
527
591
|
*/
|
|
528
592
|
export interface RouteMeta {
|
|
593
|
+
/** Fallback operation summary; the route-level `summary` wins when both are set. */
|
|
529
594
|
summary?: string;
|
|
595
|
+
/** Fallback operation description; the route-level `description` wins when both are set. */
|
|
530
596
|
description?: string;
|
|
597
|
+
/** Extra OpenAPI tags, merged (deduplicated) with the route-level `tags`. */
|
|
531
598
|
tags?: string[];
|
|
599
|
+
/** Named request/response examples ({@link RouteExample}); validated against the route's schemas by `runContractTests()`. */
|
|
532
600
|
examples?: Record<string, RouteExample>;
|
|
601
|
+
/** Free-form vendor extensions emitted as `x-<key>` on the OpenAPI operation; keys are auto-prefixed with `x-` when missing. */
|
|
533
602
|
extensions?: Record<string, unknown>;
|
|
534
603
|
}
|
package/dist/waf.js
CHANGED
|
@@ -80,7 +80,12 @@ const XSS_SIGNATURES = Object.freeze([
|
|
|
80
80
|
/<script[\s\S]{0,40}?>/i,
|
|
81
81
|
/<\/script\s*>/i,
|
|
82
82
|
/javascript:\s*\S/i,
|
|
83
|
-
|
|
83
|
+
// Inline event-handler attributes. Explicit alternation (not `on\w+`) to
|
|
84
|
+
// avoid false-positives on benign params like `online=`/`once=`, but broadened
|
|
85
|
+
// well beyond the classic four to cover the paren-less handlers commonly used
|
|
86
|
+
// to evade keyword blocklists (pointer/focus/touch/wheel/toggle events — see
|
|
87
|
+
// the ES6-for-pentesters technique in the cure53-web-frontend-offense skill).
|
|
88
|
+
/\bon(?:error|load|click|dblclick|aux(?:click)?|contextmenu|mouse(?:over|enter|move|down|up|out|leave)|pointer(?:over|enter|down|up|move|rawupdate|leave)|touch(?:start|move|end)|focus(?:in|out)?|blur|input|change|submit|reset|toggle|beforetoggle|scroll|wheel|drag|drop|copy|cut|paste|play|playing|canplay|show|hashchange|popstate|pageshow|pagehide|message|animation(?:start|end|iteration)|transitionend|key(?:down|up|press)|load(?:start|end)|progress)\s*=/i,
|
|
84
89
|
/<iframe[\s>]/i,
|
|
85
90
|
/<img[\s\S]{0,80}?\bonerror\s*=/i,
|
|
86
91
|
/<svg[\s\S]{0,40}?\bonload\s*=/i,
|
|
@@ -268,11 +273,26 @@ export function waf(opts = {}) {
|
|
|
268
273
|
if (inspectQuery && url.search.length > 1) {
|
|
269
274
|
// Scan both the raw query string and a best-effort decoded form so an
|
|
270
275
|
// encoded payload (`%27%20OR%201=1`) is caught after normalization.
|
|
276
|
+
// This is a SINGLE decode on purpose: the framework's request path also
|
|
277
|
+
// decodes the query exactly once, so the WAF sees the same bytes the
|
|
278
|
+
// handler will. Recursive decoding is deliberately avoided — it would
|
|
279
|
+
// false-positive on values that legitimately contain percent-encoded
|
|
280
|
+
// text, and a double-encoded payload stays inert (`%3Cscript%3E`) all
|
|
281
|
+
// the way to the handler. See red-team-attacks-6 "DOCUMENTED LIMITATION".
|
|
271
282
|
const raw = url.search.slice(1);
|
|
272
283
|
scanValue(raw, "query", rules, scored);
|
|
273
284
|
const decoded = safeDecode(raw);
|
|
274
285
|
if (decoded !== raw)
|
|
275
286
|
scanValue(decoded, "query", rules, scored);
|
|
287
|
+
// Additionally inspect each key/value the way the app's OWN query parser
|
|
288
|
+
// (`URLSearchParams`) decodes them: notably `+` becomes a space, which a
|
|
289
|
+
// plain `decodeURIComponent` does NOT do. Without this, `1+OR+1=1` slipped
|
|
290
|
+
// past the WAF while the handler still received `1 OR 1=1` (a parser
|
|
291
|
+
// differential — the WAF must inspect the bytes the app actually parses).
|
|
292
|
+
for (const [k, v] of url.searchParams) {
|
|
293
|
+
scanValue(k, "query", rules, scored);
|
|
294
|
+
scanValue(v, "query", rules, scored);
|
|
295
|
+
}
|
|
276
296
|
}
|
|
277
297
|
if (headerAllowlist.length > 0) {
|
|
278
298
|
for (const name of headerAllowlist) {
|
|
@@ -122,6 +122,7 @@ export interface WebhookDeadLetter {
|
|
|
122
122
|
* @since 0.37.0
|
|
123
123
|
*/
|
|
124
124
|
export interface WebhookDeadLetterSink {
|
|
125
|
+
/** Persist a permanently-failed delivery. May be async; the sender awaits it. */
|
|
125
126
|
add(letter: WebhookDeadLetter): void | Promise<void>;
|
|
126
127
|
}
|
|
127
128
|
/**
|
|
@@ -258,6 +259,11 @@ export interface WebhookSenderOptions {
|
|
|
258
259
|
* const result = await send({ url, eventType: "user.created", payload: { id } });
|
|
259
260
|
* ```
|
|
260
261
|
*
|
|
262
|
+
* @param options - Signing secret plus delivery policy; see
|
|
263
|
+
* {@link WebhookSenderOptions} for the per-field defaults.
|
|
264
|
+
* @returns A `send(event)` function that resolves to a
|
|
265
|
+
* {@link WebhookDeliveryResult}.
|
|
266
|
+
* @throws Error at construction when `secret` is missing or empty.
|
|
261
267
|
* @since 0.37.0
|
|
262
268
|
*/
|
|
263
269
|
export declare function createWebhookSender(options: WebhookSenderOptions): (event: WebhookEvent) => Promise<WebhookDeliveryResult>;
|
package/dist/webhook-delivery.js
CHANGED
|
@@ -140,6 +140,11 @@ function randomId() {
|
|
|
140
140
|
* const result = await send({ url, eventType: "user.created", payload: { id } });
|
|
141
141
|
* ```
|
|
142
142
|
*
|
|
143
|
+
* @param options - Signing secret plus delivery policy; see
|
|
144
|
+
* {@link WebhookSenderOptions} for the per-field defaults.
|
|
145
|
+
* @returns A `send(event)` function that resolves to a
|
|
146
|
+
* {@link WebhookDeliveryResult}.
|
|
147
|
+
* @throws Error at construction when `secret` is missing or empty.
|
|
143
148
|
* @since 0.37.0
|
|
144
149
|
*/
|
|
145
150
|
export function createWebhookSender(options) {
|