@orkestrel/middleware 0.0.18 → 0.0.20

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.
@@ -1,15 +1,15 @@
1
1
  import { integerShape, isBoolean, isFiniteNumber, isFunction, isRecord, isString, jsonShape, stringShape } from "@orkestrel/contract";
2
- import { HTTPError, clearCookie, clientRateKey, computeBodyETag, isCompressibleType, isHTTPError, isValidRequestId, matchesETag, mergeVary, negotiateEncoding, readSignedCookie, resolveOrigin, resolveSecure, resolveSecurityHeader, signToken, verifyToken, writeSignedCookie } from "@orkestrel/server";
2
+ import { HTTPError, clearCookie, computeBodyETag, computeClientKey, isCompressibleType, isHTTPError, isValidRequestId, matchesETag, mergeVary, negotiateEncoding, readSignedCookie, resolveOrigin, resolveSecure, resolveSecurityHeader, signToken, verifyToken, writeSignedCookie } from "@orkestrel/server";
3
3
  import { linkSignal } from "@orkestrel/abort";
4
4
  import { createBudget } from "@orkestrel/budget";
5
5
  import { createTimeout } from "@orkestrel/timeout";
6
6
  //#region src/core/constants.ts
7
- /** Default minimum buffered body size (bytes) `createCompression` will compress. */
7
+ /** Holds `1024`, the default minimum buffered body size in bytes `createCompression` will compress. */
8
8
  var DEFAULT_COMPRESSION_THRESHOLD = 1024;
9
9
  /**
10
- * Default content-codings `createCompression` offers, in preference order —
11
- * intersected at construction with what the runtime's `CompressionStream`
12
- * actually supports.
10
+ * Lists `['gzip', 'deflate']`, the default content-codings `createCompression` offers in
11
+ * preference order — intersected at construction with what the runtime's
12
+ * `CompressionStream` actually supports.
13
13
  *
14
14
  * @remarks
15
15
  * The shipped `@orkestrel/server` peer's {@link Encoding} union is
@@ -19,30 +19,31 @@ var DEFAULT_COMPRESSION_THRESHOLD = 1024;
19
19
  * admits; a node-face brotli variant, if one ships, extends this list there.
20
20
  */
21
21
  var DEFAULT_COMPRESSION_ENCODINGS = Object.freeze(["gzip", "deflate"]);
22
- /** Default `X-Frame-Options` value `createSecurity` sets. */
22
+ /** Holds `'DENY'`, the default `X-Frame-Options` value `createSecurity` sets. */
23
23
  var DEFAULT_FRAME_OPTIONS = "DENY";
24
24
  /**
25
- * Default `Content-Security-Policy` value `createSecurity` sets a custom
26
- * `csp` option REPLACES this wholesale, never merges.
25
+ * Holds `"default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'"`,
26
+ * the default `Content-Security-Policy` value `createSecurity` sets a custom
27
+ * `csp` option replaces this wholesale, never merges.
27
28
  */
28
29
  var DEFAULT_CSP = "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'";
29
- /** Default `Referrer-Policy` value `createSecurity` sets. */
30
+ /** Holds `'strict-origin-when-cross-origin'`, the default `Referrer-Policy` value `createSecurity` sets. */
30
31
  var DEFAULT_REFERRER_POLICY = "strict-origin-when-cross-origin";
31
- /** Default `Permissions-Policy` value `createSecurity` sets. */
32
+ /** Holds `'camera=(), microphone=(), geolocation=()'`, the default `Permissions-Policy` value `createSecurity` sets. */
32
33
  var DEFAULT_PERMISSIONS_POLICY = "camera=(), microphone=(), geolocation=()";
33
- /** Default `Cross-Origin-Opener-Policy` value `createSecurity` sets. */
34
+ /** Holds `'same-origin'`, the default `Cross-Origin-Opener-Policy` value `createSecurity` sets. */
34
35
  var DEFAULT_COOP = "same-origin";
35
- /** Default `Cross-Origin-Resource-Policy` value `createSecurity` sets. */
36
+ /** Holds `'same-origin'`, the default `Cross-Origin-Resource-Policy` value `createSecurity` sets. */
36
37
  var DEFAULT_CORP = "same-origin";
37
- /** Default `Origin-Agent-Cluster` value `createSecurity` sets. */
38
+ /** Holds `'?1'`, the default `Origin-Agent-Cluster` value `createSecurity` sets. */
38
39
  var DEFAULT_CLUSTER = "?1";
39
- /** Value `createSecurity` sets for `Cross-Origin-Embedder-Policy` when `coep: true`. */
40
+ /** Holds `'require-corp'`, the value `createSecurity` sets for `Cross-Origin-Embedder-Policy` when `coep: true`. */
40
41
  var DEFAULT_COEP = "require-corp";
41
- /** Value `createSecurity` sets for `Strict-Transport-Security` when `hsts: true`. */
42
+ /** Holds `'max-age=31536000; includeSubDomains'`, the value `createSecurity` sets for `Strict-Transport-Security` when `hsts: true`. */
42
43
  var DEFAULT_HSTS = "max-age=31536000; includeSubDomains";
43
- /** Default header `createSecurity` mints/echoes a request identifier into. */
44
+ /** Names `'x-request-id'`, the default header `createSecurity` mints or echoes a request identifier into. */
44
45
  var DEFAULT_IDENTIFIER_HEADER = "x-request-id";
45
- /** Default methods `createCors` advertises on a preflight response. */
46
+ /** Lists the default methods `createCors` advertises on a preflight response. */
46
47
  var DEFAULT_CORS_METHODS = Object.freeze([
47
48
  "GET",
48
49
  "POST",
@@ -51,73 +52,47 @@ var DEFAULT_CORS_METHODS = Object.freeze([
51
52
  "DELETE",
52
53
  "OPTIONS"
53
54
  ]);
54
- /** Default headers `createCors` advertises on a preflight response. */
55
+ /** Lists the default headers `createCors` advertises on a preflight response. */
55
56
  var DEFAULT_CORS_HEADERS = Object.freeze(["Content-Type", "Authorization"]);
56
- /** Default response status `createDeadline` returns when its deadline fires first. */
57
+ /** Holds `503`, the default response status `createDeadline` returns when its deadline fires first. */
57
58
  var DEFAULT_DEADLINE_STATUS = 503;
58
- /** Default header `createBearer` reads the token from. */
59
+ /** Names `'authorization'`, the default header `createBearer` reads the token from. */
59
60
  var DEFAULT_BEARER_HEADER = "authorization";
60
- /** Default scheme prefix `createBearer` strips before verification. */
61
+ /** Names `'Bearer'`, the default scheme prefix `createBearer` strips before verification. */
61
62
  var DEFAULT_BEARER_SCHEME = "Bearer";
62
- /** Default maximum number of distinct rate-limit keys `createLimiter` tracks before LRU eviction. */
63
+ /** Holds `10_000`, the default maximum number of distinct rate-limit keys `createLimiter` tracks before LRU eviction. */
63
64
  var DEFAULT_LIMITER_CAPACITY = 1e4;
64
- /** Default maximum number of distinct session ids `createMemorySessionStore` tracks before LRU (by last write) eviction. */
65
+ /** Holds `10_000`, the default maximum number of distinct session ids `createMemorySessionStore` tracks before LRU (by last write) eviction. */
65
66
  var DEFAULT_SESSION_CAPACITY = 1e4;
66
- /** Default 429 body message `createLimiter` sends when a key is over budget. */
67
+ /** Holds `'rate limit exceeded'`, the default 429 body message `createLimiter` sends when a key is over budget. */
67
68
  var DEFAULT_LIMITER_MESSAGE = "rate limit exceeded";
68
- /** Default cookie name `createCookieTransport` writes the signed session id under. */
69
+ /** Names `'session'`, the default cookie `createCookieTransport` writes the signed session id under. */
69
70
  var DEFAULT_SESSION_COOKIE = "session";
70
- /** Default header `createHeaderTransport` carries the session id in. */
71
+ /** Names `'session-id'`, the default header `createHeaderTransport` carries the session id in. */
71
72
  var DEFAULT_SESSION_HEADER = "session-id";
72
- /** Default signed-cookie name `createCSRF` writes the CSRF token under. */
73
+ /** Names `'csrf'`, the default signed cookie `createCSRF` writes the CSRF token under. */
73
74
  var DEFAULT_CSRF_COOKIE = "csrf";
74
- /** Default header `createCSRF` reads a mutating request's submitted token from. */
75
+ /** Names `'x-csrf-token'`, the default header `createCSRF` reads a mutating request's submitted token from. */
75
76
  var DEFAULT_CSRF_HEADER = "x-csrf-token";
76
- /** Default body field `createCSRF` falls back to reading a mutating request's submitted token from. */
77
+ /** Names `'_csrf'`, the default body field `createCSRF` falls back to reading a mutating request's submitted token from. */
77
78
  var DEFAULT_CSRF_FIELD = "_csrf";
78
- /** Default methods `createCSRF` treats as safe (mint instead of verify). */
79
+ /** Lists `['GET', 'HEAD', 'OPTIONS']`, the default methods `createCSRF` treats as safe (mint instead of verify). */
79
80
  var DEFAULT_CSRF_SAFE_METHODS = Object.freeze([
80
81
  "GET",
81
82
  "HEAD",
82
83
  "OPTIONS"
83
84
  ]);
84
85
  //#endregion
85
- //#region src/core/Session.ts
86
- /**
87
- * A server-managed session's default entity — the `create` option's default
88
- * value factory for `createSession` (ruling G: `Session` ships WITHOUT a
89
- * `createSession` factory of its own, since that name belongs to the
90
- * battery).
91
- *
92
- * @remarks
93
- * `data` is a live, mutable `Map` a handler reads/writes directly;
94
- * `createSession` persists it to the configured store on the way out.
95
- *
96
- * @example
97
- * ```ts
98
- * const session = new Session('abc123')
99
- * session.data.set('userId', 'u_1')
100
- * ```
101
- */
102
- var Session = class {
103
- id;
104
- data;
105
- constructor(id) {
106
- this.id = id;
107
- this.data = /* @__PURE__ */ new Map();
108
- }
109
- };
110
- //#endregion
111
86
  //#region src/core/helpers.ts
112
87
  /**
113
- * Derive `createLimiter`'s default rate-limit bucket key from a request's
88
+ * Derives `createLimiter`'s default rate-limit bucket key from a request's
114
89
  * resolved identity facts.
115
90
  *
116
91
  * @remarks
117
92
  * Prefers a verified bearer token ({@link BearerState.token}) as
118
93
  * `token:<value>`; else a resolved client IP ({@link ClientState.client.ip},
119
94
  * set when `createForwarded` is mounted) or the raw socket peer
120
- * ({@link ConnectionState.connection.ip}) collapsed via `clientRateKey`
95
+ * ({@link ConnectionState.connection.ip}) collapsed through `computeClientKey`
121
96
  * (IPv6 to its `/64` network) as `ip:<key>`; else the literal `ip:unknown`.
122
97
  * Never reads `X-Forwarded-For` itself — that trust decision belongs solely
123
98
  * to `createForwarded`.
@@ -134,12 +109,12 @@ var Session = class {
134
109
  function resolveKey(state) {
135
110
  if (state.token !== void 0) return `token:${state.token}`;
136
111
  const ip = state.client?.ip ?? state.connection?.ip;
137
- if (ip !== void 0) return `ip:${clientRateKey(ip)}`;
112
+ if (ip !== void 0) return `ip:${computeClientKey(ip)}`;
138
113
  return "ip:unknown";
139
114
  }
140
115
  /**
141
- * Build the `Retry-After` header value — whole seconds until a window reset,
142
- * floored at a minimum of `1` (ruling I).
116
+ * Builds the `Retry-After` header value — whole seconds until a window reset,
117
+ * floored at a minimum of `1`.
143
118
  *
144
119
  * @param resetAt - The window reset instant (same clock unit as `now`)
145
120
  * @param now - The current instant
@@ -155,8 +130,8 @@ function buildRetryAfter(resetAt, now) {
155
130
  return String(Math.max(1, seconds));
156
131
  }
157
132
  /**
158
- * Build the draft `RateLimit` structured header field (ruling I) emitted
159
- * only when `createLimiter`'s `policy` option is `true`.
133
+ * Builds the draft `RateLimit` structured header field emitted only when
134
+ * `createLimiter`'s `policy` option is `true`.
160
135
  *
161
136
  * @param remaining - The requests still admitted this window
162
137
  * @param resetAt - The window reset instant (same clock unit as `now`)
@@ -172,8 +147,8 @@ function buildRateLimitField(remaining, resetAt, now) {
172
147
  return `"default";r=${remaining};t=${Math.max(1, Math.ceil((resetAt - now) / 1e3))}`;
173
148
  }
174
149
  /**
175
- * Build the draft `RateLimit-Policy` structured header field (ruling I)
176
- * emitted only when `createLimiter`'s `policy` option is `true`.
150
+ * Builds the draft `RateLimit-Policy` structured header field emitted only
151
+ * when `createLimiter`'s `policy` option is `true`.
177
152
  *
178
153
  * @param max - The window's admitted request count
179
154
  * @param window - The window length in milliseconds
@@ -188,7 +163,7 @@ function buildRateLimitPolicyField(max, window) {
188
163
  return `"default";q=${max};w=${Math.ceil(window / 1e3)}`;
189
164
  }
190
165
  /**
191
- * Whether a candidate address is a bare (non-CIDR) trusted-hop match — an
166
+ * Checks whether a candidate address is a bare (non-CIDR) trusted-hop match — an
192
167
  * exact string match, or a simple prefix-CIDR match for IPv4 (`/8`–`/32`).
193
168
  * An IPv6 entry matches by exact string only — there is no IPv6 CIDR
194
169
  * support.
@@ -200,14 +175,14 @@ function buildRateLimitPolicyField(max, window) {
200
175
  * never claims full CIDR generality beyond IPv4.
201
176
  *
202
177
  * @remarks
203
- * An IPv6 `trusted` roster entry is compared as an EXACT string — it must be
178
+ * An IPv6 `trusted` roster entry is compared as an exact string — it must be
204
179
  * supplied in canonical form (no zero-compression normalization, no case
205
180
  * folding) by the caller; this function performs no IPv6 normalization of
206
181
  * its own.
207
182
  *
208
183
  * @param address - The candidate hop address
209
184
  * @param entry - One `trusted` roster entry — an exact address or an IPv4 CIDR
210
- * @returns `true` when `address` is covered by `entry`
185
+ * @returns True if `address` is covered by `entry`; false otherwise
211
186
  *
212
187
  * @example
213
188
  * ```ts
@@ -234,13 +209,13 @@ function matchesTrustedEntry(address, entry) {
234
209
  return (networkInt & mask) === (addressInt & mask);
235
210
  }
236
211
  /**
237
- * Walk `X-Forwarded-For` right-to-left and resolve the first UNTRUSTED hop
212
+ * Walks `X-Forwarded-For` right-to-left and resolves the first untrusted hop
238
213
  * address — `createForwarded`'s core algorithm.
239
214
  *
240
215
  * @remarks
241
216
  * Parses `X-Forwarded-For` only. With `proxies` set, trusts exactly that
242
217
  * many hops counted from the right (the closest to this server) and returns
243
- * the next one left of them; with `trusted` set, trusts every CONSECUTIVE
218
+ * the next one left of them; with `trusted` set, trusts every consecutive
244
219
  * hop from the right that matches one of the roster
245
220
  * ({@link matchesTrustedEntry}) and returns the first hop that does not. If
246
221
  * the rightmost hop (the immediate sender) does not match the roster, the
@@ -250,7 +225,7 @@ function matchesTrustedEntry(address, entry) {
250
225
  * socket peer).
251
226
  *
252
227
  * @param header - The raw `X-Forwarded-For` header value (comma-separated hops), if present
253
- * @param trust - Either a trusted hop COUNT or a `trusted` CIDR/exact roster
228
+ * @param trust - The {@link ForwardedOptions} form in force — a trusted hop count or a `trusted` CIDR/exact roster
254
229
  * @returns The first untrusted hop address, or `undefined` when none qualifies
255
230
  *
256
231
  * @example
@@ -276,9 +251,8 @@ function resolveForwardedFor(header, trust) {
276
251
  }
277
252
  }
278
253
  /**
279
- * Feature-detect which of `candidates` the runtime's `CompressionStream`
280
- * actually supports — `createCompression`'s construction-time intersection
281
- * (ruling J).
254
+ * Feature-detects which of `candidates` the runtime's `CompressionStream`
255
+ * actually supports — `createCompression`'s construction-time intersection.
282
256
  *
283
257
  * @remarks
284
258
  * Probes each candidate with `new CompressionStream(candidate)` inside a
@@ -305,7 +279,7 @@ function detectEncodings(candidates) {
305
279
  return supported;
306
280
  }
307
281
  /**
308
- * Compress bytes with the host-independent `CompressionStream` primitive.
282
+ * Compresses bytes with the host-independent `CompressionStream` primitive.
309
283
  *
310
284
  * @param bytes - The uncompressed response bytes
311
285
  * @param encoding - The negotiated actionable coding
@@ -325,21 +299,22 @@ async function compressBytes(bytes, encoding) {
325
299
  return new Uint8Array(compressed);
326
300
  }
327
301
  /**
328
- * Whether a response is eligible for the compression/ETag buffering pipeline
329
- * (ruling J) — the shared cheap-skip predicate both batteries apply before
330
- * ever touching `response.arrayBuffer()`.
302
+ * Checks whether a response must skip the compression and ETag buffering pipeline
303
+ * — the shared cheap-skip predicate both batteries apply before ever touching
304
+ * `response.arrayBuffer()`, true for a `HEAD` request, a `204`/`304` or
305
+ * otherwise bodyless response, an `event-stream` response, and a response
306
+ * already carrying the header the caller is about to set.
331
307
  *
332
308
  * @remarks
333
- * Skips a `HEAD` request, a `204`/`304` or otherwise bodyless response, an
334
- * `event-stream` response (SSE buffering would hang the connection), and a
335
- * response that already carries the header the caller is about to set
336
- * (`skipHeader`, e.g. `Content-Encoding` for compression, `ETag` for the
337
- * ETag battery).
309
+ * Buffering an `event-stream` response would hang the connection, so SSE skips
310
+ * whatever its size. `skipHeader` is the header whose presence already answers
311
+ * the question `Content-Encoding` for compression, `ETag` for the ETag
312
+ * battery.
338
313
  *
339
314
  * @param method - The request's HTTP method
340
315
  * @param response - The candidate response
341
316
  * @param skipHeader - The response header whose presence means "already handled"
342
- * @returns `true` when the response should be left untouched
317
+ * @returns True if the response must be left untouched; false otherwise
343
318
  *
344
319
  * @example
345
320
  * ```ts
@@ -356,11 +331,11 @@ function isBufferingIneligible(method, response, skipHeader) {
356
331
  return false;
357
332
  }
358
333
  /**
359
- * Whether a negotiated `Accept-Encoding` outcome is worth acting on —
360
- * `createCompression`'s negotiation-eligibility half of ruling J's skip list.
334
+ * Checks whether a negotiated `Accept-Encoding` outcome is worth acting on —
335
+ * `createCompression`'s negotiation-eligibility half of the skip list.
361
336
  *
362
337
  * @param encoding - The negotiated coding, or `undefined` when negotiation failed
363
- * @returns `true` when `encoding` names an actionable, non-`identity` coding
338
+ * @returns True if `encoding` names an actionable, non-`identity` coding; false otherwise
364
339
  *
365
340
  * @example
366
341
  * ```ts
@@ -372,8 +347,8 @@ function isCompressionNegotiated(encoding) {
372
347
  return encoding !== void 0 && encoding !== "identity";
373
348
  }
374
349
  /**
375
- * Resolve an opt-in, value-bearing security header — `string | boolean`
376
- * (default OFF, `true` uses the secure default), the shape `createSecurity`'s
350
+ * Resolves an opt-in, value-bearing security header — `string | boolean`
351
+ * (off by default, `true` uses the secure default), the shape `createSecurity`'s
377
352
  * `coep`/`hsts` options use, distinct from the plain value-or-`false` shape
378
353
  * `resolveSecurityHeader` (the peer substrate) handles.
379
354
  *
@@ -393,7 +368,7 @@ function resolveOptInHeader(value, fallback) {
393
368
  return value;
394
369
  }
395
370
  /**
396
- * Rebuild a `Response` around a replacement body while preserving its
371
+ * Rebuilds a `Response` around a replacement body while preserving its
397
372
  * status/statusText — the buffered-response reconstruction shared by the
398
373
  * compression and ETag batteries after they have consumed
399
374
  * `response.arrayBuffer()`.
@@ -417,7 +392,7 @@ function rebuildResponse(body, response, headers) {
417
392
  });
418
393
  }
419
394
  /**
420
- * The shared negotiate → skip → threshold → compress → header-set skeleton
395
+ * Runs the shared negotiate → skip → threshold → compress → header-set skeleton
421
396
  * both faces' `createCompression` batteries compose — response-body
422
397
  * compression over a caller-supplied set of feature-detected codings.
423
398
  *
@@ -427,17 +402,17 @@ function rebuildResponse(body, response, headers) {
427
402
  * even when a later skip declines to compress) → `negotiateEncoding` over
428
403
  * `options.encodings` → {@link isCompressionNegotiated} → `isCompressibleType`
429
404
  * on `Content-Type` → a fast skip when the response already carries a
430
- * numeric `Content-Length` BELOW `options.threshold` (avoids buffering a
431
- * body known too small to be worth compressing) → buffer via
405
+ * numeric `Content-Length` below `options.threshold` (avoids buffering a
406
+ * body known too small to be worth compressing) → buffer through
432
407
  * `response.arrayBuffer()` → a threshold passthrough when the buffered size
433
408
  * is still below `options.threshold` → `options.compress` → set
434
- * `Content-Encoding` and a fresh `Content-Length` via {@link rebuildResponse}.
409
+ * `Content-Encoding` and a fresh `Content-Length` through {@link rebuildResponse}.
435
410
  * Returns `response` unchanged (aside from the `Vary` stamp) on any skip.
436
411
  *
437
412
  * @param request - The inbound `Request` (read for `Accept-Encoding`)
438
413
  * @param context - The `MiddlewareContext` (read for `context.method`)
439
414
  * @param response - The downstream `Response` to consider compressing
440
- * @param options - The threshold, optional filter, offered encodings, and the runtime's `compress` primitive
415
+ * @param options - See {@link CompressResponseOptions}
441
416
  * @returns The original `response` when skipped, or a new compressed `Response`
442
417
  *
443
418
  * @example
@@ -473,99 +448,28 @@ async function compressResponse(request, context, response, options) {
473
448
  return rebuildResponse(compressed, response, headers);
474
449
  }
475
450
  /**
476
- * Copy every entry of one session's `data` into another — the regenerate
477
- * data-carry `createSession`'s `control.regenerate()` applies (ruling D).
478
- *
479
- * @param from - The source session whose `data` is copied
480
- * @param to - The destination session `data` is copied into
481
- *
482
- * @example
483
- * ```ts
484
- * transferSessionData(oldSession, newSession)
485
- * ```
486
- */
487
- function transferSessionData(from, to) {
488
- for (const [key, value] of from.data) to.data.set(key, value);
489
- }
490
- /**
491
- * Determine whether a value implements {@link SessionInterface} — a total
492
- * structural guard (§14): an `id` string plus a `data` `Map`. Prototype-agnostic
493
- * — accepts a plain object, a null-prototype object, AND a class instance
494
- * (a real `Session`), since a restored/stored session is routinely a class
495
- * instance, not a literal.
496
- *
497
- * @param value - The candidate value
498
- * @returns `true` when `value` is shaped like a {@link SessionInterface}
499
- *
500
- * @example
501
- * ```ts
502
- * isSession({ id: 'a', data: new Map() }) // true
503
- * isSession(new Session('a')) // true
504
- * ```
505
- */
506
- function isSession(value) {
507
- if (typeof value !== "object" || value === null) return false;
508
- const id = Reflect.get(value, "id");
509
- const data = Reflect.get(value, "data");
510
- return isString(id) && data instanceof Map;
511
- }
512
- /**
513
- * Determine whether a value implements {@link SessionControlInterface} — a
514
- * total structural guard (§14): callable `regenerate` and `destroy`.
515
- *
516
- * @param value - The candidate value
517
- * @returns `true` when `value` is shaped like a {@link SessionControlInterface}
518
- *
519
- * @example
520
- * ```ts
521
- * isSessionControl({ regenerate() {}, destroy() {} }) // true
522
- * ```
523
- */
524
- function isSessionControl(value) {
525
- if (!isRecord(value)) return false;
526
- return typeof value.regenerate === "function" && typeof value.destroy === "function";
527
- }
528
- /**
529
- * Determine whether a value is one staged {@link MultipartFile} record — a
530
- * total structural guard (§14) checking every required field's shape.
531
- *
532
- * @param value - The candidate value
533
- * @returns `true` when `value` is shaped like a {@link MultipartFile}
534
- */
535
- function isMultipartFile(value) {
536
- if (!isRecord(value)) return false;
537
- return isString(value.field) && isString(value.name) && typeof value.size === "number" && isString(value.mime) && typeof value.validated === "boolean" && isString(value.status) && isString(value.path);
538
- }
539
- /**
540
- * Determine whether a value implements {@link MultipartBody} — a total
541
- * structural guard (§14): `files` keyed by field name to arrays of
542
- * {@link MultipartFile}, and a `fields` string record.
451
+ * Copies every entry of one session's `state` into another — the regenerate
452
+ * state-carry `createSession`'s `control.regenerate()` applies.
543
453
  *
544
- * @param value - The candidate value
545
- * @returns `true` when `value` is shaped like a {@link MultipartBody}
454
+ * @param from - The source session whose `state` is copied
455
+ * @param to - The destination session the entries are written into, through
456
+ * its own `set` mutator
546
457
  *
547
458
  * @example
548
459
  * ```ts
549
- * isMultipartBody({ files: {}, fields: { name: 'a' } }) // true
460
+ * transferSessionState(oldSession, newSession)
550
461
  * ```
551
462
  */
552
- function isMultipartBody(value) {
553
- if (!isRecord(value)) return false;
554
- if (!isRecord(value.files) || !isRecord(value.fields)) return false;
555
- for (const entries of Object.values(value.files)) {
556
- if (!Array.isArray(entries)) return false;
557
- for (const entry of entries) if (!isMultipartFile(entry)) return false;
558
- }
559
- for (const fieldValue of Object.values(value.fields)) if (!isString(fieldValue)) return false;
560
- return true;
463
+ function transferSessionState(from, to) {
464
+ for (const [key, value] of from.state) to.set(key, value);
561
465
  }
562
466
  /**
563
- * Determine whether a request is a CORS PREFLIGHT — an `OPTIONS` request
467
+ * Determines whether a request is a CORS preflight — an `OPTIONS` request
564
468
  * carrying an `Access-Control-Request-Method` header.
565
469
  *
566
470
  * @param method - The request's HTTP method
567
471
  * @param headers - The request's `Headers`
568
- * @returns `true` when the request is a CORS preflight `createCors` must answer
472
+ * @returns True if the request is a CORS preflight `createCors` must answer; false otherwise
569
473
  *
570
474
  * @example
571
475
  * ```ts
@@ -576,22 +480,22 @@ function isPreflight(method, headers) {
576
480
  return method === "OPTIONS" && headers.has("access-control-request-method");
577
481
  }
578
482
  /**
579
- * A parsed client-info fact for {@link ClientInfo} a leaf shaping helper
580
- * `createForwarded` uses to build its stashed slice.
483
+ * Builds the {@link Client} slice `createForwarded` stashes, from the
484
+ * resolved client IP a leaf shaping helper.
581
485
  *
582
486
  * @param ip - The resolved client IP, if any
583
- * @returns The {@link ClientInfo} slice value
487
+ * @returns The {@link Client} slice value
584
488
  *
585
489
  * @example
586
490
  * ```ts
587
- * buildClientInfo('203.0.113.7') // { ip: '203.0.113.7' }
491
+ * buildClient('203.0.113.7') // { ip: '203.0.113.7' }
588
492
  * ```
589
493
  */
590
- function buildClientInfo(ip) {
494
+ function buildClient(ip) {
591
495
  return { ...ip !== void 0 ? { ip } : {} };
592
496
  }
593
497
  /**
594
- * Constant-time string equality — `createCSRF`'s double-submit token
498
+ * Compares two strings in constant time — `createCSRF`'s double-submit token
595
499
  * comparison, avoiding a timing oracle on the submitted-vs-cookie match.
596
500
  *
597
501
  * @remarks
@@ -603,7 +507,7 @@ function buildClientInfo(ip) {
603
507
  *
604
508
  * @param a - The first string
605
509
  * @param b - The second string
606
- * @returns `true` when `a` and `b` are exactly equal
510
+ * @returns True if `a` and `b` are exactly equal; false otherwise
607
511
  *
608
512
  * @example
609
513
  * ```ts
@@ -618,81 +522,173 @@ function equalsConstantTime(a, b) {
618
522
  return diff === 0;
619
523
  }
620
524
  /**
621
- * Whether a session has aged past its idle timeout or absolute lifetime as
525
+ * Checks whether a session has aged past its idle timeout or absolute lifetime as
622
526
  * of `now` — the pure expiry predicate `MemorySessionStore` delegates to.
623
527
  *
624
- * @param cursors - The session's `lastSeen` (idle) and `createdAt` (absolute) instants
528
+ * @param cursors - See {@link SessionCursors}
625
529
  * @param now - The current instant (same clock unit as `cursors`)
626
- * @param limits - The optional `ttl` (idle) and `lifetime` (absolute) thresholds
627
- * @returns `true` when either configured threshold has elapsed
530
+ * @param limits - See {@link SessionLimits}
531
+ * @returns True if either configured threshold has elapsed; false otherwise
628
532
  *
629
533
  * @example
630
534
  * ```ts
631
- * sessionExpired({ lastSeen: 0, createdAt: 0 }, 1_000, { ttl: 500 }) // true
535
+ * sessionExpired({ seen: 0, created: 0 }, 1_000, { ttl: 500 }) // true
632
536
  * ```
633
537
  */
634
538
  function sessionExpired(cursors, now, limits) {
635
- if (limits.ttl !== void 0 && now - cursors.lastSeen >= limits.ttl) return true;
636
- if (limits.lifetime !== void 0 && now - cursors.createdAt >= limits.lifetime) return true;
539
+ if (limits.ttl !== void 0 && now - cursors.seen >= limits.ttl) return true;
540
+ if (limits.lifetime !== void 0 && now - cursors.created >= limits.lifetime) return true;
637
541
  return false;
638
542
  }
639
543
  /**
640
- * Snapshot a session's `data` Map into a plain, serializable record — the
544
+ * Validates a store's idle and absolute-lifetime thresholds, throwing when
545
+ * either is present and malformed — the shared construction gate
546
+ * {@link MemorySessionStore} and {@link DatabaseSessionStore} both apply, so
547
+ * one malformed `ttl` is refused identically by whichever store receives it.
548
+ *
549
+ * @param limits - See {@link SessionLimits}
550
+ * @returns Nothing; a successful return means both thresholds are usable
551
+ * @throws {TypeError} Thrown when `ttl` or `lifetime` is present and is not a
552
+ * positive finite number
553
+ *
554
+ * @example
555
+ * ```ts
556
+ * validateSessionLimits({ ttl: 60_000 }) // returns; the thresholds are usable
557
+ * ```
558
+ */
559
+ function validateSessionLimits(limits) {
560
+ if (limits?.ttl !== void 0 && (!isFiniteNumber(limits.ttl) || limits.ttl <= 0)) throw new TypeError("SessionLimits.ttl must be a positive finite number when provided");
561
+ if (limits?.lifetime !== void 0 && (!isFiniteNumber(limits.lifetime) || limits.lifetime <= 0)) throw new TypeError("SessionLimits.lifetime must be a positive finite number when provided");
562
+ }
563
+ /**
564
+ * Snapshots a session's `state` into a plain, serializable record — the
641
565
  * projection a durable store's `set` writes to disk.
642
566
  *
643
567
  * @param session - The session to snapshot
644
- * @returns A plain-object copy of `session.data`, keyed alongside `session.id`
568
+ * @returns A {@link SessionSnapshot} whose `state` is a plain-object copy of
569
+ * `session.state`, keyed alongside `session.id`
645
570
  *
646
571
  * @remarks
647
- * `data` is built on a null-prototype object (`Object.create(null)`), never
572
+ * `state` is built on a null-prototype object (`Object.create(null)`), never
648
573
  * a `{}` literal — a session key literally named `__proto__` must round-trip
649
- * as an OWN enumerable property instead of hitting `Object.prototype`'s
574
+ * as an own enumerable property instead of hitting `Object.prototype`'s
650
575
  * `__proto__` accessor (which would silently drop the entry and risk
651
576
  * polluting the shared prototype).
652
577
  *
653
578
  * @example
654
579
  * ```ts
655
- * snapshotSession(session) // { id: 'abc', data: { userId: 'u_1' } }
580
+ * snapshotSession(session) // { id: 'abc', state: { userId: 'u_1' } }
656
581
  * ```
657
582
  */
658
583
  function snapshotSession(session) {
659
- const data = Object.create(null);
660
- for (const [key, value] of session.data) data[key] = value;
584
+ const state = Object.create(null);
585
+ for (const [key, value] of session.state) state[key] = value;
661
586
  return {
662
587
  id: session.id,
663
- data
588
+ state
664
589
  };
665
590
  }
591
+ //#endregion
592
+ //#region src/core/validators.ts
666
593
  /**
667
- * Rebuild a `Session` from an untrusted snapshot value (the inverse of
668
- * {@link snapshotSession}) a durable store's `get` deserialization step.
594
+ * Determines whether a value implements {@link SessionInterface} a total
595
+ * structural guard: an `id` string, a `state` `Map`, and the `set`, `delete`,
596
+ * and `clear` mutators. Prototype-agnostic — accepts a plain object, a
597
+ * null-prototype object, and a class instance (a real `Session`), because a
598
+ * restored or stored session is routinely a class instance rather than a literal.
669
599
  *
670
- * @param value - The candidate snapshot, of unknown shape
671
- * @returns A rebuilt `Session`, or `undefined` when `value` is malformed
600
+ * @param value - The candidate value
601
+ * @returns True if `value` is shaped like a {@link SessionInterface}; false otherwise
672
602
  *
673
603
  * @example
674
604
  * ```ts
675
- * restoreSession({ id: 'abc', data: { userId: 'u_1' } }) // Session { id: 'abc', data: Map }
676
- * restoreSession({ id: 1 }) // undefined
605
+ * isSession(new Session('a')) // true
606
+ * isSession({ id: 'a', state: new Map() }) // false — the mutators are missing
677
607
  * ```
678
608
  */
679
- function restoreSession(value) {
680
- if (!isRecord(value)) return void 0;
681
- if (!isString(value.id) || !isRecord(value.data)) return void 0;
682
- const session = new Session(value.id);
683
- for (const [key, entry] of Object.entries(value.data)) session.data.set(key, entry);
684
- return session;
609
+ function isSession(value) {
610
+ if (typeof value !== "object" || value === null) return false;
611
+ const id = Reflect.get(value, "id");
612
+ const state = Reflect.get(value, "state");
613
+ if (!isString(id) || !(state instanceof Map)) return false;
614
+ const set = Reflect.get(value, "set");
615
+ const remove = Reflect.get(value, "delete");
616
+ const clear = Reflect.get(value, "clear");
617
+ return typeof set === "function" && typeof remove === "function" && typeof clear === "function";
618
+ }
619
+ /**
620
+ * Determines whether a value implements {@link SessionControlInterface} — a
621
+ * total structural guard: callable `regenerate` and `destroy`.
622
+ *
623
+ * @param value - The candidate value
624
+ * @returns True if `value` is shaped like a {@link SessionControlInterface}; false otherwise
625
+ *
626
+ * @example
627
+ * ```ts
628
+ * isSessionControl({ regenerate() {}, destroy() {} }) // true
629
+ * ```
630
+ */
631
+ function isSessionControl(value) {
632
+ if (!isRecord(value)) return false;
633
+ return typeof value.regenerate === "function" && typeof value.destroy === "function";
634
+ }
635
+ /**
636
+ * Determines whether a value is one staged {@link MultipartFile} record — a
637
+ * total structural guard checking every required field's shape.
638
+ *
639
+ * @param value - The candidate value
640
+ * @returns True if `value` is shaped like a {@link MultipartFile}; false otherwise
641
+ *
642
+ * @example
643
+ * ```ts
644
+ * isMultipartFile({
645
+ * field: 'avatar',
646
+ * name: 'a.png',
647
+ * size: 3,
648
+ * mime: 'image/png',
649
+ * validated: true,
650
+ * status: 'staged',
651
+ * path: '/tmp/a',
652
+ * }) // true
653
+ * ```
654
+ */
655
+ function isMultipartFile(value) {
656
+ if (!isRecord(value)) return false;
657
+ return isString(value.field) && isString(value.name) && typeof value.size === "number" && isString(value.mime) && typeof value.validated === "boolean" && isString(value.status) && isString(value.path);
658
+ }
659
+ /**
660
+ * Determines whether a value implements {@link MultipartBody} — a total
661
+ * structural guard: `files` keyed by field name to arrays of
662
+ * {@link MultipartFile}, and a `fields` string record.
663
+ *
664
+ * @param value - The candidate value
665
+ * @returns True if `value` is shaped like a {@link MultipartBody}; false otherwise
666
+ *
667
+ * @example
668
+ * ```ts
669
+ * isMultipartBody({ files: {}, fields: { name: 'a' } }) // true
670
+ * ```
671
+ */
672
+ function isMultipartBody(value) {
673
+ if (!isRecord(value)) return false;
674
+ if (!isRecord(value.files) || !isRecord(value.fields)) return false;
675
+ for (const entries of Object.values(value.files)) {
676
+ if (!Array.isArray(entries)) return false;
677
+ for (const entry of entries) if (!isMultipartFile(entry)) return false;
678
+ }
679
+ for (const fieldValue of Object.values(value.fields)) if (!isString(fieldValue)) return false;
680
+ return true;
685
681
  }
686
682
  //#endregion
687
683
  //#region src/core/shapers.ts
688
684
  /**
689
- * The `@orkestrel/database` column shape for a
690
- * {@link import('./types.js').SessionRow} table pass as-is to
685
+ * Holds the `@orkestrel/database` column shape for a
686
+ * {@link import('./types.js').SessionRow} table. Pass it as-is to
691
687
  * `createDatabase({ tables: { sessions: sessionColumns } })` so an app
692
688
  * declaring a durable session table never hand-writes the shape.
693
689
  *
694
690
  * @remarks
695
- * `lastSeen`/`createdAt` are `integerShape({ min: 0 })` — the table validates
691
+ * `seen`/`created` are `integerShape({ min: 0 })` — the table validates
696
692
  * them as integers, so
697
693
  * {@link import('./stores/DatabaseSessionStore.js').DatabaseSessionStore}'s
698
694
  * `now` clock must yield integer milliseconds (`Date.now()`, the implicit
@@ -708,24 +704,67 @@ function restoreSession(value) {
708
704
  var sessionColumns = {
709
705
  id: stringShape(),
710
706
  session: jsonShape(),
711
- lastSeen: integerShape({ min: 0 }),
712
- createdAt: integerShape({ min: 0 })
707
+ seen: integerShape({ min: 0 }),
708
+ created: integerShape({ min: 0 })
709
+ };
710
+ //#endregion
711
+ //#region src/core/Session.ts
712
+ /**
713
+ * Represents a server-managed session's default entity — the `create` option's default
714
+ * value for `createSession`. It ships without a bare `create*` factory of its
715
+ * own, because the name `createSession` belongs to the battery;
716
+ * `createRestoredSession` rebuilds one from a stored snapshot.
717
+ *
718
+ * @remarks
719
+ * `state` is a `ReadonlyMap` view over the entity's own `Map`: TypeScript
720
+ * refuses a write through it, and `set`, `delete`, and `clear` are the write
721
+ * path. `createSession` persists the state to the configured store on the way
722
+ * out.
723
+ *
724
+ * @example
725
+ * ```ts
726
+ * const session = new Session('abc123')
727
+ * session.set('userId', 'u_1')
728
+ * ```
729
+ */
730
+ var Session = class {
731
+ #id;
732
+ #state;
733
+ constructor(id) {
734
+ this.#id = id;
735
+ this.#state = /* @__PURE__ */ new Map();
736
+ }
737
+ get id() {
738
+ return this.#id;
739
+ }
740
+ get state() {
741
+ return this.#state;
742
+ }
743
+ set(key, value) {
744
+ this.#state.set(key, value);
745
+ }
746
+ delete(key) {
747
+ return this.#state.delete(key);
748
+ }
749
+ clear() {
750
+ this.#state.clear();
751
+ }
713
752
  };
714
753
  //#endregion
715
754
  //#region src/core/stores/MemorySessionStore.ts
716
755
  /**
717
- * The default in-process {@link SessionStoreInterface} — a `Map`-backed store
756
+ * Implements the default in-process {@link SessionStoreInterface} — a `Map`-backed store
718
757
  * enforcing both an idle timeout and an absolute lifetime, with lazy
719
758
  * (read-time) eviction, a bounded capacity, and no background timers.
720
759
  *
721
- * @typeParam S - The session data payload type
760
+ * @typeParam S - The stored session entity type
722
761
  *
723
762
  * @remarks
724
- * `get` evicts a session whose idle time (`now - lastSeen >= ttl`) or
725
- * absolute lifetime (`now - createdAt >= lifetime`) has elapsed — the
726
- * lifetime check fires EVEN IF the session was continuously touched, since
727
- * `createdAt` is stamped once at the first `set` and preserved across every
728
- * later re-`set` of the same id. A live read touches `lastSeen`. `delete` of
763
+ * `get` evicts a session whose idle time (`now - seen >= ttl`) or
764
+ * absolute lifetime (`now - created >= lifetime`) has elapsed — the
765
+ * lifetime check fires even if the session was continuously touched, because
766
+ * `created` is stamped once at the first `set` and preserved across every
767
+ * later re-`set` of the same id. A live read touches `seen`. `delete` of
729
768
  * an absent id is a no-op.
730
769
  *
731
770
  * Capacity is enforced as least-recently-used **by last write**: `set`
@@ -740,7 +779,7 @@ var sessionColumns = {
740
779
  * @example
741
780
  * ```ts
742
781
  * const store = new MemorySessionStore({ ttl: 60_000, lifetime: 3_600_000 })
743
- * await store.set('abc', { userId: 'u_1' }, Date.now())
782
+ * await store.set(new Session('abc'), Date.now())
744
783
  * ```
745
784
  */
746
785
  var MemorySessionStore = class {
@@ -750,10 +789,7 @@ var MemorySessionStore = class {
750
789
  #capacity;
751
790
  #evict;
752
791
  constructor(options) {
753
- if (options?.ttl !== void 0 && !isFiniteNumber(options.ttl)) throw new TypeError("MemorySessionStore requires options.ttl to be a finite number when provided");
754
- if (options?.ttl !== void 0 && options.ttl <= 0) throw new TypeError("MemorySessionStore requires options.ttl to be positive when provided");
755
- if (options?.lifetime !== void 0 && !isFiniteNumber(options.lifetime)) throw new TypeError("MemorySessionStore requires options.lifetime to be a finite number when provided");
756
- if (options?.lifetime !== void 0 && options.lifetime <= 0) throw new TypeError("MemorySessionStore requires options.lifetime to be positive when provided");
792
+ validateSessionLimits(options);
757
793
  if (options?.capacity !== void 0 && (!isFiniteNumber(options.capacity) || !Number.isInteger(options.capacity) || options.capacity <= 0)) throw new TypeError("MemorySessionStore requires options.capacity to be a positive integer when provided");
758
794
  if (options?.evict !== void 0 && !isFunction(options.evict)) throw new TypeError("MemorySessionStore requires options.evict to be a function when provided");
759
795
  this.#entries = /* @__PURE__ */ new Map();
@@ -770,18 +806,23 @@ var MemorySessionStore = class {
770
806
  this.#notify(id);
771
807
  return;
772
808
  }
773
- entry.lastSeen = now;
809
+ this.#entries.set(id, {
810
+ session: entry.session,
811
+ seen: now,
812
+ created: entry.created
813
+ });
774
814
  return entry.session;
775
815
  }
776
- async set(id, session, now) {
816
+ async set(session, now) {
817
+ const id = session.id;
777
818
  const existing = this.#entries.get(id);
778
819
  if (existing === void 0) this.#reserve(now);
779
- const createdAt = existing?.createdAt ?? now;
820
+ const created = existing?.created ?? now;
780
821
  this.#entries.delete(id);
781
822
  this.#entries.set(id, {
782
823
  session,
783
- lastSeen: now,
784
- createdAt
824
+ seen: now,
825
+ created
785
826
  });
786
827
  }
787
828
  async delete(id) {
@@ -789,8 +830,8 @@ var MemorySessionStore = class {
789
830
  }
790
831
  #expired(entry, now) {
791
832
  return sessionExpired(entry, now, {
792
- ...this.#ttl !== void 0 ? { ttl: this.#ttl } : {},
793
- ...this.#lifetime !== void 0 ? { lifetime: this.#lifetime } : {}
833
+ ttl: this.#ttl,
834
+ lifetime: this.#lifetime
794
835
  });
795
836
  }
796
837
  #reserve(now) {
@@ -817,26 +858,28 @@ var MemorySessionStore = class {
817
858
  //#endregion
818
859
  //#region src/core/stores/DatabaseSessionStore.ts
819
860
  /**
820
- * A durable {@link SessionStoreInterface} over an `@orkestrel/database`
861
+ * Implements a durable {@link SessionStoreInterface} over an `@orkestrel/database`
821
862
  * table — the same idle-timeout + absolute-lifetime contract as
822
863
  * {@link MemorySessionStore}, backed by a caller-supplied `TableInterface`
823
864
  * instead of an in-process `Map`.
824
865
  *
825
- * @typeParam S - The session data payload type
866
+ * @typeParam S - The stored session entity type
826
867
  *
827
868
  * @remarks
828
869
  * `get` reads the row, evicts (removes the row) once `sessionExpired`
829
- * reports either threshold elapsed, then rebuilds the session via
830
- * {@link restoreSession} — a malformed snapshot or one that fails the
831
- * caller's `is` guard resolves `undefined` rather than throwing. A live read
832
- * touches `lastSeen`. `set` preserves an existing row's `createdAt` across a
870
+ * reports either threshold elapsed, then rebuilds the session through the
871
+ * `restore` step it was constructed with — a malformed snapshot or one that
872
+ * fails the caller's guard resolves `undefined` rather than throwing.
873
+ * `createDatabaseSessionStore` supplies `createRestoredSession` as that step,
874
+ * so a caller reaching the factory never states it. A live read
875
+ * touches `seen`. `set` preserves an existing row's `created` across a
833
876
  * re-`set` of the same id (stamped once at the first `set`), mirroring
834
877
  * {@link MemorySessionStore}. `delete` of an absent id is a no-op (the
835
878
  * table's `remove` contract).
836
879
  *
837
- * A malformed-snapshot or failed-guard `undefined` LEAVES the row in place —
880
+ * A malformed-snapshot or failed-guard `undefined` leaves the row in place —
838
881
  * unlike the expired path, which removes it. This is deliberate: a
839
- * caller-contextual `is` guard may reject a session that is still perfectly
882
+ * caller-contextual guard may reject a session that is still perfectly
840
883
  * valid for another flow reading the same table (a differently-shaped `S`,
841
884
  * a stricter guard mid-rollout), so `get` never destroys data on a guard
842
885
  * miss. A row that no caller's guard ever accepts again self-heals once its
@@ -844,18 +887,23 @@ var MemorySessionStore = class {
844
887
  *
845
888
  * @example
846
889
  * ```ts
847
- * const store = new DatabaseSessionStore(table, isSession, { ttl: 60_000 })
848
- * await store.set('abc', new Session('abc'), Date.now())
890
+ * const store = new DatabaseSessionStore(table, isSession, createRestoredSession, {
891
+ * ttl: 60_000,
892
+ * })
893
+ * await store.set(new Session('abc'), Date.now())
849
894
  * ```
850
895
  */
851
896
  var DatabaseSessionStore = class {
852
897
  #table;
853
- #is;
898
+ #guard;
899
+ #restore;
854
900
  #ttl;
855
901
  #lifetime;
856
- constructor(table, is, options) {
902
+ constructor(table, guard, restore, options) {
903
+ validateSessionLimits(options);
857
904
  this.#table = table;
858
- this.#is = is;
905
+ this.#guard = guard;
906
+ this.#restore = restore;
859
907
  this.#ttl = options?.ttl;
860
908
  this.#lifetime = options?.lifetime;
861
909
  }
@@ -863,24 +911,25 @@ var DatabaseSessionStore = class {
863
911
  const row = await this.#table.get(id);
864
912
  if (row === void 0) return void 0;
865
913
  if (sessionExpired(row, now, {
866
- ...this.#ttl !== void 0 ? { ttl: this.#ttl } : {},
867
- ...this.#lifetime !== void 0 ? { lifetime: this.#lifetime } : {}
914
+ ttl: this.#ttl,
915
+ lifetime: this.#lifetime
868
916
  })) {
869
917
  await this.#table.remove(id);
870
918
  return;
871
919
  }
872
- const session = restoreSession(row.session);
873
- if (session === void 0 || !this.#is(session)) return void 0;
874
- await this.#table.update(id, { lastSeen: now });
920
+ const session = this.#restore(row.session);
921
+ if (session === void 0 || !this.#guard(session)) return void 0;
922
+ await this.#table.update(id, { seen: now });
875
923
  return session;
876
924
  }
877
- async set(id, session, now) {
878
- const createdAt = (await this.#table.get(id))?.createdAt ?? now;
925
+ async set(session, now) {
926
+ const id = session.id;
927
+ const created = (await this.#table.get(id))?.created ?? now;
879
928
  await this.#table.set({
880
929
  id,
881
930
  session: snapshotSession(session),
882
- lastSeen: now,
883
- createdAt
931
+ seen: now,
932
+ created
884
933
  });
885
934
  }
886
935
  async delete(id) {
@@ -890,7 +939,7 @@ var DatabaseSessionStore = class {
890
939
  //#endregion
891
940
  //#region src/core/middlewares.ts
892
941
  /**
893
- * The outermost error-rendering battery — catches a downstream throw and
942
+ * Creates the outermost error-rendering battery — catches a downstream throw and
894
943
  * renders it as a `Response`.
895
944
  *
896
945
  * @typeParam TState - The consumer's opaque per-request state type
@@ -898,9 +947,20 @@ var DatabaseSessionStore = class {
898
947
  * @returns A `MiddlewareHandler<TState>`
899
948
  * @throws {TypeError} When `options.expose` or `options.report` is malformed
900
949
  *
901
- * @example
950
+ * @example Mount a battery
902
951
  * ```ts
952
+ * import { createBoundary, createSecurity } from '@orkestrel/middleware'
953
+ * import type { IdentifierState } from '@orkestrel/middleware'
954
+ * import { compose } from '@orkestrel/server'
955
+ *
956
+ * interface State extends IdentifierState {}
957
+ *
903
958
  * const boundary = createBoundary({ expose: false })
959
+ * const security = createSecurity({ hsts: true })
960
+ *
961
+ * const handle = compose<State>([boundary, security], async (_request, context) => {
962
+ * return Response.json({ identifier: context.state.identifier })
963
+ * })
904
964
  * ```
905
965
  */
906
966
  function createBoundary(options) {
@@ -922,7 +982,7 @@ function createBoundary(options) {
922
982
  };
923
983
  }
924
984
  /**
925
- * The access-log/timing seam — records one {@link TelemetryEntry} per request
985
+ * Creates the access-log/timing seam — records one {@link TelemetryEntry} per request
926
986
  * after the response settles.
927
987
  *
928
988
  * @typeParam TState - The consumer's opaque per-request state type
@@ -959,7 +1019,7 @@ function createTelemetry(options) {
959
1019
  };
960
1020
  }
961
1021
  /**
962
- * Response-body compression — negotiates and compresses a buffered response
1022
+ * Creates the response-body compression battery — negotiates and compresses a buffered response
963
1023
  * body over the runtime's feature-detected `CompressionStream` codings.
964
1024
  *
965
1025
  * @typeParam TState - The consumer's opaque per-request state type
@@ -989,7 +1049,8 @@ function createCompression(options) {
989
1049
  };
990
1050
  }
991
1051
  /**
992
- * Security-headers + request-identifier battery.
1052
+ * Creates the security-headers + request-identifier battery — sets each documented
1053
+ * header default, and mints or echoes a request identifier.
993
1054
  *
994
1055
  * @typeParam TState - The consumer's opaque per-request state type, must carry {@link IdentifierState}
995
1056
  * @param options - See {@link SecurityOptions}
@@ -1051,7 +1112,8 @@ function createSecurity(options) {
1051
1112
  };
1052
1113
  }
1053
1114
  /**
1054
- * Cross-Origin Resource Sharing battery.
1115
+ * Creates the Cross-Origin Resource Sharing battery — answers a preflight itself, and
1116
+ * reflects an allow-listed origin or serves the configured wildcard.
1055
1117
  *
1056
1118
  * @typeParam TState - The consumer's opaque per-request state type
1057
1119
  * @param options - See {@link CorsOptions}
@@ -1092,7 +1154,7 @@ function createCors(options) {
1092
1154
  };
1093
1155
  }
1094
1156
  /**
1095
- * The application-level per-request deadline battery.
1157
+ * Creates the application-level per-request deadline battery.
1096
1158
  *
1097
1159
  * @typeParam TState - The consumer's opaque per-request state type
1098
1160
  * @param options - See {@link DeadlineOptions}
@@ -1100,9 +1162,9 @@ function createCors(options) {
1100
1162
  * @throws {TypeError} When `options.ms` or `options.status` is malformed
1101
1163
  *
1102
1164
  * @remarks
1103
- * MUST sit OUTSIDE `createBody` in the chain — it reconstructs the inbound
1165
+ * Mount this battery outside `createBody` in the chain — it reconstructs the inbound
1104
1166
  * `Request` (to link its `signal` to the deadline `signal`), which throws if
1105
- * the body was already consumed upstream (e.g. by `createBody`'s cached read).
1167
+ * the body was already consumed upstream (for example by `createBody`'s cached read).
1106
1168
  *
1107
1169
  * @example
1108
1170
  * ```ts
@@ -1137,7 +1199,8 @@ function createDeadline(options) {
1137
1199
  };
1138
1200
  }
1139
1201
  /**
1140
- * The trusted-proxy client-IP resolver battery.
1202
+ * Creates the trusted-proxy client-IP resolver battery — walks `X-Forwarded-For` past
1203
+ * the hops its options declare trusted.
1141
1204
  *
1142
1205
  * @typeParam TState - The consumer's opaque per-request state type, must carry {@link ClientState} and {@link ConnectionState}
1143
1206
  * @param options - See {@link ForwardedOptions}
@@ -1163,12 +1226,12 @@ function createForwarded(options) {
1163
1226
  const trust = hasProxies ? { proxies: options.proxies } : { trusted: options.trusted };
1164
1227
  return async (request, context, next) => {
1165
1228
  const ip = resolveForwardedFor(request.headers.get("x-forwarded-for") ?? void 0, trust) ?? context.state.connection?.ip;
1166
- Object.assign(context.state, { client: buildClientInfo(ip) });
1229
+ Object.assign(context.state, { client: buildClient(ip) });
1167
1230
  return next();
1168
1231
  };
1169
1232
  }
1170
1233
  /**
1171
- * Dynamic response `ETag` + conditional GET battery.
1234
+ * Creates the dynamic response `ETag` + conditional GET battery (RFC 7232).
1172
1235
  *
1173
1236
  * @typeParam TState - The consumer's opaque per-request state type
1174
1237
  * @param options - See {@link ETagOptions}
@@ -1206,7 +1269,8 @@ function createETag(options) {
1206
1269
  };
1207
1270
  }
1208
1271
  /**
1209
- * Bearer-token authentication battery.
1272
+ * Creates the bearer-token authentication battery — reads the token from its header and
1273
+ * verifies it with `verifyToken`.
1210
1274
  *
1211
1275
  * @typeParam TState - The consumer's opaque per-request state type, must carry {@link BearerState}
1212
1276
  * @param options - See {@link BearerOptions}
@@ -1242,7 +1306,8 @@ function createBearer(options) {
1242
1306
  };
1243
1307
  }
1244
1308
  /**
1245
- * Fixed-window rate-limiting battery.
1309
+ * Creates the fixed-window rate-limiting battery — checks a key's budget before
1310
+ * consuming it, so one window admits exactly `max` requests.
1246
1311
  *
1247
1312
  * @typeParam TState - The consumer's opaque per-request state type, must carry {@link BearerState}, {@link ClientState}, and {@link ConnectionState}
1248
1313
  * @param options - See {@link LimiterOptions}
@@ -1289,7 +1354,7 @@ function createLimiter(options) {
1289
1354
  bucket = {
1290
1355
  budget: createBudget({
1291
1356
  max,
1292
- consume: Number
1357
+ consumer: Number
1293
1358
  }),
1294
1359
  resetAt: now + window
1295
1360
  };
@@ -1319,7 +1384,7 @@ function createLimiter(options) {
1319
1384
  };
1320
1385
  }
1321
1386
  /**
1322
- * The body-driving battery — eagerly awaits the cached `context.body()` so
1387
+ * Creates the body-driving battery — eagerly awaits the cached `context.body()` so
1323
1388
  * its throws (or a malformed-JSON `undefined`) surface before the handler
1324
1389
  * runs, and stashes the resolved value onto {@link BodyState.body}.
1325
1390
  *
@@ -1330,9 +1395,8 @@ function createLimiter(options) {
1330
1395
  * @remarks
1331
1396
  * The shipped `MiddlewareContext.body()` is a parameterless, server-owned
1332
1397
  * cache (`ServerOptions.limit` governs its size cap) — this battery carries
1333
- * no `limit`/`decompression` options (a deliberate break from the deleted old
1334
- * `createBodyParser` surface, which configured them itself). `state.body` is
1335
- * stashed from the SAME awaited call the 400 check reads — `context.body()`
1398
+ * no `limit`/`decompression` options. `state.body` is
1399
+ * stashed from the same awaited call the 400 check reads — `context.body()`
1336
1400
  * is never invoked twice.
1337
1401
  *
1338
1402
  * @example
@@ -1350,7 +1414,7 @@ function createBody() {
1350
1414
  };
1351
1415
  }
1352
1416
  /**
1353
- * The generic session battery — resolves, mints, and persists a session
1417
+ * Creates the generic session battery — resolves, mints, and persists a session
1354
1418
  * across the request, with a mid-handler `regenerate`/`destroy` control handle.
1355
1419
  *
1356
1420
  * @typeParam S - The session entity type the store persists (must implement {@link SessionInterface})
@@ -1358,7 +1422,7 @@ function createBody() {
1358
1422
  * @param options - See {@link SessionOptions}
1359
1423
  * @returns A `MiddlewareHandler<TState>`
1360
1424
  * @throws {TypeError} When any option is malformed
1361
- * @throws {HTTPError} `404` when `require` is set and no session resolves or mints
1425
+ * @throws {HTTPError} `404` when `required` is set and no session resolves or mints
1362
1426
  *
1363
1427
  * @example
1364
1428
  * ```ts
@@ -1366,14 +1430,13 @@ function createBody() {
1366
1430
  * ```
1367
1431
  */
1368
1432
  function createSession(options) {
1369
- if (!isFunction(options.transport.read) || !isFunction(options.transport.write) || !isFunction(options.transport.clear)) throw new TypeError("SessionOptions.transport must implement SessionTransport");
1433
+ if (!isFunction(options.transport.read) || !isFunction(options.transport.write) || !isFunction(options.transport.clear)) throw new TypeError("SessionOptions.transport must implement SessionTransportInterface");
1370
1434
  if (options.store !== void 0 && (!isFunction(options.store.get) || !isFunction(options.store.set) || !isFunction(options.store.delete))) throw new TypeError("SessionOptions.store must implement SessionStoreInterface when provided");
1371
1435
  if (options.ttl !== void 0 && !isFiniteNumber(options.ttl)) throw new TypeError("SessionOptions.ttl must be a finite number when provided");
1372
1436
  if (options.lifetime !== void 0 && !isFiniteNumber(options.lifetime)) throw new TypeError("SessionOptions.lifetime must be a finite number when provided");
1373
1437
  if (options.create !== void 0 && !isFunction(options.create)) throw new TypeError("SessionOptions.create must be a function when provided");
1374
1438
  if (options.mint !== void 0 && !isFunction(options.mint)) throw new TypeError("SessionOptions.mint must be a function when provided");
1375
- if (options.require !== void 0 && !isBoolean(options.require)) throw new TypeError("SessionOptions.require must be a boolean when provided");
1376
- if (options.ends !== void 0 && !isBoolean(options.ends)) throw new TypeError("SessionOptions.ends must be a boolean when provided");
1439
+ if (options.required !== void 0 && !isBoolean(options.required)) throw new TypeError("SessionOptions.required must be a boolean when provided");
1377
1440
  if (options.clock !== void 0 && !isFunction(options.clock)) throw new TypeError("SessionOptions.clock must be a function when provided");
1378
1441
  const transport = options.transport;
1379
1442
  const clock = options.clock ?? Date.now;
@@ -1385,17 +1448,12 @@ function createSession(options) {
1385
1448
  });
1386
1449
  const create = options.create;
1387
1450
  const mint = options.mint;
1388
- const requireSession = options.require ?? false;
1389
- const ends = options.ends ?? false;
1451
+ const requireSession = options.required ?? false;
1390
1452
  return async (request, context, next) => {
1391
1453
  const now = clock();
1392
1454
  const encrypted = context.state.connection?.encrypted ?? false;
1393
1455
  const incomingId = await transport.read(request);
1394
1456
  let session = incomingId !== void 0 ? await store.get(incomingId, now) : void 0;
1395
- if (ends && context.method === "DELETE" && session !== void 0) {
1396
- await store.delete(session.id);
1397
- return new Response(null, { status: 204 });
1398
- }
1399
1457
  let minted = false;
1400
1458
  if (session === void 0) {
1401
1459
  if (mint !== void 0 ? await mint(context) : true) {
@@ -1413,7 +1471,7 @@ function createSession(options) {
1413
1471
  regenerate() {
1414
1472
  if (destroyed) return;
1415
1473
  const newSession = create === void 0 ? new Session(crypto.randomUUID()) : create(crypto.randomUUID());
1416
- transferSessionData(activeSession, newSession);
1474
+ transferSessionState(activeSession, newSession);
1417
1475
  regenerated = newSession;
1418
1476
  },
1419
1477
  destroy() {
@@ -1428,11 +1486,11 @@ function createSession(options) {
1428
1486
  await store.delete(activeSession.id);
1429
1487
  await transport.clear(response);
1430
1488
  } else if (regenerated !== void 0) {
1431
- await store.set(regenerated.id, regenerated, clock());
1489
+ await store.set(regenerated, clock());
1432
1490
  await store.delete(activeSession.id);
1433
1491
  await transport.write(response, regenerated.id, encrypted);
1434
1492
  } else {
1435
- await store.set(activeSession.id, activeSession, clock());
1493
+ await store.set(activeSession, clock());
1436
1494
  if (minted) await transport.write(response, activeSession.id, encrypted);
1437
1495
  }
1438
1496
  }
@@ -1440,7 +1498,7 @@ function createSession(options) {
1440
1498
  };
1441
1499
  }
1442
1500
  /**
1443
- * Session-bound double-submit CSRF protection battery.
1501
+ * Creates the session-bound double-submit CSRF protection battery.
1444
1502
  *
1445
1503
  * @typeParam TState - The consumer's opaque per-request state type, must carry {@link CSRFState}, {@link SessionState}, and {@link ConnectionState}
1446
1504
  * @param options - See {@link CSRFOptions}
@@ -1497,8 +1555,8 @@ function createCSRF(options) {
1497
1555
  };
1498
1556
  }
1499
1557
  /**
1500
- * Scope a battery to run ONLY on a set of exact pathnames — elsewhere it
1501
- * steps aside via `next()`.
1558
+ * Scopes a battery to a set of exact pathnames and nowhere else outside that set
1559
+ * it steps aside through `next()`.
1502
1560
  *
1503
1561
  * @typeParam TState - The consumer's opaque per-request state type
1504
1562
  * @param paths - One pathname, or a set of pathnames, matched exactly against `context.url.pathname`
@@ -1518,8 +1576,8 @@ function only(paths, handler) {
1518
1576
  };
1519
1577
  }
1520
1578
  /**
1521
- * Scope a battery to run everywhere EXCEPT a set of exact pathnamesthere
1522
- * it steps aside via `next()`.
1579
+ * Scopes a battery to every pathname outside a set of exact oneson that set it
1580
+ * steps aside through `next()`.
1523
1581
  *
1524
1582
  * @typeParam TState - The consumer's opaque per-request state type
1525
1583
  * @param paths - One pathname, or a set of pathnames, matched exactly against `context.url.pathname`
@@ -1541,11 +1599,11 @@ function except(paths, handler) {
1541
1599
  //#endregion
1542
1600
  //#region src/core/factories.ts
1543
1601
  /**
1544
- * Create a signed-cookie {@link SessionTransport} — the session id travels as
1602
+ * Creates a signed-cookie {@link SessionTransportInterface} — the session id travels as
1545
1603
  * a `signToken`-signed cookie value.
1546
1604
  *
1547
1605
  * @param options - See {@link CookieTransportOptions}
1548
- * @returns A {@link SessionTransport}
1606
+ * @returns A {@link SessionTransportInterface}
1549
1607
  * @throws {TypeError} When `options.secret` or `options.name` is malformed
1550
1608
  *
1551
1609
  * @example
@@ -1576,11 +1634,11 @@ function createCookieTransport(options) {
1576
1634
  };
1577
1635
  }
1578
1636
  /**
1579
- * Create a bare-header {@link SessionTransport} — the session id travels
1637
+ * Creates a bare-header {@link SessionTransportInterface} — the session id travels
1580
1638
  * verbatim in a request/response header.
1581
1639
  *
1582
1640
  * @param options - See {@link HeaderTransportOptions}
1583
- * @returns A {@link SessionTransport}
1641
+ * @returns A {@link SessionTransportInterface}
1584
1642
  * @throws {TypeError} When `options.header` is malformed
1585
1643
  *
1586
1644
  * @example
@@ -1604,18 +1662,17 @@ function createHeaderTransport(options) {
1604
1662
  };
1605
1663
  }
1606
1664
  /**
1607
- * Create the default in-process {@link SessionStoreInterface} — a `Map`-backed
1665
+ * Creates the default in-process {@link SessionStoreInterface} — a `Map`-backed
1608
1666
  * store enforcing an idle timeout and an absolute lifetime.
1609
1667
  *
1610
- * @typeParam S - The session data payload type
1668
+ * @typeParam S - The session entity type
1611
1669
  * @param options - See {@link MemorySessionStoreOptions}
1612
1670
  * @returns A {@link SessionStoreInterface}
1613
1671
  * @throws {TypeError} When `options.ttl` or `options.lifetime` is malformed
1614
1672
  *
1615
1673
  * @remarks
1616
- * The {@link Session} entity is the `create` option's default value factory
1617
- * for `createSession` and deliberately ships WITHOUT its own `create*`
1618
- * factory — the name `createSession` belongs to the battery, not this class.
1674
+ * The store holds whatever {@link SessionInterface} entity `createSession`'s
1675
+ * `create` option produced, keyed by that entity's own `id`.
1619
1676
  *
1620
1677
  * @example
1621
1678
  * ```ts
@@ -1626,30 +1683,53 @@ function createMemorySessionStore(options) {
1626
1683
  return new MemorySessionStore(options);
1627
1684
  }
1628
1685
  /**
1629
- * Create a {@link DatabaseSessionStore} as a {@link SessionStoreInterface} —
1686
+ * Creates a {@link DatabaseSessionStore} as a {@link SessionStoreInterface} —
1630
1687
  * the durable counterpart to `createMemorySessionStore`, over a caller-opened
1631
1688
  * `@orkestrel/database` table (declare it with {@link sessionColumns}).
1632
1689
  *
1633
- * @typeParam S - The session data payload type
1690
+ * @typeParam S - The session entity type
1634
1691
  * @param table - The backing `TableInterface<SessionRow>`
1635
- * @param is - A {@link Guard} narrowing a restored snapshot to `S`
1636
- * @param options - The idle `ttl` / absolute `lifetime` thresholds
1692
+ * @param guard - A {@link Guard} narrowing a restored snapshot to `S`
1693
+ * @param options - See {@link SessionLimits}
1637
1694
  * @returns A {@link SessionStoreInterface}
1695
+ * @throws {TypeError} When `options.ttl` or `options.lifetime` is malformed
1638
1696
  *
1639
1697
  * @remarks
1640
1698
  * This factory only wraps `new DatabaseSessionStore(...)` — it never opens a
1641
1699
  * database or driver itself; the caller owns that lifecycle and passes in an
1642
- * already-open table.
1700
+ * already-open table. It supplies {@link createRestoredSession} as the store's
1701
+ * snapshot rebuild step, which is why the store imports nothing from this
1702
+ * file.
1643
1703
  *
1644
1704
  * @example
1645
1705
  * ```ts
1646
1706
  * const store = createDatabaseSessionStore(db.table('sessions'), isSession, { ttl: 60_000 })
1647
1707
  * ```
1648
1708
  */
1649
- function createDatabaseSessionStore(table, is, options) {
1650
- return new DatabaseSessionStore(table, is, options);
1709
+ function createDatabaseSessionStore(table, guard, options) {
1710
+ return new DatabaseSessionStore(table, guard, createRestoredSession, options);
1711
+ }
1712
+ /**
1713
+ * Rebuilds a {@link Session} from an untrusted snapshot value — the inverse of
1714
+ * `snapshotSession` and a durable store's `get` deserialization step.
1715
+ *
1716
+ * @param value - The candidate snapshot, of unknown shape
1717
+ * @returns A rebuilt {@link Session}, or `undefined` when `value` is malformed
1718
+ *
1719
+ * @example
1720
+ * ```ts
1721
+ * createRestoredSession({ id: 'abc', state: { userId: 'u_1' } }) // Session { id: 'abc' }
1722
+ * createRestoredSession({ id: 1 }) // undefined
1723
+ * ```
1724
+ */
1725
+ function createRestoredSession(value) {
1726
+ if (!isRecord(value)) return void 0;
1727
+ if (!isString(value.id) || !isRecord(value.state)) return void 0;
1728
+ const session = new Session(value.id);
1729
+ for (const [key, entry] of Object.entries(value.state)) session.set(key, entry);
1730
+ return session;
1651
1731
  }
1652
1732
  //#endregion
1653
- export { DEFAULT_BEARER_HEADER, DEFAULT_BEARER_SCHEME, DEFAULT_CLUSTER, DEFAULT_COEP, DEFAULT_COMPRESSION_ENCODINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_COOP, DEFAULT_CORP, DEFAULT_CORS_HEADERS, DEFAULT_CORS_METHODS, DEFAULT_CSP, DEFAULT_CSRF_COOKIE, DEFAULT_CSRF_FIELD, DEFAULT_CSRF_HEADER, DEFAULT_CSRF_SAFE_METHODS, DEFAULT_DEADLINE_STATUS, DEFAULT_FRAME_OPTIONS, DEFAULT_HSTS, DEFAULT_IDENTIFIER_HEADER, DEFAULT_LIMITER_CAPACITY, DEFAULT_LIMITER_MESSAGE, DEFAULT_PERMISSIONS_POLICY, DEFAULT_REFERRER_POLICY, DEFAULT_SESSION_CAPACITY, DEFAULT_SESSION_COOKIE, DEFAULT_SESSION_HEADER, DatabaseSessionStore, MemorySessionStore, Session, buildClientInfo, buildRateLimitField, buildRateLimitPolicyField, buildRetryAfter, compressBytes, compressResponse, createBearer, createBody, createBoundary, createCSRF, createCompression, createCookieTransport, createCors, createDatabaseSessionStore, createDeadline, createETag, createForwarded, createHeaderTransport, createLimiter, createMemorySessionStore, createSecurity, createSession, createTelemetry, detectEncodings, equalsConstantTime, except, isBufferingIneligible, isCompressionNegotiated, isMultipartBody, isMultipartFile, isPreflight, isSession, isSessionControl, matchesTrustedEntry, only, rebuildResponse, resolveForwardedFor, resolveKey, resolveOptInHeader, restoreSession, sessionColumns, sessionExpired, snapshotSession, transferSessionData };
1733
+ export { DEFAULT_BEARER_HEADER, DEFAULT_BEARER_SCHEME, DEFAULT_CLUSTER, DEFAULT_COEP, DEFAULT_COMPRESSION_ENCODINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_COOP, DEFAULT_CORP, DEFAULT_CORS_HEADERS, DEFAULT_CORS_METHODS, DEFAULT_CSP, DEFAULT_CSRF_COOKIE, DEFAULT_CSRF_FIELD, DEFAULT_CSRF_HEADER, DEFAULT_CSRF_SAFE_METHODS, DEFAULT_DEADLINE_STATUS, DEFAULT_FRAME_OPTIONS, DEFAULT_HSTS, DEFAULT_IDENTIFIER_HEADER, DEFAULT_LIMITER_CAPACITY, DEFAULT_LIMITER_MESSAGE, DEFAULT_PERMISSIONS_POLICY, DEFAULT_REFERRER_POLICY, DEFAULT_SESSION_CAPACITY, DEFAULT_SESSION_COOKIE, DEFAULT_SESSION_HEADER, DatabaseSessionStore, MemorySessionStore, Session, buildClient, buildRateLimitField, buildRateLimitPolicyField, buildRetryAfter, compressBytes, compressResponse, createBearer, createBody, createBoundary, createCSRF, createCompression, createCookieTransport, createCors, createDatabaseSessionStore, createDeadline, createETag, createForwarded, createHeaderTransport, createLimiter, createMemorySessionStore, createRestoredSession, createSecurity, createSession, createTelemetry, detectEncodings, equalsConstantTime, except, isBufferingIneligible, isCompressionNegotiated, isMultipartBody, isMultipartFile, isPreflight, isSession, isSessionControl, matchesTrustedEntry, only, rebuildResponse, resolveForwardedFor, resolveKey, resolveOptInHeader, sessionColumns, sessionExpired, snapshotSession, transferSessionState, validateSessionLimits };
1654
1734
 
1655
1735
  //# sourceMappingURL=index.js.map