@remix-run/multipart-parser 0.13.0 → 0.14.1

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `multipart-parser` is a fast, streaming multipart parser that works in **any JavaScript environment**, from serverless functions to traditional servers. Whether you're handling file uploads, parsing email attachments, or working with multipart API responses, `multipart-parser` has you covered.
4
4
 
5
- ## 🚀 Why multipart-parser?
5
+ ## Why multipart-parser?
6
6
 
7
7
  - **Universal JavaScript** - One library that works everywhere: Node.js, Bun, Deno, Cloudflare Workers, and browsers
8
8
  - **Blazing Fast** - Consistently outperforms popular alternatives like busboy in benchmarks
@@ -12,14 +12,14 @@
12
12
  - **Standards Based** - Built on the web standard [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) for maximum compatibility
13
13
  - **Production Ready** - Battle-tested error handling with specific error types for common scenarios
14
14
 
15
- ## 📦 Features
15
+ ## Features
16
16
 
17
- - Parse file uploads (`multipart/form-data`) with automatic field and file detection
18
- - Support for all `multipart/*` content types (mixed, alternative, related, etc.)
19
- - Convenient `MultipartPart` API with `arrayBuffer`, `bytes`, `text`, `size`, and metadata access
20
- - Built-in file size limiting to prevent abuse
21
- - First-class Node.js support with native `http.IncomingMessage` compatibility
22
- - [Demos for every major runtime](https://github.com/remix-run/remix/tree/main/packages/multipart-parser/demos)
17
+ - **File Upload Parsing** - Parse file uploads (`multipart/form-data`) with automatic field and file detection
18
+ - **Full Multipart Support** - Support for all `multipart/*` content types (mixed, alternative, related, etc.)
19
+ - **Convenient API** - `MultipartPart` API with `arrayBuffer`, `bytes`, `text`, `size`, and metadata access
20
+ - **File Size Limiting** - Built-in file size limiting to prevent abuse
21
+ - **Node.js Support** - First-class Node.js support with native `http.IncomingMessage` compatibility
22
+ - **Runtime Demos** - [Demos for every major runtime](https://github.com/remix-run/remix/tree/main/packages/multipart-parser/demos)
23
23
 
24
24
  ## Installation
25
25
 
@@ -1 +1 @@
1
- {"version":3,"file":"buffer-search.d.ts","sourceRoot":"","sources":["../../src/lib/buffer-search.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC/C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA+B5D;AAED,MAAM,WAAW,yBAAyB;IACxC,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAA;CAC/B;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,yBAAyB,CAyBlF"}
1
+ {"version":3,"file":"buffer-search.d.ts","sourceRoot":"","sources":["../../src/lib/buffer-search.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC/C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAkC5D;AAED,MAAM,WAAW,yBAAyB;IACxC,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAA;CAC/B;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,yBAAyB,CAyBlF"}
@@ -1,9 +1,10 @@
1
1
  export function createSearch(pattern) {
2
2
  let needle = new TextEncoder().encode(pattern);
3
3
  let search;
4
- if ('Buffer' in globalThis && !('Bun' in globalThis || 'Deno' in globalThis)) {
5
- // Use the built-in Buffer.indexOf method on Node.js for better perf.
6
- search = (haystack, start = 0) => Buffer.prototype.indexOf.call(haystack, needle, start);
4
+ // Use the built-in Buffer.indexOf method on Node.js for better perf.
5
+ let BufferClass = globalThis.Buffer;
6
+ if (BufferClass && !('Bun' in globalThis || 'Deno' in globalThis)) {
7
+ search = (haystack, start = 0) => BufferClass.prototype.indexOf.call(haystack, needle, start);
7
8
  }
8
9
  else {
9
10
  let needleEnd = needle.length - 1;
@@ -3,14 +3,14 @@ import type { MultipartParserOptions, MultipartPart } from './multipart.ts';
3
3
  * Extracts the boundary string from a `multipart/*` content type.
4
4
  *
5
5
  * @param contentType The `Content-Type` header value from the request
6
- * @return The boundary string if found, or null if not present
6
+ * @returns The boundary string if found, or null if not present
7
7
  */
8
8
  export declare function getMultipartBoundary(contentType: string): string | null;
9
9
  /**
10
10
  * Returns true if the given request contains multipart data.
11
11
  *
12
12
  * @param request The `Request` object to check
13
- * @return `true` if the request is a multipart request, `false` otherwise
13
+ * @returns `true` if the request is a multipart request, `false` otherwise
14
14
  */
15
15
  export declare function isMultipartRequest(request: Request): boolean;
16
16
  /**
@@ -19,7 +19,7 @@ export declare function isMultipartRequest(request: Request): boolean;
19
19
  *
20
20
  * @param request The `Request` object containing multipart data
21
21
  * @param options Optional parser options, such as `maxHeaderSize` and `maxFileSize`
22
- * @return An async generator yielding `MultipartPart` objects
22
+ * @returns An async generator yielding `MultipartPart` objects
23
23
  */
24
24
  export declare function parseMultipartRequest(request: Request, options?: MultipartParserOptions): AsyncGenerator<MultipartPart, void, unknown>;
25
25
  //# sourceMappingURL=multipart-request.d.ts.map
@@ -3,7 +3,7 @@ import { MultipartParseError, parseMultipartStream } from "./multipart.js";
3
3
  * Extracts the boundary string from a `multipart/*` content type.
4
4
  *
5
5
  * @param contentType The `Content-Type` header value from the request
6
- * @return The boundary string if found, or null if not present
6
+ * @returns The boundary string if found, or null if not present
7
7
  */
8
8
  export function getMultipartBoundary(contentType) {
9
9
  let match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType);
@@ -13,7 +13,7 @@ export function getMultipartBoundary(contentType) {
13
13
  * Returns true if the given request contains multipart data.
14
14
  *
15
15
  * @param request The `Request` object to check
16
- * @return `true` if the request is a multipart request, `false` otherwise
16
+ * @returns `true` if the request is a multipart request, `false` otherwise
17
17
  */
18
18
  export function isMultipartRequest(request) {
19
19
  let contentType = request.headers.get('Content-Type');
@@ -25,7 +25,7 @@ export function isMultipartRequest(request) {
25
25
  *
26
26
  * @param request The `Request` object containing multipart data
27
27
  * @param options Optional parser options, such as `maxHeaderSize` and `maxFileSize`
28
- * @return An async generator yielding `MultipartPart` objects
28
+ * @returns An async generator yielding `MultipartPart` objects
29
29
  */
30
30
  export async function* parseMultipartRequest(request, options) {
31
31
  if (!isMultipartRequest(request)) {
@@ -1,22 +1,33 @@
1
- import Headers from '@remix-run/headers';
2
1
  /**
3
2
  * The base class for errors thrown by the multipart parser.
4
3
  */
5
4
  export declare class MultipartParseError extends Error {
5
+ /**
6
+ * @param message The error message
7
+ */
6
8
  constructor(message: string);
7
9
  }
8
10
  /**
9
11
  * An error thrown when the maximum allowed size of a header is exceeded.
10
12
  */
11
13
  export declare class MaxHeaderSizeExceededError extends MultipartParseError {
14
+ /**
15
+ * @param maxHeaderSize The maximum header size that was exceeded
16
+ */
12
17
  constructor(maxHeaderSize: number);
13
18
  }
14
19
  /**
15
20
  * An error thrown when the maximum allowed size of a file is exceeded.
16
21
  */
17
22
  export declare class MaxFileSizeExceededError extends MultipartParseError {
23
+ /**
24
+ * @param maxFileSize The maximum file size that was exceeded
25
+ */
18
26
  constructor(maxFileSize: number);
19
27
  }
28
+ /**
29
+ * Options for parsing a multipart message.
30
+ */
20
31
  export interface ParseMultipartOptions {
21
32
  /**
22
33
  * The boundary string used to separate parts in the multipart message,
@@ -27,14 +38,14 @@ export interface ParseMultipartOptions {
27
38
  * The maximum allowed size of a header in bytes. If an individual part's header
28
39
  * exceeds this size, a `MaxHeaderSizeExceededError` will be thrown.
29
40
  *
30
- * Default: 8 KiB
41
+ * @default 8192 (8 KiB)
31
42
  */
32
43
  maxHeaderSize?: number;
33
44
  /**
34
45
  * The maximum allowed size of a file in bytes. If an individual part's content
35
46
  * exceeds this size, a `MaxFileSizeExceededError` will be thrown.
36
47
  *
37
- * Default: 2 MiB
48
+ * @default 2097152 (2 MiB)
38
49
  */
39
50
  maxFileSize?: number;
40
51
  }
@@ -46,7 +57,7 @@ export interface ParseMultipartOptions {
46
57
  *
47
58
  * @param message The multipart message as a `Uint8Array` or an iterable of `Uint8Array` chunks
48
59
  * @param options Options for the parser
49
- * @return A generator that yields `MultipartPart` objects
60
+ * @returns A generator that yields `MultipartPart` objects
50
61
  */
51
62
  export declare function parseMultipart(message: Uint8Array | Iterable<Uint8Array>, options: ParseMultipartOptions): Generator<MultipartPart, void, unknown>;
52
63
  /**
@@ -57,9 +68,12 @@ export declare function parseMultipart(message: Uint8Array | Iterable<Uint8Array
57
68
  *
58
69
  * @param stream A stream containing multipart data as a `ReadableStream<Uint8Array>`
59
70
  * @param options Options for the parser
60
- * @return An async generator that yields `MultipartPart` objects
71
+ * @returns An async generator that yields `MultipartPart` objects
61
72
  */
62
73
  export declare function parseMultipartStream(stream: ReadableStream<Uint8Array>, options: ParseMultipartOptions): AsyncGenerator<MultipartPart, void, unknown>;
74
+ /**
75
+ * Options for configuring a `MultipartParser`.
76
+ */
63
77
  export type MultipartParserOptions = Omit<ParseMultipartOptions, 'boundary'>;
64
78
  /**
65
79
  * A streaming parser for `multipart/*` HTTP messages.
@@ -69,12 +83,16 @@ export declare class MultipartParser {
69
83
  readonly boundary: string;
70
84
  readonly maxHeaderSize: number;
71
85
  readonly maxFileSize: number;
86
+ /**
87
+ * @param boundary The boundary string used to separate parts
88
+ * @param options Options for the parser
89
+ */
72
90
  constructor(boundary: string, options?: MultipartParserOptions);
73
91
  /**
74
92
  * Write a chunk of data to the parser.
75
93
  *
76
94
  * @param chunk A chunk of data to write to the parser
77
- * @return A generator yielding `MultipartPart` objects as they are parsed
95
+ * @returns A generator yielding `MultipartPart` objects as they are parsed
78
96
  */
79
97
  write(chunk: Uint8Array): Generator<MultipartPart, void, unknown>;
80
98
  /**
@@ -82,8 +100,6 @@ export declare class MultipartParser {
82
100
  *
83
101
  * Note: This will throw if the multipart message is incomplete or
84
102
  * wasn't properly terminated.
85
- *
86
- * @return void
87
103
  */
88
104
  finish(): void;
89
105
  }
@@ -96,6 +112,10 @@ export declare class MultipartPart {
96
112
  * The raw content of this part as an array of `Uint8Array` chunks.
97
113
  */
98
114
  readonly content: Uint8Array[];
115
+ /**
116
+ * @param header The raw header bytes
117
+ * @param content The content chunks
118
+ */
99
119
  constructor(header: Uint8Array, content: Uint8Array[]);
100
120
  /**
101
121
  * The content of this part as an `ArrayBuffer`.
@@ -1 +1 @@
1
- {"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/lib/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,oBAAoB,CAAA;AAMxC;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,mBAAmB;gBACrD,aAAa,EAAE,MAAM;CAIlC;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,mBAAmB;gBACnD,WAAW,EAAE,MAAM;CAIhC;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAiB,cAAc,CAC7B,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,EAC1C,OAAO,EAAE,qBAAqB,GAC7B,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAmBzC;AAED;;;;;;;;;GASG;AACH,wBAAuB,oBAAoB,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,EAAE,qBAAqB,GAC7B,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAe9C;AAED,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,CAAA;AAa5E;;GAEG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;gBAahB,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAY9D;;;;;OAKG;IACF,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC;IA0HlE;;;;;;;OAOG;IACH,MAAM,IAAI,IAAI;CAKf;AAID;;GAEG;AACH,qBAAa,aAAa;;IACxB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,CAAA;gBAKlB,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE;IAKrD;;OAEG;IACH,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED;;;OAGG;IACH,IAAI,KAAK,IAAI,UAAU,CAUtB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,GAAG,SAAS,CAEjC;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAQjB;IAED;;;;;OAKG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
1
+ {"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/lib/multipart.ts"],"names":[],"mappings":"AAUA;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C;;OAEG;IACH,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,mBAAmB;IACjE;;OAEG;IACH,YAAY,aAAa,EAAE,MAAM,EAGhC;CACF;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,mBAAmB;IAC/D;;OAEG;IACH,YAAY,WAAW,EAAE,MAAM,EAG9B;CACF;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAiB,cAAc,CAC7B,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,EAC1C,OAAO,EAAE,qBAAqB,GAC7B,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAmBzC;AAED;;;;;;;;;GASG;AACH,wBAAuB,oBAAoB,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,EAAE,qBAAqB,GAC7B,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAe9C;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,CAAA;AAa5E;;GAEG;AACH,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAa5B;;;OAGG;IACH,YAAY,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,EAU7D;IAED;;;;;OAKG;IACF,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CA+GjE;IAWD;;;;;OAKG;IACH,MAAM,IAAI,IAAI,CAIb;CACF;AAID;;GAEG;AACH,qBAAa,aAAa;;IACxB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,CAAA;IAK9B;;;OAGG;IACH,YAAY,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,EAGpD;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED;;;OAGG;IACH,IAAI,KAAK,IAAI,UAAU,CAUtB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,GAAG,SAAS,CAEjC;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,GAAG,SAAS,CAE7B;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAQjB;IAED;;;;;OAKG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -1,10 +1,13 @@
1
- import Headers from '@remix-run/headers';
1
+ import { ContentDisposition, ContentType, parse as parseRawHeaders } from '@remix-run/headers';
2
+ import { createSearch, createPartialTailSearch, } from "./buffer-search.js";
2
3
  import { readStream } from "./read-stream.js";
3
- import { createSearch, createPartialTailSearch } from "./buffer-search.js";
4
4
  /**
5
5
  * The base class for errors thrown by the multipart parser.
6
6
  */
7
7
  export class MultipartParseError extends Error {
8
+ /**
9
+ * @param message The error message
10
+ */
8
11
  constructor(message) {
9
12
  super(message);
10
13
  this.name = 'MultipartParseError';
@@ -14,6 +17,9 @@ export class MultipartParseError extends Error {
14
17
  * An error thrown when the maximum allowed size of a header is exceeded.
15
18
  */
16
19
  export class MaxHeaderSizeExceededError extends MultipartParseError {
20
+ /**
21
+ * @param maxHeaderSize The maximum header size that was exceeded
22
+ */
17
23
  constructor(maxHeaderSize) {
18
24
  super(`Multipart header size exceeds maximum allowed size of ${maxHeaderSize} bytes`);
19
25
  this.name = 'MaxHeaderSizeExceededError';
@@ -23,6 +29,9 @@ export class MaxHeaderSizeExceededError extends MultipartParseError {
23
29
  * An error thrown when the maximum allowed size of a file is exceeded.
24
30
  */
25
31
  export class MaxFileSizeExceededError extends MultipartParseError {
32
+ /**
33
+ * @param maxFileSize The maximum file size that was exceeded
34
+ */
26
35
  constructor(maxFileSize) {
27
36
  super(`File size exceeds maximum allowed size of ${maxFileSize} bytes`);
28
37
  this.name = 'MaxFileSizeExceededError';
@@ -36,7 +45,7 @@ export class MaxFileSizeExceededError extends MultipartParseError {
36
45
  *
37
46
  * @param message The multipart message as a `Uint8Array` or an iterable of `Uint8Array` chunks
38
47
  * @param options Options for the parser
39
- * @return A generator that yields `MultipartPart` objects
48
+ * @returns A generator that yields `MultipartPart` objects
40
49
  */
41
50
  export function* parseMultipart(message, options) {
42
51
  let parser = new MultipartParser(options.boundary, {
@@ -64,7 +73,7 @@ export function* parseMultipart(message, options) {
64
73
  *
65
74
  * @param stream A stream containing multipart data as a `ReadableStream<Uint8Array>`
66
75
  * @param options Options for the parser
67
- * @return An async generator that yields `MultipartPart` objects
76
+ * @returns An async generator that yields `MultipartPart` objects
68
77
  */
69
78
  export async function* parseMultipartStream(stream, options) {
70
79
  let parser = new MultipartParser(options.boundary, {
@@ -103,6 +112,10 @@ export class MultipartParser {
103
112
  #buffer = null;
104
113
  #currentPart = null;
105
114
  #contentLength = 0;
115
+ /**
116
+ * @param boundary The boundary string used to separate parts
117
+ * @param options Options for the parser
118
+ */
106
119
  constructor(boundary, options) {
107
120
  this.boundary = boundary;
108
121
  this.maxHeaderSize = options?.maxHeaderSize ?? 8 * oneKb;
@@ -117,7 +130,7 @@ export class MultipartParser {
117
130
  * Write a chunk of data to the parser.
118
131
  *
119
132
  * @param chunk A chunk of data to write to the parser
120
- * @return A generator yielding `MultipartPart` objects as they are parsed
133
+ * @returns A generator yielding `MultipartPart` objects as they are parsed
121
134
  */
122
135
  *write(chunk) {
123
136
  if (this.#state === MultipartParserStateDone) {
@@ -216,8 +229,6 @@ export class MultipartParser {
216
229
  *
217
230
  * Note: This will throw if the multipart message is incomplete or
218
231
  * wasn't properly terminated.
219
- *
220
- * @return void
221
232
  */
222
233
  finish() {
223
234
  if (this.#state !== MultipartParserStateDone) {
@@ -236,6 +247,10 @@ export class MultipartPart {
236
247
  content;
237
248
  #header;
238
249
  #headers;
250
+ /**
251
+ * @param header The raw header bytes
252
+ * @param content The content chunks
253
+ */
239
254
  constructor(header, content) {
240
255
  this.#header = header;
241
256
  this.content = content;
@@ -264,7 +279,7 @@ export class MultipartPart {
264
279
  */
265
280
  get headers() {
266
281
  if (!this.#headers) {
267
- this.#headers = new Headers(decoder.decode(this.#header));
282
+ this.#headers = parseRawHeaders(decoder.decode(this.#header));
268
283
  }
269
284
  return this.#headers;
270
285
  }
@@ -284,19 +299,19 @@ export class MultipartPart {
284
299
  * The filename of the part, if it is a file upload.
285
300
  */
286
301
  get filename() {
287
- return this.headers.contentDisposition.preferredFilename;
302
+ return ContentDisposition.from(this.headers.get('content-disposition')).preferredFilename;
288
303
  }
289
304
  /**
290
305
  * The media type of the part.
291
306
  */
292
307
  get mediaType() {
293
- return this.headers.contentType.mediaType;
308
+ return ContentType.from(this.headers.get('content-type')).mediaType;
294
309
  }
295
310
  /**
296
311
  * The name of the part, usually the `name` of the field in the `<form>` that submitted the request.
297
312
  */
298
313
  get name() {
299
- return this.headers.contentDisposition.name;
314
+ return ContentDisposition.from(this.headers.get('content-disposition')).name;
300
315
  }
301
316
  /**
302
317
  * The size of the content in bytes.
@@ -9,7 +9,7 @@ import type { ParseMultipartOptions, MultipartParserOptions, MultipartPart } fro
9
9
  *
10
10
  * @param message The multipart message as a `Buffer` or an iterable of `Buffer` chunks
11
11
  * @param options Options for the parser
12
- * @return A generator yielding `MultipartPart` objects
12
+ * @returns A generator yielding `MultipartPart` objects
13
13
  */
14
14
  export declare function parseMultipart(message: Buffer | Iterable<Buffer>, options: ParseMultipartOptions): Generator<MultipartPart, void, unknown>;
15
15
  /**
@@ -20,14 +20,14 @@ export declare function parseMultipart(message: Buffer | Iterable<Buffer>, optio
20
20
  *
21
21
  * @param stream A Node.js `Readable` stream containing multipart data
22
22
  * @param options Options for the parser
23
- * @return An async generator yielding `MultipartPart` objects
23
+ * @returns An async generator yielding `MultipartPart` objects
24
24
  */
25
25
  export declare function parseMultipartStream(stream: Readable, options: ParseMultipartOptions): AsyncGenerator<MultipartPart, void, unknown>;
26
26
  /**
27
27
  * Returns true if the given request is a multipart request.
28
28
  *
29
29
  * @param req The Node.js `http.IncomingMessage` object to check
30
- * @return `true` if the request is a multipart request, `false` otherwise
30
+ * @returns `true` if the request is a multipart request, `false` otherwise
31
31
  */
32
32
  export declare function isMultipartRequest(req: http.IncomingMessage): boolean;
33
33
  /**
@@ -35,7 +35,7 @@ export declare function isMultipartRequest(req: http.IncomingMessage): boolean;
35
35
  *
36
36
  * @param req The Node.js `http.IncomingMessage` object containing multipart data
37
37
  * @param options Options for the parser
38
- * @return An async generator yielding `MultipartPart` objects
38
+ * @returns An async generator yielding `MultipartPart` objects
39
39
  */
40
40
  export declare function parseMultipartRequest(req: http.IncomingMessage, options?: MultipartParserOptions): AsyncGenerator<MultipartPart, void, unknown>;
41
41
  //# sourceMappingURL=multipart.node.d.ts.map
@@ -9,7 +9,7 @@ import { getMultipartBoundary } from "./multipart-request.js";
9
9
  *
10
10
  * @param message The multipart message as a `Buffer` or an iterable of `Buffer` chunks
11
11
  * @param options Options for the parser
12
- * @return A generator yielding `MultipartPart` objects
12
+ * @returns A generator yielding `MultipartPart` objects
13
13
  */
14
14
  export function* parseMultipart(message, options) {
15
15
  yield* parseMultipartWeb(message, options);
@@ -22,7 +22,7 @@ export function* parseMultipart(message, options) {
22
22
  *
23
23
  * @param stream A Node.js `Readable` stream containing multipart data
24
24
  * @param options Options for the parser
25
- * @return An async generator yielding `MultipartPart` objects
25
+ * @returns An async generator yielding `MultipartPart` objects
26
26
  */
27
27
  export async function* parseMultipartStream(stream, options) {
28
28
  yield* parseMultipartStreamWeb(Readable.toWeb(stream), options);
@@ -31,7 +31,7 @@ export async function* parseMultipartStream(stream, options) {
31
31
  * Returns true if the given request is a multipart request.
32
32
  *
33
33
  * @param req The Node.js `http.IncomingMessage` object to check
34
- * @return `true` if the request is a multipart request, `false` otherwise
34
+ * @returns `true` if the request is a multipart request, `false` otherwise
35
35
  */
36
36
  export function isMultipartRequest(req) {
37
37
  let contentType = req.headers['content-type'];
@@ -42,7 +42,7 @@ export function isMultipartRequest(req) {
42
42
  *
43
43
  * @param req The Node.js `http.IncomingMessage` object containing multipart data
44
44
  * @param options Options for the parser
45
- * @return An async generator yielding `MultipartPart` objects
45
+ * @returns An async generator yielding `MultipartPart` objects
46
46
  */
47
47
  export async function* parseMultipartRequest(req, options) {
48
48
  if (!isMultipartRequest(req)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/multipart-parser",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "A fast, efficient parser for multipart streams in any JavaScript environment",
5
5
  "author": "Michael Jackson <mjijackson@gmail.com>",
6
6
  "repository": {
@@ -29,12 +29,12 @@
29
29
  },
30
30
  "./package.json": "./package.json"
31
31
  },
32
- "dependencies": {
33
- "@remix-run/headers": "^0.15.0"
32
+ "peerDependencies": {
33
+ "@remix-run/headers": "^0.19.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^24.6.0",
37
- "typescript": "^5.9.3"
37
+ "@typescript/native-preview": "7.0.0-dev.20251125.1"
38
38
  },
39
39
  "keywords": [
40
40
  "multipart",
@@ -47,9 +47,9 @@
47
47
  "bench:bun": "bun run ./bench/runner.ts",
48
48
  "bench:deno": "deno run --allow-sys ./bench/runner.ts",
49
49
  "bench:node": "node --disable-warning=ExperimentalWarning ./bench/runner.ts",
50
- "build": "tsc -p tsconfig.build.json",
50
+ "build": "tsgo -p tsconfig.build.json",
51
51
  "clean": "git clean -fdX",
52
- "test": "node --disable-warning=ExperimentalWarning --test './src/**/*.test.ts'",
53
- "typecheck": "tsc --noEmit"
52
+ "test": "node --disable-warning=ExperimentalWarning --test",
53
+ "typecheck": "tsgo --noEmit"
54
54
  }
55
55
  }
@@ -6,9 +6,12 @@ export function createSearch(pattern: string): SearchFunction {
6
6
  let needle = new TextEncoder().encode(pattern)
7
7
 
8
8
  let search: SearchFunction
9
- if ('Buffer' in globalThis && !('Bun' in globalThis || 'Deno' in globalThis)) {
10
- // Use the built-in Buffer.indexOf method on Node.js for better perf.
11
- search = (haystack, start = 0) => Buffer.prototype.indexOf.call(haystack, needle, start)
9
+ // Use the built-in Buffer.indexOf method on Node.js for better perf.
10
+ let BufferClass = (globalThis as any).Buffer as
11
+ | { prototype: { indexOf(this: Uint8Array, needle: Uint8Array, start: number): number } }
12
+ | undefined
13
+ if (BufferClass && !('Bun' in globalThis || 'Deno' in globalThis)) {
14
+ search = (haystack, start = 0) => BufferClass.prototype.indexOf.call(haystack, needle, start)
12
15
  } else {
13
16
  let needleEnd = needle.length - 1
14
17
  let skipTable = new Uint8Array(256).fill(needle.length)
@@ -5,7 +5,7 @@ import { MultipartParseError, parseMultipartStream } from './multipart.ts'
5
5
  * Extracts the boundary string from a `multipart/*` content type.
6
6
  *
7
7
  * @param contentType The `Content-Type` header value from the request
8
- * @return The boundary string if found, or null if not present
8
+ * @returns The boundary string if found, or null if not present
9
9
  */
10
10
  export function getMultipartBoundary(contentType: string): string | null {
11
11
  let match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType)
@@ -16,7 +16,7 @@ export function getMultipartBoundary(contentType: string): string | null {
16
16
  * Returns true if the given request contains multipart data.
17
17
  *
18
18
  * @param request The `Request` object to check
19
- * @return `true` if the request is a multipart request, `false` otherwise
19
+ * @returns `true` if the request is a multipart request, `false` otherwise
20
20
  */
21
21
  export function isMultipartRequest(request: Request): boolean {
22
22
  let contentType = request.headers.get('Content-Type')
@@ -29,7 +29,7 @@ export function isMultipartRequest(request: Request): boolean {
29
29
  *
30
30
  * @param request The `Request` object containing multipart data
31
31
  * @param options Optional parser options, such as `maxHeaderSize` and `maxFileSize`
32
- * @return An async generator yielding `MultipartPart` objects
32
+ * @returns An async generator yielding `MultipartPart` objects
33
33
  */
34
34
  export async function* parseMultipartRequest(
35
35
  request: Request,
@@ -17,7 +17,7 @@ import { getMultipartBoundary } from './multipart-request.ts'
17
17
  *
18
18
  * @param message The multipart message as a `Buffer` or an iterable of `Buffer` chunks
19
19
  * @param options Options for the parser
20
- * @return A generator yielding `MultipartPart` objects
20
+ * @returns A generator yielding `MultipartPart` objects
21
21
  */
22
22
  export function* parseMultipart(
23
23
  message: Buffer | Iterable<Buffer>,
@@ -34,7 +34,7 @@ export function* parseMultipart(
34
34
  *
35
35
  * @param stream A Node.js `Readable` stream containing multipart data
36
36
  * @param options Options for the parser
37
- * @return An async generator yielding `MultipartPart` objects
37
+ * @returns An async generator yielding `MultipartPart` objects
38
38
  */
39
39
  export async function* parseMultipartStream(
40
40
  stream: Readable,
@@ -47,7 +47,7 @@ export async function* parseMultipartStream(
47
47
  * Returns true if the given request is a multipart request.
48
48
  *
49
49
  * @param req The Node.js `http.IncomingMessage` object to check
50
- * @return `true` if the request is a multipart request, `false` otherwise
50
+ * @returns `true` if the request is a multipart request, `false` otherwise
51
51
  */
52
52
  export function isMultipartRequest(req: http.IncomingMessage): boolean {
53
53
  let contentType = req.headers['content-type']
@@ -59,7 +59,7 @@ export function isMultipartRequest(req: http.IncomingMessage): boolean {
59
59
  *
60
60
  * @param req The Node.js `http.IncomingMessage` object containing multipart data
61
61
  * @param options Options for the parser
62
- * @return An async generator yielding `MultipartPart` objects
62
+ * @returns An async generator yielding `MultipartPart` objects
63
63
  */
64
64
  export async function* parseMultipartRequest(
65
65
  req: http.IncomingMessage,
@@ -1,13 +1,20 @@
1
- import Headers from '@remix-run/headers'
2
-
1
+ import { ContentDisposition, ContentType, parse as parseRawHeaders } from '@remix-run/headers'
2
+
3
+ import {
4
+ createSearch,
5
+ createPartialTailSearch,
6
+ type SearchFunction,
7
+ type PartialTailSearchFunction,
8
+ } from './buffer-search.ts'
3
9
  import { readStream } from './read-stream.ts'
4
- import type { SearchFunction, PartialTailSearchFunction } from './buffer-search.ts'
5
- import { createSearch, createPartialTailSearch } from './buffer-search.ts'
6
10
 
7
11
  /**
8
12
  * The base class for errors thrown by the multipart parser.
9
13
  */
10
14
  export class MultipartParseError extends Error {
15
+ /**
16
+ * @param message The error message
17
+ */
11
18
  constructor(message: string) {
12
19
  super(message)
13
20
  this.name = 'MultipartParseError'
@@ -18,6 +25,9 @@ export class MultipartParseError extends Error {
18
25
  * An error thrown when the maximum allowed size of a header is exceeded.
19
26
  */
20
27
  export class MaxHeaderSizeExceededError extends MultipartParseError {
28
+ /**
29
+ * @param maxHeaderSize The maximum header size that was exceeded
30
+ */
21
31
  constructor(maxHeaderSize: number) {
22
32
  super(`Multipart header size exceeds maximum allowed size of ${maxHeaderSize} bytes`)
23
33
  this.name = 'MaxHeaderSizeExceededError'
@@ -28,12 +38,18 @@ export class MaxHeaderSizeExceededError extends MultipartParseError {
28
38
  * An error thrown when the maximum allowed size of a file is exceeded.
29
39
  */
30
40
  export class MaxFileSizeExceededError extends MultipartParseError {
41
+ /**
42
+ * @param maxFileSize The maximum file size that was exceeded
43
+ */
31
44
  constructor(maxFileSize: number) {
32
45
  super(`File size exceeds maximum allowed size of ${maxFileSize} bytes`)
33
46
  this.name = 'MaxFileSizeExceededError'
34
47
  }
35
48
  }
36
49
 
50
+ /**
51
+ * Options for parsing a multipart message.
52
+ */
37
53
  export interface ParseMultipartOptions {
38
54
  /**
39
55
  * The boundary string used to separate parts in the multipart message,
@@ -44,14 +60,14 @@ export interface ParseMultipartOptions {
44
60
  * The maximum allowed size of a header in bytes. If an individual part's header
45
61
  * exceeds this size, a `MaxHeaderSizeExceededError` will be thrown.
46
62
  *
47
- * Default: 8 KiB
63
+ * @default 8192 (8 KiB)
48
64
  */
49
65
  maxHeaderSize?: number
50
66
  /**
51
67
  * The maximum allowed size of a file in bytes. If an individual part's content
52
68
  * exceeds this size, a `MaxFileSizeExceededError` will be thrown.
53
69
  *
54
- * Default: 2 MiB
70
+ * @default 2097152 (2 MiB)
55
71
  */
56
72
  maxFileSize?: number
57
73
  }
@@ -64,7 +80,7 @@ export interface ParseMultipartOptions {
64
80
  *
65
81
  * @param message The multipart message as a `Uint8Array` or an iterable of `Uint8Array` chunks
66
82
  * @param options Options for the parser
67
- * @return A generator that yields `MultipartPart` objects
83
+ * @returns A generator that yields `MultipartPart` objects
68
84
  */
69
85
  export function* parseMultipart(
70
86
  message: Uint8Array | Iterable<Uint8Array>,
@@ -98,7 +114,7 @@ export function* parseMultipart(
98
114
  *
99
115
  * @param stream A stream containing multipart data as a `ReadableStream<Uint8Array>`
100
116
  * @param options Options for the parser
101
- * @return An async generator that yields `MultipartPart` objects
117
+ * @returns An async generator that yields `MultipartPart` objects
102
118
  */
103
119
  export async function* parseMultipartStream(
104
120
  stream: ReadableStream<Uint8Array>,
@@ -120,6 +136,9 @@ export async function* parseMultipartStream(
120
136
  parser.finish()
121
137
  }
122
138
 
139
+ /**
140
+ * Options for configuring a `MultipartParser`.
141
+ */
123
142
  export type MultipartParserOptions = Omit<ParseMultipartOptions, 'boundary'>
124
143
 
125
144
  const MultipartParserStateStart = 0
@@ -152,6 +171,10 @@ export class MultipartParser {
152
171
  #currentPart: MultipartPart | null = null
153
172
  #contentLength = 0
154
173
 
174
+ /**
175
+ * @param boundary The boundary string used to separate parts
176
+ * @param options Options for the parser
177
+ */
155
178
  constructor(boundary: string, options?: MultipartParserOptions) {
156
179
  this.boundary = boundary
157
180
  this.maxHeaderSize = options?.maxHeaderSize ?? 8 * oneKb
@@ -168,7 +191,7 @@ export class MultipartParser {
168
191
  * Write a chunk of data to the parser.
169
192
  *
170
193
  * @param chunk A chunk of data to write to the parser
171
- * @return A generator yielding `MultipartPart` objects as they are parsed
194
+ * @returns A generator yielding `MultipartPart` objects as they are parsed
172
195
  */
173
196
  *write(chunk: Uint8Array): Generator<MultipartPart, void, unknown> {
174
197
  if (this.#state === MultipartParserStateDone) {
@@ -297,8 +320,6 @@ export class MultipartParser {
297
320
  *
298
321
  * Note: This will throw if the multipart message is incomplete or
299
322
  * wasn't properly terminated.
300
- *
301
- * @return void
302
323
  */
303
324
  finish(): void {
304
325
  if (this.#state !== MultipartParserStateDone) {
@@ -321,6 +342,10 @@ export class MultipartPart {
321
342
  #header: Uint8Array
322
343
  #headers?: Headers
323
344
 
345
+ /**
346
+ * @param header The raw header bytes
347
+ * @param content The content chunks
348
+ */
324
349
  constructor(header: Uint8Array, content: Uint8Array[]) {
325
350
  this.#header = header
326
351
  this.content = content
@@ -354,7 +379,7 @@ export class MultipartPart {
354
379
  */
355
380
  get headers(): Headers {
356
381
  if (!this.#headers) {
357
- this.#headers = new Headers(decoder.decode(this.#header))
382
+ this.#headers = parseRawHeaders(decoder.decode(this.#header))
358
383
  }
359
384
 
360
385
  return this.#headers
@@ -378,21 +403,21 @@ export class MultipartPart {
378
403
  * The filename of the part, if it is a file upload.
379
404
  */
380
405
  get filename(): string | undefined {
381
- return this.headers.contentDisposition.preferredFilename
406
+ return ContentDisposition.from(this.headers.get('content-disposition')).preferredFilename
382
407
  }
383
408
 
384
409
  /**
385
410
  * The media type of the part.
386
411
  */
387
412
  get mediaType(): string | undefined {
388
- return this.headers.contentType.mediaType
413
+ return ContentType.from(this.headers.get('content-type')).mediaType
389
414
  }
390
415
 
391
416
  /**
392
417
  * The name of the part, usually the `name` of the field in the `<form>` that submitted the request.
393
418
  */
394
419
  get name(): string | undefined {
395
- return this.headers.contentDisposition.name
420
+ return ContentDisposition.from(this.headers.get('content-disposition')).name
396
421
  }
397
422
 
398
423
  /**