@orkestrel/middleware 0.0.18 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,13 @@
1
- import { Encoding } from '@orkestrel/server';
2
- import { FileHandle } from 'node:fs/promises';
3
- import { MiddlewareHandler } from '@orkestrel/server';
4
- import { MultipartBody } from '@orkestrel/middleware';
5
- import { MultipartFile } from '@orkestrel/middleware';
6
- import { MultipartState } from '@orkestrel/middleware';
1
+ import type { Encoding } from '@orkestrel/server';
2
+ import type { FileHandle } from 'node:fs/promises';
3
+ import { HTTPError } from '@orkestrel/server';
4
+ import type { MiddlewareHandler } from '@orkestrel/server';
5
+ import type { MultipartBody } from '@orkestrel/middleware';
6
+ import type { MultipartFile } from '@orkestrel/middleware';
7
+ import type { MultipartState } from '@orkestrel/middleware';
7
8
 
8
9
  /**
9
- * One in-memory asset representation returned by an {@link AssetSourceInterface}.
10
+ * Describes one in-memory asset representation returned by an {@link AssetSourceInterface}.
10
11
  *
11
12
  * @remarks
12
13
  * - `body` — the representation bytes. `createAssets` copies them before use.
@@ -19,26 +20,32 @@ export declare interface Asset {
19
20
  }
20
21
 
21
22
  /**
22
- * Options for `createAssets` — in-memory identity/Brotli asset serving.
23
+ * Configures `createAssets` — in-memory identity/Brotli asset serving.
23
24
  *
24
- * @param options - See fields below
25
25
  * @remarks
26
- * - `source` — the required in-memory asset reader.
26
+ * - `source` — the required in-memory asset reader. It must answer a bounded
27
+ * key set and return `undefined` for every key outside it, because
28
+ * `createAssets` retains every successful result for the factory's lifetime
29
+ * and evicts nothing. A `source` that synthesizes a representation for an
30
+ * arbitrary key therefore grows that cache without limit under request
31
+ * pressure.
27
32
  */
28
33
  export declare interface AssetOptions {
29
34
  readonly source: AssetSourceInterface;
30
35
  }
31
36
 
32
37
  /**
33
- * Read in-memory assets by decoded, browser-build-relative path.
38
+ * Reads in-memory assets by decoded, browser-build-relative path.
34
39
  *
35
40
  * @remarks
36
41
  * A successful result is cached by `createAssets`; later source changes do
37
42
  * not alter that path's response. A miss may be read again on a later request.
43
+ * `read` therefore owes a bounded key set: that cache retains every
44
+ * successful result for the factory's lifetime and evicts nothing.
38
45
  */
39
46
  export declare interface AssetSourceInterface {
40
47
  /**
41
- * Read one asset representation.
48
+ * Reads one identity or Brotli asset representation for a validated relative key.
42
49
  *
43
50
  * @param path - The validated relative asset path
44
51
  * @returns The asset, or `undefined` when the path is absent
@@ -47,7 +54,24 @@ export declare interface AssetSourceInterface {
47
54
  }
48
55
 
49
56
  /**
50
- * Compress response bytes with Node's guaranteed zlib gzip/deflate codecs.
57
+ * Describes one inclusive byte range over a file — `streamFile`'s optional
58
+ * `range` argument and the shape `createStatic` builds for a satisfiable
59
+ * `Range` request.
60
+ *
61
+ * @remarks
62
+ * - `start` — the first byte offset served.
63
+ * - `end` — the last byte offset served.
64
+ *
65
+ * Both bounds are 0-indexed and inclusive, matching `node:fs`'s
66
+ * `createReadStream` options.
67
+ */
68
+ export declare interface ByteRange {
69
+ readonly start: number;
70
+ readonly end: number;
71
+ }
72
+
73
+ /**
74
+ * Compresses response bytes with Node's guaranteed zlib gzip/deflate codecs.
51
75
  *
52
76
  * @param bytes - The uncompressed response bytes
53
77
  * @param encoding - The negotiated actionable coding
@@ -62,7 +86,7 @@ export declare interface AssetSourceInterface {
62
86
  export declare function compressNodeBytes(bytes: Uint8Array<ArrayBuffer>, encoding: Exclude<Encoding, 'identity'>): Promise<Uint8Array<ArrayBuffer>>;
63
87
 
64
88
  /**
65
- * Compute a static file's weak ETag from its size and modification time.
89
+ * Computes a static file's weak ETag from its size and modification time.
66
90
  *
67
91
  * @param size - The file's byte size
68
92
  * @param mtimeMs - The file's modification time in milliseconds
@@ -76,7 +100,7 @@ export declare function compressNodeBytes(bytes: Uint8Array<ArrayBuffer>, encodi
76
100
  export declare function computeFileETag(size: number, mtimeMs: number): string;
77
101
 
78
102
  /**
79
- * Serve validated in-memory assets with identity/Brotli negotiation.
103
+ * Serves validated in-memory assets with identity/Brotli negotiation.
80
104
  *
81
105
  * @remarks
82
106
  * Only `GET` and `HEAD` are served. `/` resolves to `index.html`. Every other
@@ -112,20 +136,18 @@ export declare function computeFileETag(size: number, mtimeMs: number): string;
112
136
  export declare function createAssets<TState>(options: AssetOptions): MiddlewareHandler<TState>;
113
137
 
114
138
  /**
115
- * Compress response bodies via `node:zlib` the node-bound sibling of the
116
- * core face's `CompressionStream`-feature-detected `createCompression`,
117
- * guaranteed available on any Node runtime rather than dependent on the
118
- * WHATWG `CompressionStream` global (PROPOSAL §4.3, ruling J). Ships as a
119
- * SEPARATE package entry point (`@orkestrel/middleware/server`) from the core
120
- * face's `createCompression`, so the shared name is unambiguous per
121
- * consumer import path (ruling H).
139
+ * Compresses response bodies through `node:zlib`, guaranteed on any Node runtime rather
140
+ * than dependent on the WHATWG `CompressionStream` global. This battery is the
141
+ * node-bound sibling of the core face's feature-detected `createCompression`, and it
142
+ * ships from a separate package entry point (`@orkestrel/middleware/server`) so the
143
+ * shared name is unambiguous per consumer import path.
122
144
  *
123
145
  * @remarks
124
- * Peer-type limitation (same one U1 recorded on the core face): the shipped
146
+ * Peer-type limitation, the same one the core face carries: the shipped
125
147
  * `@orkestrel/server` `Encoding` union is `'gzip' | 'deflate' | 'identity'`
126
148
  * — it does not include `'br'`, so this battery cannot honestly type or
127
149
  * negotiate a guaranteed brotli coding despite `node:zlib` shipping
128
- * `brotliCompress`. It guarantees `gzip`/`deflate` via `node:zlib` (never
150
+ * `brotliCompress`. It guarantees `gzip`/`deflate` through `node:zlib` (never
129
151
  * feature-detected — always available) and negotiates only those.
130
152
  *
131
153
  * @typeParam TState - The consumer's opaque per-request state type
@@ -144,9 +166,9 @@ export declare function createAssets<TState>(options: AssetOptions): MiddlewareH
144
166
  export declare function createCompression<TState>(options?: NodeCompressionOptions): MiddlewareHandler<TState>;
145
167
 
146
168
  /**
147
- * Parse a streamed `multipart/form-data` request body and stash its
169
+ * Parses a streamed `multipart/form-data` request body and stashes its
148
170
  * {@link MultipartBody} on `context.state.multipart` — the node-bound
149
- * streaming multipart battery (PROPOSAL §4.15, ruling C).
171
+ * streaming multipart battery.
150
172
  *
151
173
  * @remarks
152
174
  * A non-multipart request passes through untouched. Consumes `request.body`
@@ -155,7 +177,7 @@ export declare function createCompression<TState>(options?: NodeCompressionOptio
155
177
  * {@link MultipartError} this battery's parser throws is re-thrown as an
156
178
  * {@link HTTPError} carrying the same status/message, so `createBoundary`
157
179
  * (or any HTTPError-aware renderer) maps it correctly without depending on
158
- * this node face's error type. Fail-closed on the DOWNSTREAM handler too: if
180
+ * this node face's error type. Fail-closed on the downstream handler too: if
159
181
  * `next()` throws, every still-`'staged'` uploaded file is unlinked
160
182
  * (best-effort) before the error is re-thrown, so an unhandled downstream
161
183
  * failure never leaks temp files. A normal return leaves staged files
@@ -177,23 +199,23 @@ export declare function createCompression<TState>(options?: NodeCompressionOptio
177
199
  export declare function createMultipart<TState extends MultipartState>(options?: MultipartOptions): MiddlewareHandler<TState>;
178
200
 
179
201
  /**
180
- * Serve static files from `options.root` over `node:fs` — the node-bound
181
- * static-file battery (PROPOSAL §4.14).
202
+ * Serves static files from `options.root` over `node:fs` — the node-bound static-file
203
+ * battery, answering conditional, ranged, and SPA-fallback requests.
182
204
  *
183
205
  * @remarks
184
- * Containment is enforced on CANONICAL paths, not merely the lexically
206
+ * Containment is enforced on canonical paths, not merely the lexically
185
207
  * resolved one: `options.root` is canonicalized once (memoized) and every
186
208
  * request's candidate path is re-canonicalized (`fs.realpath`) before it is
187
209
  * served, so a symlink whose target escapes `root` is refused (falls through
188
210
  * to `next()`) even though the lexical path resolved inside `root`. A
189
- * symlink that resolves to a target still INSIDE `root` is unaffected and
211
+ * symlink that resolves to a target still inside `root` is unaffected and
190
212
  * still serves normally. A dangling symlink (`realpath` throws `ENOENT`) or
191
213
  * any other `realpath` failure is treated as a miss — this battery never
192
214
  * throws or 500s on a symlink surprise. On a streamed response (a 200 or 206
193
215
  * that carries a file body), the open `FileHandle` is owned by the
194
216
  * `Response` body and is released only once that body is fully read or
195
217
  * cancelled — Node HTTP servers do this automatically when sending the
196
- * response, but a caller that holds an unread `Response` (e.g. in a test)
218
+ * response, but a caller that holds an unread `Response` (for example in a test)
197
219
  * must cancel its body to release the handle promptly.
198
220
  *
199
221
  * @typeParam TState - The consumer's opaque per-request state type
@@ -211,47 +233,56 @@ export declare function createMultipart<TState extends MultipartState>(options?:
211
233
  export declare function createStatic<TState>(options: StaticOptions): MiddlewareHandler<TState>;
212
234
 
213
235
  /**
214
- * Build a frozen {@link UploadedFileInterface} record.
236
+ * Builds a frozen {@link UploadedFile} record.
215
237
  *
216
238
  * @param input - Every field of the record
217
- * @returns A frozen {@link UploadedFileInterface}
239
+ * @returns A frozen {@link UploadedFile}
218
240
  *
219
241
  * @example
220
242
  * ```ts
221
243
  * createUploadedFile({ field: 'avatar', name: 'a.png', size: 1024, mime: 'image/png', validated: true, status: 'staged', path: '/tmp/x' })
222
244
  * ```
223
245
  */
224
- export declare function createUploadedFile(input: UploadedFileInput): UploadedFileInterface;
246
+ export declare function createUploadedFile(input: UploadedFile): UploadedFile;
225
247
 
226
- /** The MIME type served when a file extension has no known mapping. */
248
+ /** Names `'application/octet-stream'`, the MIME type served when a file extension has no known mapping. */
227
249
  export declare const DEFAULT_CONTENT_TYPE = "application/octet-stream";
228
250
 
229
- /** `createMultipart`'s default per-field byte-size cap. */
230
- export declare const DEFAULT_MULTIPART_FIELD = 65536;
251
+ /** Holds `100`, `createMultipart`'s default maximum field-part count. */
252
+ export declare const DEFAULT_MULTIPART_FIELD_COUNT = 100;
231
253
 
232
- /** `createMultipart`'s default maximum field-part count. */
233
- export declare const DEFAULT_MULTIPART_FIELDS = 100;
254
+ /** Holds `65_536`, `createMultipart`'s default per-field byte-size cap. */
255
+ export declare const DEFAULT_MULTIPART_FIELD_SIZE = 65536;
234
256
 
235
- /** `createMultipart`'s default per-file byte-size cap. */
236
- export declare const DEFAULT_MULTIPART_FILE = 10485760;
257
+ /** Holds `10`, `createMultipart`'s default maximum file-part count. */
258
+ export declare const DEFAULT_MULTIPART_FILE_COUNT = 10;
237
259
 
238
- /** `createMultipart`'s default maximum file-part count. */
239
- export declare const DEFAULT_MULTIPART_FILES = 10;
260
+ /** Holds `10_485_760`, `createMultipart`'s default per-file byte-size cap. */
261
+ export declare const DEFAULT_MULTIPART_FILE_SIZE = 10485760;
240
262
 
241
- /** `createMultipart`'s default combined request-body byte-size cap. */
263
+ /** Holds `52_428_800`, `createMultipart`'s default combined request-body byte-size cap. */
242
264
  export declare const DEFAULT_MULTIPART_TOTAL = 52428800;
243
265
 
244
- /** `createStatic`'s `fallback: true` default excluded path prefix. */
266
+ /** Names `'ignore'`, `createStatic`'s default policy for a path carrying a dotfile segment. */
267
+ export declare const DEFAULT_STATIC_DOTFILES: NonNullable<StaticOptions['dotfiles']>;
268
+
269
+ /** Names `'/api'`, `createStatic`'s `fallback: true` default excluded path prefix. */
245
270
  export declare const DEFAULT_STATIC_FALLBACK_EXCLUDE = "/api";
246
271
 
247
- /** `createStatic`'s default directory-index filename. */
272
+ /** Names `'index.html'`, `createStatic`'s default directory-index filename. */
248
273
  export declare const DEFAULT_STATIC_INDEX = "index.html";
249
274
 
250
275
  /**
251
- * Sniff a MIME type from a file's leading bytes against a small magic-byte
252
- * table (jpeg, png, gif87a/89a, webp, pdf, zip) — the SNIFF-AUTHORITATIVE
253
- * signal `createMultipart`'s type validation rests on, never the declared
254
- * `Content-Type`.
276
+ * Sniffs a MIME type from a file's leading bytes against a small magic-byte
277
+ * table (jpeg, png, gif87a/89a, webp, pdf, zip).
278
+ *
279
+ * @remarks
280
+ * `createMultipart`'s `allowed` check reads this signal alone and never the
281
+ * declared `Content-Type`, so a file whose bytes match no signature can never
282
+ * be placed on an `allowed` list. The record's `mime` field is a different
283
+ * question: it falls back to the declared `Content-Type` (then to
284
+ * {@link DEFAULT_CONTENT_TYPE}) when nothing sniffs, and its `validated` flag
285
+ * reports whether the sniffed and declared types agreed.
255
286
  *
256
287
  * @param head - The file's first bytes (16 is sufficient for every signature)
257
288
  * @returns The detected MIME type, or `undefined` when no signature matches
@@ -263,16 +294,32 @@ export declare const DEFAULT_STATIC_INDEX = "index.html";
263
294
  */
264
295
  export declare function detectMIME(head: Uint8Array): string | undefined;
265
296
 
266
- /** File-extension (lowercase, with leading `.`) → MIME type lookup table for static serving. */
297
+ /** Holds the file-extension (lowercase, with leading `.`) → MIME type lookup table for static serving. */
267
298
  export declare const EXTENSION_TYPES: Readonly<Record<string, string>>;
268
299
 
269
300
  /**
270
- * Whether `child` is `parent` itself or lies inside it on-disk the
271
- * FILESYSTEM containment predicate `createStatic` applies to `fs.realpath`
301
+ * Extracts the `boundary` parameter from a `Content-Type` header, or
302
+ * `undefined` when the request is not `multipart/form-data`.
303
+ *
304
+ * @param contentType - The request's `Content-Type` header value, if present
305
+ * @returns The multipart boundary token, or `undefined` for a non-multipart
306
+ * (or malformed/boundary-less) content type
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * extractMultipartBoundary('multipart/form-data; boundary=abc123') // 'abc123'
311
+ * extractMultipartBoundary('application/json') // undefined
312
+ * ```
313
+ */
314
+ export declare function extractMultipartBoundary(contentType: string | null): string | undefined;
315
+
316
+ /**
317
+ * Checks whether `child` is `parent` itself or lies inside it on-disk — the
318
+ * filesystem containment predicate `createStatic` applies to `fs.realpath`
272
319
  * output (never to a URL pathname — that is {@link isUnderPath}'s job).
273
320
  *
274
321
  * @remarks
275
- * Argument order is `(child, parent)` — deliberately the OPPOSITE conceptual
322
+ * Argument order is `(child, parent)` — deliberately the opposite conceptual
276
323
  * order from {@link isUnderPath}`(pathname, prefix)`, so a call site cannot
277
324
  * casually swap one predicate in for the other. Built on `path.relative`,
278
325
  * this is separator-correct on both POSIX (`/`) and win32 (`\`) — unlike a
@@ -281,7 +328,7 @@ export declare const EXTENSION_TYPES: Readonly<Record<string, string>>;
281
328
  *
282
329
  * @param child - The absolute on-disk path to test
283
330
  * @param parent - The absolute on-disk directory it must lie under
284
- * @returns `true` when `child` equals `parent` or resolves inside it
331
+ * @returns True if `child` equals `parent` or resolves inside it; false otherwise
285
332
  *
286
333
  * @example
287
334
  * ```ts
@@ -292,11 +339,11 @@ export declare const EXTENSION_TYPES: Readonly<Record<string, string>>;
292
339
  export declare function isContainedPath(child: string, parent: string): boolean;
293
340
 
294
341
  /**
295
- * Whether a relative path (already resolved under a static root) has any
342
+ * Checks whether a relative path (already resolved under a static root) has any
296
343
  * segment starting with `.` — a dotfile or dot-directory.
297
344
  *
298
345
  * @param relativePath - A path relative to the static root
299
- * @returns `true` when any segment starts with `.`
346
+ * @returns True if any segment starts with `.`; false otherwise
300
347
  *
301
348
  * @example
302
349
  * ```ts
@@ -307,16 +354,16 @@ export declare function isContainedPath(child: string, parent: string): boolean;
307
354
  export declare function isDotfilePath(relativePath: string): boolean;
308
355
 
309
356
  /**
310
- * Narrow an unknown caught value to a {@link MultipartError}.
357
+ * Narrows an unknown caught value to a {@link MultipartError}.
311
358
  *
312
359
  * @remarks
313
360
  * Structural, not `instanceof` — tests that `value` is a non-null object
314
- * carrying the module-scope brand, a numeric `status`, and a `reason` in the
315
- * parser's set of reason strings (`'limit' | 'malformed' | 'rejected'`).
361
+ * carrying {@link MULTIPART_ERROR_BRAND}, a numeric `status`, and a `code`
362
+ * in the parser's {@link MultipartErrorCode} set (`'limit' | 'malformed' | 'rejected'`).
316
363
  * Total: never throws, returns `false` for any off-shape input.
317
364
  *
318
365
  * @param value - The value to test (typically a `catch` binding)
319
- * @returns `true` when `value` is a {@link MultipartError}
366
+ * @returns True if `value` is a {@link MultipartError}; false otherwise
320
367
  *
321
368
  * @example
322
369
  * ```ts
@@ -325,24 +372,24 @@ export declare function isDotfilePath(relativePath: string): boolean;
325
372
  * try {
326
373
  * await parse(request)
327
374
  * } catch (error) {
328
- * if (isMultipartError(error)) console.log(error.status, error.reason)
375
+ * if (isMultipartError(error)) console.log(error.status, error.code)
329
376
  * }
330
377
  * ```
331
378
  */
332
379
  export declare function isMultipartError(value: unknown): value is MultipartError;
333
380
 
334
381
  /**
335
- * Whether a path segment is a Windows reserved device name (CVE-2025-27210).
382
+ * Checks whether a path segment is a Windows reserved device name (CVE-2025-27210).
336
383
  *
337
384
  * @remarks
338
385
  * Normalizes superscript digits (`¹²³` → `123`) first, strips trailing dots
339
- * and spaces (Windows drops them), takes the STEM before the first `.`,
386
+ * and spaces (Windows drops them), takes the stem before the first `.`,
340
387
  * upper-cases it, and tests it against {@link RESERVED_DEVICE_NAMES}
341
388
  * (`CON PRN AUX NUL COM1-9 LPT1-9`) — an exact-stem match only, so
342
389
  * `console.js` and `nullable.css` are never flagged.
343
390
  *
344
391
  * @param segment - One path segment (no separators)
345
- * @returns `true` when `segment` names a reserved device
392
+ * @returns True if `segment` names a reserved device; false otherwise
346
393
  *
347
394
  * @example
348
395
  * ```ts
@@ -354,14 +401,14 @@ export declare function isMultipartError(value: unknown): value is MultipartErro
354
401
  export declare function isReservedDeviceName(segment: string): boolean;
355
402
 
356
403
  /**
357
- * Whether `pathname` is `prefix` itself or lies under it on a SEGMENT
404
+ * Checks whether `pathname` is `prefix` itself or lies under it on a segment
358
405
  * boundary — the shared under-path test `resolveStaticPath`'s prefix strip
359
406
  * and `createStatic`'s SPA-fallback `exclude` both apply, so `exclude:
360
407
  * '/api'` matches `/api` and `/api/x` but never `/apifoo`.
361
408
  *
362
409
  * @param pathname - The request pathname to test
363
410
  * @param prefix - The path prefix to test against
364
- * @returns `true` when `pathname` equals `prefix` or starts with `prefix` + `/`
411
+ * @returns True if `pathname` equals `prefix` or starts with `prefix` + `/`; false otherwise
365
412
  *
366
413
  * @example
367
414
  * ```ts
@@ -372,7 +419,7 @@ export declare function isReservedDeviceName(segment: string): boolean;
372
419
  export declare function isUnderPath(pathname: string, prefix: string): boolean;
373
420
 
374
421
  /**
375
- * Look up the MIME type for a static file path by its extension.
422
+ * Looks up the MIME type for a static file path by its extension.
376
423
  *
377
424
  * @param pathname - The file's path (only its extension is read)
378
425
  * @returns The mapped MIME type, or {@link DEFAULT_CONTENT_TYPE} when unknown
@@ -385,12 +432,12 @@ export declare function isUnderPath(pathname: string, prefix: string): boolean;
385
432
  export declare function lookupContentType(pathname: string): string;
386
433
 
387
434
  /**
388
- * Whether `bytes` contains `signature` at the requested offset.
435
+ * Checks whether `bytes` contains `signature` at the requested offset.
389
436
  *
390
437
  * @param bytes - The bytes to inspect
391
438
  * @param signature - The exact byte sequence to match
392
439
  * @param offset - The starting byte offset, defaulting to zero
393
- * @returns `true` when the complete signature matches
440
+ * @returns True if the complete signature matches; false otherwise
394
441
  *
395
442
  * @example
396
443
  * ```ts
@@ -400,59 +447,59 @@ export declare function lookupContentType(pathname: string): string;
400
447
  export declare function matchesBytes(bytes: Uint8Array, signature: readonly number[], offset?: number): boolean;
401
448
 
402
449
  /**
403
- * Move a staged uploaded file to its final `destination`.
450
+ * Moves a staged uploaded file to its final `destination`.
404
451
  *
405
452
  * @remarks
406
453
  * Attempts a `rename` first; on a cross-device error (`EXDEV`) falls back to
407
454
  * `copyFile` + `unlink`. Returns a new frozen record with `status: 'moved'`
408
- * and `path: destination` — the input record is never mutated.
455
+ * and `path: destination` — the input record is never mutated. The suite
456
+ * drives the `EXDEV` fallback only on a host whose device probe finds a
457
+ * second filesystem, so a single-device host leaves that branch unproven.
409
458
  *
410
- * @param file - The {@link UploadedFileInterface} record to move
459
+ * @param file - The {@link UploadedFile} record to move
411
460
  * @param destination - The final on-disk path
412
- * @returns A new {@link UploadedFileInterface} record reflecting the move
461
+ * @returns A new {@link UploadedFile} record reflecting the move
413
462
  *
414
463
  * @example
415
464
  * ```ts
416
465
  * const moved = await moveUploadedFile(file, '/var/uploads/final.png')
417
466
  * ```
418
467
  */
419
- export declare function moveUploadedFile(file: UploadedFileInterface, destination: string): Promise<UploadedFileInterface>;
468
+ export declare function moveUploadedFile(file: UploadedFile, destination: string): Promise<UploadedFile>;
469
+
470
+ /**
471
+ * Holds the `Symbol.for` brand {@link MultipartError} carries so
472
+ * {@link isMultipartError} recognizes an instance across duplicate copies of
473
+ * this package — a registry symbol rather than a module-local `Symbol()`,
474
+ * which would mint an unequal symbol per copy.
475
+ */
476
+ export declare const MULTIPART_ERROR_BRAND: unique symbol;
420
477
 
421
- /** The maximum bytes a single multipart part's header block may occupy before it is malformed. */
478
+ /** Holds `16_384`, the maximum bytes a single multipart part's header block may occupy before it is malformed. */
422
479
  export declare const MULTIPART_MAX_HEADER_BLOCK = 16384;
423
480
 
424
- /** The maximum bytes scanned before the first multipart boundary is found before it is malformed. */
481
+ /** Holds `65_536`, the maximum bytes scanned before the first multipart boundary is found before it is malformed. */
425
482
  export declare const MULTIPART_MAX_PREAMBLE = 65536;
426
483
 
427
- /** The HTTP status `createMultipart` renders for each {@link MultipartReason}. */
428
- export declare const MULTIPART_REASON_STATUS: Readonly<Record<MultipartReason, number>>;
429
-
430
484
  /**
431
- * Extract the `boundary` parameter from a `Content-Type` header, or
432
- * `undefined` when the request is not `multipart/form-data`.
433
- *
434
- * @param contentType - The request's `Content-Type` header value, if present
435
- * @returns The multipart boundary token, or `undefined` for a non-multipart
436
- * (or malformed/boundary-less) content type
437
- *
438
- * @example
439
- * ```ts
440
- * multipartBoundary('multipart/form-data; boundary=abc123') // 'abc123'
441
- * multipartBoundary('application/json') // undefined
442
- * ```
485
+ * Holds the HTTP status `createMultipart` renders for each {@link MultipartErrorCode}:
486
+ * `'limit'` is 413, `'malformed'` is 400, and `'rejected'` is 415.
443
487
  */
444
- export declare function multipartBoundary(contentType: string | null): string | undefined;
488
+ export declare const MULTIPART_STATUS: Readonly<Record<MultipartErrorCode, number>>;
445
489
 
446
490
  /**
447
- * An error `createMultipart` throws when a streamed multipart request fails
491
+ * Represents an error `createMultipart` throws when a streamed multipart request fails
448
492
  * a mid-stream limit, is structurally malformed, or has a file whose sniffed
449
493
  * bytes are rejected by the configured `allowed` MIME list.
450
494
  *
451
495
  * @remarks
452
- * Carries the HTTP `status` derived from `reason` (limit → 413, malformed →
453
- * 400, rejected 415) and an optional `context` record. Rendered by
454
- * `createBoundary` like any other `HTTPError`-shaped throw. Narrow a caught
455
- * value with {@link isMultipartError}.
496
+ * Extends the peer `HTTPError`, which already publishes the `status`,
497
+ * `context`, and brand members every fleet error of this shape carries, and
498
+ * adds the machine-readable `code` axis a caller narrows on. `status` is
499
+ * derived from `code` through {@link MULTIPART_STATUS} (limit → 413, malformed →
500
+ * 400, rejected → 415), so `createBoundary` — or any other `isHTTPError`-aware
501
+ * renderer — maps it without knowing this face's error type. Narrow a caught
502
+ * value to the richer type with {@link isMultipartError}.
456
503
  *
457
504
  * @example
458
505
  * ```ts
@@ -461,74 +508,107 @@ export declare function multipartBoundary(contentType: string | null): string |
461
508
  * throw new MultipartError('limit', 'too many files')
462
509
  * ```
463
510
  */
464
- export declare class MultipartError extends Error {
465
- readonly status: number;
466
- readonly reason: MultipartReason;
467
- readonly context?: Readonly<Record<string, unknown>>;
468
- constructor(reason: MultipartReason, message: string, context?: Readonly<Record<string, unknown>>);
511
+ export declare class MultipartError extends HTTPError {
512
+ readonly code: MultipartErrorCode;
513
+ readonly [MULTIPART_ERROR_BRAND] = true;
514
+ constructor(code: MultipartErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
469
515
  }
470
516
 
471
517
  /**
472
- * Per-category size/count caps `createMultipart` enforces MID-STREAM.
518
+ * Names the reason `createMultipart` rejected a request — the machine-readable code
519
+ * {@link MultipartError} carries and maps onto its HTTP status: `'limit'` →
520
+ * 413, `'malformed'` → 400, `'rejected'` → 415.
521
+ */
522
+ export declare type MultipartErrorCode = 'limit' | 'malformed' | 'rejected';
523
+
524
+ /**
525
+ * Describes the per-category size/count caps `createMultipart` enforces mid-stream — the
526
+ * effective limits, every documented default already applied.
473
527
  *
474
528
  * @remarks
475
- * - `file` — the maximum size in bytes of one uploaded file; defaults to
476
- * {@link DEFAULT_MULTIPART_FILE}.
477
- * - `files` — the maximum number of file parts; defaults to
478
- * {@link DEFAULT_MULTIPART_FILES}.
479
- * - `field` — the maximum size in bytes of one text field; defaults to
480
- * {@link DEFAULT_MULTIPART_FIELD}.
481
- * - `fields` — the maximum number of text field parts; defaults to
482
- * {@link DEFAULT_MULTIPART_FIELDS}.
529
+ * - `file.size` — the maximum size in bytes of one uploaded file; defaults to
530
+ * {@link DEFAULT_MULTIPART_FILE_SIZE}.
531
+ * - `file.count` — the maximum number of file parts; defaults to
532
+ * {@link DEFAULT_MULTIPART_FILE_COUNT}.
533
+ * - `field.size` — the maximum size in bytes of one text field; defaults to
534
+ * {@link DEFAULT_MULTIPART_FIELD_SIZE}.
535
+ * - `field.count` — the maximum number of text field parts; defaults to
536
+ * {@link DEFAULT_MULTIPART_FIELD_COUNT}.
483
537
  * - `total` — the maximum combined byte size of the whole request body;
484
538
  * defaults to {@link DEFAULT_MULTIPART_TOTAL}.
485
539
  */
486
540
  export declare interface MultipartLimits {
487
- readonly file?: number;
488
- readonly files?: number;
489
- readonly field?: number;
490
- readonly fields?: number;
541
+ readonly file: {
542
+ readonly size: number;
543
+ readonly count: number;
544
+ };
545
+ readonly field: {
546
+ readonly size: number;
547
+ readonly count: number;
548
+ };
549
+ readonly total: number;
550
+ }
551
+
552
+ /**
553
+ * Describes the caller's partial {@link MultipartLimits} — `createMultipart`'s `limits`
554
+ * option, with every member optional.
555
+ *
556
+ * @remarks
557
+ * `resolveMultipartLimits` applies each documented default to an omitted leaf,
558
+ * so a caller states only the caps it wants to move.
559
+ * - `file` — the per-file caps: `size` in bytes, `count` of file parts.
560
+ * - `field` — the per-field caps: `size` in bytes, `count` of text field parts.
561
+ * - `total` — the maximum combined byte size of the whole request body.
562
+ */
563
+ export declare interface MultipartLimitsInput {
564
+ readonly file?: {
565
+ readonly size?: number;
566
+ readonly count?: number;
567
+ };
568
+ readonly field?: {
569
+ readonly size?: number;
570
+ readonly count?: number;
571
+ };
491
572
  readonly total?: number;
492
573
  }
493
574
 
494
575
  /**
495
- * Options for `createMultipart` — node `fs`/`os`/`crypto`-backed streaming
576
+ * Configures `createMultipart` — node `fs`/`os`/`crypto`-backed streaming
496
577
  * multipart upload parsing.
497
578
  *
498
- * @param options - See fields below
499
579
  * @remarks
500
- * - `limits` — see {@link MultipartLimits}.
501
- * - `allowed` — a MIME allow-list validated against SNIFFED (not merely
580
+ * - `limits` — see {@link MultipartLimitsInput}.
581
+ * - `allowed` — a MIME allow-list validated against sniffed (not merely
502
582
  * declared) bytes; an empty array allows nothing. Omitted ⇒ no type
503
583
  * rejection.
504
584
  * - `directory` — the directory staged files are written to; defaults to
505
585
  * `os.tmpdir()`.
506
586
  */
507
587
  export declare interface MultipartOptions {
508
- readonly limits?: MultipartLimits;
588
+ readonly limits?: MultipartLimitsInput;
509
589
  readonly allowed?: readonly string[];
510
590
  readonly directory?: string;
511
591
  }
512
592
 
513
593
  /**
514
- * Why `createMultipart` rejected a request the axis {@link MultipartError}
515
- * maps onto its HTTP status: `'limit'` 413, `'malformed'` 400,
516
- * `'rejected'` → 415.
594
+ * Lists `['gzip', 'deflate']`, the content-codings the node face's `createCompression`
595
+ * offers what `node:zlib` guarantees on every Node runtime, so this face never
596
+ * feature-detects.
517
597
  */
518
- export declare type MultipartReason = 'limit' | 'malformed' | 'rejected';
598
+ export declare const NODE_COMPRESSION_ENCODINGS: readonly Encoding[];
519
599
 
520
600
  /**
521
- * Options for the node face's `createCompression` — `node:zlib`-backed
601
+ * Configures the node face's `createCompression` — `node:zlib`-backed
522
602
  * response compression.
523
603
  *
524
- * @param options - See fields below
525
604
  * @remarks
526
605
  * - `threshold` — the minimum buffered body size (bytes) worth compressing;
527
606
  * defaults to {@link DEFAULT_COMPRESSION_THRESHOLD}.
528
607
  * - `filter` — an additional predicate a response must pass before
529
- * compression is attempted; defaults to always-allow. `encodings` is fixed
530
- * to `['gzip', 'deflate']` and is not configurable (see the peer `Encoding`
531
- * type limitation documented on `createCompression`).
608
+ * compression is attempted; defaults to always-allow. The offered codings
609
+ * are fixed to {@link NODE_COMPRESSION_ENCODINGS} and are not configurable
610
+ * (see the peer `Encoding` type limitation documented on
611
+ * `createCompression`).
532
612
  */
533
613
  export declare interface NodeCompressionOptions {
534
614
  readonly threshold?: number;
@@ -536,34 +616,38 @@ export declare interface NodeCompressionOptions {
536
616
  }
537
617
 
538
618
  /**
539
- * Stream-parse a `multipart/form-data` request into its files and fields —
540
- * the mid-stream state machine `createMultipart` drives (PROPOSAL §4.15).
619
+ * Stream-parses a `multipart/form-data` request into its files and fields —
620
+ * the mid-stream state machine `createMultipart` drives.
541
621
  *
542
622
  * @remarks
543
- * Reads `request.body` chunk by chunk via its `ReadableStream` reader —
544
- * NEVER buffers the whole body — enforcing every {@link MultipartLimits} cap
623
+ * Reads `request.body` chunk by chunk through its `ReadableStream` reader —
624
+ * never buffers the whole body — enforcing every {@link MultipartLimits} cap
545
625
  * the instant it is exceeded (reading stops, every already-staged temp file
546
- * is deleted, throws {@link MultipartError} with reason `'limit'`). Each file
626
+ * is deleted, throws {@link MultipartError} with code `'limit'`). Each file
547
627
  * part streams to `join(directory, randomUUID())` — the client's declared
548
- * filename is METADATA ONLY, never a path component. A field OR file part
628
+ * filename is metadata only, never a path component. A field or file part
549
629
  * named `__proto__` / `constructor` / `prototype` is silently skipped and
550
630
  * never keyed onto the returned {@link MultipartBody} (a skipped file's
551
631
  * staged temp file is unlinked immediately, since it can never be
552
632
  * referenced). A file part with an empty declared filename (`filename=""`)
553
- * AND a zero-byte body — the browser convention for an unselected optional
633
+ * and a zero-byte body — the browser convention for an unselected optional
554
634
  * `<input type="file">` — is a silent no-op: its temp file is unlinked, it is
555
- * never counted against the `files` limit, and it never runs the `allowed`
556
- * check. A malformed
635
+ * never counted against the `file.count` limit, and it never runs the
636
+ * `allowed` check. A malformed
557
637
  * structure (missing/unterminated boundary, nameless part, an oversized
558
638
  * header block, or a preamble exceeding {@link MULTIPART_MAX_PREAMBLE} before
559
- * the first boundary) throws with reason `'malformed'`. A file is accepted
560
- * against the configured `allowed` MIME list iff its SNIFFED bytes detect a
639
+ * the first boundary) throws with code `'malformed'`. A file is accepted
640
+ * against the configured `allowed` MIME list exactly when its sniffed bytes detect a
561
641
  * type present in the list — sniff-authoritative, independent of whether the
562
642
  * declared `Content-Type` matches (that agreement is exposed separately as
563
- * `validated`); otherwise throws with reason `'rejected'`. A
643
+ * `validated`); otherwise throws with code `'rejected'`. A
564
644
  * request abort mid-upload triggers the same fail-closed cleanup as a limit
565
645
  * breach. Returns `undefined` for a non-multipart request (untouched).
566
646
  *
647
+ * Staging defaults to a process-owned directory created once (lazily,
648
+ * memoized across calls) with `mkdtemp` under `os.tmpdir()` and locked to
649
+ * mode `0o700`; `options.directory` overrides it.
650
+ *
567
651
  * @param request - The incoming multipart request
568
652
  * @param options - See {@link MultipartOptions}
569
653
  * @returns The parsed {@link MultipartBody}, or `undefined` when the request
@@ -579,41 +663,41 @@ export declare interface NodeCompressionOptions {
579
663
  export declare function parseMultipartRequest(request: Request, options?: MultipartOptions): Promise<MultipartBody | undefined>;
580
664
 
581
665
  /**
582
- * Parse one multipart part's raw header block into its `name` (from
666
+ * Parses one multipart part's raw header block into its `name` (from
583
667
  * `Content-Disposition`), optional `filename`, and optional `Content-Type`.
584
668
  *
585
669
  * @param block - The raw header block for one multipart part (before the
586
670
  * terminating blank line)
587
- * @returns The parsed `name`, `filename`, and `contentType` (each
588
- * `undefined` when absent)
671
+ * @returns The parsed `name`, `filename`, and `mime` (each `undefined` when
672
+ * absent)
589
673
  *
590
674
  * @example
591
675
  * ```ts
592
676
  * parsePartHeaders('Content-Disposition: form-data; name="title"')
593
- * // { name: 'title', filename: undefined, contentType: undefined }
677
+ * // { name: 'title', filename: undefined, mime: undefined }
594
678
  * ```
595
679
  */
596
680
  export declare function parsePartHeaders(block: string): PartHeaders;
597
681
 
598
682
  /**
599
- * One multipart part's parsed header block — `parsePartHeaders`'s return
683
+ * Describes one multipart part's parsed header block — `parsePartHeaders`'s return
600
684
  * shape.
601
685
  *
602
686
  * @remarks
603
687
  * - `name` — the `Content-Disposition` `name` parameter, or `undefined` when absent.
604
688
  * - `filename` — the `Content-Disposition` `filename` parameter, or `undefined` when absent.
605
- * - `contentType` — the part's declared `Content-Type` header value, or `undefined` when absent.
689
+ * - `mime` — the part's declared `Content-Type` header value, or `undefined` when absent.
606
690
  */
607
691
  export declare interface PartHeaders {
608
692
  readonly name: string | undefined;
609
693
  readonly filename: string | undefined;
610
- readonly contentType: string | undefined;
694
+ readonly mime: string | undefined;
611
695
  }
612
696
 
613
697
  /**
614
- * Read a staged/moved uploaded file's full contents into memory.
698
+ * Reads a staged/moved uploaded file's full contents into memory.
615
699
  *
616
- * @param file - The {@link UploadedFileInterface} record to read
700
+ * @param file - The {@link UploadedFile} record to read
617
701
  * @returns The file's bytes
618
702
  *
619
703
  * @example
@@ -621,46 +705,65 @@ export declare interface PartHeaders {
621
705
  * const bytes = await readUploadedFile(file)
622
706
  * ```
623
707
  */
624
- export declare function readUploadedFile(file: UploadedFileInterface): Promise<Uint8Array>;
708
+ export declare function readUploadedFile(file: UploadedFile): Promise<Uint8Array>;
625
709
 
626
710
  /**
627
- * Windows reserved device-name stems (CVE-2025-27210) — matched
711
+ * Lists the Windows reserved device-name stems (CVE-2025-27210) — matched
628
712
  * case-insensitively against the segment's stem (before its first `.`).
629
713
  */
630
714
  export declare const RESERVED_DEVICE_NAMES: ReadonlySet<string>;
631
715
 
632
716
  /**
633
- * Resolve `parseMultipartRequest`'s default staging directory when the
634
- * caller did not configure one a process-owned directory created ONCE
635
- * (lazily, memoized across calls) via `mkdtemp` under `os.tmpdir()` and
636
- * locked to mode `0o700`.
717
+ * Canonicalizes `candidate` and returns it only when it lies inside `rootReal`
718
+ * the shared realpath-then-contain step `createStatic` applies to a
719
+ * directory index and to its SPA shell.
637
720
  *
638
- * @returns The absolute path of the process-owned staging directory
721
+ * @remarks
722
+ * Total: a `realpath` failure (a dangling symlink, a missing file, a
723
+ * permission refusal) and an escape from `rootReal` both resolve `undefined`,
724
+ * so a caller that treats those two outcomes identically needs no `try`. A
725
+ * caller that must tell them apart keeps its own explicit branch instead.
726
+ *
727
+ * @param candidate - The on-disk path to canonicalize
728
+ * @param rootReal - The already-canonical root the result must lie under
729
+ * @returns The canonical path inside `rootReal`, or `undefined`
639
730
  *
640
731
  * @example
641
732
  * ```ts
642
- * const directory = await resolveDefaultDirectory()
733
+ * await resolveContainedRealPath('/srv/public/index.html', '/srv/public')
734
+ * // '/srv/public/index.html'
643
735
  * ```
644
736
  */
645
- export declare function resolveDefaultDirectory(): Promise<string>;
737
+ export declare function resolveContainedRealPath(candidate: string, rootReal: string): Promise<string | undefined>;
646
738
 
647
739
  /**
648
- * Resolve `createMultipart`'s effective {@link MultipartLimits}, applying
649
- * every documented default.
740
+ * Resolves `createMultipart`'s effective {@link MultipartLimits}, applying
741
+ * every documented default to an omitted leaf.
650
742
  *
651
743
  * @param limits - The caller's partial limits
652
744
  * @returns The fully-resolved limits
745
+ *
746
+ * @example
747
+ * ```ts
748
+ * resolveMultipartLimits({ file: { size: 1_048_576 } })
749
+ * ```
653
750
  */
654
- export declare function resolveMultipartLimits(limits: MultipartLimits | undefined): Required<MultipartLimits>;
751
+ export declare function resolveMultipartLimits(limits: MultipartLimitsInput | undefined): MultipartLimits;
655
752
 
656
753
  /**
657
- * Resolve the fixed SPA shell path when a static-file miss is eligible for
754
+ * Resolves the fixed SPA shell path when a static-file miss is eligible for
658
755
  * fallback.
659
756
  *
757
+ * @remarks
758
+ * `GET` and `HEAD` are both eligible, and resolve the same shell: `HEAD` is
759
+ * defined as `GET` without a body (RFC 9110 §9.3.2), so a navigation probe
760
+ * that answered `404` while its `GET` answered `200` would report a resource
761
+ * the very next request serves.
762
+ *
660
763
  * @param root - The configured static root
661
764
  * @param index - The configured shell filename
662
765
  * @param exclude - The URL prefix excluded from fallback
663
- * @param method - The request method
766
+ * @param method - The request method; only `GET` and `HEAD` are eligible
664
767
  * @param pathname - The request pathname
665
768
  * @param accept - The request's `Accept` header value
666
769
  * @returns The fixed shell path, or `undefined` when fallback is ineligible
@@ -674,14 +777,14 @@ export declare function resolveMultipartLimits(limits: MultipartLimits | undefin
674
777
  export declare function resolveStaticFallbackPath(root: string, index: string, exclude: string, method: string, pathname: string, accept: string): string | undefined;
675
778
 
676
779
  /**
677
- * Resolve a request pathname to an on-disk path UNDER `root`, or `undefined`
678
- * when it cannot — the traversal guard, EXACT algorithm and order (PROPOSAL
679
- * §4.14): strip `prefix` on a segment boundary → `decodeURIComponent` (a
780
+ * Resolves a request pathname to an on-disk path under `root`, or `undefined`
781
+ * when it cannot — the traversal guard, whose algorithm and order are exact:
782
+ * strip `prefix` on a segment boundary → `decodeURIComponent` (a
680
783
  * malformed escape refuses, never throws) → reject a NUL byte → strip the
681
- * leading path separator FIRST (so a leading `..` survives `normalize` as a
682
- * genuine climbing segment) → `normalize` → refuse any Windows reserved-
683
- * device-name segment ({@link isReservedDeviceName}) → `resolve` and require
684
- * the result under `root`.
784
+ * leading path separator first (so a leading `..` survives `normalize` as a
785
+ * genuine climbing segment) → `normalize` → refuse any Windows
786
+ * reserved-device-name segment ({@link isReservedDeviceName}) → `resolve` and
787
+ * require the result under `root`.
685
788
  *
686
789
  * @param root - The absolute root directory every result must resolve under
687
790
  * @param prefix - An optional URL path prefix stripped on a segment boundary
@@ -698,24 +801,29 @@ export declare function resolveStaticFallbackPath(root: string, index: string, e
698
801
  export declare function resolveStaticPath(root: string, prefix: string | undefined, pathname: string): string | undefined;
699
802
 
700
803
  /**
701
- * Options for `createStatic` — node `fs`-backed static file serving.
804
+ * Configures `createStatic` — node `fs`-backed static file serving.
702
805
  *
703
- * @param options - See fields below
704
806
  * @remarks
705
- * - `root` — the directory every request resolves under, resolved once at
706
- * construction. REQUIRED.
807
+ * - `root` — the required directory every request resolves under, resolved
808
+ * once at construction.
707
809
  * - `prefix` — a URL path prefix stripped (on a segment boundary) before
708
810
  * resolving under `root`.
709
- * - `index` — the filename served for a directory hit; defaults to
710
- * {@link DEFAULT_STATIC_INDEX}.
811
+ * - `index` — the filename served for a directory hit and by the SPA
812
+ * fallback; defaults to {@link DEFAULT_STATIC_INDEX}. The fallback serves
813
+ * it whatever `dotfiles` is set to, because this path is
814
+ * operator-configured rather than request-derived.
711
815
  * - `dotfiles` — the policy for a path with a dotfile segment: `'ignore'`
712
- * (default, falls through to `next()`), `'deny'` (403), or `'allow'`
713
- * (serves it).
816
+ * (falls through to `next()`), `'deny'` (403), or `'allow'` (serves it);
817
+ * defaults to {@link DEFAULT_STATIC_DOTFILES}.
714
818
  * - `cache` — `Cache-Control: max-age=<cache>` in seconds, when set.
715
819
  * - `etag` — whether to compute and honor a weak file `ETag`; defaults to `true`.
716
820
  * - `fallback` — SPA fallback: `false` (default, off), `true` (on, excluding
717
821
  * {@link DEFAULT_STATIC_FALLBACK_EXCLUDE}), or `{ exclude }` for a custom
718
- * excluded prefix.
822
+ * excluded prefix. An eligible `GET` or `HEAD` navigation miss answers with
823
+ * `index` through the same handle-`fstat` header block a directly requested
824
+ * file answers through, so `cache`, `etag`, conditional revalidation,
825
+ * `HEAD`, and ranges are identical on both routes; `index` reaches the
826
+ * client through this route whatever `dotfiles` is set to.
719
827
  */
720
828
  export declare interface StaticOptions {
721
829
  readonly root: string;
@@ -730,35 +838,34 @@ export declare interface StaticOptions {
730
838
  }
731
839
 
732
840
  /**
733
- * Adapt a `node:fs` read stream over `path` (or an already-open
841
+ * Adapts a `node:fs` read stream over a file path (or an already-open
734
842
  * `FileHandle`) into a DOM-compatible `ReadableStream<Uint8Array>` — the
735
843
  * single shared node↔web stream bridge every static-file and uploaded-file
736
844
  * response body routes through.
737
845
  *
738
846
  * @remarks
739
- * PULL-driven, not push-driven: the underlying node stream's async iterator
847
+ * Pull-driven, not push-driven: the underlying node stream's async iterator
740
848
  * is only advanced (`iterator.next()`) from inside `pull(controller)`, which
741
849
  * the web `ReadableStream` invokes exactly when its internal queue has room
742
850
  * for more data. Exactly one disk chunk is read and enqueued per `pull` —
743
851
  * never more — so a slow or stalled consumer (a stalled HTTP connection)
744
- * simply stops triggering `pull` calls and the source stops reading ahead;
852
+ * stops triggering `pull` calls and the source stops reading ahead;
745
853
  * this is genuine consumer backpressure, not the "naturally backpressured"
746
854
  * `for await`/`enqueue` pattern (which does not block on a slow consumer at
747
- * all, since `enqueue` returns synchronously). The controller is closed on
855
+ * all, because `enqueue` returns synchronously). The controller is closed on
748
856
  * iterator completion and errored (never thrown into the process) on a
749
- * mid-stream read failure. Cancelling the returned `ReadableStream` (e.g. the
750
- * consumer aborts the response) calls the iterator's `return()`, which
857
+ * mid-stream read failure. Cancelling the returned `ReadableStream` (for
858
+ * example the consumer aborts the response) calls the iterator's `return()`, which
751
859
  * destroys the underlying node read stream so the file descriptor is
752
- * released. When `path` is a `FileHandle`, `FileHandle.createReadStream`'s
860
+ * released. When `source` is a `FileHandle`, `FileHandle.createReadStream`'s
753
861
  * default `autoClose` closes the handle on every terminal path (end, error,
754
- * or `destroy()` via the iterator's `return()`) — the caller never needs a
755
- * separate `handle.close()` for a handle passed here.
862
+ * or `destroy()` through the iterator's `return()`) — the caller never needs a
863
+ * separate `handle.close()` for a `FileHandle` passed as `source`.
756
864
  *
757
865
  * @param source - The absolute on-disk file path to stream, or an already-open
758
- * `FileHandle` (e.g. one already `fstat`'d so the served bytes match the
866
+ * `FileHandle` (for example one already `fstat`'d so the served bytes match the
759
867
  * headers computed from that same `fstat`)
760
- * @param range - An optional inclusive byte range (`start`/`end`, both
761
- * 0-indexed and inclusive, matching `node:fs`'s `createReadStream` options)
868
+ * @param range - An optional inclusive byte range; see {@link ByteRange}
762
869
  * @returns A `ReadableStream<Uint8Array>` valid as a fetch `BodyInit`
763
870
  *
764
871
  * @example
@@ -766,15 +873,12 @@ export declare interface StaticOptions {
766
873
  * new Response(streamFile('/srv/public/index.html'))
767
874
  * ```
768
875
  */
769
- export declare function streamFile(source: string | FileHandle, range?: {
770
- readonly start: number;
771
- readonly end: number;
772
- }): ReadableStream<Uint8Array>;
876
+ export declare function streamFile(source: string | FileHandle, range?: ByteRange): ReadableStream<Uint8Array>;
773
877
 
774
878
  /**
775
- * Open a staged/moved uploaded file as a web `ReadableStream`.
879
+ * Opens a staged/moved uploaded file as a web `ReadableStream`.
776
880
  *
777
- * @param file - The {@link UploadedFileInterface} record to stream
881
+ * @param file - The {@link UploadedFile} record to stream
778
882
  * @returns A `ReadableStream<Uint8Array>` over the file's current on-disk path
779
883
  *
780
884
  * @example
@@ -782,10 +886,10 @@ export declare function streamFile(source: string | FileHandle, range?: {
782
886
  * new Response(streamUploadedFile(file))
783
887
  * ```
784
888
  */
785
- export declare function streamUploadedFile(file: UploadedFileInterface): ReadableStream<Uint8Array>;
889
+ export declare function streamUploadedFile(file: UploadedFile): ReadableStream<Uint8Array>;
786
890
 
787
891
  /**
788
- * Best-effort unlink every still-`'staged'` file in a parsed
892
+ * Attempts to unlink every still-`'staged'` file in a parsed
789
893
  * {@link MultipartBody} — the fail-closed cleanup `createMultipart` runs when
790
894
  * its downstream handler throws, mirroring `parseMultipartRequest`'s own
791
895
  * cleanup pattern (a missing file is already gone; failures are swallowed).
@@ -801,30 +905,7 @@ export declare function streamUploadedFile(file: UploadedFileInterface): Readabl
801
905
  export declare function unlinkStagedFiles(body: MultipartBody): Promise<void>;
802
906
 
803
907
  /**
804
- * The full field set `createUploadedFile` needs to build an
805
- * {@link UploadedFileInterface} record.
806
- *
807
- * @remarks
808
- * - `field` — the multipart field name the file was submitted under.
809
- * - `name` — the client-declared filename (metadata only).
810
- * - `size` — the file's byte size.
811
- * - `mime` — the sniffed MIME type.
812
- * - `validated` — `true` when the sniffed type matches the declared `Content-Type`.
813
- * - `status` — see {@link UploadStatus}.
814
- * - `path` — the file's current on-disk path.
815
- */
816
- export declare interface UploadedFileInput {
817
- readonly field: string;
818
- readonly name: string;
819
- readonly size: number;
820
- readonly mime: string;
821
- readonly validated: boolean;
822
- readonly status: UploadStatus;
823
- readonly path: string;
824
- }
825
-
826
- /**
827
- * One uploaded file's post-parse record — the node-bound, richer sibling of
908
+ * Describes one uploaded file's post-parse record the node-bound, richer sibling of
828
909
  * the pure core's {@link MultipartFile} (identical fields, `status` narrowed
829
910
  * to {@link UploadStatus}). Structurally assignable into {@link MultipartFile}
830
911
  * so a `createMultipart`-built {@link MultipartBody} satisfies the shared
@@ -832,21 +913,24 @@ export declare interface UploadedFileInput {
832
913
  *
833
914
  * @remarks
834
915
  * - `field` — the multipart field name the file was submitted under.
835
- * - `name` — the client-declared filename (METADATA ONLY — never used to
916
+ * - `name` — the client-declared filename (metadata only — never used to
836
917
  * build a filesystem path).
837
918
  * - `size` — the file's byte size.
838
- * - `mime` — the SNIFFED (magic-byte-detected) MIME type.
839
- * - `validated` — `true` when the sniffed type matches the declared
840
- * `Content-Type`.
919
+ * - `mime` — the sniffed (magic-byte-detected) MIME type when a signature
920
+ * matches; otherwise the part's declared `Content-Type`; otherwise
921
+ * {@link DEFAULT_CONTENT_TYPE}. Read `validated` to tell which.
922
+ * - `validated` — `true` when a signature matched and the sniffed type equals
923
+ * the declared `Content-Type`, so `mime` is the sniffed fact. `false` means
924
+ * `mime` may be the client-declared value.
841
925
  * - `status` — see {@link UploadStatus}.
842
926
  * - `path` — the file's current on-disk path.
843
927
  */
844
- export declare interface UploadedFileInterface extends Omit<MultipartFile, 'status'> {
928
+ export declare interface UploadedFile extends Omit<MultipartFile, 'status'> {
845
929
  readonly status: UploadStatus;
846
930
  }
847
931
 
848
932
  /**
849
- * The lifecycle stage of one staged upload's temp file.
933
+ * Names the lifecycle stage of one staged upload's temp file.
850
934
  *
851
935
  * @remarks
852
936
  * `'staged'` — written to the configured temp directory under a random name,