@orkestrel/middleware 0.0.19 → 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.
@@ -10,7 +10,10 @@ let node_crypto = require("node:crypto");
10
10
  let node_os = require("node:os");
11
11
  let _src_core = require("../core/index.cjs");
12
12
  //#region src/server/constants.ts
13
- /** Holds the HTTP status `createMultipart` renders for each {@link MultipartErrorCode}. */
13
+ /**
14
+ * Holds the HTTP status `createMultipart` renders for each {@link MultipartErrorCode}:
15
+ * `'limit'` is 413, `'malformed'` is 400, and `'rejected'` is 415.
16
+ */
14
17
  var MULTIPART_STATUS = Object.freeze({
15
18
  limit: 413,
16
19
  malformed: 400,
@@ -23,33 +26,33 @@ var MULTIPART_STATUS = Object.freeze({
23
26
  * which would mint an unequal symbol per copy.
24
27
  */
25
28
  var MULTIPART_ERROR_BRAND = Symbol.for("@orkestrel/middleware.MultipartError");
26
- /** Names `createStatic`'s default directory-index filename. */
29
+ /** Names `'index.html'`, `createStatic`'s default directory-index filename. */
27
30
  var DEFAULT_STATIC_INDEX = "index.html";
28
- /** Names `createStatic`'s `fallback: true` default excluded path prefix. */
31
+ /** Names `'/api'`, `createStatic`'s `fallback: true` default excluded path prefix. */
29
32
  var DEFAULT_STATIC_FALLBACK_EXCLUDE = "/api";
30
- /** Names `createStatic`'s default policy for a path carrying a dotfile segment. */
33
+ /** Names `'ignore'`, `createStatic`'s default policy for a path carrying a dotfile segment. */
31
34
  var DEFAULT_STATIC_DOTFILES = "ignore";
32
35
  /**
33
- * Lists the content-codings the node face's `createCompression` offers — the two
34
- * `node:zlib` guarantees on every Node runtime, so this face never
36
+ * Lists `['gzip', 'deflate']`, the content-codings the node face's `createCompression`
37
+ * offers — what `node:zlib` guarantees on every Node runtime, so this face never
35
38
  * feature-detects.
36
39
  */
37
40
  var NODE_COMPRESSION_ENCODINGS = Object.freeze(["gzip", "deflate"]);
38
- /** Names the MIME type served when a file extension has no known mapping. */
41
+ /** Names `'application/octet-stream'`, the MIME type served when a file extension has no known mapping. */
39
42
  var DEFAULT_CONTENT_TYPE = "application/octet-stream";
40
- /** Holds `createMultipart`'s default per-file byte-size cap. */
43
+ /** Holds `10_485_760`, `createMultipart`'s default per-file byte-size cap. */
41
44
  var DEFAULT_MULTIPART_FILE_SIZE = 10485760;
42
- /** Holds `createMultipart`'s default maximum file-part count. */
45
+ /** Holds `10`, `createMultipart`'s default maximum file-part count. */
43
46
  var DEFAULT_MULTIPART_FILE_COUNT = 10;
44
- /** Holds `createMultipart`'s default per-field byte-size cap. */
47
+ /** Holds `65_536`, `createMultipart`'s default per-field byte-size cap. */
45
48
  var DEFAULT_MULTIPART_FIELD_SIZE = 65536;
46
- /** Holds `createMultipart`'s default maximum field-part count. */
49
+ /** Holds `100`, `createMultipart`'s default maximum field-part count. */
47
50
  var DEFAULT_MULTIPART_FIELD_COUNT = 100;
48
- /** Holds `createMultipart`'s default combined request-body byte-size cap. */
51
+ /** Holds `52_428_800`, `createMultipart`'s default combined request-body byte-size cap. */
49
52
  var DEFAULT_MULTIPART_TOTAL = 52428800;
50
- /** Holds the maximum bytes a single multipart part's header block may occupy before it is malformed. */
53
+ /** Holds `16_384`, the maximum bytes a single multipart part's header block may occupy before it is malformed. */
51
54
  var MULTIPART_MAX_HEADER_BLOCK = 16384;
52
- /** Holds the maximum bytes scanned before the first multipart boundary is found before it is malformed. */
55
+ /** Holds `65_536`, the maximum bytes scanned before the first multipart boundary is found before it is malformed. */
53
56
  var MULTIPART_MAX_PREAMBLE = 65536;
54
57
  /**
55
58
  * Lists the Windows reserved device-name stems (CVE-2025-27210) — matched
@@ -168,7 +171,7 @@ function isMultipartError(value) {
168
171
  //#endregion
169
172
  //#region src/server/helpers.ts
170
173
  /**
171
- * Checks whether `pathname` is `prefix` itself or lies under it on a SEGMENT
174
+ * Checks whether `pathname` is `prefix` itself or lies under it on a segment
172
175
  * boundary — the shared under-path test `resolveStaticPath`'s prefix strip
173
176
  * and `createStatic`'s SPA-fallback `exclude` both apply, so `exclude:
174
177
  * '/api'` matches `/api` and `/api/x` but never `/apifoo`.
@@ -193,7 +196,7 @@ function isUnderPath(pathname, prefix) {
193
196
  * fallback.
194
197
  *
195
198
  * @remarks
196
- * `GET` and `HEAD` are both eligible, and resolve the SAME shell: `HEAD` is
199
+ * `GET` and `HEAD` are both eligible, and resolve the same shell: `HEAD` is
197
200
  * defined as `GET` without a body (RFC 9110 §9.3.2), so a navigation probe
198
201
  * that answered `404` while its `GET` answered `200` would report a resource
199
202
  * the very next request serves.
@@ -221,11 +224,11 @@ function resolveStaticFallbackPath(root, index, exclude, method, pathname, accep
221
224
  }
222
225
  /**
223
226
  * Checks whether `child` is `parent` itself or lies inside it on-disk — the
224
- * FILESYSTEM containment predicate `createStatic` applies to `fs.realpath`
227
+ * filesystem containment predicate `createStatic` applies to `fs.realpath`
225
228
  * output (never to a URL pathname — that is {@link isUnderPath}'s job).
226
229
  *
227
230
  * @remarks
228
- * Argument order is `(child, parent)` — deliberately the OPPOSITE conceptual
231
+ * Argument order is `(child, parent)` — deliberately the opposite conceptual
229
232
  * order from {@link isUnderPath}`(pathname, prefix)`, so a call site cannot
230
233
  * casually swap one predicate in for the other. Built on `path.relative`,
231
234
  * this is separator-correct on both POSIX (`/`) and win32 (`\`) — unlike a
@@ -248,14 +251,14 @@ function isContainedPath(child, parent) {
248
251
  return rel.length > 0 && rel !== ".." && !rel.startsWith(`..${node_path.sep}`) && !(0, node_path.isAbsolute)(rel);
249
252
  }
250
253
  /**
251
- * Resolves a request pathname to an on-disk path UNDER `root`, or `undefined`
254
+ * Resolves a request pathname to an on-disk path under `root`, or `undefined`
252
255
  * when it cannot — the traversal guard, whose algorithm and order are exact:
253
256
  * strip `prefix` on a segment boundary → `decodeURIComponent` (a
254
257
  * malformed escape refuses, never throws) → reject a NUL byte → strip the
255
- * leading path separator FIRST (so a leading `..` survives `normalize` as a
256
- * genuine climbing segment) → `normalize` → refuse any Windows reserved-
257
- * device-name segment ({@link isReservedDeviceName}) → `resolve` and require
258
- * the result under `root`.
258
+ * leading path separator first (so a leading `..` survives `normalize` as a
259
+ * genuine climbing segment) → `normalize` → refuse any Windows
260
+ * reserved-device-name segment ({@link isReservedDeviceName}) → `resolve` and
261
+ * require the result under `root`.
259
262
  *
260
263
  * @param root - The absolute root directory every result must resolve under
261
264
  * @param prefix - An optional URL path prefix stripped on a segment boundary
@@ -324,7 +327,7 @@ async function resolveContainedRealPath(candidate, rootReal) {
324
327
  *
325
328
  * @remarks
326
329
  * Normalizes superscript digits (`¹²³` → `123`) first, strips trailing dots
327
- * and spaces (Windows drops them), takes the STEM before the first `.`,
330
+ * and spaces (Windows drops them), takes the stem before the first `.`,
328
331
  * upper-cases it, and tests it against {@link RESERVED_DEVICE_NAMES}
329
332
  * (`CON PRN AUX NUL COM1-9 LPT1-9`) — an exact-stem match only, so
330
333
  * `console.js` and `nullable.css` are never flagged.
@@ -643,7 +646,7 @@ async function unlinkStagedFiles(body) {
643
646
  * response body routes through.
644
647
  *
645
648
  * @remarks
646
- * PULL-driven, not push-driven: the underlying node stream's async iterator
649
+ * Pull-driven, not push-driven: the underlying node stream's async iterator
647
650
  * is only advanced (`iterator.next()`) from inside `pull(controller)`, which
648
651
  * the web `ReadableStream` invokes exactly when its internal queue has room
649
652
  * for more data. Exactly one disk chunk is read and enqueued per `pull` —
@@ -980,30 +983,30 @@ var MultipartParser = class MultipartParser {
980
983
  *
981
984
  * @remarks
982
985
  * Reads `request.body` chunk by chunk through its `ReadableStream` reader —
983
- * NEVER buffers the whole body — enforcing every {@link MultipartLimits} cap
986
+ * never buffers the whole body — enforcing every {@link MultipartLimits} cap
984
987
  * the instant it is exceeded (reading stops, every already-staged temp file
985
988
  * is deleted, throws {@link MultipartError} with code `'limit'`). Each file
986
989
  * part streams to `join(directory, randomUUID())` — the client's declared
987
- * filename is METADATA ONLY, never a path component. A field OR file part
990
+ * filename is metadata only, never a path component. A field or file part
988
991
  * named `__proto__` / `constructor` / `prototype` is silently skipped and
989
992
  * never keyed onto the returned {@link MultipartBody} (a skipped file's
990
993
  * staged temp file is unlinked immediately, since it can never be
991
994
  * referenced). A file part with an empty declared filename (`filename=""`)
992
- * AND a zero-byte body — the browser convention for an unselected optional
995
+ * and a zero-byte body — the browser convention for an unselected optional
993
996
  * `<input type="file">` — is a silent no-op: its temp file is unlinked, it is
994
997
  * never counted against the `file.count` limit, and it never runs the
995
998
  * `allowed` check. A malformed
996
999
  * structure (missing/unterminated boundary, nameless part, an oversized
997
1000
  * header block, or a preamble exceeding {@link MULTIPART_MAX_PREAMBLE} before
998
1001
  * the first boundary) throws with code `'malformed'`. A file is accepted
999
- * against the configured `allowed` MIME list iff its SNIFFED bytes detect a
1002
+ * against the configured `allowed` MIME list exactly when its sniffed bytes detect a
1000
1003
  * type present in the list — sniff-authoritative, independent of whether the
1001
1004
  * declared `Content-Type` matches (that agreement is exposed separately as
1002
1005
  * `validated`); otherwise throws with code `'rejected'`. A
1003
1006
  * request abort mid-upload triggers the same fail-closed cleanup as a limit
1004
1007
  * breach. Returns `undefined` for a non-multipart request (untouched).
1005
1008
  *
1006
- * Staging defaults to a process-owned directory created ONCE (lazily,
1009
+ * Staging defaults to a process-owned directory created once (lazily,
1007
1010
  * memoized across calls) with `mkdtemp` under `os.tmpdir()` and locked to
1008
1011
  * mode `0o700`; `options.directory` overrides it.
1009
1012
  *
@@ -1124,16 +1127,16 @@ function createAssets(options) {
1124
1127
  };
1125
1128
  }
1126
1129
  /**
1127
- * Serves static files from `options.root` over `node:fs` — the node-bound
1128
- * static-file battery.
1130
+ * Serves static files from `options.root` over `node:fs` — the node-bound static-file
1131
+ * battery, answering conditional, ranged, and SPA-fallback requests.
1129
1132
  *
1130
1133
  * @remarks
1131
- * Containment is enforced on CANONICAL paths, not merely the lexically
1134
+ * Containment is enforced on canonical paths, not merely the lexically
1132
1135
  * resolved one: `options.root` is canonicalized once (memoized) and every
1133
1136
  * request's candidate path is re-canonicalized (`fs.realpath`) before it is
1134
1137
  * served, so a symlink whose target escapes `root` is refused (falls through
1135
1138
  * to `next()`) even though the lexical path resolved inside `root`. A
1136
- * symlink that resolves to a target still INSIDE `root` is unaffected and
1139
+ * symlink that resolves to a target still inside `root` is unaffected and
1137
1140
  * still serves normally. A dangling symlink (`realpath` throws `ENOENT`) or
1138
1141
  * any other `realpath` failure is treated as a miss — this battery never
1139
1142
  * throws or 500s on a symlink surprise. On a streamed response (a 200 or 206
@@ -1329,7 +1332,7 @@ function createStatic(options) {
1329
1332
  * {@link MultipartError} this battery's parser throws is re-thrown as an
1330
1333
  * {@link HTTPError} carrying the same status/message, so `createBoundary`
1331
1334
  * (or any HTTPError-aware renderer) maps it correctly without depending on
1332
- * this node face's error type. Fail-closed on the DOWNSTREAM handler too: if
1335
+ * this node face's error type. Fail-closed on the downstream handler too: if
1333
1336
  * `next()` throws, every still-`'staged'` uploaded file is unlinked
1334
1337
  * (best-effort) before the error is re-thrown, so an unhandled downstream
1335
1338
  * failure never leaks temp files. A normal return leaves staged files
@@ -1368,12 +1371,11 @@ function createMultipart(options = {}) {
1368
1371
  };
1369
1372
  }
1370
1373
  /**
1371
- * Compresses response bodies through `node:zlib` the node-bound sibling of
1372
- * the core face's `CompressionStream`-feature-detected `createCompression`,
1373
- * guaranteed available on any Node runtime rather than dependent on the
1374
- * WHATWG `CompressionStream` global. Ships as a SEPARATE package entry point
1375
- * (`@orkestrel/middleware/server`) from the core face's `createCompression`,
1376
- * so the shared name is unambiguous per consumer import path.
1374
+ * Compresses response bodies through `node:zlib`, guaranteed on any Node runtime rather
1375
+ * than dependent on the WHATWG `CompressionStream` global. This battery is the
1376
+ * node-bound sibling of the core face's feature-detected `createCompression`, and it
1377
+ * ships from a separate package entry point (`@orkestrel/middleware/server`) so the
1378
+ * shared name is unambiguous per consumer import path.
1377
1379
  *
1378
1380
  * @remarks
1379
1381
  * Peer-type limitation, the same one the core face carries: the shipped