@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.
- package/README.md +4 -4
- package/dist/src/core/index.cjs +357 -294
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +390 -290
- package/dist/src/core/index.d.ts +390 -290
- package/dist/src/core/index.js +355 -293
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +472 -410
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +290 -208
- package/dist/src/server/index.d.ts +290 -208
- package/dist/src/server/index.js +464 -405
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -19
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
119
|
-
*
|
|
120
|
-
*
|
|
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
|
|
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`
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
181
|
-
* static-file battery
|
|
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` (
|
|
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
|
-
*
|
|
237
|
+
* Builds a frozen {@link UploadedFile} record.
|
|
215
238
|
*
|
|
216
239
|
* @param input - Every field of the record
|
|
217
|
-
* @returns A frozen {@link
|
|
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:
|
|
247
|
+
export declare function createUploadedFile(input: UploadedFile): UploadedFile;
|
|
225
248
|
|
|
226
|
-
/**
|
|
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
|
|
230
|
-
export declare const
|
|
252
|
+
/** Holds `createMultipart`'s default maximum field-part count. */
|
|
253
|
+
export declare const DEFAULT_MULTIPART_FIELD_COUNT = 100;
|
|
231
254
|
|
|
232
|
-
/** `createMultipart`'s default
|
|
233
|
-
export declare const
|
|
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
|
|
236
|
-
export declare const
|
|
258
|
+
/** Holds `createMultipart`'s default maximum file-part count. */
|
|
259
|
+
export declare const DEFAULT_MULTIPART_FILE_COUNT = 10;
|
|
237
260
|
|
|
238
|
-
/** `createMultipart`'s default
|
|
239
|
-
export declare const
|
|
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
|
|
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
|
-
*
|
|
252
|
-
* table (jpeg, png, gif87a/89a, webp, pdf, zip)
|
|
253
|
-
*
|
|
254
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
315
|
-
* parser's
|
|
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
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
460
|
+
* @param file - The {@link UploadedFile} record to move
|
|
411
461
|
* @param destination - The final on-disk path
|
|
412
|
-
* @returns A new {@link
|
|
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:
|
|
469
|
+
export declare function moveUploadedFile(file: UploadedFile, destination: string): Promise<UploadedFile>;
|
|
420
470
|
|
|
421
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
428
|
-
export declare const
|
|
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
|
-
*
|
|
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
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* `
|
|
455
|
-
*
|
|
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
|
|
465
|
-
readonly
|
|
466
|
-
readonly
|
|
467
|
-
|
|
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
|
-
*
|
|
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
|
|
477
|
-
* - `
|
|
478
|
-
* {@link
|
|
479
|
-
* - `field` — the maximum size in bytes of one text field; defaults to
|
|
480
|
-
* {@link
|
|
481
|
-
* - `
|
|
482
|
-
* {@link
|
|
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
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
*
|
|
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
|
|
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?:
|
|
586
|
+
readonly limits?: MultipartLimitsInput;
|
|
509
587
|
readonly allowed?: readonly string[];
|
|
510
588
|
readonly directory?: string;
|
|
511
589
|
}
|
|
512
590
|
|
|
513
591
|
/**
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
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
|
|
596
|
+
export declare const NODE_COMPRESSION_ENCODINGS: readonly Encoding[];
|
|
519
597
|
|
|
520
598
|
/**
|
|
521
|
-
*
|
|
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.
|
|
530
|
-
* to
|
|
531
|
-
* type limitation documented on
|
|
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-
|
|
540
|
-
* the mid-stream state machine `createMultipart` drives
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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 `
|
|
588
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
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
|
-
* - `
|
|
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
|
|
692
|
+
readonly mime: string | undefined;
|
|
611
693
|
}
|
|
612
694
|
|
|
613
695
|
/**
|
|
614
|
-
*
|
|
696
|
+
* Reads a staged/moved uploaded file's full contents into memory.
|
|
615
697
|
*
|
|
616
|
-
* @param file - The {@link
|
|
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:
|
|
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
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
731
|
+
* await resolveContainedRealPath('/srv/public/index.html', '/srv/public')
|
|
732
|
+
* // '/srv/public/index.html'
|
|
643
733
|
* ```
|
|
644
734
|
*/
|
|
645
|
-
export declare function
|
|
735
|
+
export declare function resolveContainedRealPath(candidate: string, rootReal: string): Promise<string | undefined>;
|
|
646
736
|
|
|
647
737
|
/**
|
|
648
|
-
*
|
|
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:
|
|
749
|
+
export declare function resolveMultipartLimits(limits: MultipartLimitsInput | undefined): MultipartLimits;
|
|
655
750
|
|
|
656
751
|
/**
|
|
657
|
-
*
|
|
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
|
-
*
|
|
678
|
-
* when it cannot — the traversal guard,
|
|
679
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
* (
|
|
713
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
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` (
|
|
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 `
|
|
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()`
|
|
755
|
-
* separate `handle.close()` for a
|
|
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` (
|
|
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
|
|
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
|
-
*
|
|
877
|
+
* Opens a staged/moved uploaded file as a web `ReadableStream`.
|
|
776
878
|
*
|
|
777
|
-
* @param file - The {@link
|
|
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:
|
|
887
|
+
export declare function streamUploadedFile(file: UploadedFile): ReadableStream<Uint8Array>;
|
|
786
888
|
|
|
787
889
|
/**
|
|
788
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
840
|
-
* `
|
|
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
|
|
926
|
+
export declare interface UploadedFile extends Omit<MultipartFile, 'status'> {
|
|
845
927
|
readonly status: UploadStatus;
|
|
846
928
|
}
|
|
847
929
|
|
|
848
930
|
/**
|
|
849
|
-
*
|
|
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,
|