@orkestrel/middleware 0.0.18 → 0.0.19

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