@orkestrel/middleware 0.0.1
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/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/src/core/index.cjs +1463 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +1339 -0
- package/dist/src/core/index.d.ts +1339 -0
- package/dist/src/core/index.js +1399 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/server/index.cjs +1217 -0
- package/dist/src/server/index.cjs.map +1 -0
- package/dist/src/server/index.d.cts +729 -0
- package/dist/src/server/index.d.ts +729 -0
- package/dist/src/server/index.js +1180 -0
- package/dist/src/server/index.js.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
import { ConnectionInfo } from '@orkestrel/server';
|
|
2
|
+
import { CookieOptions } from '@orkestrel/server';
|
|
3
|
+
import { Encoding } from '@orkestrel/server';
|
|
4
|
+
import { MiddlewareContext } from '@orkestrel/server';
|
|
5
|
+
import { MiddlewareHandler } from '@orkestrel/server';
|
|
6
|
+
import { TokenSecret } from '@orkestrel/server';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Options for `createBearer` — bearer-token authentication.
|
|
10
|
+
*
|
|
11
|
+
* @param options - See fields below
|
|
12
|
+
* @remarks
|
|
13
|
+
* - `secret` — the {@link TokenSecret} `verifyToken` checks the extracted
|
|
14
|
+
* token against (rotation-aware).
|
|
15
|
+
* - `header` — the header the token is read from; defaults to
|
|
16
|
+
* {@link DEFAULT_BEARER_HEADER}.
|
|
17
|
+
* - `scheme` — the scheme prefix stripped before verification (case-
|
|
18
|
+
* insensitive); defaults to {@link DEFAULT_BEARER_SCHEME}. An empty string
|
|
19
|
+
* means the whole header value is the raw token.
|
|
20
|
+
*/
|
|
21
|
+
export declare interface BearerOptions {
|
|
22
|
+
readonly secret: TokenSecret;
|
|
23
|
+
readonly header?: string;
|
|
24
|
+
readonly scheme?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The bearer-authentication state slice `createBearer` stashes on
|
|
29
|
+
* `context.state` once a token verifies.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* `token` is optional-mutable: absent until `createBearer` runs, then
|
|
33
|
+
* written in place — the pattern every stateful battery's slice follows so a
|
|
34
|
+
* consumer intersects only the slices it mounts into its own `TState`.
|
|
35
|
+
*/
|
|
36
|
+
export declare interface BearerState {
|
|
37
|
+
token?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Options for `createBoundary` — the outermost error-rendering battery.
|
|
42
|
+
*
|
|
43
|
+
* @param options - See fields below
|
|
44
|
+
* @remarks
|
|
45
|
+
* - `expose` — when `true`, a non-`HTTPError` throw's `error.message` is
|
|
46
|
+
* surfaced in the 500 body instead of a generic message. Defaults to
|
|
47
|
+
* `false` (nothing leaks).
|
|
48
|
+
* - `report` — an optional fire-and-forget sink invoked with every caught
|
|
49
|
+
* error; its own throw is swallowed and can never alter the response.
|
|
50
|
+
*/
|
|
51
|
+
export declare interface BoundaryOptions {
|
|
52
|
+
readonly expose?: boolean;
|
|
53
|
+
readonly report?: (error: unknown) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A parsed client-info fact for {@link ClientInfo} — a leaf shaping helper
|
|
58
|
+
* `createForwarded` uses to build its stashed slice.
|
|
59
|
+
*
|
|
60
|
+
* @param ip - The resolved client IP, if any
|
|
61
|
+
* @returns The {@link ClientInfo} slice value
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* buildClientInfo('203.0.113.7') // { ip: '203.0.113.7' }
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildClientInfo(ip: string | undefined): ClientInfo;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build the draft `RateLimit` structured header field (ruling I) — emitted
|
|
72
|
+
* only when `createLimiter`'s `policy` option is `true`.
|
|
73
|
+
*
|
|
74
|
+
* @param remaining - The requests still admitted this window
|
|
75
|
+
* @param resetAt - The window reset instant (same clock unit as `now`)
|
|
76
|
+
* @param now - The current instant
|
|
77
|
+
* @returns The `RateLimit` header value
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* buildRateLimitField(4, 1_500, 1_000) // '"default";r=4;t=1'
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildRateLimitField(remaining: number, resetAt: number, now: number): string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build the draft `RateLimit-Policy` structured header field (ruling I) —
|
|
88
|
+
* emitted only when `createLimiter`'s `policy` option is `true`.
|
|
89
|
+
*
|
|
90
|
+
* @param max - The window's admitted request count
|
|
91
|
+
* @param window - The window length in milliseconds
|
|
92
|
+
* @returns The `RateLimit-Policy` header value
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* buildRateLimitPolicyField(10, 60_000) // '"default";q=10;w=60'
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function buildRateLimitPolicyField(max: number, window: number): string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build the `Retry-After` header value — whole seconds until a window reset,
|
|
103
|
+
* floored at a minimum of `1` (ruling I).
|
|
104
|
+
*
|
|
105
|
+
* @param resetAt - The window reset instant (same clock unit as `now`)
|
|
106
|
+
* @param now - The current instant
|
|
107
|
+
* @returns The `Retry-After` value in whole seconds, minimum `1`
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* buildRetryAfter(1_500, 1_000) // '1'
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export declare function buildRetryAfter(resetAt: number, now: number): string;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The resolved client connection facts `createForwarded` stashes.
|
|
118
|
+
*
|
|
119
|
+
* @remarks
|
|
120
|
+
* `ip` is the first untrusted address walking `X-Forwarded-For` /
|
|
121
|
+
* `Forwarded` right-to-left past the configured trusted hops, falling back
|
|
122
|
+
* to the socket peer when no proxy hop qualifies.
|
|
123
|
+
*/
|
|
124
|
+
export declare interface ClientInfo {
|
|
125
|
+
readonly ip?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The client-facts state slice `createForwarded` stashes.
|
|
130
|
+
*/
|
|
131
|
+
export declare interface ClientState {
|
|
132
|
+
client?: ClientInfo;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Options for `createCompression` — response-body compression.
|
|
137
|
+
*
|
|
138
|
+
* @param options - See fields below
|
|
139
|
+
* @remarks
|
|
140
|
+
* - `threshold` — the minimum buffered body size (bytes) worth compressing;
|
|
141
|
+
* defaults to {@link DEFAULT_COMPRESSION_THRESHOLD}.
|
|
142
|
+
* - `encodings` — the codings offered, in preference order, intersected at
|
|
143
|
+
* CONSTRUCTION with what the runtime's `CompressionStream` actually
|
|
144
|
+
* supports; defaults to {@link DEFAULT_COMPRESSION_ENCODINGS}.
|
|
145
|
+
* - `filter` — an optional per-response opt-out predicate (the BREACH
|
|
146
|
+
* posture escape hatch); a response the predicate declines is never
|
|
147
|
+
* buffered or compressed. Defaults to always allowing.
|
|
148
|
+
*/
|
|
149
|
+
export declare interface CompressionOptions {
|
|
150
|
+
readonly threshold?: number;
|
|
151
|
+
readonly encodings?: readonly Encoding[];
|
|
152
|
+
readonly filter?: (request: Request, response: Response) => boolean;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The shared negotiate → skip → threshold → compress → header-set skeleton
|
|
157
|
+
* both faces' `createCompression` batteries compose — response-body
|
|
158
|
+
* compression over a caller-supplied set of feature-detected codings.
|
|
159
|
+
*
|
|
160
|
+
* @remarks
|
|
161
|
+
* Decision order: {@link isBufferingIneligible} → `options.filter` → stamp
|
|
162
|
+
* `Vary: Accept-Encoding` (every negotiation-eligible response carries it,
|
|
163
|
+
* even when a later skip declines to compress) → `negotiateEncoding` over
|
|
164
|
+
* `options.encodings` → {@link isCompressionNegotiated} → `isCompressibleType`
|
|
165
|
+
* on `Content-Type` → a fast skip when the response already carries a
|
|
166
|
+
* numeric `Content-Length` BELOW `options.threshold` (avoids buffering a
|
|
167
|
+
* body known too small to be worth compressing) → buffer via
|
|
168
|
+
* `response.arrayBuffer()` → a threshold passthrough when the buffered size
|
|
169
|
+
* is still below `options.threshold` → `options.compress` → set
|
|
170
|
+
* `Content-Encoding` and a fresh `Content-Length` via {@link rebuildResponse}.
|
|
171
|
+
* Returns `response` unchanged (aside from the `Vary` stamp) on any skip.
|
|
172
|
+
*
|
|
173
|
+
* @param request - The inbound `Request` (read for `Accept-Encoding`)
|
|
174
|
+
* @param context - The `MiddlewareContext` (read for `context.method`)
|
|
175
|
+
* @param response - The downstream `Response` to consider compressing
|
|
176
|
+
* @param options - The threshold, optional filter, offered encodings, and the runtime's `compress` primitive
|
|
177
|
+
* @returns The original `response` when skipped, or a new compressed `Response`
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* await compressResponse(request, context, response, {
|
|
182
|
+
* threshold: 1024,
|
|
183
|
+
* encodings: ['gzip'],
|
|
184
|
+
* compress: async (bytes, encoding) => gzip(bytes),
|
|
185
|
+
* })
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export declare function compressResponse(request: Request, context: MiddlewareContext<unknown>, response: Response, options: {
|
|
189
|
+
readonly threshold: number;
|
|
190
|
+
readonly filter?: (request: Request, response: Response) => boolean;
|
|
191
|
+
readonly encodings: readonly Encoding[];
|
|
192
|
+
readonly compress: (bytes: Uint8Array<ArrayBuffer>, encoding: Exclude<Encoding, 'identity'>) => Promise<Uint8Array<ArrayBuffer>>;
|
|
193
|
+
}): Promise<Response>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The connection-facts state slice `createLimiter`'s default key derivation
|
|
197
|
+
* falls back to when neither {@link BearerState} nor {@link ClientState} is
|
|
198
|
+
* present — the raw socket peer surfaced on `context.state` by the server's
|
|
199
|
+
* `state` option.
|
|
200
|
+
*/
|
|
201
|
+
export declare interface ConnectionState {
|
|
202
|
+
readonly connection?: ConnectionInfo;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Options for `createCookieTransport` — the signed-cookie {@link SessionTransport}.
|
|
207
|
+
*
|
|
208
|
+
* @param options - See fields below
|
|
209
|
+
* @remarks
|
|
210
|
+
* - `name` — the cookie name; defaults to {@link DEFAULT_SESSION_COOKIE}.
|
|
211
|
+
* - `secret` — the {@link TokenSecret} the session id is signed with (`signToken`).
|
|
212
|
+
* - `cookie` — extra {@link CookieOptions} attributes; `Max-Age` is derived
|
|
213
|
+
* from `SessionOptions.ttl` unless overridden here.
|
|
214
|
+
*/
|
|
215
|
+
export declare interface CookieTransportOptions {
|
|
216
|
+
readonly name?: string;
|
|
217
|
+
readonly secret: TokenSecret;
|
|
218
|
+
readonly cookie?: CookieOptions;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Options for `createCors` — Cross-Origin Resource Sharing.
|
|
223
|
+
*
|
|
224
|
+
* @param options - See fields below
|
|
225
|
+
* @remarks
|
|
226
|
+
* - `origin` — the allowed origin(s): `'*'` (default), a single origin
|
|
227
|
+
* string, or an allow-list `readonly string[]` (reflects the request
|
|
228
|
+
* `Origin` when it matches, and merges `Vary: Origin`). The literal
|
|
229
|
+
* `Origin: null` is never reflected even when `'null'` is allow-listed.
|
|
230
|
+
* - `methods` — the methods advertised on a preflight; defaults to {@link DEFAULT_CORS_METHODS}.
|
|
231
|
+
* - `headers` — the headers advertised on a preflight; defaults to {@link DEFAULT_CORS_HEADERS}.
|
|
232
|
+
*/
|
|
233
|
+
export declare interface CorsOptions {
|
|
234
|
+
readonly origin?: string | readonly string[];
|
|
235
|
+
readonly methods?: readonly string[];
|
|
236
|
+
readonly headers?: readonly string[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Bearer-token authentication battery.
|
|
241
|
+
*
|
|
242
|
+
* @typeParam TState - The consumer's opaque per-request state type, must carry {@link BearerState}
|
|
243
|
+
* @param options - See {@link BearerOptions}
|
|
244
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
245
|
+
* @throws {TypeError} When any option is malformed
|
|
246
|
+
* @throws {HTTPError} `401` when the token is missing, invalid, or expired
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* const bearer = createBearer({ secret: 'shh' })
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
export declare function createBearer<TState extends BearerState>(options: BearerOptions): MiddlewareHandler<TState>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* The body-driving battery — eagerly awaits the cached `context.body()` so
|
|
257
|
+
* its throws (or a malformed-JSON `undefined`) surface before the handler runs.
|
|
258
|
+
*
|
|
259
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
260
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
261
|
+
* @throws {HTTPError} `400` when the request declares `application/json` and the body resolves `undefined`
|
|
262
|
+
*
|
|
263
|
+
* @remarks
|
|
264
|
+
* The shipped `MiddlewareContext.body()` is a parameterless, server-owned
|
|
265
|
+
* cache (`ServerOptions.limit` governs its size cap) — this battery carries
|
|
266
|
+
* no `limit`/`decompression` options (a deliberate break from the deleted old
|
|
267
|
+
* `createBodyParser` surface, which configured them itself).
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* const body = createBody()
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
export declare function createBody<TState>(): MiddlewareHandler<TState>;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The outermost error-rendering battery — catches a downstream throw and
|
|
278
|
+
* renders it as a `Response`.
|
|
279
|
+
*
|
|
280
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
281
|
+
* @param options - See {@link BoundaryOptions}
|
|
282
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
283
|
+
* @throws {TypeError} When `options.expose` or `options.report` is malformed
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```ts
|
|
287
|
+
* const boundary = createBoundary({ expose: false })
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export declare function createBoundary<TState>(options?: BoundaryOptions): MiddlewareHandler<TState>;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Response-body compression — negotiates and compresses a buffered response
|
|
294
|
+
* body over the runtime's feature-detected `CompressionStream` codings.
|
|
295
|
+
*
|
|
296
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
297
|
+
* @param options - See {@link CompressionOptions}
|
|
298
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
299
|
+
* @throws {TypeError} When `options.threshold` or `options.filter` is malformed
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* const compression = createCompression({ threshold: 1024 })
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
export declare function createCompression<TState>(options?: CompressionOptions): MiddlewareHandler<TState>;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Create a signed-cookie {@link SessionTransport} — the session id travels as
|
|
310
|
+
* a `signToken`-signed cookie value.
|
|
311
|
+
*
|
|
312
|
+
* @param options - See {@link CookieTransportOptions}
|
|
313
|
+
* @returns A {@link SessionTransport}
|
|
314
|
+
* @throws {TypeError} When `options.secret` or `options.name` is malformed
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* const transport = createCookieTransport({ secret: 'shh' })
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export declare function createCookieTransport(options: CookieTransportOptions): SessionTransport;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Cross-Origin Resource Sharing battery.
|
|
325
|
+
*
|
|
326
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
327
|
+
* @param options - See {@link CorsOptions}
|
|
328
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
329
|
+
* @throws {TypeError} When any option is malformed
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```ts
|
|
333
|
+
* const cors = createCors({ origin: ['https://app.example'] })
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
export declare function createCors<TState>(options?: CorsOptions): MiddlewareHandler<TState>;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Session-bound double-submit CSRF protection battery.
|
|
340
|
+
*
|
|
341
|
+
* @typeParam TState - The consumer's opaque per-request state type, must carry {@link CSRFState}, {@link SessionState}, and {@link ConnectionState}
|
|
342
|
+
* @param options - See {@link CSRFOptions}
|
|
343
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
344
|
+
* @throws {TypeError} When any option is malformed
|
|
345
|
+
* @throws {HTTPError} `403` when the submitted token is missing, mismatched, or bound to a different session
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* const csrf = createCSRF({ secret: 'shh' })
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
export declare function createCSRF<TState extends CSRFState & SessionState & ConnectionState>(options: CSRFOptions): MiddlewareHandler<TState>;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The application-level per-request deadline battery.
|
|
356
|
+
*
|
|
357
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
358
|
+
* @param options - See {@link DeadlineOptions}
|
|
359
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
360
|
+
* @throws {TypeError} When `options.ms` or `options.status` is malformed
|
|
361
|
+
*
|
|
362
|
+
* @remarks
|
|
363
|
+
* MUST sit OUTSIDE `createBody` in the chain — it reconstructs the inbound
|
|
364
|
+
* `Request` (to link its `signal` to the deadline `signal`), which throws if
|
|
365
|
+
* the body was already consumed upstream (e.g. by `createBody`'s cached read).
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* const deadline = createDeadline({ ms: 5_000 })
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
export declare function createDeadline<TState>(options: DeadlineOptions): MiddlewareHandler<TState>;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Dynamic response `ETag` + conditional GET battery.
|
|
376
|
+
*
|
|
377
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
378
|
+
* @param options - See {@link ETagOptions}
|
|
379
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
380
|
+
* @throws {TypeError} When `options.weak` is not a boolean
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```ts
|
|
384
|
+
* const etag = createETag({ weak: true })
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
export declare function createETag<TState>(options?: ETagOptions): MiddlewareHandler<TState>;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* The trusted-proxy client-IP resolver battery.
|
|
391
|
+
*
|
|
392
|
+
* @typeParam TState - The consumer's opaque per-request state type, must carry {@link ClientState} and {@link ConnectionState}
|
|
393
|
+
* @param options - See {@link ForwardedOptions}
|
|
394
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
395
|
+
* @throws {TypeError} When neither or both of `proxies`/`trusted` are provided, or either is malformed
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```ts
|
|
399
|
+
* const forwarded = createForwarded({ proxies: 1 })
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
export declare function createForwarded<TState extends ClientState & ConnectionState>(options: ForwardedOptions): MiddlewareHandler<TState>;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Create a bare-header {@link SessionTransport} — the session id travels
|
|
406
|
+
* verbatim in a request/response header.
|
|
407
|
+
*
|
|
408
|
+
* @param options - See {@link HeaderTransportOptions}
|
|
409
|
+
* @returns A {@link SessionTransport}
|
|
410
|
+
* @throws {TypeError} When `options.header` is malformed
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```ts
|
|
414
|
+
* const transport = createHeaderTransport()
|
|
415
|
+
* ```
|
|
416
|
+
*/
|
|
417
|
+
export declare function createHeaderTransport(options?: HeaderTransportOptions): SessionTransport;
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Fixed-window rate-limiting battery.
|
|
421
|
+
*
|
|
422
|
+
* @typeParam TState - The consumer's opaque per-request state type, must carry {@link BearerState}, {@link ClientState}, and {@link ConnectionState}
|
|
423
|
+
* @param options - See {@link LimiterOptions}
|
|
424
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
425
|
+
* @throws {TypeError} When any option is malformed
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* const limiter = createLimiter({ max: 100, window: 60_000 })
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
export declare function createLimiter<TState extends BearerState & ClientState & ConnectionState>(options: LimiterOptions<TState>): MiddlewareHandler<TState>;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Create the default in-process {@link SessionStoreInterface} — a `Map`-backed
|
|
436
|
+
* store enforcing an idle timeout and an absolute lifetime.
|
|
437
|
+
*
|
|
438
|
+
* @typeParam S - The session data payload type
|
|
439
|
+
* @param options - See {@link MemorySessionStoreOptions}
|
|
440
|
+
* @returns A {@link SessionStoreInterface}
|
|
441
|
+
* @throws {TypeError} When `options.ttl` or `options.lifetime` is malformed
|
|
442
|
+
*
|
|
443
|
+
* @remarks
|
|
444
|
+
* The {@link Session} entity is the `create` option's default value factory
|
|
445
|
+
* for `createSession` and deliberately ships WITHOUT its own `create*`
|
|
446
|
+
* factory — the name `createSession` belongs to the battery, not this class.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* const store = createMemorySessionStore({ ttl: 60_000 })
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
export declare function createMemorySessionStore<S>(options?: MemorySessionStoreOptions): SessionStoreInterface<S>;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Security-headers + request-identifier battery.
|
|
457
|
+
*
|
|
458
|
+
* @typeParam TState - The consumer's opaque per-request state type, must carry {@link IdentifierState}
|
|
459
|
+
* @param options - See {@link SecurityOptions}
|
|
460
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
461
|
+
* @throws {TypeError} When any option is malformed
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* const security = createSecurity({ hsts: true })
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
export declare function createSecurity<TState extends IdentifierState>(options?: SecurityOptions): MiddlewareHandler<TState>;
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* The generic session battery — resolves, mints, and persists a session
|
|
472
|
+
* across the request, with a mid-handler `regenerate`/`destroy` control handle.
|
|
473
|
+
*
|
|
474
|
+
* @typeParam S - The session entity type the store persists (must implement {@link SessionInterface})
|
|
475
|
+
* @typeParam TState - The consumer's opaque per-request state type, must carry {@link SessionState} and {@link ConnectionState}
|
|
476
|
+
* @param options - See {@link SessionOptions}
|
|
477
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
478
|
+
* @throws {TypeError} When any option is malformed
|
|
479
|
+
* @throws {HTTPError} `404` when `require` is set and no session resolves or mints
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```ts
|
|
483
|
+
* const session = createSession({ transport: createHeaderTransport() })
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
export declare function createSession<S extends SessionInterface = SessionInterface, TState extends SessionState & ConnectionState = SessionState & ConnectionState>(options: SessionOptions<S, TState>): MiddlewareHandler<TState>;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* The access-log/timing seam — records one {@link TelemetryEntry} per request
|
|
490
|
+
* after the response settles.
|
|
491
|
+
*
|
|
492
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
493
|
+
* @param options - See {@link TelemetryOptions}
|
|
494
|
+
* @returns A `MiddlewareHandler<TState>`
|
|
495
|
+
* @throws {TypeError} When `options.record` is not a function
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* const telemetry = createTelemetry({ record: (entry) => console.log(entry) })
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
export declare function createTelemetry<TState>(options: TelemetryOptions): MiddlewareHandler<TState>;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Options for `createCSRF` — session-bound double-submit CSRF protection.
|
|
506
|
+
*
|
|
507
|
+
* @param options - See fields below
|
|
508
|
+
* @remarks
|
|
509
|
+
* - `secret` — the {@link TokenSecret} the CSRF token is signed with.
|
|
510
|
+
* - `cookie` — the signed-cookie name; defaults to {@link DEFAULT_CSRF_COOKIE}.
|
|
511
|
+
* - `header` — the header a mutating request submits its token in; defaults
|
|
512
|
+
* to {@link DEFAULT_CSRF_HEADER}.
|
|
513
|
+
* - `field` — the body field a mutating request may submit its token in
|
|
514
|
+
* instead of the header (requires `createBody` ahead for form posts);
|
|
515
|
+
* defaults to {@link DEFAULT_CSRF_FIELD}.
|
|
516
|
+
* - `safe` — the methods that mint instead of verify; defaults to
|
|
517
|
+
* {@link DEFAULT_CSRF_SAFE_METHODS}.
|
|
518
|
+
*/
|
|
519
|
+
export declare interface CSRFOptions {
|
|
520
|
+
readonly secret: TokenSecret;
|
|
521
|
+
readonly cookie?: string;
|
|
522
|
+
readonly header?: string;
|
|
523
|
+
readonly field?: string;
|
|
524
|
+
readonly safe?: readonly string[];
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* The CSRF state slice `createCSRF` stashes — the raw token a safe-method
|
|
529
|
+
* response exposes for a subsequent mutating request to submit back.
|
|
530
|
+
*/
|
|
531
|
+
export declare interface CSRFState {
|
|
532
|
+
csrf?: string;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Options for `createDeadline` — the application-level per-request deadline.
|
|
537
|
+
*
|
|
538
|
+
* @param options - See fields below
|
|
539
|
+
* @remarks
|
|
540
|
+
* - `ms` — the deadline in milliseconds, armed via `@orkestrel/timeout` and
|
|
541
|
+
* linked to the request's `signal` via `@orkestrel/abort`'s `linkSignal`.
|
|
542
|
+
* - `status` — the response status returned when the deadline fires before
|
|
543
|
+
* the downstream chain settles; defaults to {@link DEFAULT_DEADLINE_STATUS}.
|
|
544
|
+
*/
|
|
545
|
+
export declare interface DeadlineOptions {
|
|
546
|
+
readonly ms: number;
|
|
547
|
+
readonly status?: number;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Default header `createBearer` reads the token from. */
|
|
551
|
+
export declare const DEFAULT_BEARER_HEADER = "authorization";
|
|
552
|
+
|
|
553
|
+
/** Default scheme prefix `createBearer` strips before verification. */
|
|
554
|
+
export declare const DEFAULT_BEARER_SCHEME = "Bearer";
|
|
555
|
+
|
|
556
|
+
/** Default `Origin-Agent-Cluster` value `createSecurity` sets. */
|
|
557
|
+
export declare const DEFAULT_CLUSTER = "?1";
|
|
558
|
+
|
|
559
|
+
/** Value `createSecurity` sets for `Cross-Origin-Embedder-Policy` when `coep: true`. */
|
|
560
|
+
export declare const DEFAULT_COEP = "require-corp";
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Default content-codings `createCompression` offers, in preference order —
|
|
564
|
+
* intersected at construction with what the runtime's `CompressionStream`
|
|
565
|
+
* actually supports.
|
|
566
|
+
*
|
|
567
|
+
* @remarks
|
|
568
|
+
* The shipped `@orkestrel/server` peer's {@link Encoding} union is
|
|
569
|
+
* `'gzip' | 'deflate' | 'identity'` — it does not include `'br'` (see the
|
|
570
|
+
* deviation recorded against this constant in the build report). This
|
|
571
|
+
* default therefore offers every non-`identity` coding the peer's type
|
|
572
|
+
* admits; a node-face brotli variant, if one ships, extends this list there.
|
|
573
|
+
*/
|
|
574
|
+
export declare const DEFAULT_COMPRESSION_ENCODINGS: readonly Encoding[];
|
|
575
|
+
|
|
576
|
+
/** Default minimum buffered body size (bytes) `createCompression` will compress. */
|
|
577
|
+
export declare const DEFAULT_COMPRESSION_THRESHOLD = 1024;
|
|
578
|
+
|
|
579
|
+
/** Default `Cross-Origin-Opener-Policy` value `createSecurity` sets. */
|
|
580
|
+
export declare const DEFAULT_COOP = "same-origin";
|
|
581
|
+
|
|
582
|
+
/** Default `Cross-Origin-Resource-Policy` value `createSecurity` sets. */
|
|
583
|
+
export declare const DEFAULT_CORP = "same-origin";
|
|
584
|
+
|
|
585
|
+
/** Default headers `createCors` advertises on a preflight response. */
|
|
586
|
+
export declare const DEFAULT_CORS_HEADERS: readonly string[];
|
|
587
|
+
|
|
588
|
+
/** Default methods `createCors` advertises on a preflight response. */
|
|
589
|
+
export declare const DEFAULT_CORS_METHODS: readonly string[];
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Default `Content-Security-Policy` value `createSecurity` sets — a custom
|
|
593
|
+
* `csp` option REPLACES this wholesale, never merges.
|
|
594
|
+
*/
|
|
595
|
+
export declare const DEFAULT_CSP = "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'";
|
|
596
|
+
|
|
597
|
+
/** Default signed-cookie name `createCSRF` writes the CSRF token under. */
|
|
598
|
+
export declare const DEFAULT_CSRF_COOKIE = "csrf";
|
|
599
|
+
|
|
600
|
+
/** Default body field `createCSRF` falls back to reading a mutating request's submitted token from. */
|
|
601
|
+
export declare const DEFAULT_CSRF_FIELD = "_csrf";
|
|
602
|
+
|
|
603
|
+
/** Default header `createCSRF` reads a mutating request's submitted token from. */
|
|
604
|
+
export declare const DEFAULT_CSRF_HEADER = "x-csrf-token";
|
|
605
|
+
|
|
606
|
+
/** Default methods `createCSRF` treats as safe (mint instead of verify). */
|
|
607
|
+
export declare const DEFAULT_CSRF_SAFE_METHODS: readonly string[];
|
|
608
|
+
|
|
609
|
+
/** Default response status `createDeadline` returns when its deadline fires first. */
|
|
610
|
+
export declare const DEFAULT_DEADLINE_STATUS = 503;
|
|
611
|
+
|
|
612
|
+
/** Default `X-Frame-Options` value `createSecurity` sets. */
|
|
613
|
+
export declare const DEFAULT_FRAME_OPTIONS = "DENY";
|
|
614
|
+
|
|
615
|
+
/** Value `createSecurity` sets for `Strict-Transport-Security` when `hsts: true`. */
|
|
616
|
+
export declare const DEFAULT_HSTS = "max-age=31536000; includeSubDomains";
|
|
617
|
+
|
|
618
|
+
/** Default header `createSecurity` mints/echoes a request identifier into. */
|
|
619
|
+
export declare const DEFAULT_IDENTIFIER_HEADER = "x-request-id";
|
|
620
|
+
|
|
621
|
+
/** Default maximum number of distinct rate-limit keys `createLimiter` tracks before LRU eviction. */
|
|
622
|
+
export declare const DEFAULT_LIMITER_CAPACITY = 10000;
|
|
623
|
+
|
|
624
|
+
/** Default 429 body message `createLimiter` sends when a key is over budget. */
|
|
625
|
+
export declare const DEFAULT_LIMITER_MESSAGE = "rate limit exceeded";
|
|
626
|
+
|
|
627
|
+
/** Default `Permissions-Policy` value `createSecurity` sets. */
|
|
628
|
+
export declare const DEFAULT_PERMISSIONS_POLICY = "camera=(), microphone=(), geolocation=()";
|
|
629
|
+
|
|
630
|
+
/** Default `Referrer-Policy` value `createSecurity` sets. */
|
|
631
|
+
export declare const DEFAULT_REFERRER_POLICY = "strict-origin-when-cross-origin";
|
|
632
|
+
|
|
633
|
+
/** Default maximum number of distinct session ids `createMemorySessionStore` tracks before LRU (by last write) eviction. */
|
|
634
|
+
export declare const DEFAULT_SESSION_CAPACITY = 10000;
|
|
635
|
+
|
|
636
|
+
/** Default cookie name `createCookieTransport` writes the signed session id under. */
|
|
637
|
+
export declare const DEFAULT_SESSION_COOKIE = "session";
|
|
638
|
+
|
|
639
|
+
/** Default header `createHeaderTransport` carries the session id in. */
|
|
640
|
+
export declare const DEFAULT_SESSION_HEADER = "session-id";
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Feature-detect which of `candidates` the runtime's `CompressionStream`
|
|
644
|
+
* actually supports — `createCompression`'s construction-time intersection
|
|
645
|
+
* (ruling J).
|
|
646
|
+
*
|
|
647
|
+
* @remarks
|
|
648
|
+
* Probes each candidate with `new CompressionStream(candidate)` inside a
|
|
649
|
+
* `try`/`catch`; a coding the runtime rejects is dropped silently. `identity`
|
|
650
|
+
* is never probed (it has no `CompressionStream` coding and is always
|
|
651
|
+
* implicitly acceptable to negotiation).
|
|
652
|
+
*
|
|
653
|
+
* @param candidates - The codings to probe, in preference order
|
|
654
|
+
* @returns The subset of `candidates` the runtime's `CompressionStream` supports, in order
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* ```ts
|
|
658
|
+
* detectEncodings(['gzip', 'deflate']) // ['gzip', 'deflate'] on a runtime with both
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
661
|
+
export declare function detectEncodings(candidates: readonly Encoding[]): readonly Encoding[];
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Constant-time string equality — `createCSRF`'s double-submit token
|
|
665
|
+
* comparison, avoiding a timing oracle on the submitted-vs-cookie match.
|
|
666
|
+
*
|
|
667
|
+
* @remarks
|
|
668
|
+
* Length-guarded XOR-accumulate over char codes: a length mismatch short
|
|
669
|
+
* circuits (safe — the lengths of two independently-generated tokens are
|
|
670
|
+
* not a useful timing signal), but once lengths match every character is
|
|
671
|
+
* compared with no early return, so a per-character mismatch never affects
|
|
672
|
+
* how long the comparison takes.
|
|
673
|
+
*
|
|
674
|
+
* @param a - The first string
|
|
675
|
+
* @param b - The second string
|
|
676
|
+
* @returns `true` when `a` and `b` are exactly equal
|
|
677
|
+
*
|
|
678
|
+
* @example
|
|
679
|
+
* ```ts
|
|
680
|
+
* equalsConstantTime('abc', 'abc') // true
|
|
681
|
+
* equalsConstantTime('abc', 'abd') // false
|
|
682
|
+
* ```
|
|
683
|
+
*/
|
|
684
|
+
export declare function equalsConstantTime(a: string, b: string): boolean;
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Options for `createETag` — dynamic response ETag + conditional GET.
|
|
688
|
+
*
|
|
689
|
+
* @param options - See fields below
|
|
690
|
+
* @remarks
|
|
691
|
+
* - `weak` — mint a weak `W/"…"` ETag (default `true`) or a strong `"…"` one
|
|
692
|
+
* (`false`).
|
|
693
|
+
*/
|
|
694
|
+
export declare interface ETagOptions {
|
|
695
|
+
readonly weak?: boolean;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Options for `createForwarded` — the trusted-proxy client-IP resolver.
|
|
700
|
+
*
|
|
701
|
+
* @param options - See fields below
|
|
702
|
+
* @remarks
|
|
703
|
+
* Construction requires EXACTLY ONE of the two forms (a `TypeError` guards
|
|
704
|
+
* both-set and neither-set):
|
|
705
|
+
* - `proxies` — trust exactly this many hops from the right of
|
|
706
|
+
* `X-Forwarded-For` / `Forwarded`.
|
|
707
|
+
* - `trusted` — trust every hop matching one of these CIDR entries.
|
|
708
|
+
*/
|
|
709
|
+
export declare type ForwardedOptions = {
|
|
710
|
+
readonly proxies: number;
|
|
711
|
+
} | {
|
|
712
|
+
readonly trusted: readonly string[];
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Options for `createHeaderTransport` — the bare-header {@link SessionTransport}.
|
|
717
|
+
*
|
|
718
|
+
* @param options - See fields below
|
|
719
|
+
* @remarks
|
|
720
|
+
* - `header` — the header carrying the session id; defaults to
|
|
721
|
+
* {@link DEFAULT_SESSION_HEADER}.
|
|
722
|
+
*/
|
|
723
|
+
export declare interface HeaderTransportOptions {
|
|
724
|
+
readonly header?: string;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* The request-identifier state slice `createSecurity` stashes when its
|
|
729
|
+
* `identifier` option is enabled.
|
|
730
|
+
*/
|
|
731
|
+
export declare interface IdentifierState {
|
|
732
|
+
identifier?: string;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Whether a response is eligible for the compression/ETag buffering pipeline
|
|
737
|
+
* (ruling J) — the shared cheap-skip predicate both batteries apply before
|
|
738
|
+
* ever touching `response.arrayBuffer()`.
|
|
739
|
+
*
|
|
740
|
+
* @remarks
|
|
741
|
+
* Skips a `HEAD` request, a `204`/`304` or otherwise bodyless response, an
|
|
742
|
+
* `event-stream` response (SSE — buffering would hang the connection), and a
|
|
743
|
+
* response that already carries the header the caller is about to set
|
|
744
|
+
* (`skipHeader`, e.g. `Content-Encoding` for compression, `ETag` for the
|
|
745
|
+
* ETag battery).
|
|
746
|
+
*
|
|
747
|
+
* @param method - The request's HTTP method
|
|
748
|
+
* @param response - The candidate response
|
|
749
|
+
* @param skipHeader - The response header whose presence means "already handled"
|
|
750
|
+
* @returns `true` when the response should be left untouched
|
|
751
|
+
*
|
|
752
|
+
* @example
|
|
753
|
+
* ```ts
|
|
754
|
+
* isBufferingIneligible('GET', new Response(null, { status: 204 }), 'content-encoding') // true
|
|
755
|
+
* ```
|
|
756
|
+
*/
|
|
757
|
+
export declare function isBufferingIneligible(method: string, response: Response, skipHeader: string): boolean;
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Whether a negotiated `Accept-Encoding` outcome is worth acting on —
|
|
761
|
+
* `createCompression`'s negotiation-eligibility half of ruling J's skip list.
|
|
762
|
+
*
|
|
763
|
+
* @param encoding - The negotiated coding, or `undefined` when negotiation failed
|
|
764
|
+
* @returns `true` when `encoding` names an actionable, non-`identity` coding
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* ```ts
|
|
768
|
+
* isCompressionNegotiated('gzip') // true
|
|
769
|
+
* isCompressionNegotiated(undefined) // false
|
|
770
|
+
* ```
|
|
771
|
+
*/
|
|
772
|
+
export declare function isCompressionNegotiated(encoding: Encoding | undefined): encoding is Exclude<Encoding, 'identity'>;
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Determine whether a value implements {@link MultipartBody} — a total
|
|
776
|
+
* structural guard (§14): `files` keyed by field name to arrays of
|
|
777
|
+
* {@link MultipartFile}, and a `fields` string record.
|
|
778
|
+
*
|
|
779
|
+
* @param value - The candidate value
|
|
780
|
+
* @returns `true` when `value` is shaped like a {@link MultipartBody}
|
|
781
|
+
*
|
|
782
|
+
* @example
|
|
783
|
+
* ```ts
|
|
784
|
+
* isMultipartBody({ files: {}, fields: { name: 'a' } }) // true
|
|
785
|
+
* ```
|
|
786
|
+
*/
|
|
787
|
+
export declare function isMultipartBody(value: unknown): value is MultipartBody;
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Determine whether a value is one staged {@link MultipartFile} record — a
|
|
791
|
+
* total structural guard (§14) checking every required field's shape.
|
|
792
|
+
*
|
|
793
|
+
* @param value - The candidate value
|
|
794
|
+
* @returns `true` when `value` is shaped like a {@link MultipartFile}
|
|
795
|
+
*/
|
|
796
|
+
export declare function isMultipartFile(value: unknown): value is MultipartFile;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Determine whether a request is a CORS PREFLIGHT — an `OPTIONS` request
|
|
800
|
+
* carrying an `Access-Control-Request-Method` header.
|
|
801
|
+
*
|
|
802
|
+
* @param method - The request's HTTP method
|
|
803
|
+
* @param headers - The request's `Headers`
|
|
804
|
+
* @returns `true` when the request is a CORS preflight `createCors` must answer
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```ts
|
|
808
|
+
* isPreflight('OPTIONS', new Headers({ 'access-control-request-method': 'POST' })) // true
|
|
809
|
+
* ```
|
|
810
|
+
*/
|
|
811
|
+
export declare function isPreflight(method: string, headers: Headers): boolean;
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Determine whether a value implements {@link SessionInterface} — a total
|
|
815
|
+
* structural guard (§14): an `id` string plus a `data` `Map`.
|
|
816
|
+
*
|
|
817
|
+
* @param value - The candidate value
|
|
818
|
+
* @returns `true` when `value` is shaped like a {@link SessionInterface}
|
|
819
|
+
*
|
|
820
|
+
* @example
|
|
821
|
+
* ```ts
|
|
822
|
+
* isSession({ id: 'a', data: new Map() }) // true
|
|
823
|
+
* ```
|
|
824
|
+
*/
|
|
825
|
+
export declare function isSession(value: unknown): value is SessionInterface;
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Determine whether a value implements {@link SessionControlInterface} — a
|
|
829
|
+
* total structural guard (§14): callable `regenerate` and `destroy`.
|
|
830
|
+
*
|
|
831
|
+
* @param value - The candidate value
|
|
832
|
+
* @returns `true` when `value` is shaped like a {@link SessionControlInterface}
|
|
833
|
+
*
|
|
834
|
+
* @example
|
|
835
|
+
* ```ts
|
|
836
|
+
* isSessionControl({ regenerate() {}, destroy() {} }) // true
|
|
837
|
+
* ```
|
|
838
|
+
*/
|
|
839
|
+
export declare function isSessionControl(value: unknown): value is SessionControlInterface;
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Options for `createLimiter` — fixed-window rate limiting.
|
|
843
|
+
*
|
|
844
|
+
* @typeParam TState - The consumer's opaque per-request state type `key` reads
|
|
845
|
+
* @param options - See fields below
|
|
846
|
+
* @remarks
|
|
847
|
+
* - `max` — the number of requests admitted per key per `window`.
|
|
848
|
+
* - `window` — the window length in milliseconds.
|
|
849
|
+
* - `capacity` — the maximum number of distinct keys tracked before the
|
|
850
|
+
* least-recently-used key is evicted (true LRU — every access, not just
|
|
851
|
+
* insertion, refreshes recency); defaults to {@link DEFAULT_LIMITER_CAPACITY}.
|
|
852
|
+
* - `key` — derives the bucket key from the request; defaults to the
|
|
853
|
+
* bearer-token-then-client-IP idiom (see the battery's guide).
|
|
854
|
+
* - `message` — the 429 body message; defaults to {@link DEFAULT_LIMITER_MESSAGE}.
|
|
855
|
+
* - `clock` — the injected time source for all window math; defaults to `Date.now`.
|
|
856
|
+
* - `policy` — when `true`, also emits the draft `RateLimit`/`RateLimit-Policy`
|
|
857
|
+
* structured header fields; defaults to `false`. `Retry-After` always ships.
|
|
858
|
+
* - `evict` — invoked with a bucket's key when it is evicted for capacity;
|
|
859
|
+
* its own throw is swallowed and can never fail the request. It is a
|
|
860
|
+
* notification sink only — it must never call back into the limiter
|
|
861
|
+
* (no re-entrant reads/writes); mutations during eviction are unsupported.
|
|
862
|
+
*/
|
|
863
|
+
export declare interface LimiterOptions<TState = unknown> {
|
|
864
|
+
readonly max: number;
|
|
865
|
+
readonly window: number;
|
|
866
|
+
readonly capacity?: number;
|
|
867
|
+
readonly key?: (context: MiddlewareContext<TState>) => string;
|
|
868
|
+
readonly message?: string;
|
|
869
|
+
readonly clock?: () => number;
|
|
870
|
+
readonly policy?: boolean;
|
|
871
|
+
readonly evict?: (key: string) => void;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Whether a candidate address is a bare (non-CIDR) trusted-hop match — an
|
|
876
|
+
* exact string match, or a simple prefix-CIDR match for IPv4 (`/8`–`/32`).
|
|
877
|
+
* An IPv6 entry matches by exact string only — there is no IPv6 CIDR
|
|
878
|
+
* support.
|
|
879
|
+
*
|
|
880
|
+
* @remarks
|
|
881
|
+
* Supports exact addresses and dotted-decimal IPv4 CIDR (`10.0.0.0/8`
|
|
882
|
+
* through `/32`) verbatim. An IPv6 entry is matched by exact string only
|
|
883
|
+
* (no CIDR expansion) — documented as the supported subset; `createForwarded`
|
|
884
|
+
* never claims full CIDR generality beyond IPv4.
|
|
885
|
+
*
|
|
886
|
+
* @remarks
|
|
887
|
+
* An IPv6 `trusted` roster entry is compared as an EXACT string — it must be
|
|
888
|
+
* supplied in canonical form (no zero-compression normalization, no case
|
|
889
|
+
* folding) by the caller; this function performs no IPv6 normalization of
|
|
890
|
+
* its own.
|
|
891
|
+
*
|
|
892
|
+
* @param address - The candidate hop address
|
|
893
|
+
* @param entry - One `trusted` roster entry — an exact address or an IPv4 CIDR
|
|
894
|
+
* @returns `true` when `address` is covered by `entry`
|
|
895
|
+
*
|
|
896
|
+
* @example
|
|
897
|
+
* ```ts
|
|
898
|
+
* matchesTrustedEntry('10.1.2.3', '10.0.0.0/8') // true
|
|
899
|
+
* matchesTrustedEntry('192.168.1.1', '10.0.0.0/8') // false
|
|
900
|
+
* ```
|
|
901
|
+
*/
|
|
902
|
+
export declare function matchesTrustedEntry(address: string, entry: string): boolean;
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* The default in-process {@link SessionStoreInterface} — a `Map`-backed store
|
|
906
|
+
* enforcing both an idle timeout and an absolute lifetime, with lazy
|
|
907
|
+
* (read-time) eviction, a bounded capacity, and no background timers.
|
|
908
|
+
*
|
|
909
|
+
* @typeParam S - The session data payload type
|
|
910
|
+
*
|
|
911
|
+
* @remarks
|
|
912
|
+
* `get` evicts a session whose idle time (`now - lastSeen >= ttl`) or
|
|
913
|
+
* absolute lifetime (`now - createdAt >= lifetime`) has elapsed — the
|
|
914
|
+
* lifetime check fires EVEN IF the session was continuously touched, since
|
|
915
|
+
* `createdAt` is stamped once at the first `set` and preserved across every
|
|
916
|
+
* later re-`set` of the same id. A live read touches `lastSeen`. `delete` of
|
|
917
|
+
* an absent id is a no-op.
|
|
918
|
+
*
|
|
919
|
+
* Capacity is enforced as least-recently-used **by last write**: `set`
|
|
920
|
+
* refreshes an id's recency (deleting then re-inserting so the Map's
|
|
921
|
+
* iteration tail is the most-recently-written id). Inserting a brand-new id
|
|
922
|
+
* once the store is at `capacity` first prunes expired entries; if the store
|
|
923
|
+
* is still full, the least-recently-written id (the Map's head) is evicted.
|
|
924
|
+
* `options.evict` — when provided — is invoked (throw-isolated) with the id
|
|
925
|
+
* on every eviction the store's own policy performs (a capacity eviction or
|
|
926
|
+
* an expired-entry prune), but never for an explicit `delete`.
|
|
927
|
+
*
|
|
928
|
+
* @example
|
|
929
|
+
* ```ts
|
|
930
|
+
* const store = new MemorySessionStore({ ttl: 60_000, lifetime: 3_600_000 })
|
|
931
|
+
* await store.set('abc', { userId: 'u_1' }, Date.now())
|
|
932
|
+
* ```
|
|
933
|
+
*/
|
|
934
|
+
export declare class MemorySessionStore<S> implements SessionStoreInterface<S> {
|
|
935
|
+
#private;
|
|
936
|
+
constructor(options?: MemorySessionStoreOptions);
|
|
937
|
+
get(id: string, now: number): Promise<S | undefined>;
|
|
938
|
+
set(id: string, session: S, now: number): Promise<void>;
|
|
939
|
+
delete(id: string): Promise<void>;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Options for `createMemorySessionStore` — the default in-process {@link SessionStoreInterface}.
|
|
944
|
+
*
|
|
945
|
+
* @param options - See fields below
|
|
946
|
+
* @remarks
|
|
947
|
+
* - `ttl` — the idle timeout in milliseconds (lazy eviction on `get`).
|
|
948
|
+
* - `lifetime` — the absolute lifetime in milliseconds from first `set`
|
|
949
|
+
* (evicts even a continuously-touched session).
|
|
950
|
+
* - `capacity` — the maximum number of distinct session ids tracked before
|
|
951
|
+
* the least-recently-written id is evicted (LRU by last write — every
|
|
952
|
+
* `set` refreshes an id's recency); defaults to {@link DEFAULT_SESSION_CAPACITY}.
|
|
953
|
+
* - `evict` — invoked with a session id when it is evicted by the store's
|
|
954
|
+
* own policy (a capacity eviction or an expired-entry prune) — never for
|
|
955
|
+
* an explicit `delete`. Its own throw is swallowed. It is a notification
|
|
956
|
+
* sink only — it must never call back into the store (no re-entrant
|
|
957
|
+
* `get`/`set`); mutations during eviction are unsupported.
|
|
958
|
+
*/
|
|
959
|
+
export declare interface MemorySessionStoreOptions {
|
|
960
|
+
readonly ttl?: number;
|
|
961
|
+
readonly lifetime?: number;
|
|
962
|
+
readonly capacity?: number;
|
|
963
|
+
readonly evict?: (id: string) => void;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* The parsed multipart request body `createMultipart` stashes — files keyed
|
|
968
|
+
* by their field name, plus every plain text field.
|
|
969
|
+
*/
|
|
970
|
+
export declare interface MultipartBody {
|
|
971
|
+
readonly files: Readonly<Record<string, readonly MultipartFile[]>>;
|
|
972
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* One staged multipart upload's public record — the shape the node-face
|
|
977
|
+
* `createMultipart` battery (`@orkestrel/middleware/server`) produces per
|
|
978
|
+
* uploaded file.
|
|
979
|
+
*
|
|
980
|
+
* @remarks
|
|
981
|
+
* Declared here rather than in the node-bound server surface so the
|
|
982
|
+
* fetch/string-pure {@link MultipartState} slice — referenced by any
|
|
983
|
+
* environment narrowing `context.state` — never depends on the node face.
|
|
984
|
+
* The server's concrete `UploadedFileInterface` is structurally compatible
|
|
985
|
+
* with this shape.
|
|
986
|
+
*/
|
|
987
|
+
export declare interface MultipartFile {
|
|
988
|
+
readonly field: string;
|
|
989
|
+
readonly name: string;
|
|
990
|
+
readonly size: number;
|
|
991
|
+
readonly mime: string;
|
|
992
|
+
readonly validated: boolean;
|
|
993
|
+
readonly status: string;
|
|
994
|
+
readonly path: string;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* The multipart state slice `createMultipart` stashes.
|
|
999
|
+
*
|
|
1000
|
+
* @remarks
|
|
1001
|
+
* Present only once `createMultipart` has fully parsed a multipart request.
|
|
1002
|
+
* After it runs, `context.body()` must not be called for that request — the
|
|
1003
|
+
* multipart battery consumes `request.body` as a stream, so the seam's
|
|
1004
|
+
* cached body has nothing left to read.
|
|
1005
|
+
*/
|
|
1006
|
+
export declare interface MultipartState {
|
|
1007
|
+
multipart?: MultipartBody;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Rebuild a `Response` around a replacement body while preserving its
|
|
1012
|
+
* status/statusText — the buffered-response reconstruction shared by the
|
|
1013
|
+
* compression and ETag batteries after they have consumed
|
|
1014
|
+
* `response.arrayBuffer()`.
|
|
1015
|
+
*
|
|
1016
|
+
* @param body - The replacement body (already-buffered bytes, or `null`)
|
|
1017
|
+
* @param response - The original response whose status/statusText/headers are preserved
|
|
1018
|
+
* @param headers - The headers to apply; defaults to a fresh copy of `response.headers`
|
|
1019
|
+
* @returns A new `Response` carrying `body` with `response`'s status/statusText
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* rebuildResponse(bytes, response) // same status/statusText, response's headers copied
|
|
1024
|
+
* rebuildResponse(bytes, response, headers) // explicit replacement headers
|
|
1025
|
+
* ```
|
|
1026
|
+
*/
|
|
1027
|
+
export declare function rebuildResponse(body: ConstructorParameters<typeof Response>[0], response: Response, headers?: ConstructorParameters<typeof Headers>[0]): Response;
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Walk `X-Forwarded-For` right-to-left and resolve the first UNTRUSTED hop
|
|
1031
|
+
* address — `createForwarded`'s core algorithm.
|
|
1032
|
+
*
|
|
1033
|
+
* @remarks
|
|
1034
|
+
* Parses `X-Forwarded-For` only. With `proxies` set, trusts exactly that
|
|
1035
|
+
* many hops counted from the right (the closest to this server) and returns
|
|
1036
|
+
* the next one left of them; with `trusted` set, trusts every CONSECUTIVE
|
|
1037
|
+
* hop from the right that matches one of the roster
|
|
1038
|
+
* ({@link matchesTrustedEntry}) and returns the first hop that does not. If
|
|
1039
|
+
* the rightmost hop (the immediate sender) does not match the roster, the
|
|
1040
|
+
* whole header is untrustworthy and this returns `undefined` rather than
|
|
1041
|
+
* returning any client-supplied hop value. When every hop is trusted, or the
|
|
1042
|
+
* header is absent/empty, returns `undefined` (the caller falls back to the
|
|
1043
|
+
* socket peer).
|
|
1044
|
+
*
|
|
1045
|
+
* @param header - The raw `X-Forwarded-For` header value (comma-separated hops), if present
|
|
1046
|
+
* @param trust - Either a trusted hop COUNT or a `trusted` CIDR/exact roster
|
|
1047
|
+
* @returns The first untrusted hop address, or `undefined` when none qualifies
|
|
1048
|
+
*
|
|
1049
|
+
* @example
|
|
1050
|
+
* ```ts
|
|
1051
|
+
* resolveForwardedFor('203.0.113.7, 10.0.0.1', { proxies: 1 }) // '203.0.113.7'
|
|
1052
|
+
* ```
|
|
1053
|
+
*/
|
|
1054
|
+
export declare function resolveForwardedFor(header: string | undefined, trust: {
|
|
1055
|
+
readonly proxies: number;
|
|
1056
|
+
} | {
|
|
1057
|
+
readonly trusted: readonly string[];
|
|
1058
|
+
}): string | undefined;
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Derive `createLimiter`'s default rate-limit bucket key from a request's
|
|
1062
|
+
* resolved identity facts.
|
|
1063
|
+
*
|
|
1064
|
+
* @remarks
|
|
1065
|
+
* Prefers a verified bearer token ({@link BearerState.token}) as
|
|
1066
|
+
* `token:<value>`; else a resolved client IP ({@link ClientState.client.ip},
|
|
1067
|
+
* set when `createForwarded` is mounted) or the raw socket peer
|
|
1068
|
+
* ({@link ConnectionState.connection.ip}) collapsed via `clientRateKey`
|
|
1069
|
+
* (IPv6 to its `/64` network) as `ip:<key>`; else the literal `ip:unknown`.
|
|
1070
|
+
* Never reads `X-Forwarded-For` itself — that trust decision belongs solely
|
|
1071
|
+
* to `createForwarded`.
|
|
1072
|
+
*
|
|
1073
|
+
* @param state - The slices `createLimiter`'s default key may read
|
|
1074
|
+
* @returns The bucket key for the request
|
|
1075
|
+
*
|
|
1076
|
+
* @example
|
|
1077
|
+
* ```ts
|
|
1078
|
+
* resolveKey({ token: 'abc' }) // 'token:abc'
|
|
1079
|
+
* resolveKey({ client: { ip: '2001:db8::1' } }) // 'ip:2001:db8:0:0::/64'
|
|
1080
|
+
* ```
|
|
1081
|
+
*/
|
|
1082
|
+
export declare function resolveKey(state: BearerState & ClientState & ConnectionState): string;
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Resolve an opt-in, value-bearing security header — `string | boolean`
|
|
1086
|
+
* (default OFF, `true` uses the secure default), the shape `createSecurity`'s
|
|
1087
|
+
* `coep`/`hsts` options use, distinct from the plain value-or-`false` shape
|
|
1088
|
+
* `resolveSecurityHeader` (the peer substrate) handles.
|
|
1089
|
+
*
|
|
1090
|
+
* @param value - The option value — a `string` override, `true` for the secure default, or `false`/`undefined` to omit
|
|
1091
|
+
* @param fallback - The secure-default value used when `value` is `true`
|
|
1092
|
+
* @returns The header value to set, or `undefined` to omit the header
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* ```ts
|
|
1096
|
+
* resolveOptInHeader(true, 'require-corp') // 'require-corp'
|
|
1097
|
+
* resolveOptInHeader(undefined, 'require-corp') // undefined — omitted
|
|
1098
|
+
* ```
|
|
1099
|
+
*/
|
|
1100
|
+
export declare function resolveOptInHeader(value: string | boolean | undefined, fallback: string): string | undefined;
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* `createSecurity`'s `identifier` sub-option — request-id minting/echo
|
|
1104
|
+
* policy, or `false` to disable the feature entirely.
|
|
1105
|
+
*
|
|
1106
|
+
* @remarks
|
|
1107
|
+
* - `trust` — when `true`, an incoming `X-Request-ID` that passes {@link
|
|
1108
|
+
* import('@orkestrel/server').isValidRequestId} is echoed back instead of
|
|
1109
|
+
* replaced by a fresh mint. Defaults to `false` (always mint).
|
|
1110
|
+
*/
|
|
1111
|
+
export declare type SecurityIdentifierOptions = {
|
|
1112
|
+
readonly trust?: boolean;
|
|
1113
|
+
} | false;
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Options for `createSecurity` — the security-headers + request-id battery.
|
|
1117
|
+
*
|
|
1118
|
+
* @param options - See fields below
|
|
1119
|
+
* @remarks
|
|
1120
|
+
* Every header option is `string | false` (a custom value replaces the
|
|
1121
|
+
* default wholesale, `false` omits the header) unless noted; unset uses the
|
|
1122
|
+
* documented default. `X-Content-Type-Options: nosniff` is unconditional and
|
|
1123
|
+
* has no option.
|
|
1124
|
+
* - `frame` — `X-Frame-Options`; `'DENY' | 'SAMEORIGIN' | false`, default `'DENY'`.
|
|
1125
|
+
* - `csp` — `Content-Security-Policy`; default {@link DEFAULT_CSP}.
|
|
1126
|
+
* - `referrer` — `Referrer-Policy`; default {@link DEFAULT_REFERRER_POLICY}.
|
|
1127
|
+
* - `permissions` — `Permissions-Policy`; default {@link DEFAULT_PERMISSIONS_POLICY}.
|
|
1128
|
+
* - `coop` — `Cross-Origin-Opener-Policy`; default {@link DEFAULT_COOP}.
|
|
1129
|
+
* - `corp` — `Cross-Origin-Resource-Policy`; default {@link DEFAULT_CORP}.
|
|
1130
|
+
* - `cluster` — `Origin-Agent-Cluster`; default {@link DEFAULT_CLUSTER}.
|
|
1131
|
+
* - `coep` — `Cross-Origin-Embedder-Policy`; `string | boolean`, OFF by
|
|
1132
|
+
* default (opt-in, breaks cross-origin subresources); `true` → {@link DEFAULT_COEP}.
|
|
1133
|
+
* - `hsts` — `Strict-Transport-Security`; `string | boolean`, OFF by default
|
|
1134
|
+
* (opt-in, destructive if misconfigured); `true` → {@link DEFAULT_HSTS}.
|
|
1135
|
+
* - `identifier` — {@link SecurityIdentifierOptions}; ON by default (mints
|
|
1136
|
+
* and stashes {@link IdentifierState}).
|
|
1137
|
+
*/
|
|
1138
|
+
export declare interface SecurityOptions {
|
|
1139
|
+
readonly frame?: 'DENY' | 'SAMEORIGIN' | false;
|
|
1140
|
+
readonly csp?: string | false;
|
|
1141
|
+
readonly referrer?: string | false;
|
|
1142
|
+
readonly permissions?: string | false;
|
|
1143
|
+
readonly coop?: string | false;
|
|
1144
|
+
readonly corp?: string | false;
|
|
1145
|
+
readonly cluster?: string | false;
|
|
1146
|
+
readonly coep?: string | boolean;
|
|
1147
|
+
readonly hsts?: string | boolean;
|
|
1148
|
+
readonly identifier?: SecurityIdentifierOptions;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* A server-managed session's default entity — the `create` option's default
|
|
1153
|
+
* value factory for `createSession` (ruling G: `Session` ships WITHOUT a
|
|
1154
|
+
* `createSession` factory of its own, since that name belongs to the
|
|
1155
|
+
* battery).
|
|
1156
|
+
*
|
|
1157
|
+
* @remarks
|
|
1158
|
+
* `data` is a live, mutable `Map` a handler reads/writes directly;
|
|
1159
|
+
* `createSession` persists it to the configured store on the way out.
|
|
1160
|
+
*
|
|
1161
|
+
* @example
|
|
1162
|
+
* ```ts
|
|
1163
|
+
* const session = new Session('abc123')
|
|
1164
|
+
* session.data.set('userId', 'u_1')
|
|
1165
|
+
* ```
|
|
1166
|
+
*/
|
|
1167
|
+
export declare class Session implements SessionInterface {
|
|
1168
|
+
readonly id: string;
|
|
1169
|
+
readonly data: Map<string, unknown>;
|
|
1170
|
+
constructor(id: string);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* The mid-handler control handle `createSession` stashes alongside the
|
|
1175
|
+
* session itself — the OWASP anti-fixation / logout primitives.
|
|
1176
|
+
*
|
|
1177
|
+
* @remarks
|
|
1178
|
+
* `regenerate` and `destroy` record intent SYNCHRONOUSLY when called; the
|
|
1179
|
+
* store I/O and transport write happen after the handler's `next()` returns
|
|
1180
|
+
* (`destroy` supersedes a prior `regenerate`). `regenerate` mints a new id,
|
|
1181
|
+
* carries the session's `data` over, and invalidates the old id.
|
|
1182
|
+
*/
|
|
1183
|
+
export declare interface SessionControlInterface {
|
|
1184
|
+
regenerate(): void;
|
|
1185
|
+
destroy(): void;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* A server-managed session's public surface — an id and its mutable data bag.
|
|
1190
|
+
*
|
|
1191
|
+
* @remarks
|
|
1192
|
+
* `data` is a live `Map` a handler reads/writes directly; `createSession`
|
|
1193
|
+
* persists it to the configured {@link SessionStoreInterface} on the way out.
|
|
1194
|
+
*/
|
|
1195
|
+
export declare interface SessionInterface {
|
|
1196
|
+
readonly id: string;
|
|
1197
|
+
readonly data: Map<string, unknown>;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Options for `createSession` — the generic session battery.
|
|
1202
|
+
*
|
|
1203
|
+
* @typeParam S - The session data payload type `create` produces
|
|
1204
|
+
* @typeParam TState - The consumer's opaque per-request state type `mint` reads
|
|
1205
|
+
* @param options - See fields below
|
|
1206
|
+
* @remarks
|
|
1207
|
+
* - `transport` — the {@link SessionTransport} (`createCookieTransport(...)`,
|
|
1208
|
+
* `createHeaderTransport(...)`, or a custom one).
|
|
1209
|
+
* - `store` — the {@link SessionStoreInterface}; defaults to
|
|
1210
|
+
* `createMemorySessionStore({ ttl, lifetime, capacity, evict })`.
|
|
1211
|
+
* - `ttl` — the idle timeout in milliseconds.
|
|
1212
|
+
* - `lifetime` — the absolute session lifetime in milliseconds from mint.
|
|
1213
|
+
* - `capacity` — the maximum number of distinct session ids the DEFAULT
|
|
1214
|
+
* memory store tracks before LRU eviction; ignored when `store` is
|
|
1215
|
+
* provided. Defaults to {@link DEFAULT_SESSION_CAPACITY}.
|
|
1216
|
+
* - `evict` — invoked with a session id evicted by the DEFAULT memory
|
|
1217
|
+
* store's own policy; ignored when `store` is provided. It is a
|
|
1218
|
+
* notification sink only — it must never call back into the store
|
|
1219
|
+
* (no re-entrant `get`/`set`); mutations during eviction are unsupported.
|
|
1220
|
+
* - `create` — builds a fresh session's public entity from a minted id;
|
|
1221
|
+
* defaults to `new Session(id)`.
|
|
1222
|
+
* - `mint` — decides whether to auto-mint a session when none resolves;
|
|
1223
|
+
* defaults to always minting (auto-session).
|
|
1224
|
+
* - `require` — when `true`, a request that resolves no session and does not
|
|
1225
|
+
* mint one renders a 404 instead of proceeding sessionless. Defaults to `false`.
|
|
1226
|
+
* - `ends` — when `true`, a `DELETE` request carrying a valid session id
|
|
1227
|
+
* deletes the session and short-circuits with `204`. Defaults to `false`.
|
|
1228
|
+
* - `clock` — the injected time source fed to the store; defaults to `Date.now`.
|
|
1229
|
+
*/
|
|
1230
|
+
export declare interface SessionOptions<S, TState = unknown> {
|
|
1231
|
+
readonly transport: SessionTransport;
|
|
1232
|
+
readonly store?: SessionStoreInterface<S>;
|
|
1233
|
+
readonly ttl?: number;
|
|
1234
|
+
readonly lifetime?: number;
|
|
1235
|
+
readonly capacity?: number;
|
|
1236
|
+
readonly evict?: (id: string) => void;
|
|
1237
|
+
readonly create?: (id: string) => S;
|
|
1238
|
+
readonly mint?: (context: MiddlewareContext<TState>) => boolean | Promise<boolean>;
|
|
1239
|
+
readonly require?: boolean;
|
|
1240
|
+
readonly ends?: boolean;
|
|
1241
|
+
readonly clock?: () => number;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* The session state slice `createSession` stashes.
|
|
1246
|
+
*
|
|
1247
|
+
* @remarks
|
|
1248
|
+
* `session` is present whenever a request resolves or mints a session;
|
|
1249
|
+
* `control` is present whenever `session` is (the handle to act on it).
|
|
1250
|
+
*/
|
|
1251
|
+
export declare interface SessionState {
|
|
1252
|
+
session?: SessionInterface;
|
|
1253
|
+
control?: SessionControlInterface;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* The pluggable session persistence seam `createSession`'s `store` option
|
|
1258
|
+
* implements — a point-access store (AGENTS §5) keyed by session id.
|
|
1259
|
+
*
|
|
1260
|
+
* @typeParam S - The session data payload type
|
|
1261
|
+
* @remarks
|
|
1262
|
+
* Every primitive is async and takes a trailing `now` clock reading (the
|
|
1263
|
+
* same seam `createSession`'s `clock` option feeds) so a store can apply its
|
|
1264
|
+
* own idle/absolute expiry against the caller's injected time rather than
|
|
1265
|
+
* its own wall clock. `delete` of an absent id is a no-op, never throws.
|
|
1266
|
+
*/
|
|
1267
|
+
export declare interface SessionStoreInterface<S> {
|
|
1268
|
+
get(id: string, now: number): Promise<S | undefined>;
|
|
1269
|
+
set(id: string, session: S, now: number): Promise<void>;
|
|
1270
|
+
delete(id: string): Promise<void>;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* The transport seam `createSession`'s `transport` option implements — how a
|
|
1275
|
+
* session id travels to and from the client (a signed cookie, a header, …).
|
|
1276
|
+
*
|
|
1277
|
+
* @remarks
|
|
1278
|
+
* `read` is total (a malformed/tampered credential resolves `undefined`,
|
|
1279
|
+
* never throws). `write` and `clear` mutate the RETURNED `Response` on the
|
|
1280
|
+
* way out — the returning onion makes "before send" automatic. `write` is
|
|
1281
|
+
* called only when a session is freshly minted or regenerated; `clear` is
|
|
1282
|
+
* called on `destroy()`. `write`'s `encrypted` flag is the request's resolved
|
|
1283
|
+
* transport security (derived from `context.url.protocol`) so a cookie
|
|
1284
|
+
* transport can resolve its own `Secure` attribute via `resolveSecure`
|
|
1285
|
+
* without re-deriving connection facts itself.
|
|
1286
|
+
*/
|
|
1287
|
+
export declare interface SessionTransport {
|
|
1288
|
+
read(request: Request): string | undefined | Promise<string | undefined>;
|
|
1289
|
+
write(response: Response, id: string, encrypted: boolean): void | Promise<void>;
|
|
1290
|
+
clear(response: Response): void;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* One access-log-style entry `createTelemetry` records after a response
|
|
1295
|
+
* settles — the access-log/timing seam's payload shape.
|
|
1296
|
+
*
|
|
1297
|
+
* @remarks
|
|
1298
|
+
* - `method` — the request's HTTP verb.
|
|
1299
|
+
* - `pathname` — the request URL's pathname.
|
|
1300
|
+
* - `status` — the response's final status (the boundary-mapped status when
|
|
1301
|
+
* a downstream throw was rendered by `createBoundary`).
|
|
1302
|
+
* - `duration` — the wall-clock time in milliseconds the whole onion took
|
|
1303
|
+
* beneath `createTelemetry`.
|
|
1304
|
+
*/
|
|
1305
|
+
export declare interface TelemetryEntry {
|
|
1306
|
+
readonly method: string;
|
|
1307
|
+
readonly pathname: string;
|
|
1308
|
+
readonly status: number;
|
|
1309
|
+
readonly duration: number;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* Options for `createTelemetry` — the request timing/access-log seam.
|
|
1314
|
+
*
|
|
1315
|
+
* @param options - See fields below
|
|
1316
|
+
* @remarks
|
|
1317
|
+
* - `record` — invoked once per request with the settled {@link
|
|
1318
|
+
* TelemetryEntry}; its own throw is swallowed so a broken sink can never
|
|
1319
|
+
* fail the response.
|
|
1320
|
+
*/
|
|
1321
|
+
export declare interface TelemetryOptions {
|
|
1322
|
+
readonly record: (entry: TelemetryEntry) => void;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Copy every entry of one session's `data` into another — the regenerate
|
|
1327
|
+
* data-carry `createSession`'s `control.regenerate()` applies (ruling D).
|
|
1328
|
+
*
|
|
1329
|
+
* @param from - The source session whose `data` is copied
|
|
1330
|
+
* @param to - The destination session `data` is copied into
|
|
1331
|
+
*
|
|
1332
|
+
* @example
|
|
1333
|
+
* ```ts
|
|
1334
|
+
* transferSessionData(oldSession, newSession)
|
|
1335
|
+
* ```
|
|
1336
|
+
*/
|
|
1337
|
+
export declare function transferSessionData(from: SessionInterface, to: SessionInterface): void;
|
|
1338
|
+
|
|
1339
|
+
export { }
|