@remix-run/form-data-middleware 0.0.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/LICENSE +22 -0
- package/README.md +79 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/form-data.d.ts +21 -0
- package/dist/lib/form-data.d.ts.map +1 -0
- package/dist/lib/form-data.js +31 -0
- package/package.json +52 -0
- package/src/index.ts +5 -0
- package/src/lib/form-data.ts +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Shopify Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# form-data-middleware
|
|
2
|
+
|
|
3
|
+
Middleware for parsing [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) from incoming request bodies for use with [`@remix-run/fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @remix-run/form-data-middleware
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Use the `formData()` middleware at the router level to parse `FormData` from the request body and make it available on the request context as `context.formData`.
|
|
14
|
+
|
|
15
|
+
`context.files` will also be available as a map of `File` objects keyed by the name of the form field.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createRouter } from '@remix-run/fetch-router'
|
|
19
|
+
import { formData } from '@remix-run/form-data-middleware'
|
|
20
|
+
|
|
21
|
+
let router = createRouter({
|
|
22
|
+
middleware: [formData()],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
router.post('/users', async (context) => {
|
|
26
|
+
let name = context.formData.get('name')
|
|
27
|
+
let email = context.formData.get('email')
|
|
28
|
+
|
|
29
|
+
// Handle file uploads
|
|
30
|
+
let avatar = context.files?.get('avatar')
|
|
31
|
+
|
|
32
|
+
return Response.json({ name, email, hasAvatar: !!avatar })
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Custom File Upload Handler
|
|
37
|
+
|
|
38
|
+
You can use a custom upload handler to customize how file uploads are handled. The return value of the upload handler will be used as the value of the form field in the `FormData` object.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { formData } from '@remix-run/form-data-middleware'
|
|
42
|
+
import { writeFile } from 'node:fs/promises'
|
|
43
|
+
|
|
44
|
+
let router = createRouter({
|
|
45
|
+
middleware: [
|
|
46
|
+
formData({
|
|
47
|
+
async uploadHandler(upload) {
|
|
48
|
+
// Save to disk and return path
|
|
49
|
+
let path = `./uploads/${upload.name}`
|
|
50
|
+
await writeFile(path, Buffer.from(await upload.arrayBuffer()))
|
|
51
|
+
return path
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Suppress Parse Errors
|
|
59
|
+
|
|
60
|
+
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.formData` will be an empty `FormData` object.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
let router = createRouter({
|
|
64
|
+
middleware: [
|
|
65
|
+
formData({
|
|
66
|
+
suppressErrors: true, // Invalid form data won't throw
|
|
67
|
+
}),
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Related Packages
|
|
73
|
+
|
|
74
|
+
- [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
|
|
75
|
+
- [`form-data-parser`](https://github.com/remix-run/remix/tree/main/packages/form-data-parser) - The underlying form data parser
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAEhF,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FileUploadHandler, type ParseFormDataOptions } from '@remix-run/form-data-parser';
|
|
2
|
+
import type { Middleware } from '@remix-run/fetch-router';
|
|
3
|
+
export interface FormDataOptions extends ParseFormDataOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Set `true` to suppress parse errors. Default is `false`.
|
|
6
|
+
*/
|
|
7
|
+
suppressErrors?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* A function that handles file uploads. It receives a `FileUpload` object and may return any
|
|
10
|
+
* value that is a valid `FormData` value. Default is `undefined`, which means file uploads are
|
|
11
|
+
* stored in memory.
|
|
12
|
+
*/
|
|
13
|
+
uploadHandler?: FileUploadHandler;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Middleware that parses `FormData` from the request body and populates `context.formData`.
|
|
17
|
+
* @param options Options for parsing form data
|
|
18
|
+
* @returns A middleware function that parses form data
|
|
19
|
+
*/
|
|
20
|
+
export declare function formData(options?: FormDataOptions): Middleware;
|
|
21
|
+
//# sourceMappingURL=form-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-data.d.ts","sourceRoot":"","sources":["../../src/lib/form-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,6BAA6B,CAAA;AAEpC,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,yBAAyB,CAAA;AAEzE,MAAM,WAAW,eAAgB,SAAQ,oBAAoB;IAC3D;;OAEG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,UAAU,CA8B9D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FormDataParseError, parseFormData, } from '@remix-run/form-data-parser';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware that parses `FormData` from the request body and populates `context.formData`.
|
|
4
|
+
* @param options Options for parsing form data
|
|
5
|
+
* @returns A middleware function that parses form data
|
|
6
|
+
*/
|
|
7
|
+
export function formData(options) {
|
|
8
|
+
let suppressErrors = options?.suppressErrors ?? false;
|
|
9
|
+
let uploadHandler = options?.uploadHandler;
|
|
10
|
+
return async (context) => {
|
|
11
|
+
// Get the method from context to respect any method override middleware
|
|
12
|
+
let method = context.method;
|
|
13
|
+
if (method === 'GET' || method === 'HEAD') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let contentType = context.headers.get('Content-Type');
|
|
17
|
+
if (contentType == null ||
|
|
18
|
+
(!contentType.startsWith('multipart/') &&
|
|
19
|
+
!contentType.startsWith('application/x-www-form-urlencoded'))) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
context.formData = await parseFormData(context.request, options, uploadHandler);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (!suppressErrors || !(error instanceof FormDataParseError)) {
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@remix-run/form-data-middleware",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Middleware for parsing FormData from request bodies",
|
|
5
|
+
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/remix-run/remix.git",
|
|
10
|
+
"directory": "packages/form-data-middleware"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/remix-run/remix/tree/main/packages/form-data-middleware#readme",
|
|
13
|
+
"files": [
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md",
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"!src/**/*.test.ts"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^24.6.0",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"@remix-run/fetch-router": "0.9.0",
|
|
32
|
+
"@remix-run/form-data-parser": "0.14.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@remix-run/fetch-router": "^0.9.0",
|
|
36
|
+
"@remix-run/form-data-parser": "^0.14.0"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"fetch",
|
|
40
|
+
"router",
|
|
41
|
+
"middleware",
|
|
42
|
+
"form-data",
|
|
43
|
+
"multipart",
|
|
44
|
+
"file-upload"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.build.json",
|
|
48
|
+
"clean": "git clean -fdX",
|
|
49
|
+
"test": "node --disable-warning=ExperimentalWarning --test './src/**/*.test.ts'",
|
|
50
|
+
"typecheck": "tsc --noEmit"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FormDataParseError,
|
|
3
|
+
parseFormData,
|
|
4
|
+
type FileUploadHandler,
|
|
5
|
+
type ParseFormDataOptions,
|
|
6
|
+
} from '@remix-run/form-data-parser'
|
|
7
|
+
|
|
8
|
+
import type { Middleware, RequestContext } from '@remix-run/fetch-router'
|
|
9
|
+
|
|
10
|
+
export interface FormDataOptions extends ParseFormDataOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Set `true` to suppress parse errors. Default is `false`.
|
|
13
|
+
*/
|
|
14
|
+
suppressErrors?: boolean
|
|
15
|
+
/**
|
|
16
|
+
* A function that handles file uploads. It receives a `FileUpload` object and may return any
|
|
17
|
+
* value that is a valid `FormData` value. Default is `undefined`, which means file uploads are
|
|
18
|
+
* stored in memory.
|
|
19
|
+
*/
|
|
20
|
+
uploadHandler?: FileUploadHandler
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Middleware that parses `FormData` from the request body and populates `context.formData`.
|
|
25
|
+
* @param options Options for parsing form data
|
|
26
|
+
* @returns A middleware function that parses form data
|
|
27
|
+
*/
|
|
28
|
+
export function formData(options?: FormDataOptions): Middleware {
|
|
29
|
+
let suppressErrors = options?.suppressErrors ?? false
|
|
30
|
+
let uploadHandler = options?.uploadHandler
|
|
31
|
+
|
|
32
|
+
return async (context: RequestContext) => {
|
|
33
|
+
// Get the method from context to respect any method override middleware
|
|
34
|
+
let method = context.method
|
|
35
|
+
|
|
36
|
+
if (method === 'GET' || method === 'HEAD') {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let contentType = context.headers.get('Content-Type')
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
contentType == null ||
|
|
44
|
+
(!contentType.startsWith('multipart/') &&
|
|
45
|
+
!contentType.startsWith('application/x-www-form-urlencoded'))
|
|
46
|
+
) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
context.formData = await parseFormData(context.request, options, uploadHandler)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (!suppressErrors || !(error instanceof FormDataParseError)) {
|
|
54
|
+
throw error
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|