@remix-run/form-data-middleware 0.1.4 → 0.2.0
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 +28 -9
- package/dist/lib/form-data.d.ts +7 -4
- package/dist/lib/form-data.d.ts.map +1 -1
- package/dist/lib/form-data.js +16 -6
- package/package.json +6 -6
- package/src/lib/form-data.ts +32 -9
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# form-data-middleware
|
|
2
2
|
|
|
3
|
-
Form body parsing middleware for Remix. It parses incoming [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) and exposes
|
|
3
|
+
Form body parsing middleware for Remix. It parses incoming [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) and exposes it via `context.get(FormData)`.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **Request Form Parsing** - Parses request body form data once per request
|
|
8
|
-
- **File Access** -
|
|
8
|
+
- **File Access** - Uploaded files are available from `context.get(FormData)`
|
|
9
9
|
- **Custom Upload Handling** - Supports pluggable upload handlers for file processing
|
|
10
10
|
- **Error Control** - Optional suppression for malformed form data
|
|
11
11
|
|
|
@@ -17,9 +17,9 @@ npm i remix
|
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
|
-
Use the `formData()` middleware at the router level to parse `FormData` from the request body and make it available on
|
|
20
|
+
Use the `formData()` middleware at the router level to parse `FormData` from the request body and make it available on request context via `context.get(FormData)`.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Uploaded files are available in the parsed `FormData` object. For a single file field, use `formData.get(name)`. For repeated file fields, use `formData.getAll(name)`.
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
25
|
import { createRouter } from 'remix/fetch-router'
|
|
@@ -30,13 +30,14 @@ let router = createRouter({
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
router.post('/users', async (context) => {
|
|
33
|
-
let
|
|
34
|
-
let
|
|
33
|
+
let formData = context.get(FormData)
|
|
34
|
+
let name = formData.get('name')
|
|
35
|
+
let email = formData.get('email')
|
|
35
36
|
|
|
36
37
|
// Handle file uploads
|
|
37
|
-
let avatar =
|
|
38
|
+
let avatar = formData.get('avatar')
|
|
38
39
|
|
|
39
|
-
return Response.json({ name, email, hasAvatar:
|
|
40
|
+
return Response.json({ name, email, hasAvatar: avatar instanceof File })
|
|
40
41
|
})
|
|
41
42
|
```
|
|
42
43
|
|
|
@@ -62,9 +63,27 @@ let router = createRouter({
|
|
|
62
63
|
})
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
### Limit Multipart Growth
|
|
67
|
+
|
|
68
|
+
`formData()` forwards multipart limit options to `parseFormData()`, so you can cap uploads with
|
|
69
|
+
`maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, and `maxTotalSize`.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
let router = createRouter({
|
|
73
|
+
middleware: [
|
|
74
|
+
formData({
|
|
75
|
+
maxFiles: 5,
|
|
76
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
77
|
+
maxParts: 25,
|
|
78
|
+
maxTotalSize: 12 * 1024 * 1024,
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
65
84
|
### Suppress Parse Errors
|
|
66
85
|
|
|
67
|
-
Some requests may contain invalid form data that cannot be parsed. You can suppress parse errors by setting `suppressErrors` to `true`. In these cases, `context.
|
|
86
|
+
Some requests may contain invalid form data that cannot be parsed. You can suppress those malformed-body parse errors by setting `suppressErrors` to `true`. In these cases, `context.get(FormData)` will be an empty `FormData` object. Multipart limit violations from `maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, or `maxTotalSize` are never suppressed.
|
|
68
87
|
|
|
69
88
|
```ts
|
|
70
89
|
let router = createRouter({
|
package/dist/lib/form-data.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type FileUploadHandler, type ParseFormDataOptions } from '@remix-run/form-data-parser';
|
|
2
2
|
import type { Middleware } from '@remix-run/fetch-router';
|
|
3
|
+
type SetFormDataContextTransform = readonly [readonly [typeof FormData, FormData]];
|
|
3
4
|
/**
|
|
4
|
-
* Options for the
|
|
5
|
+
* Options for the {@link formData} middleware.
|
|
5
6
|
*/
|
|
6
7
|
export interface FormDataOptions extends ParseFormDataOptions {
|
|
7
8
|
/**
|
|
8
|
-
* Set `true` to suppress parse errors.
|
|
9
|
+
* Set `true` to suppress malformed form-data parse errors. Multipart limit violations always
|
|
10
|
+
* surface as errors even when suppression is enabled.
|
|
9
11
|
*
|
|
10
12
|
* @default false
|
|
11
13
|
*/
|
|
@@ -18,10 +20,11 @@ export interface FormDataOptions extends ParseFormDataOptions {
|
|
|
18
20
|
uploadHandler?: FileUploadHandler;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
|
-
* Middleware that parses `FormData` from the request body and populates
|
|
23
|
+
* Middleware that parses `FormData` from the request body and populates request context.
|
|
22
24
|
*
|
|
23
25
|
* @param options Options for parsing form data
|
|
24
26
|
* @returns A middleware function that parses form data
|
|
25
27
|
*/
|
|
26
|
-
export declare function formData(options?: FormDataOptions): Middleware
|
|
28
|
+
export declare function formData(options?: FormDataOptions): Middleware<any, any, SetFormDataContextTransform>;
|
|
29
|
+
export {};
|
|
27
30
|
//# sourceMappingURL=form-data.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-data.d.ts","sourceRoot":"","sources":["../../src/lib/form-data.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"form-data.d.ts","sourceRoot":"","sources":["../../src/lib/form-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,6BAA6B,CAAA;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEzD,KAAK,2BAA2B,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;AAYlF;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,OAAO,CAAC,EAAE,eAAe,GACxB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,2BAA2B,CAAC,CAiCnD"}
|
package/dist/lib/form-data.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MaxFilesExceededError, MaxFileSizeExceededError, MaxHeaderSizeExceededError, MaxPartsExceededError, MaxTotalSizeExceededError, parseFormData, } from '@remix-run/form-data-parser';
|
|
2
|
+
function isMultipartLimitError(error) {
|
|
3
|
+
return (error instanceof MaxFilesExceededError ||
|
|
4
|
+
error instanceof MaxHeaderSizeExceededError ||
|
|
5
|
+
error instanceof MaxFileSizeExceededError ||
|
|
6
|
+
error instanceof MaxPartsExceededError ||
|
|
7
|
+
error instanceof MaxTotalSizeExceededError);
|
|
8
|
+
}
|
|
2
9
|
/**
|
|
3
|
-
* Middleware that parses `FormData` from the request body and populates
|
|
10
|
+
* Middleware that parses `FormData` from the request body and populates request context.
|
|
4
11
|
*
|
|
5
12
|
* @param options Options for parsing form data
|
|
6
13
|
* @returns A middleware function that parses form data
|
|
@@ -9,6 +16,9 @@ export function formData(options) {
|
|
|
9
16
|
let suppressErrors = options?.suppressErrors ?? false;
|
|
10
17
|
let uploadHandler = options?.uploadHandler;
|
|
11
18
|
return async (context) => {
|
|
19
|
+
if (context.has(FormData)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
12
22
|
if (context.method === 'GET' || context.method === 'HEAD') {
|
|
13
23
|
return;
|
|
14
24
|
}
|
|
@@ -16,17 +26,17 @@ export function formData(options) {
|
|
|
16
26
|
if (contentType == null ||
|
|
17
27
|
(!contentType.startsWith('multipart/') &&
|
|
18
28
|
!contentType.startsWith('application/x-www-form-urlencoded'))) {
|
|
19
|
-
context.
|
|
29
|
+
context.set(FormData, new FormData());
|
|
20
30
|
return;
|
|
21
31
|
}
|
|
22
32
|
try {
|
|
23
|
-
context.
|
|
33
|
+
context.set(FormData, await parseFormData(context.request, options, uploadHandler));
|
|
24
34
|
}
|
|
25
35
|
catch (error) {
|
|
26
|
-
if (!suppressErrors ||
|
|
36
|
+
if (!suppressErrors || isMultipartLimitError(error)) {
|
|
27
37
|
throw error;
|
|
28
38
|
}
|
|
29
|
-
context.
|
|
39
|
+
context.set(FormData, new FormData());
|
|
30
40
|
}
|
|
31
41
|
};
|
|
32
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/form-data-middleware",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Middleware for parsing FormData from request bodies",
|
|
5
5
|
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^24.6.0",
|
|
30
30
|
"@typescript/native-preview": "7.0.0-dev.20251125.1",
|
|
31
|
-
"@remix-run/fetch-router": "0.
|
|
32
|
-
"@remix-run/form-data-parser": "0.
|
|
31
|
+
"@remix-run/fetch-router": "0.18.0",
|
|
32
|
+
"@remix-run/form-data-parser": "0.16.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@remix-run/
|
|
36
|
-
"@remix-run/
|
|
35
|
+
"@remix-run/fetch-router": "^0.18.0",
|
|
36
|
+
"@remix-run/form-data-parser": "^0.16.0"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
39
|
"fetch",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsgo -p tsconfig.build.json",
|
|
48
48
|
"clean": "git clean -fdX",
|
|
49
|
-
"test": "node --
|
|
49
|
+
"test": "node --test",
|
|
50
50
|
"typecheck": "tsgo --noEmit"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/src/lib/form-data.ts
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
MaxFilesExceededError,
|
|
3
|
+
MaxFileSizeExceededError,
|
|
4
|
+
MaxHeaderSizeExceededError,
|
|
5
|
+
MaxPartsExceededError,
|
|
6
|
+
MaxTotalSizeExceededError,
|
|
3
7
|
parseFormData,
|
|
4
8
|
type FileUploadHandler,
|
|
5
9
|
type ParseFormDataOptions,
|
|
6
10
|
} from '@remix-run/form-data-parser'
|
|
7
11
|
import type { Middleware } from '@remix-run/fetch-router'
|
|
8
12
|
|
|
13
|
+
type SetFormDataContextTransform = readonly [readonly [typeof FormData, FormData]]
|
|
14
|
+
|
|
15
|
+
function isMultipartLimitError(error: unknown): boolean {
|
|
16
|
+
return (
|
|
17
|
+
error instanceof MaxFilesExceededError ||
|
|
18
|
+
error instanceof MaxHeaderSizeExceededError ||
|
|
19
|
+
error instanceof MaxFileSizeExceededError ||
|
|
20
|
+
error instanceof MaxPartsExceededError ||
|
|
21
|
+
error instanceof MaxTotalSizeExceededError
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
/**
|
|
10
|
-
* Options for the
|
|
26
|
+
* Options for the {@link formData} middleware.
|
|
11
27
|
*/
|
|
12
28
|
export interface FormDataOptions extends ParseFormDataOptions {
|
|
13
29
|
/**
|
|
14
|
-
* Set `true` to suppress parse errors.
|
|
30
|
+
* Set `true` to suppress malformed form-data parse errors. Multipart limit violations always
|
|
31
|
+
* surface as errors even when suppression is enabled.
|
|
15
32
|
*
|
|
16
33
|
* @default false
|
|
17
34
|
*/
|
|
@@ -25,16 +42,22 @@ export interface FormDataOptions extends ParseFormDataOptions {
|
|
|
25
42
|
}
|
|
26
43
|
|
|
27
44
|
/**
|
|
28
|
-
* Middleware that parses `FormData` from the request body and populates
|
|
45
|
+
* Middleware that parses `FormData` from the request body and populates request context.
|
|
29
46
|
*
|
|
30
47
|
* @param options Options for parsing form data
|
|
31
48
|
* @returns A middleware function that parses form data
|
|
32
49
|
*/
|
|
33
|
-
export function formData(
|
|
50
|
+
export function formData(
|
|
51
|
+
options?: FormDataOptions,
|
|
52
|
+
): Middleware<any, any, SetFormDataContextTransform> {
|
|
34
53
|
let suppressErrors = options?.suppressErrors ?? false
|
|
35
54
|
let uploadHandler = options?.uploadHandler
|
|
36
55
|
|
|
37
56
|
return async (context) => {
|
|
57
|
+
if (context.has(FormData)) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
38
61
|
if (context.method === 'GET' || context.method === 'HEAD') {
|
|
39
62
|
return
|
|
40
63
|
}
|
|
@@ -45,18 +68,18 @@ export function formData(options?: FormDataOptions): Middleware {
|
|
|
45
68
|
(!contentType.startsWith('multipart/') &&
|
|
46
69
|
!contentType.startsWith('application/x-www-form-urlencoded'))
|
|
47
70
|
) {
|
|
48
|
-
context.
|
|
71
|
+
context.set(FormData, new FormData())
|
|
49
72
|
return
|
|
50
73
|
}
|
|
51
74
|
|
|
52
75
|
try {
|
|
53
|
-
context.
|
|
76
|
+
context.set(FormData, await parseFormData(context.request, options, uploadHandler))
|
|
54
77
|
} catch (error) {
|
|
55
|
-
if (!suppressErrors ||
|
|
78
|
+
if (!suppressErrors || isMultipartLimitError(error)) {
|
|
56
79
|
throw error
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
context.
|
|
82
|
+
context.set(FormData, new FormData())
|
|
60
83
|
}
|
|
61
84
|
}
|
|
62
85
|
}
|