@orkestrel/scaffold 0.0.66 → 0.0.68

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.
Files changed (74) hide show
  1. package/dist/bin/main.js +67 -44
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/agents/templates/brief.md +9 -0
  4. package/dist/host/claude/agents/orkestrel.md +8 -8
  5. package/dist/host/claude/rules/names.md +15 -0
  6. package/dist/host/claude/rules/tests.md +33 -4
  7. package/dist/host/claude/rules/workspace.md +14 -2
  8. package/dist/host/dotfiles/prettierignore +3 -0
  9. package/dist/host/guides/README.md +65 -0
  10. package/dist/host/guides/abort.md +169 -0
  11. package/dist/host/guides/agent.md +1509 -0
  12. package/dist/host/guides/brief.md +1266 -0
  13. package/dist/host/guides/browser.md +2200 -0
  14. package/dist/host/guides/budget.md +196 -0
  15. package/dist/host/guides/codec.md +519 -0
  16. package/dist/host/guides/console.md +785 -0
  17. package/dist/host/guides/contract.md +1193 -0
  18. package/dist/host/guides/csv.md +541 -0
  19. package/dist/host/guides/database.md +2518 -0
  20. package/dist/host/guides/emitter.md +233 -0
  21. package/dist/host/guides/form.md +1791 -0
  22. package/dist/host/guides/html.md +717 -0
  23. package/dist/host/guides/indexeddb.md +505 -0
  24. package/dist/host/guides/interpret.md +1029 -0
  25. package/dist/host/guides/lsp.md +515 -0
  26. package/dist/host/guides/markdown.md +964 -0
  27. package/dist/host/guides/mcp.md +5554 -0
  28. package/dist/host/guides/middleware.md +927 -0
  29. package/dist/host/guides/msg.md +440 -0
  30. package/dist/host/guides/ndjson.md +120 -0
  31. package/dist/host/guides/ollama.md +380 -0
  32. package/dist/host/guides/pool.md +280 -0
  33. package/dist/host/guides/probe.md +1210 -0
  34. package/dist/host/guides/process.md +1620 -0
  35. package/dist/host/guides/program.md +1110 -0
  36. package/dist/host/guides/qualifier.md +854 -0
  37. package/dist/host/guides/queue.md +370 -0
  38. package/dist/host/guides/rater.md +330 -0
  39. package/dist/host/guides/reason.md +1122 -0
  40. package/dist/host/guides/relation.md +373 -0
  41. package/dist/host/guides/router.md +753 -0
  42. package/dist/host/guides/scaffold.md +192 -31
  43. package/dist/host/guides/sea.md +383 -0
  44. package/dist/host/guides/server.md +752 -0
  45. package/dist/host/guides/sqlite.md +330 -0
  46. package/dist/host/guides/sse.md +187 -0
  47. package/dist/host/guides/supervisor.md +4890 -0
  48. package/dist/host/guides/table.md +1556 -0
  49. package/dist/host/guides/template.md +280 -0
  50. package/dist/host/guides/terminal.md +1145 -0
  51. package/dist/host/guides/test.md +2969 -0
  52. package/dist/host/guides/timeout.md +252 -0
  53. package/dist/host/guides/tool.md +311 -0
  54. package/dist/host/guides/toolbox.md +1038 -0
  55. package/dist/host/guides/websocket.md +282 -0
  56. package/dist/host/guides/worker.md +615 -0
  57. package/dist/host/guides/workflow.md +1507 -0
  58. package/dist/host/guides/workspace.md +595 -0
  59. package/dist/host/manifest.json +1218 -10
  60. package/dist/host/tests/policy.test.ts +279 -2
  61. package/dist/host/tests/setupPolicy.ts +437 -6
  62. package/dist/src/core/index.cjs +44 -22
  63. package/dist/src/core/index.cjs.map +1 -1
  64. package/dist/src/core/index.d.cts +33 -9
  65. package/dist/src/core/index.d.ts +33 -9
  66. package/dist/src/core/index.js +43 -23
  67. package/dist/src/core/index.js.map +1 -1
  68. package/dist/src/server/index.cjs +1750 -1567
  69. package/dist/src/server/index.cjs.map +1 -1
  70. package/dist/src/server/index.d.cts +106 -24
  71. package/dist/src/server/index.d.ts +106 -24
  72. package/dist/src/server/index.js +1751 -1570
  73. package/dist/src/server/index.js.map +1 -1
  74. package/package.json +9 -9
@@ -0,0 +1,927 @@
1
+ # Middleware
2
+
3
+ > Batteries for the `@orkestrel/server` middleware seam:
4
+ > `create{Noun}(options) => MiddlewareHandler<TState>` factories for error
5
+ > boundaries, telemetry, compression, security headers, CORS, deadlines,
6
+ > trusted-proxy client facts, ETag, bearer authentication, rate limiting, body
7
+ > parsing, sessions, and CSRF in the fetch-native core, plus in-memory assets,
8
+ > static files, streaming multipart uploads, and a `node:zlib` compression
9
+ > sibling in the node face.
10
+
11
+ This is the package's one guide, and it covers the core and the node face
12
+ together. Every battery composes the frozen `@orkestrel/server` middleware seam
13
+ (`MiddlewareHandler`, `MiddlewareContext`, `compose`) and its substrate —
14
+ cookies, WebCrypto tokens, negotiation, conditionals, and security primitives.
15
+ This package never re-implements that seam, and it supplies mechanism rather
16
+ than product policy. Source: [`src/core`](../src/core),
17
+ [`src/server`](../src/server). Surfaced through the `@orkestrel/middleware` and
18
+ `@orkestrel/middleware/server` barrels (aliased `@src/core` and `@src/server`
19
+ inside this repo).
20
+
21
+ ## Surface
22
+
23
+ A battery closes over its guarded options and returns a
24
+ `MiddlewareHandler<TState>`, which composes with the others over the shipped
25
+ seam.
26
+
27
+ ### Mount a battery
28
+
29
+ The fence composes an error boundary and the security battery over `compose`, then answers with the request identifier `createSecurity` stashed on `context.state`.
30
+
31
+ ```ts
32
+ import { createBoundary, createSecurity } from '@orkestrel/middleware'
33
+ import type { IdentifierState } from '@orkestrel/middleware'
34
+ import { compose } from '@orkestrel/server'
35
+
36
+ interface State extends IdentifierState {}
37
+
38
+ const boundary = createBoundary({ expose: false })
39
+ const security = createSecurity({ hsts: true })
40
+
41
+ const handle = compose<State>([boundary, security], async (_request, context) => {
42
+ return Response.json({ identifier: context.state.identifier })
43
+ })
44
+ ```
45
+
46
+ ### Middlewares — core
47
+
48
+ | API | Kind | Summary |
49
+ | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | `createBoundary` | function | Creates the outermost error-rendering battery — catches a downstream throw and renders it as a `Response`. |
51
+ | `createTelemetry` | function | Creates the access-log/timing seam — records one `TelemetryEntry` per request after the response settles. |
52
+ | `createCompression` | function | Creates the response-body compression battery — negotiates and compresses a buffered response body over the runtime's feature-detected `CompressionStream` codings. |
53
+ | `createSecurity` | function | Creates the security-headers + request-identifier battery — sets each documented header default, and mints or echoes a request identifier. |
54
+ | `createCors` | function | Creates the Cross-Origin Resource Sharing battery — answers a preflight itself, and reflects an allow-listed origin or serves the configured wildcard. |
55
+ | `createDeadline` | function | Creates the application-level per-request deadline battery. |
56
+ | `createForwarded` | function | Creates the trusted-proxy client-IP resolver battery — walks `X-Forwarded-For` past the hops its options declare trusted. |
57
+ | `createETag` | function | Creates the dynamic response `ETag` + conditional GET battery (RFC 7232). |
58
+ | `createBearer` | function | Creates the bearer-token authentication battery — reads the token from its header and verifies it with `verifyToken`. |
59
+ | `createLimiter` | function | Creates the fixed-window rate-limiting battery — checks a key's budget before consuming it, so one window admits exactly `max` requests. |
60
+ | `createBody` | function | Creates the body-driving battery — eagerly awaits the cached `context.body()` so its throws (or a malformed-JSON `undefined`) surface before the handler runs, and stashes the resolved value onto `BodyState.body`. |
61
+ | `createSession` | function | Creates the generic session battery — resolves, mints, and persists a session across the request, with a mid-handler `regenerate`/`destroy` control handle. |
62
+ | `createCSRF` | function | Creates the session-bound double-submit CSRF protection battery. |
63
+ | `only` | function | Scopes a battery to a set of exact pathnames and nowhere else — outside that set it steps aside through `next()`. |
64
+ | `except` | function | Scopes a battery to every pathname outside a set of exact ones — on that set it steps aside through `next()`. |
65
+
66
+ ### Middlewares — node
67
+
68
+ | API | Kind | Summary |
69
+ | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `createAssets` | function | Serves validated in-memory assets with identity/Brotli negotiation. |
71
+ | `createStatic` | function | Serves static files from `options.root` over `node:fs` — the node-bound static-file battery, answering conditional, ranged, and SPA-fallback requests. |
72
+ | `createMultipart` | function | Parses a streamed `multipart/form-data` request body and stashes its `MultipartBody` on `context.state.multipart` — the node-bound streaming multipart battery. |
73
+ | `createCompression` | function | Compresses response bodies through `node:zlib`, guaranteed on any Node runtime rather than dependent on the WHATWG `CompressionStream` global. This battery is the node-bound sibling of the core face's feature-detected `createCompression`, and it ships from a separate package entry point (`@orkestrel/middleware/server`) so the shared name is unambiguous per consumer import path. |
74
+
75
+ ### Types
76
+
77
+ A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`. An extended interface's name comes before `plus`, with the members it adds after.
78
+
79
+ | Type | Kind | Shape | Summary |
80
+ | --------------------------- | --------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
81
+ | `BoundaryOptions` | interface | `{ expose?, report? }` | Configures `createBoundary` — the outermost error-rendering battery. |
82
+ | `TelemetryEntry` | interface | `{ method, pathname, status, duration }` | Represents one access-log-style entry `createTelemetry` records after a response settles — the access-log/timing seam's payload shape. |
83
+ | `TelemetryOptions` | interface | `{ record }` | Configures `createTelemetry` — the request timing/access-log seam. |
84
+ | `CompressionOptions` | interface | `{ threshold?, encodings?, filter? }` | Configures `createCompression` — response-body compression. |
85
+ | `CompressResponseOptions` | interface | `{ threshold, filter?, encodings, compress }` | Describes the already-resolved settings `compressResponse` runs its shared negotiate → skip → threshold → compress skeleton against — the shape each face's `createCompression` builds from its own option bag. |
86
+ | `SecurityIdentifierOptions` | type | `{ trust? } \| false` | Describes `createSecurity`'s `identifier` sub-option — request-id minting/echo policy, or `false` to disable the feature entirely. |
87
+ | `SecurityOptions` | interface | `{ frame?, csp?, referrer?, permissions?, coop?, corp?, cluster?, coep?, hsts?, identifier? }` | Configures `createSecurity` — the security-headers + request-id battery. |
88
+ | `CorsOptions` | interface | `{ origin?, methods?, headers? }` | Configures `createCors` — Cross-Origin Resource Sharing. |
89
+ | `DeadlineOptions` | interface | `{ ms, status? }` | Configures `createDeadline` — the application-level per-request deadline. |
90
+ | `ForwardedOptions` | type | `{ proxies } \| { trusted }` | Configures `createForwarded` — the trusted-proxy client-IP resolver. |
91
+ | `ETagOptions` | interface | `{ weak? }` | Configures `createETag` — dynamic response ETag + conditional GET. |
92
+ | `BearerOptions` | interface | `{ secret, header?, scheme? }` | Configures `createBearer` — bearer-token authentication. |
93
+ | `LimiterOptions` | interface | `{ max, window, capacity?, key?, message?, clock?, policy?, evict? }` | Configures `createLimiter` — fixed-window rate limiting. |
94
+ | `BearerState` | interface | `{ token? }` | Describes the bearer-authentication state slice `createBearer` stashes on `context.state` once a token verifies. |
95
+ | `IdentifierState` | interface | `{ identifier? }` | Describes the request-identifier state slice `createSecurity` stashes when its `identifier` option is enabled. |
96
+ | `Client` | interface | `{ ip? }` | Describes the resolved client connection facts `createForwarded` stashes. |
97
+ | `ClientState` | interface | `{ client? }` | Describes the client-facts state slice `createForwarded` stashes. |
98
+ | `ConnectionState` | interface | `{ connection? }` | Describes the connection-facts state slice `createLimiter`'s default key derivation falls back to when neither `BearerState` nor `ClientState` is present — the raw socket peer surfaced on `context.state` by the server's `state` option. |
99
+ | `SessionInterface` | interface | `{ id, state } plus set, delete, clear` | Represents a server-managed session's public surface — an id, its live state, and the mutators that write it. |
100
+ | `SessionControlInterface` | interface | `{} plus regenerate, destroy` | Describes the mid-handler control handle `createSession` stashes alongside the session itself — the OWASP anti-fixation / logout primitives. |
101
+ | `SessionState` | interface | `{ session?, control? }` | Describes the session state slice `createSession` stashes. |
102
+ | `BodyState` | interface | `{ body? }` | Describes the body state slice `createBody` stashes. |
103
+ | `SessionStoreInterface` | interface | `{} plus get, set, delete` | Describes the pluggable session persistence seam `createSession`'s `store` option implements — a point-access store keyed by session id. |
104
+ | `SessionTransportInterface` | interface | `{} plus read, write, clear` | Describes the transport seam `createSession`'s `transport` option implements — how a session id travels to and from the client (a signed cookie, a header, …). |
105
+ | `SessionOptions` | interface | `{ transport, store?, ttl?, lifetime?, capacity?, evict?, create?, mint?, required?, clock? }` | Configures `createSession` — the generic session battery. |
106
+ | `CookieTransportOptions` | interface | `{ name?, secret, cookie? }` | Configures `createCookieTransport` — the signed-cookie `SessionTransportInterface`. |
107
+ | `HeaderTransportOptions` | interface | `{ header? }` | Configures `createHeaderTransport` — the bare-header `SessionTransportInterface`. |
108
+ | `MemorySessionStoreOptions` | interface | `SessionLimits plus { capacity?, evict? }` | Configures `createMemorySessionStore` — the default in-process `SessionStoreInterface`. |
109
+ | `SessionLimits` | interface | `{ ttl?, lifetime? }` | Describes the idle and absolute-lifetime thresholds a session store enforces — `sessionExpired`'s limits argument and both shipped stores' construction options. |
110
+ | `SessionCursors` | interface | `{ seen, created }` | Describes the per-session instants a store stamps and `sessionExpired` measures against. |
111
+ | `SessionRow` | interface | `SessionCursors plus { id, session }` | Represents one persisted session row — an opaque snapshot column plus the store-owned idle/absolute-lifetime cursors, the shape a `DatabaseSessionStore`'s backing table holds. |
112
+ | `SessionEntry` | interface | `SessionCursors plus { session }` | Represents one in-process session entry — the payload `MemorySessionStore` holds against an id, alongside the same cursors a persisted row carries. |
113
+ | `SessionSnapshot` | interface | `{ id, state }` | Represents a session's serializable projection — the value `snapshotSession` produces and a durable store's `set` writes. |
114
+ | `SessionRestoreFunction` | type | `(value: unknown) => SessionInterface \| undefined` | Rebuilds a session entity from an untrusted stored snapshot, or resolves `undefined` when the value is malformed. |
115
+ | `CSRFState` | interface | `{ csrf? }` | Describes the CSRF state slice `createCSRF` stashes — the raw token a safe-method response exposes for a subsequent mutating request to submit back. |
116
+ | `CSRFOptions` | interface | `{ secret, cookie?, header?, field?, safe? }` | Configures `createCSRF` — session-bound double-submit CSRF protection. |
117
+ | `MultipartFile` | interface | `{ field, name, size, mime, validated, status, path }` | Represents one staged multipart upload's public record — the shape the node-face `createMultipart` battery (`@orkestrel/middleware/server`) produces per uploaded file. |
118
+ | `MultipartBody` | interface | `{ files, fields }` | Describes the parsed multipart request body `createMultipart` stashes — files keyed by their field name, plus every plain text field. |
119
+ | `MultipartState` | interface | `{ multipart? }` | Describes the multipart state slice `createMultipart` stashes. |
120
+ | `Asset` | interface | `{ body, encoding? }` | Describes one in-memory asset representation returned by an `AssetSourceInterface`. |
121
+ | `AssetSourceInterface` | interface | `{} plus read` | Reads in-memory assets by decoded, browser-build-relative path. |
122
+ | `AssetOptions` | interface | `{ source }` | Configures `createAssets` — in-memory identity/Brotli asset serving. |
123
+ | `StaticOptions` | interface | `{ root, prefix?, index?, dotfiles?, cache?, etag?, fallback? }` | Configures `createStatic` — node `fs`-backed static file serving. |
124
+ | `MultipartLimitsInput` | interface | `{ file?, field?, total? }` | Describes the caller's partial `MultipartLimits` — `createMultipart`'s `limits` option, with every member optional. |
125
+ | `MultipartLimits` | interface | `{ file, field, total }` | Describes the per-category size/count caps `createMultipart` enforces mid-stream — the effective limits, every documented default already applied. |
126
+ | `MultipartOptions` | interface | `{ limits?, allowed?, directory? }` | Configures `createMultipart` — node `fs`/`os`/`crypto`-backed streaming multipart upload parsing. |
127
+ | `NodeCompressionOptions` | interface | `{ threshold?, filter? }` | Configures the node face's `createCompression` — `node:zlib`-backed response compression. |
128
+ | `MultipartErrorCode` | type | `'limit' \| 'malformed' \| 'rejected'` | Names the reason `createMultipart` rejected a request — the machine-readable code `MultipartError` carries and maps onto its HTTP status: `'limit'` → 413, `'malformed'` → 400, `'rejected'` → 415. |
129
+ | `UploadStatus` | type | `'staged' \| 'moved'` | Names the lifecycle stage of one staged upload's temp file. |
130
+ | `UploadedFile` | interface | `Omit<MultipartFile, 'status'> plus { status }` | Describes one uploaded file's post-parse record — the node-bound, richer sibling of the pure core's `MultipartFile` (identical fields, `status` narrowed to `UploadStatus`). Structurally assignable into `MultipartFile` so a `createMultipart`-built `MultipartBody` satisfies the shared core shape. |
131
+ | `PartHeaders` | interface | `{ name, filename, mime }` | Describes one multipart part's parsed header block — `parsePartHeaders`'s return shape. |
132
+ | `ByteRange` | interface | `{ start, end }` | Describes one inclusive byte range over a file — `streamFile`'s optional `range` argument and the shape `createStatic` builds for a satisfiable `Range` request. |
133
+
134
+ ### Constants
135
+
136
+ A `Shape` cell holds the constant's declared type.
137
+
138
+ | API | Kind | Shape | Summary |
139
+ | --------------------------------- | ----- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
+ | `DEFAULT_COMPRESSION_THRESHOLD` | const | `number` | Holds `1024`, the default minimum buffered body size in bytes `createCompression` will compress. |
141
+ | `DEFAULT_COMPRESSION_ENCODINGS` | const | `readonly Encoding[]` | Lists `['gzip', 'deflate']`, the default content-codings `createCompression` offers in preference order — intersected at construction with what the runtime's `CompressionStream` actually supports. |
142
+ | `DEFAULT_FRAME_OPTIONS` | const | `string` | Holds `'DENY'`, the default `X-Frame-Options` value `createSecurity` sets. |
143
+ | `DEFAULT_CSP` | const | `string` | Holds `"default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; form-action 'self'"`, the default `Content-Security-Policy` value `createSecurity` sets — a custom `csp` option replaces this wholesale, never merges. |
144
+ | `DEFAULT_REFERRER_POLICY` | const | `string` | Holds `'strict-origin-when-cross-origin'`, the default `Referrer-Policy` value `createSecurity` sets. |
145
+ | `DEFAULT_PERMISSIONS_POLICY` | const | `string` | Holds `'camera=(), microphone=(), geolocation=()'`, the default `Permissions-Policy` value `createSecurity` sets. |
146
+ | `DEFAULT_COOP` | const | `string` | Holds `'same-origin'`, the default `Cross-Origin-Opener-Policy` value `createSecurity` sets. |
147
+ | `DEFAULT_CORP` | const | `string` | Holds `'same-origin'`, the default `Cross-Origin-Resource-Policy` value `createSecurity` sets. |
148
+ | `DEFAULT_CLUSTER` | const | `string` | Holds `'?1'`, the default `Origin-Agent-Cluster` value `createSecurity` sets. |
149
+ | `DEFAULT_COEP` | const | `string` | Holds `'require-corp'`, the value `createSecurity` sets for `Cross-Origin-Embedder-Policy` when `coep: true`. |
150
+ | `DEFAULT_HSTS` | const | `string` | Holds `'max-age=31536000; includeSubDomains'`, the value `createSecurity` sets for `Strict-Transport-Security` when `hsts: true`. |
151
+ | `DEFAULT_IDENTIFIER_HEADER` | const | `string` | Names `'x-request-id'`, the default header `createSecurity` mints or echoes a request identifier into. |
152
+ | `DEFAULT_CORS_METHODS` | const | `readonly string[]` | Lists the default methods `createCors` advertises on a preflight response. |
153
+ | `DEFAULT_CORS_HEADERS` | const | `readonly string[]` | Lists the default headers `createCors` advertises on a preflight response. |
154
+ | `DEFAULT_DEADLINE_STATUS` | const | `number` | Holds `503`, the default response status `createDeadline` returns when its deadline fires first. |
155
+ | `DEFAULT_BEARER_HEADER` | const | `string` | Names `'authorization'`, the default header `createBearer` reads the token from. |
156
+ | `DEFAULT_BEARER_SCHEME` | const | `string` | Names `'Bearer'`, the default scheme prefix `createBearer` strips before verification. |
157
+ | `DEFAULT_LIMITER_CAPACITY` | const | `number` | Holds `10_000`, the default maximum number of distinct rate-limit keys `createLimiter` tracks before LRU eviction. |
158
+ | `DEFAULT_LIMITER_MESSAGE` | const | `string` | Holds `'rate limit exceeded'`, the default 429 body message `createLimiter` sends when a key is over budget. |
159
+ | `DEFAULT_SESSION_CAPACITY` | const | `number` | Holds `10_000`, the default maximum number of distinct session ids `createMemorySessionStore` tracks before LRU (by last write) eviction. |
160
+ | `DEFAULT_SESSION_COOKIE` | const | `string` | Names `'session'`, the default cookie `createCookieTransport` writes the signed session id under. |
161
+ | `DEFAULT_SESSION_HEADER` | const | `string` | Names `'session-id'`, the default header `createHeaderTransport` carries the session id in. |
162
+ | `DEFAULT_CSRF_COOKIE` | const | `string` | Names `'csrf'`, the default signed cookie `createCSRF` writes the CSRF token under. |
163
+ | `DEFAULT_CSRF_HEADER` | const | `string` | Names `'x-csrf-token'`, the default header `createCSRF` reads a mutating request's submitted token from. |
164
+ | `DEFAULT_CSRF_FIELD` | const | `string` | Names `'_csrf'`, the default body field `createCSRF` falls back to reading a mutating request's submitted token from. |
165
+ | `DEFAULT_CSRF_SAFE_METHODS` | const | `readonly string[]` | Lists `['GET', 'HEAD', 'OPTIONS']`, the default methods `createCSRF` treats as safe (mint instead of verify). |
166
+ | `MULTIPART_STATUS` | const | `Readonly<Record<MultipartErrorCode, number>>` | Holds the HTTP status `createMultipart` renders for each `MultipartErrorCode`: `'limit'` is 413, `'malformed'` is 400, and `'rejected'` is 415. |
167
+ | `MULTIPART_ERROR_BRAND` | const | `unique symbol` | Holds the `Symbol.for` brand `MultipartError` carries so `isMultipartError` recognizes an instance across duplicate copies of this package — a registry symbol rather than a module-local `Symbol()`, which would mint an unequal symbol per copy. |
168
+ | `NODE_COMPRESSION_ENCODINGS` | const | `readonly Encoding[]` | Lists `['gzip', 'deflate']`, the content-codings the node face's `createCompression` offers — what `node:zlib` guarantees on every Node runtime, so this face never feature-detects. |
169
+ | `DEFAULT_STATIC_INDEX` | const | `string` | Names `'index.html'`, `createStatic`'s default directory-index filename. |
170
+ | `DEFAULT_STATIC_FALLBACK_EXCLUDE` | const | `string` | Names `'/api'`, `createStatic`'s `fallback: true` default excluded path prefix. |
171
+ | `DEFAULT_STATIC_DOTFILES` | const | `NonNullable<StaticOptions['dotfiles']>` | Names `'ignore'`, `createStatic`'s default policy for a path carrying a dotfile segment. |
172
+ | `DEFAULT_CONTENT_TYPE` | const | `string` | Names `'application/octet-stream'`, the MIME type served when a file extension has no known mapping. |
173
+ | `DEFAULT_MULTIPART_FILE_SIZE` | const | `number` | Holds `10_485_760`, `createMultipart`'s default per-file byte-size cap. |
174
+ | `DEFAULT_MULTIPART_FILE_COUNT` | const | `number` | Holds `10`, `createMultipart`'s default maximum file-part count. |
175
+ | `DEFAULT_MULTIPART_FIELD_SIZE` | const | `number` | Holds `65_536`, `createMultipart`'s default per-field byte-size cap. |
176
+ | `DEFAULT_MULTIPART_FIELD_COUNT` | const | `number` | Holds `100`, `createMultipart`'s default maximum field-part count. |
177
+ | `DEFAULT_MULTIPART_TOTAL` | const | `number` | Holds `52_428_800`, `createMultipart`'s default combined request-body byte-size cap. |
178
+ | `MULTIPART_MAX_HEADER_BLOCK` | const | `number` | Holds `16_384`, the maximum bytes a single multipart part's header block may occupy before it is malformed. |
179
+ | `MULTIPART_MAX_PREAMBLE` | const | `number` | Holds `65_536`, the maximum bytes scanned before the first multipart boundary is found before it is malformed. |
180
+ | `RESERVED_DEVICE_NAMES` | const | `ReadonlySet<string>` | Lists the Windows reserved device-name stems (CVE-2025-27210) — matched case-insensitively against the segment's stem (before its first `.`). |
181
+ | `EXTENSION_TYPES` | const | `Readonly<Record<string, string>>` | Holds the file-extension (lowercase, with leading `.`) → MIME type lookup table for static serving. |
182
+
183
+ ### Shapers
184
+
185
+ A `Shape` cell holds the constant's declared type.
186
+
187
+ | API | Kind | Shape | Summary |
188
+ | ---------------- | ----- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
189
+ | `sessionColumns` | const | `{ id, session, seen, created }` | Holds the `@orkestrel/database` column shape for a `SessionRow` table. Pass it as-is to `createDatabase({ tables: { sessions: sessionColumns } })` so an app declaring a durable session table never hand-writes the shape. |
190
+
191
+ ### Helpers — core
192
+
193
+ | API | Kind | Summary |
194
+ | --------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
195
+ | `resolveKey` | function | Derives `createLimiter`'s default rate-limit bucket key from a request's resolved identity facts. |
196
+ | `resolveOptInHeader` | function | Resolves an opt-in, value-bearing security header — `string \| boolean` (off by default, `true` uses the secure default), the shape `createSecurity`'s `coep`/`hsts` options use, distinct from the plain value-or-`false` shape `resolveSecurityHeader` (the peer substrate) handles. |
197
+ | `buildRetryAfter` | function | Builds the `Retry-After` header value — whole seconds until a window reset, floored at a minimum of `1`. |
198
+ | `buildRateLimitField` | function | Builds the draft `RateLimit` structured header field — emitted only when `createLimiter`'s `policy` option is `true`. |
199
+ | `buildRateLimitPolicyField` | function | Builds the draft `RateLimit-Policy` structured header field — emitted only when `createLimiter`'s `policy` option is `true`. |
200
+ | `matchesTrustedEntry` | function | Checks whether a candidate address is a bare (non-CIDR) trusted-hop match — an exact string match, or a simple prefix-CIDR match for IPv4 (`/8`–`/32`). An IPv6 entry matches by exact string only — there is no IPv6 CIDR support. |
201
+ | `resolveForwardedFor` | function | Walks `X-Forwarded-For` right-to-left and resolves the first untrusted hop address — `createForwarded`'s core algorithm. |
202
+ | `detectEncodings` | function | Feature-detects which of `candidates` the runtime's `CompressionStream` actually supports — `createCompression`'s construction-time intersection. |
203
+ | `compressBytes` | function | Compresses bytes with the host-independent `CompressionStream` primitive. |
204
+ | `isBufferingIneligible` | function | Checks whether a response must skip the compression and ETag buffering pipeline — the shared cheap-skip predicate both batteries apply before ever touching `response.arrayBuffer()`, true for a `HEAD` request, a `204`/`304` or otherwise bodyless response, an `event-stream` response, and a response already carrying the header the caller is about to set. |
205
+ | `isCompressionNegotiated` | function | Checks whether a negotiated `Accept-Encoding` outcome is worth acting on — `createCompression`'s negotiation-eligibility half of the skip list. |
206
+ | `rebuildResponse` | function | Rebuilds a `Response` around a replacement body while preserving its status/statusText — the buffered-response reconstruction shared by the compression and ETag batteries after they have consumed `response.arrayBuffer()`. |
207
+ | `compressResponse` | function | Runs the shared negotiate → skip → threshold → compress → header-set skeleton both faces' `createCompression` batteries compose — response-body compression over a caller-supplied set of feature-detected codings. |
208
+ | `transferSessionState` | function | Copies every entry of one session's `state` into another — the regenerate state-carry `createSession`'s `control.regenerate()` applies. |
209
+ | `sessionExpired` | function | Checks whether a session has aged past its idle timeout or absolute lifetime as of `now` — the pure expiry predicate `MemorySessionStore` delegates to. |
210
+ | `snapshotSession` | function | Snapshots a session's `state` into a plain, serializable record — the projection a durable store's `set` writes to disk. |
211
+ | `validateSessionLimits` | function | Validates a store's idle and absolute-lifetime thresholds, throwing when either is present and malformed — the shared construction gate `MemorySessionStore` and `DatabaseSessionStore` both apply, so one malformed `ttl` is refused identically by whichever store receives it. |
212
+ | `isPreflight` | function | Determines whether a request is a CORS preflight — an `OPTIONS` request carrying an `Access-Control-Request-Method` header. |
213
+ | `buildClient` | function | Builds the `Client` slice `createForwarded` stashes, from the resolved client IP — a leaf shaping helper. |
214
+ | `equalsConstantTime` | function | Compares two strings in constant time — `createCSRF`'s double-submit token comparison, avoiding a timing oracle on the submitted-vs-cookie match. |
215
+
216
+ ### Validators — core
217
+
218
+ Each guard in the following table is total: it accepts any input, returns `false` off-shape, and never throws.
219
+
220
+ In a guard table a `Shape` cell holds the type the guard narrows to.
221
+
222
+ | API | Kind | Shape | Summary |
223
+ | ------------------ | -------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
+ | `isSession` | function | `SessionInterface` | Determines whether a value implements `SessionInterface` — a total structural guard: an `id` string, a `state` `Map`, and the `set`, `delete`, and `clear` mutators. Prototype-agnostic — accepts a plain object, a null-prototype object, and a class instance (a real `Session`), because a restored or stored session is routinely a class instance rather than a literal. |
225
+ | `isSessionControl` | function | `SessionControlInterface` | Determines whether a value implements `SessionControlInterface` — a total structural guard: callable `regenerate` and `destroy`. |
226
+ | `isMultipartFile` | function | `MultipartFile` | Determines whether a value is one staged `MultipartFile` record — a total structural guard checking every required field's shape. |
227
+ | `isMultipartBody` | function | `MultipartBody` | Determines whether a value implements `MultipartBody` — a total structural guard: `files` keyed by field name to arrays of `MultipartFile`, and a `fields` string record. |
228
+
229
+ ### Helpers — node
230
+
231
+ | API | Kind | Summary |
232
+ | --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
233
+ | `resolveStaticPath` | function | Resolves a request pathname to an on-disk path under `root`, or `undefined` when it cannot — the traversal guard, whose algorithm and order are exact: strip `prefix` on a segment boundary → `decodeURIComponent` (a malformed escape refuses, never throws) → reject a NUL byte → strip the leading path separator first (so a leading `..` survives `normalize` as a genuine climbing segment) → `normalize` → refuse any Windows reserved-device-name segment (`isReservedDeviceName`) → `resolve` and require the result under `root`. |
234
+ | `isUnderPath` | function | Checks whether `pathname` is `prefix` itself or lies under it on a segment boundary — the shared under-path test `resolveStaticPath`'s prefix strip and `createStatic`'s SPA-fallback `exclude` both apply, so `exclude: '/api'` matches `/api` and `/api/x` but never `/apifoo`. |
235
+ | `resolveStaticFallbackPath` | function | Resolves the fixed SPA shell path when a static-file miss is eligible for fallback. |
236
+ | `isContainedPath` | function | Checks whether `child` is `parent` itself or lies inside it on-disk — the filesystem containment predicate `createStatic` applies to `fs.realpath` output (never to a URL pathname — that is `isUnderPath`'s job). |
237
+ | `resolveContainedRealPath` | function | Canonicalizes `candidate` and returns it only when it lies inside `rootReal` — the shared realpath-then-contain step `createStatic` applies to a directory index and to its SPA shell. |
238
+ | `isReservedDeviceName` | function | Checks whether a path segment is a Windows reserved device name (CVE-2025-27210). |
239
+ | `isDotfilePath` | function | Checks whether a relative path (already resolved under a static root) has any segment starting with `.` — a dotfile or dot-directory. |
240
+ | `lookupContentType` | function | Looks up the MIME type for a static file path by its extension. |
241
+ | `computeFileETag` | function | Computes a static file's weak ETag from its size and modification time. |
242
+ | `detectMIME` | function | Sniffs a MIME type from a file's leading bytes against a small magic-byte table (jpeg, png, gif87a/89a, webp, pdf, zip). |
243
+ | `matchesBytes` | function | Checks whether `bytes` contains `signature` at the requested offset. |
244
+ | `compressNodeBytes` | function | Compresses response bytes with Node's guaranteed zlib gzip/deflate codecs. |
245
+ | `extractMultipartBoundary` | function | Extracts the `boundary` parameter from a `Content-Type` header, or `undefined` when the request is not `multipart/form-data`. |
246
+ | `parsePartHeaders` | function | Parses one multipart part's raw header block into its `name` (from `Content-Disposition`), optional `filename`, and optional `Content-Type`. |
247
+ | `resolveMultipartLimits` | function | Resolves `createMultipart`'s effective `MultipartLimits`, applying every documented default to an omitted leaf. |
248
+ | `createUploadedFile` | function | Builds a frozen `UploadedFile` record. |
249
+ | `streamFile` | function | Adapts a `node:fs` read stream over a file path (or an already-open `FileHandle`) into a DOM-compatible `ReadableStream<Uint8Array>` — the single shared node↔web stream bridge every static-file and uploaded-file response body routes through. |
250
+ | `streamUploadedFile` | function | Opens a staged/moved uploaded file as a web `ReadableStream`. |
251
+ | `readUploadedFile` | function | Reads a staged/moved uploaded file's full contents into memory. |
252
+ | `moveUploadedFile` | function | Moves a staged uploaded file to its final `destination`. |
253
+ | `unlinkStagedFiles` | function | Attempts to unlink every still-`'staged'` file in a parsed `MultipartBody` — the fail-closed cleanup `createMultipart` runs when its downstream handler throws, mirroring `parseMultipartRequest`'s own cleanup pattern (a missing file is already gone; failures are swallowed). |
254
+
255
+ ### Parsers — node
256
+
257
+ | API | Kind | Summary |
258
+ | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
259
+ | `parseMultipartRequest` | function | Stream-parses a `multipart/form-data` request into its files and fields — the mid-stream state machine `createMultipart` drives. |
260
+
261
+ ### Classes
262
+
263
+ | API | Kind | Summary |
264
+ | ---------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
265
+ | `Session` | class | Represents a server-managed session's default entity — the `create` option's default value for `createSession`. It ships without a bare `create*` factory of its own, because the name `createSession` belongs to the battery; `createRestoredSession` rebuilds one from a stored snapshot. |
266
+ | `MemorySessionStore` | class | Implements the default in-process `SessionStoreInterface` — a `Map`-backed store enforcing both an idle timeout and an absolute lifetime, with lazy (read-time) eviction, a bounded capacity, and no background timers. |
267
+ | `DatabaseSessionStore` | class | Implements a durable `SessionStoreInterface` over an `@orkestrel/database` table — the same idle-timeout + absolute-lifetime contract as `MemorySessionStore`, backed by a caller-supplied `TableInterface` instead of an in-process `Map`. |
268
+
269
+ Multipart parsing has no entity row because it exposes no entity. The server source declares a
270
+ multipart lifecycle engine that `parseMultipartRequest` composes internally, and the barrel does not
271
+ export it: a consumer reaches every part of that behaviour through `parseMultipartRequest`, whose
272
+ result is a `MultipartBody`. `tests/guides.test.ts` names the class in its `INTERNAL` list, so the
273
+ omission is asserted rather than assumed, and adding it to the barrel would turn that assertion red.
274
+
275
+ ### Factories
276
+
277
+ | API | Kind | Summary |
278
+ | ---------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
279
+ | `createCookieTransport` | function | Creates a signed-cookie `SessionTransportInterface` — the session id travels as a `signToken`-signed cookie value. |
280
+ | `createHeaderTransport` | function | Creates a bare-header `SessionTransportInterface` — the session id travels verbatim in a request/response header. |
281
+ | `createMemorySessionStore` | function | Creates the default in-process `SessionStoreInterface` — a `Map`-backed store enforcing an idle timeout and an absolute lifetime. |
282
+ | `createDatabaseSessionStore` | function | Creates a `DatabaseSessionStore` as a `SessionStoreInterface` — the durable counterpart to `createMemorySessionStore`, over a caller-opened `@orkestrel/database` table (declare it with `sessionColumns`). |
283
+ | `createRestoredSession` | function | Rebuilds a `Session` from an untrusted snapshot value — the inverse of `snapshotSession` and a durable store's `get` deserialization step. |
284
+
285
+ ### Errors
286
+
287
+ | API | Kind | Summary |
288
+ | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
289
+ | `MultipartError` | class | Represents an error `createMultipart` throws when a streamed multipart request fails a mid-stream limit, is structurally malformed, or has a file whose sniffed bytes are rejected by the configured `allowed` MIME list. |
290
+ | `isMultipartError` | function | Narrows an unknown caught value to a `MultipartError`. |
291
+
292
+ ## Methods
293
+
294
+ The public methods of `AssetSourceInterface`, `SessionInterface`,
295
+ `SessionControlInterface`, `SessionStoreInterface`, and
296
+ `SessionTransportInterface` — the behavioral seams the middleware factories
297
+ compose (their `readonly` data members, where any exist, stay Surface rows
298
+ in the preceding section).
299
+
300
+ #### `AssetSourceInterface`
301
+
302
+ The in-memory lookup seam. `createAssets` validates a request key before it
303
+ calls `read`, copies each successful result, and caches that key for the
304
+ factory's lifetime. A miss may be read again later.
305
+
306
+ | Method | Returns | Summary |
307
+ | ------ | -------------------- | ------------------------------------------------------------------------------- |
308
+ | `read` | `Asset \| undefined` | Reads one identity or Brotli asset representation for a validated relative key. |
309
+
310
+ #### `SessionInterface`
311
+
312
+ The session entity's own write seam. `state` is a `ReadonlyMap` view:
313
+ TypeScript refuses a write through it, and `set`, `delete`, and `clear` are
314
+ the write path.
315
+
316
+ | Method | Returns | Summary |
317
+ | -------- | --------- | -------------------------------------------------------- |
318
+ | `set` | `void` | Writes one key's value into the session's state. |
319
+ | `delete` | `boolean` | Removes one key from the session's state. |
320
+ | `clear` | `void` | Empties the state, leaving the session and its id alive. |
321
+
322
+ #### `SessionControlInterface`
323
+
324
+ `regenerate` is the OWASP anti-fixation primitive (rotate the id, keep the
325
+ state); `destroy` ends the session outright. Both record intent
326
+ synchronously; the store I/O and transport write happen after `next()`
327
+ returns (`destroy` supersedes a prior `regenerate`).
328
+
329
+ | Method | Returns | Summary |
330
+ | ------------ | ------- | --------------------------------------------------------------------------------- |
331
+ | `regenerate` | `void` | Mints a fresh id, carries the session's `state` over, and invalidates the old id. |
332
+ | `destroy` | `void` | Ends the session — deletes it from the store and clears its transport. |
333
+
334
+ #### `SessionStoreInterface`
335
+
336
+ The pluggable point-access persistence seam — `get`/`set`/`delete`, every
337
+ primitive async with a trailing injected `now`. `set` reads the id from the
338
+ session it is handed, so no separate id is passed. `DatabaseSessionStore`
339
+ takes its snapshot rebuild step as a constructor argument, and
340
+ `createDatabaseSessionStore` supplies `createRestoredSession` as that step.
341
+
342
+ | Method | Returns | Summary |
343
+ | -------- | ------------------------- | --------------------------------------------------------------------------- |
344
+ | `get` | `Promise<S \| undefined>` | Reads a session by id, applying the idle and absolute expiry against `now`. |
345
+ | `set` | `Promise<void>` | Persists a session under its own `id`, refreshing its idle window. |
346
+ | `delete` | `Promise<void>` | Removes a session by id — a no-op on an absent id, never throws. |
347
+
348
+ #### `SessionTransportInterface`
349
+
350
+ How a session id travels to and from the client — `read` is total (never
351
+ throws); `write`/`clear` mutate the returned `Response` on the way out (the
352
+ returning onion makes "before send" automatic).
353
+
354
+ | Method | Returns | Summary |
355
+ | ------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
356
+ | `read` | `string \| undefined \| Promise<...>` | Reads the incoming session id from the request — `undefined` on any failure. |
357
+ | `write` | `void \| Promise<void>` | Writes a freshly minted or regenerated session id onto the response, together with the request's encrypted-transport fact. |
358
+ | `clear` | `void` | Clears the transport's credential on `destroy()`. |
359
+
360
+ ## Contract
361
+
362
+ These invariants hold across `src/core` / `src/server` ↔ `middleware.md`.
363
+
364
+ 1. **Guide ↔ source bijection.** Every `function` / `class` / `interface` /
365
+ `type` / `const` row in the `## Surface` tables is a real export of its
366
+ source directory, and every export appears as a Surface row — exhaustive,
367
+ both directions.
368
+ 2. **Guide ↔ source method bijection.** The `## Methods` tables list exactly
369
+ `AssetSourceInterface`'s, `SessionInterface`'s, `SessionControlInterface`'s,
370
+ `SessionStoreInterface`'s, and `SessionTransportInterface`'s public methods —
371
+ exhaustive, both directions.
372
+
373
+ ### The ordering doctrine
374
+
375
+ The canonical onion, outermost first, and the failure each position
376
+ prevents:
377
+
378
+ 3. **`createTelemetry` is outermost.** It sees the mapped status (after
379
+ `createBoundary` renders) and measures honest wall-clock duration —
380
+ anything mounted inside it would be excluded from the timing.
381
+ 4. **`createCompression` sits outside `createBoundary`.** An error `Response`
382
+ the boundary renders from a caught throw still passes through
383
+ compression — mount it inside instead and error bodies ship uncompressed.
384
+ 5. **`createBoundary` is the renderer.** Everything mounted beneath it may
385
+ throw `HTTPError` (or anything else) and have it mapped to a `Response`;
386
+ nothing below it needs its own try/catch.
387
+ 6. **`createDeadline` sits inside the boundary.** A downstream `AbortError`
388
+ from the deadline firing is another throw the boundary maps cleanly
389
+ — mounting it outside would bypass that rendering.
390
+ 7. **`createSecurity` is a documented tradeoff, not a fixed position.** The
391
+ canonical order places it inside the boundary, so an error `Response` a
392
+ handler explicitly returns still carries security headers, but an error
393
+ the boundary renders from a throw does not (`createSecurity`'s `next()`
394
+ never resolves for a throw beneath it). A consumer who wants headers on
395
+ every response, thrown errors included, mounts `createSecurity` above
396
+ `createBoundary` instead — both orders are legitimate; pick the one your
397
+ threat model wants.
398
+ 8. **`createCors` claims preflights before the dispatcher's auto-`OPTIONS`.**
399
+ Its `OPTIONS` short-circuit runs before the terminal `Dispatcher.handle`
400
+ ever sees the request, by construction (any middleware position ahead of
401
+ the terminal works — CORS needs only array membership, not a fragile
402
+ slot).
403
+ 9. **`createForwarded` resolves client facts before anything keys off
404
+ them.** `createLimiter`'s default key and any session/telemetry logic
405
+ that wants `ClientState.client.ip` must run downstream of it.
406
+ 10. **`createETag` sits inside `createCompression`.** The hash is computed
407
+ over the uncompressed representation — hashing the compressed bytes
408
+ would break revalidation the instant the negotiated coding changes.
409
+ 11. **`createBearer` sits before `createLimiter`.** The limiter's default key
410
+ derivation prefers `BearerState.token` (the `token:<value>` idiom) over
411
+ a client-IP fallback — bearer must have already stashed it.
412
+ 12. **`createBody` sits before `createSession`/`createCSRF`.** `createSession`'s
413
+ async `mint` and `createCSRF`'s `_csrf` body-field read both need the
414
+ cached `context.body()` already resolved.
415
+ 13. **`createSession` sits before `createCSRF`.** CSRF's session-binding —
416
+ the security acceptance bar's CSRF item — requires `context.state.session`
417
+ to already be stashed.
418
+
419
+ ### The security acceptance bar, as documented behavior
420
+
421
+ 14. **CORS.** `Vary: Origin` is merged only on the reflect (allow-list) path,
422
+ never on the `'*'` wildcard path; the literal `Origin: null` is never
423
+ reflected even when the string `'null'` is allow-listed.
424
+ 15. **Headers.** A hostile `X-Request-ID` (off-charset, oversize, or
425
+ CRLF-bearing) is never echoed — `createSecurity` mints a fresh
426
+ `crypto.randomUUID()` instead; a custom `csp` string replaces the
427
+ default wholesale (never merges); `X-Content-Type-Options: nosniff` is
428
+ unconditional, with no opt-out.
429
+ 16. **Bearer.** `verifyToken` is total over garbage, tampered, expired, or
430
+ empty-rotation input — every failure renders `401`, never a crash;
431
+ verification is constant-time through `crypto.subtle.verify`.
432
+ 17. **Limiter.** The key derivation never reads `X-Forwarded-For` itself (only
433
+ `createForwarded`'s already-resolved `ClientState` does, when mounted) —
434
+ so an unmounted `createForwarded` leaves XFF completely untrusted;
435
+ same-socket requests with different XFF values still share one bucket
436
+ without it; IPv6 addresses collapse to their `/64` network through
437
+ `computeClientKey`; the exhausted check runs before `consume`, admitting
438
+ exactly `max` requests per window; capacity eviction is true LRU — every
439
+ access (not only insertion) refreshes a key's recency, so an attacker
440
+ re-requesting a hot key can never keep it evicted-and-reset; a bucket
441
+ evicted for capacity invokes the optional `evict` sink (throw-isolated).
442
+ 18. **Body.** `createBody` maps a malformed-JSON `undefined` resolution to a
443
+ `400`; the size/decompression caps and the `__proto__`/`constructor`/
444
+ `prototype` scrub are the substrate's own `readBody` behavior
445
+ (`ServerOptions.limit`) — this battery only drives the cache eagerly and
446
+ maps its outcomes.
447
+ 19. **Session.** The default `MemorySessionStore` enforces both an idle
448
+ timeout (`ttl`, lazy eviction on `get`) and an absolute lifetime
449
+ (`lifetime`, evicting even a continuously-touched session — `created`
450
+ is stamped once at first `set` and preserved across every later
451
+ re-persist); it is also capacity-capped (`capacity`, default
452
+ `DEFAULT_SESSION_CAPACITY`) and evicts the least-recently-written id —
453
+ every `set` (not `get`) refreshes recency — invoking the optional
454
+ `evict` sink (throw-isolated) on a capacity eviction or an expired-entry
455
+ prune, but never for an explicit `delete`; `control.regenerate()` rotates the id while carrying the
456
+ session's `state` over and invalidating the old id; a signed cookie
457
+ transport inherits the full substrate injection-hardening matrix
458
+ (`__Host-` spoof rejection, `Domain`/`Path` injection throws,
459
+ `SameSite=None` forces `Secure`, `Secure` derived from the connection's
460
+ TLS fact when omitted). The battery installs no route of its own: it
461
+ answers `404` when `required` is set and no session resolves or mints, and
462
+ a consumer ending a session on `DELETE` mounts its own handler over
463
+ `control.destroy()`.
464
+ 20. **CSRF.** With a session ahead, the minted token is bound to that
465
+ session's id (`signToken(sessionId)`) — a mutating request's recovered
466
+ bound id must equal its own session's id, so a token minted under
467
+ session A replayed against session B is `403` even with matching
468
+ double-submit halves; without a session, `createCSRF` falls back to
469
+ signed-random double-submit (documented weaker — no cross-session
470
+ binding is possible without one).
471
+ 21. **Static.** Every served response is opened as a `FileHandle` and its
472
+ headers (`Content-Length`/`ETag`) are computed from that same handle's
473
+ `fstat` — the bytes `streamFile` later reads can never diverge from the
474
+ headers already sent, closing the stat-to-stream TOCTOU. The traversal
475
+ guard's algorithm order is load-bearing —
476
+ strip `prefix` on a segment boundary, `decodeURIComponent` (refusing
477
+ malformed escapes), reject NUL, make relative before `normalize` (so a
478
+ leading `..` survives as a climbing segment), `normalize`, refuse a
479
+ Windows reserved-device segment (`NUL.json` refused, `nullable.css`
480
+ served), then `resolve` under `root` and require containment; a
481
+ multi-range or malformed `Range` header serves the full body (`200`),
482
+ never a partial guess; the SPA fallback shell path is a fixed,
483
+ non-user-controlled join — never re-run through the traversal resolver.
484
+ 22. **Assets.** `createAssets` decodes a browser-relative key and refuses
485
+ malformed escapes, backslashes, empty segments, `.`/`..`, and dotfiles
486
+ before it calls `AssetSourceInterface.read`. A successful value is copied
487
+ and cached. A Brotli value is decompressed once; Brotli and identity share
488
+ the identity body's `computeBodyETag` validator and vary on
489
+ `Accept-Encoding`. Gzip is not offered. `Range` is ignored and serves the
490
+ selected complete representation as `200`.
491
+ 23. **Multipart.** Type rejection applies only when `allowed` is configured:
492
+ a file whose sniffed (magic-byte) bytes detect no type on that list is
493
+ rejected `415`, and a signature-less file is always rejected because
494
+ sniffing cannot place it on the list. A declared `Content-Type`
495
+ disagreeing with the sniffed type is reported as `validated: false` and
496
+ is never rejected on its own — with no `allowed` list, a disagreement
497
+ produces a normal response. Staged temp filenames are `randomUUID()`, never
498
+ derived from the client-declared filename (traversal-by-filename is
499
+ impossible by construction); every limit trips mid-stream with already-
500
+ staged files cleaned up; a mid-upload client disconnect triggers the
501
+ same fail-closed cleanup; a preamble longer than
502
+ `MULTIPART_MAX_PREAMBLE` before the first boundary is rejected
503
+ `'malformed'` rather than scanned unbounded; an empty-filename part
504
+ with a zero-byte body (a file input submitted with no file chosen) is a
505
+ no-op — staged then discarded, never counted against `limits.file.count`,
506
+ never surfaced as an upload; staged files default to a
507
+ process-owned `mkdtemp` directory under `os.tmpdir()` locked to mode
508
+ `0o700`, with each staged file opened at mode `0o600` (both overridable
509
+ through `options.directory`).
510
+ 24. **Boundary.** `expose: false` leaks nothing (a non-`HTTPError` throw's
511
+ message never reaches the body); an `HTTPError`'s own `message` always
512
+ surfaces (it is the handler's deliberate signal); a `report` sink's own
513
+ throw is swallowed and can never alter the response.
514
+ 25. **`only`/`except` are not a security boundary.** Both match
515
+ `context.url.pathname` exactly — a trailing slash (`/login/` vs `/login`),
516
+ a case variant, or a percent-encoded path silently falls outside an
517
+ `only()`-scoped path set, and a security battery scoped that way goes
518
+ dark on that request with no signal. Prefer `except()` for security
519
+ batteries (CSRF, bearer, rate limiting) — its failure mode is fail-closed
520
+ (an unlisted or misspelled path still gets the battery; only the
521
+ explicitly excluded paths lose it), where `only()`'s failure mode is
522
+ fail-open. Whichever combinator is used, keep its path set in lockstep
523
+ with the router's actual routes — a route added after the fact and not
524
+ added to the set is silently unscoped.
525
+
526
+ ## Patterns
527
+
528
+ ### Canonical onion — fetch-native runtime
529
+
530
+ The full ordering doctrine, composed directly over `compose` (no `@orkestrel/server`
531
+ `Server` required — any fetch-native runtime works):
532
+
533
+ ```ts
534
+ import {
535
+ createBearer,
536
+ createBody,
537
+ createBoundary,
538
+ createCompression,
539
+ createCors,
540
+ createCSRF,
541
+ createDeadline,
542
+ createETag,
543
+ createForwarded,
544
+ createLimiter,
545
+ createSecurity,
546
+ createSession,
547
+ createTelemetry,
548
+ createCookieTransport,
549
+ } from '@orkestrel/middleware'
550
+ import type {
551
+ BearerState,
552
+ ClientState,
553
+ CSRFState,
554
+ IdentifierState,
555
+ SessionState,
556
+ } from '@orkestrel/middleware'
557
+ import { compose } from '@orkestrel/server'
558
+
559
+ interface State extends BearerState, ClientState, CSRFState, IdentifierState, SessionState {
560
+ readonly connection?: { readonly ip?: string }
561
+ }
562
+
563
+ const onion = [
564
+ createTelemetry({ record: (entry) => console.log(entry) }),
565
+ createCompression(),
566
+ createBoundary({ expose: false }),
567
+ createDeadline({ ms: 5_000 }),
568
+ createSecurity({ hsts: true }),
569
+ createCors({ origin: ['https://app.example'] }),
570
+ createForwarded({ proxies: 1 }),
571
+ createETag(),
572
+ createBearer({ secret: 'shh' }),
573
+ createLimiter({ max: 100, window: 60_000 }),
574
+ createBody(),
575
+ createSession({ transport: createCookieTransport({ secret: 'shh' }) }),
576
+ createCSRF({ secret: 'shh' }),
577
+ ]
578
+
579
+ const handle = compose<State>(onion, async (_request, context) => {
580
+ return Response.json({ session: context.state.session?.id })
581
+ })
582
+ ```
583
+
584
+ ### Canonical onion — behind `@orkestrel/server`
585
+
586
+ The fence hands the same chain to `createServer` as its `middleware` option, so the server owns the listening socket and the dispatcher the chain terminates in.
587
+
588
+ ```ts
589
+ import { createBoundary, createSecurity } from '@orkestrel/middleware'
590
+ import type { IdentifierState } from '@orkestrel/middleware'
591
+ import { createServer } from '@orkestrel/server'
592
+ import { createDispatcher } from '@orkestrel/router'
593
+
594
+ interface State extends IdentifierState {}
595
+
596
+ const dispatcher = createDispatcher<State>()
597
+ dispatcher.add({ method: 'GET', path: '/health', handler: () => new Response('ok') })
598
+
599
+ const server = createServer<State>({
600
+ dispatcher,
601
+ state: () => ({}),
602
+ middleware: [createBoundary(), createSecurity()],
603
+ })
604
+ const port = await server.start()
605
+ await server.stop()
606
+ ```
607
+
608
+ ### Body: eager cache drive
609
+
610
+ The fence constructs the body battery with no options of its own.
611
+
612
+ ```ts
613
+ import { createBody } from '@orkestrel/middleware'
614
+
615
+ const body = createBody() // no options — the seam's context.body() owns limits
616
+ ```
617
+
618
+ `createBody` stashes a defined resolved value on `context.state.body` (and
619
+ leaves the optional property absent when resolution yields `undefined`), so its
620
+ `TState` must extend `BodyState`. Zero-annotation usage (`createBody()`) infers
621
+ `BodyState` by default. An explicitly-typed chain state
622
+ (`createBody<SomeState>()`) must include the `BodyState` slice —
623
+ `SomeState & BodyState`, or `SomeState` extending `BodyState` — unless it
624
+ already carries a `body` field.
625
+
626
+ ### Session: control handle, header transport, injected store
627
+
628
+ The fence wires the session battery to a header transport and an injected memory store, then rotates and ends the session through the control handle a downstream handler reads.
629
+
630
+ ```ts
631
+ import {
632
+ createHeaderTransport,
633
+ createMemorySessionStore,
634
+ createSession,
635
+ } from '@orkestrel/middleware'
636
+ import type { SessionState } from '@orkestrel/middleware'
637
+
638
+ interface State extends SessionState {}
639
+
640
+ const store = createMemorySessionStore({ ttl: 900_000, lifetime: 86_400_000 })
641
+ const session = createSession<import('@orkestrel/middleware').SessionInterface, State>({
642
+ transport: createHeaderTransport({ header: 'session-id' }),
643
+ store,
644
+ mint: () => true,
645
+ })
646
+
647
+ // Inside a handler downstream of `session`:
648
+ declare const context: { readonly state: State }
649
+ context.state.control?.regenerate() // rotate the id after a privilege change (anti-fixation)
650
+ context.state.control?.destroy() // end the session outright
651
+ ```
652
+
653
+ ### Session store seam — direct calls
654
+
655
+ The fence drives a memory store's `set`, `get`, and `delete` methods directly, with no middleware chain around them.
656
+
657
+ ```ts
658
+ import { createMemorySessionStore } from '@orkestrel/middleware'
659
+ import { Session } from '@orkestrel/middleware'
660
+
661
+ const store = createMemorySessionStore({ ttl: 60_000 })
662
+ const now = Date.now()
663
+ await store.set(new Session('id-1'), now)
664
+ await store.get('id-1', now) // resolves the session, or undefined if expired
665
+ await store.delete('id-1') // no-op on an already-absent id
666
+ ```
667
+
668
+ ### Session store seam — durable database-backed store
669
+
670
+ The `@orkestrel/database` peer is optional and type-only inside this
671
+ package's `src` — a memory-only consumer installs nothing extra. An app that
672
+ wants durable sessions installs `@orkestrel/database` itself, declares a
673
+ table with `sessionColumns`, and passes the open table + a guard to
674
+ `createDatabaseSessionStore`:
675
+
676
+ ```ts
677
+ import {
678
+ createDatabaseSessionStore,
679
+ isSession,
680
+ Session,
681
+ sessionColumns,
682
+ } from '@orkestrel/middleware'
683
+ import { createDatabase, createMemoryDriver } from '@orkestrel/database'
684
+
685
+ const db = createDatabase({ driver: createMemoryDriver(), tables: { sessions: sessionColumns } })
686
+ const store = createDatabaseSessionStore(db.table('sessions'), isSession, { ttl: 900_000 })
687
+ const now = Date.now()
688
+ await store.set(new Session('id-1'), now)
689
+ await store.get('id-1', now) // resolves the session, or undefined if expired/removed
690
+ ```
691
+
692
+ The `session` column holds the `SessionSnapshot` JSON that `snapshotSession`
693
+ writes and `createRestoredSession` reads back: `{ id, state }`, where `state`
694
+ carries the session's entries. The cursor columns are `seen` and `created`. A
695
+ table still declared with the earlier `lastSeen` and `createdAt` columns fails
696
+ closed rather than reading a stale row as live: the table's own read guard
697
+ refuses a row that does not satisfy `sessionColumns`, `store.get` resolves
698
+ `undefined`, and the row stays in place until the table is migrated or
699
+ recreated.
700
+
701
+ ### Session transport seam — direct calls
702
+
703
+ The fence drives a header transport's `read`, `write`, and `clear` methods over a real `Request` and `Response`.
704
+
705
+ ```ts
706
+ import { createHeaderTransport } from '@orkestrel/middleware'
707
+
708
+ const transport = createHeaderTransport()
709
+ const request = new Request('https://x', { headers: { 'session-id': 'abc' } })
710
+ await transport.read(request) // 'abc'
711
+ const response = new Response('ok')
712
+ await transport.write(response, 'abc', false) // sets the session-id header
713
+ transport.clear(response) // removes it
714
+ ```
715
+
716
+ ### CSRF: session-bound double-submit
717
+
718
+ The fence mounts the CSRF battery behind a cookie-transport session, which is what binds each minted token to that session's id.
719
+
720
+ ```ts
721
+ import { createCSRF, createSession, createCookieTransport } from '@orkestrel/middleware'
722
+ import type { CSRFState, SessionState } from '@orkestrel/middleware'
723
+
724
+ interface State extends SessionState, CSRFState {}
725
+
726
+ const session = createSession<import('@orkestrel/middleware').SessionInterface, State>({
727
+ transport: createCookieTransport({ secret: 'session-secret' }),
728
+ })
729
+ const csrf = createCSRF({ secret: 'csrf-secret' }) // session ahead binds the token to its id
730
+ ```
731
+
732
+ ### Multipart: node face, sniffed-type allow-list
733
+
734
+ The fence mounts the node-face multipart battery with a sniffed-type allow-list and narrows its parsed result on `context.state` through the shipped guards.
735
+
736
+ ```ts
737
+ import { createMultipart } from '@orkestrel/middleware/server'
738
+ import { isMultipartBody, isMultipartFile } from '@orkestrel/middleware'
739
+ import type { MultipartState } from '@orkestrel/middleware'
740
+
741
+ interface State extends MultipartState {}
742
+
743
+ const uploads = createMultipart<State>({ allowed: ['image/png', 'image/jpeg'] })
744
+
745
+ declare const context: { readonly state: State }
746
+ if (isMultipartBody(context.state.multipart)) {
747
+ context.state.multipart.files // narrowed, ready to stream/read/move
748
+ for (const files of Object.values(context.state.multipart.files)) {
749
+ files.every((file) => isMultipartFile(file)) // true — every entry is a staged MultipartFile
750
+ }
751
+ }
752
+ ```
753
+
754
+ ### Multipart limits — direct resolution
755
+
756
+ The fence resolves one partial limits input into the effective caps, with no battery around it.
757
+
758
+ ```ts
759
+ import { resolveMultipartLimits } from '@orkestrel/middleware/server'
760
+
761
+ resolveMultipartLimits({ file: { size: 1_048_576 } }) // fills in every other default cap
762
+ ```
763
+
764
+ Multipart processing reports no progress.
765
+
766
+ ### Assets: in-memory source
767
+
768
+ The fence implements an `AssetSourceInterface` over a single in-memory entry and hands it to `createAssets`.
769
+
770
+ ```ts
771
+ import type { AssetSourceInterface } from '@orkestrel/middleware/server'
772
+ import { createAssets } from '@orkestrel/middleware/server'
773
+
774
+ const source: AssetSourceInterface = {
775
+ read(path) {
776
+ return path === 'index.html'
777
+ ? { body: new TextEncoder().encode('<!doctype html><title>App</title>') }
778
+ : undefined
779
+ },
780
+ }
781
+
782
+ const serveAssets = createAssets({ source })
783
+ ```
784
+
785
+ Return `{ body, encoding: 'br' }` when `body` contains Brotli bytes.
786
+ `createAssets` keeps those bytes for Brotli clients and caches one identity
787
+ decompression for every other client. Both responses share an identity-body
788
+ ETag. Gzip is not offered.
789
+
790
+ `read` must answer a bounded key set and return `undefined` for every key
791
+ outside it. `createAssets` retains every successful result for the factory's
792
+ lifetime and evicts nothing, so a `read` that synthesizes a representation for
793
+ an arbitrary key grows that cache without limit under request pressure. A miss
794
+ is never retained, so an absent key is read again on its next request.
795
+
796
+ ### Static: SPA fallback
797
+
798
+ The fence serves a directory over `node:fs` with the SPA fallback on, which answers an eligible miss with the shell.
799
+
800
+ ```ts
801
+ import { createStatic } from '@orkestrel/middleware/server'
802
+
803
+ const serveApp = createStatic({ root: '/srv/public', fallback: true }) // excludes '/api' by default
804
+ ```
805
+
806
+ An eligible miss is a `GET` or `HEAD` request for an extensionless pathname
807
+ outside the excluded prefix whose `Accept` admits `text/html`. It answers with
808
+ `index` through the same opened-handle `fstat` header block a directly
809
+ requested file answers through. `Cache-Control`, `ETag`, `Content-Length`, and
810
+ `Accept-Ranges` therefore carry the shell's own facts, `If-None-Match`
811
+ revalidates to `304`, and `Range` serves `206` — identically on both routes. A
812
+ `HEAD` navigation resolves the same shell as its `GET` and answers `200` with
813
+ those headers and no body.
814
+
815
+ The shell path is a fixed `root`-plus-`index` join rather than a request-derived
816
+ path, so the `dotfiles` policy screens the request pathname alone. A dotfile
817
+ `index` is refused when it is requested directly under `dotfiles: 'deny'` and is
818
+ still served through the fallback, because the operator configured that path.
819
+
820
+ ### Seam adaptations — read before wiring sessions or multipart
821
+
822
+ - **`createBody` carries no `limit`/`decompression` options.** The shipped
823
+ `MiddlewareContext.body()` is a parameterless, server-owned cache
824
+ (`ServerOptions.limit` governs its size cap) — this battery eagerly
825
+ awaits it and maps its outcomes (a `ContentTooLargeError`/`HTTPError`
826
+ propagates untouched; `undefined` under a declared `application/json`
827
+ maps to `400`).
828
+ - **`createMultipart` consumes `request.body` as a stream — never
829
+ `context.body()`.** Its parsed result is stashed on
830
+ `context.state.multipart`, narrowed with `isMultipartBody`. After it
831
+ runs, `context.body()` must not be called for that request — the
832
+ underlying stream is exhausted.
833
+ - **`SessionTransportInterface.write`/`clear` mutate the returned `Response` on the
834
+ way out.** `createSession` applies store I/O and transport writes after
835
+ `next()` resolves: `destroy()` → `store.delete` + `transport.clear`;
836
+ `regenerate()` → `store.set` the new session, `store.delete` the old,
837
+ `transport.write` the new id; otherwise → `store.set` the resolved/minted
838
+ session, `transport.write` only when freshly minted. `destroy()`
839
+ supersedes a prior `regenerate()`.
840
+
841
+ ### Practices
842
+
843
+ - **Mount `createTelemetry` and `createCompression` outside `createBoundary`**
844
+ — error bodies still compress, and duration still measures the whole
845
+ onion (Contract §3–4).
846
+ - **Mount `createForwarded` before anything that keys off `ClientState`** —
847
+ `createLimiter`'s default key and any client-IP-sensitive logic
848
+ downstream (Contract §9).
849
+ - **Never derive a rate-limit key from `X-Forwarded-For` yourself** — mount
850
+ `createForwarded` and let its resolved `ClientState` do it (Contract §17).
851
+ - **Call `control.regenerate()` on every privilege change** (login,
852
+ elevation) — the OWASP anti-fixation requirement session-based auth
853
+ depends on.
854
+ - **Install a `report` sink on `createBoundary`** for observability — its
855
+ own throw is swallowed, so it can never crash a response.
856
+ - **Pick your `createSecurity` position deliberately** — inside the
857
+ boundary (default; headers only on returned responses) or above it
858
+ (headers on every response, thrown errors included) — see Contract §7.
859
+
860
+ ## Tests
861
+
862
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` +
863
+ `src/server` bijection (value and type exports), the `AssetSourceInterface`,
864
+ `SessionInterface`, `SessionControlInterface`, `SessionStoreInterface`, and
865
+ `SessionTransportInterface` method bijections, and the equality gate: every `Summary`
866
+ cell against its declaration's description paragraph, the titled `Mount a battery`
867
+ fence against the `@example` block of that title (pinned so the titled pair cannot be
868
+ retired silently), and the README pitch against this guide's tagline.
869
+ - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) —
870
+ `resolveKey` precedence, `buildRetryAfter`/`buildRateLimitField`/
871
+ `buildRateLimitPolicyField` exact wire strings, `matchesTrustedEntry`/
872
+ `resolveForwardedFor` matrices, `detectEncodings`, `compressBytes`, buffering-eligibility
873
+ predicates, `transferSessionState`, `isPreflight`, `buildClient`,
874
+ `validateSessionLimits`.
875
+ - [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) —
876
+ the `isSession`/`isSessionControl`/`isMultipartFile`/`isMultipartBody`
877
+ totality guards, each driven over a well-shaped value and over the hostile
878
+ inputs it must answer `false` to.
879
+ - [`tests/src/core/Session.test.ts`](../tests/src/core/Session.test.ts) —
880
+ the entity shape (`id`, an independent `state` view and its mutators per instance).
881
+ - [`tests/src/core/stores/MemorySessionStore.test.ts`](../tests/src/core/stores/MemorySessionStore.test.ts) —
882
+ construction guards, get/set/delete, idle + absolute-lifetime eviction,
883
+ `created` stamped once and preserved across re-set.
884
+ - [`tests/src/core/stores/DatabaseSessionStore.test.ts`](../tests/src/core/stores/DatabaseSessionStore.test.ts) —
885
+ get/set/delete over a real `@orkestrel/database` memory-driver table, idle +
886
+ absolute-lifetime eviction (including the underlying row's removal),
887
+ `created` stamped once and preserved across re-set, guard rejection,
888
+ construction guards, rebuild through the injected restore step, and the
889
+ fail-closed read of a row stored under the earlier cursor columns.
890
+ - [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) —
891
+ `createCookieTransport`/`createHeaderTransport` round-trips over real
892
+ `Request`/`Response`, `createMemorySessionStore` shallow mirror,
893
+ `createRestoredSession` snapshot rebuilds and malformed refusals,
894
+ `createDatabaseSessionStore` construction guard.
895
+ - [`tests/src/core/middlewares.test.ts`](../tests/src/core/middlewares.test.ts) —
896
+ every battery's defaults, options, skip conditions, and the security
897
+ acceptance bar's invariants; the canonical onion composed end-to-end.
898
+ - [`tests/src/server/helpers.test.ts`](../tests/src/server/helpers.test.ts) —
899
+ traversal and SPA-fallback resolution, byte-signature matching, node zlib
900
+ compression, multipart limit resolution, boundary extraction, part-header
901
+ parsing, `streamFile`'s pull-driven backpressure and descriptor release, and
902
+ the uploaded-file operations, including `moveUploadedFile`'s cross-device
903
+ fallback where a runtime device probe finds a second filesystem.
904
+ - [`tests/src/server/parsers.test.ts`](../tests/src/server/parsers.test.ts) —
905
+ `parseMultipartRequest` end to end: field and file staging, dangerous-key
906
+ refusal, the sniff-authoritative allow-list, every limit and its boundary,
907
+ the malformed matrix, reader cancellation, abort mid-upload, and the staged
908
+ file permission bits.
909
+ - [`tests/src/server/MultipartParser.test.ts`](../tests/src/server/MultipartParser.test.ts) —
910
+ the interned state machine driven directly: the preamble cap, the
911
+ header-block cap, the total-bytes cap, the abort-mid-upload path, and the
912
+ staged-file cleanup each throw performs.
913
+ - [`tests/src/server/middlewares.test.ts`](../tests/src/server/middlewares.test.ts) —
914
+ in-memory asset ownership, key refusal, identity/Brotli negotiation,
915
+ conditional and HEAD responses, filesystem static serving, multipart
916
+ middleware, and the server-face composition.
917
+
918
+ ## See also
919
+
920
+ - [`AGENTS.md`](../AGENTS.md) — the coding rules this package is written under.
921
+ - `@orkestrel/server` — the frozen seam and substrate every battery in this
922
+ package is built over. Its mirrored guide is [`server.md`](server.md).
923
+ - `@orkestrel/contract` — the guards backing every construction boundary.
924
+ - `@orkestrel/budget` — `createLimiter`'s per-key tally.
925
+ - `@orkestrel/abort` / `@orkestrel/timeout` — `createDeadline`'s
926
+ signal-linking and timer.
927
+ - [`README.md`](README.md) — the guides index.