remix 3.0.0-beta.0 → 3.0.0-beta.2
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/dist/fetch-router.d.ts +7 -0
- package/dist/fetch-router.d.ts.map +1 -1
- package/dist/node-tsx/load-module.d.ts +2 -0
- package/dist/node-tsx/load-module.d.ts.map +1 -0
- package/dist/node-tsx/load-module.js +2 -0
- package/dist/node-tsx.d.ts +3 -0
- package/dist/node-tsx.d.ts.map +1 -0
- package/{src/node-serve.ts → dist/node-tsx.js} +2 -1
- package/dist/render-middleware.d.ts +2 -0
- package/dist/render-middleware.d.ts.map +1 -0
- package/dist/render-middleware.js +2 -0
- package/dist/route-pattern/href.d.ts +2 -0
- package/dist/route-pattern/href.d.ts.map +1 -0
- package/dist/route-pattern/href.js +2 -0
- package/dist/route-pattern/join.d.ts +2 -0
- package/dist/route-pattern/join.d.ts.map +1 -0
- package/dist/route-pattern/join.js +2 -0
- package/dist/route-pattern/match.d.ts +2 -0
- package/dist/route-pattern/match.d.ts.map +1 -0
- package/dist/route-pattern/match.js +2 -0
- package/package.json +158 -44
- package/src/assert/README.md +109 -0
- package/src/assets/README.md +539 -0
- package/src/async-context-middleware/README.md +100 -0
- package/src/auth/README.md +445 -0
- package/src/auth-middleware/README.md +246 -0
- package/src/cli/README.md +78 -0
- package/src/compression-middleware/README.md +176 -0
- package/src/cookie/README.md +106 -0
- package/src/cop-middleware/README.md +117 -0
- package/src/cors-middleware/README.md +174 -0
- package/src/csrf-middleware/README.md +99 -0
- package/src/data-schema/README.md +422 -0
- package/src/data-table/README.md +552 -0
- package/src/data-table-mysql/README.md +97 -0
- package/src/data-table-postgres/README.md +74 -0
- package/src/data-table-sqlite/README.md +84 -0
- package/src/fetch-proxy/README.md +46 -0
- package/src/fetch-router/README.md +902 -0
- package/src/fetch-router.ts +7 -0
- package/src/file-storage/README.md +57 -0
- package/src/file-storage-s3/README.md +47 -0
- package/src/form-data-middleware/README.md +109 -0
- package/src/form-data-parser/README.md +160 -0
- package/src/fs/README.md +60 -0
- package/src/headers/README.md +629 -0
- package/src/html-template/README.md +101 -0
- package/src/lazy-file/README.md +109 -0
- package/src/logger-middleware/README.md +132 -0
- package/src/method-override-middleware/README.md +71 -0
- package/src/mime/README.md +110 -0
- package/src/multipart-parser/README.md +241 -0
- package/src/node-fetch-server/README.md +352 -0
- package/src/node-tsx/README.md +79 -0
- package/src/node-tsx/load-module.ts +2 -0
- package/{dist/node-serve.js → src/node-tsx.ts} +2 -1
- package/src/render-middleware/README.md +99 -0
- package/src/render-middleware.ts +2 -0
- package/src/route-pattern/README.md +291 -0
- package/src/route-pattern/href.ts +2 -0
- package/src/route-pattern/join.ts +2 -0
- package/src/route-pattern/match.ts +2 -0
- package/src/session/README.md +171 -0
- package/src/session-middleware/README.md +109 -0
- package/src/session-storage-memcache/README.md +37 -0
- package/src/session-storage-redis/README.md +37 -0
- package/src/static-middleware/README.md +89 -0
- package/src/tar-parser/README.md +74 -0
- package/src/terminal/README.md +92 -0
- package/src/test/README.md +430 -0
- package/src/ui/README.md +219 -0
- package/src/ui/accordion/README.md +166 -0
- package/src/ui/anchor/README.md +153 -0
- package/src/ui/animation/README.md +316 -0
- package/src/ui/breadcrumbs/README.md +55 -0
- package/src/ui/button/README.md +44 -0
- package/src/ui/combobox/README.md +145 -0
- package/src/ui/glyph/README.md +72 -0
- package/src/ui/listbox/README.md +115 -0
- package/src/ui/menu/README.md +96 -0
- package/src/ui/popover/README.md +122 -0
- package/src/ui/scroll-lock/README.md +33 -0
- package/src/ui/select/README.md +107 -0
- package/src/ui/server/README.md +90 -0
- package/src/ui/test/README.md +107 -0
- package/src/ui/theme/README.md +103 -0
- package/dist/node-serve.d.ts +0 -2
- package/dist/node-serve.d.ts.map +0 -1
package/src/fetch-router.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
// IMPORTANT: This file is auto-generated, please do not edit manually.
|
|
2
2
|
export * from '@remix-run/fetch-router'
|
|
3
|
+
|
|
4
|
+
export interface RouterTypes {}
|
|
5
|
+
type RemixRouterTypes = RouterTypes
|
|
6
|
+
|
|
7
|
+
declare module '@remix-run/fetch-router' {
|
|
8
|
+
interface RouterTypes extends RemixRouterTypes {}
|
|
9
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# file-storage
|
|
2
|
+
|
|
3
|
+
Key/value storage interfaces for server-side [`File` objects](https://developer.mozilla.org/en-US/docs/Web/API/File). `file-storage` gives Remix apps one consistent API across local disk and memory backends.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Simple API** - Intuitive key/value API (like [Web Storage](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API), but for `File`s instead of strings)
|
|
8
|
+
- **Multiple Backends** - Built-in filesystem and memory backends
|
|
9
|
+
- **Streaming Support** - Stream file content to and from storage
|
|
10
|
+
- **Metadata Preservation** - Preserves all `File` metadata including `file.name`, `file.type`, `file.size`, and `file.lastModified`
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm i remix
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### File System
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createFsFileStorage } from 'remix/file-storage/fs'
|
|
24
|
+
|
|
25
|
+
let storage = createFsFileStorage('./user/files')
|
|
26
|
+
|
|
27
|
+
let file = new File(['hello world'], 'hello.txt', { type: 'text/plain' })
|
|
28
|
+
let key = 'hello-key'
|
|
29
|
+
|
|
30
|
+
// Put the file in storage.
|
|
31
|
+
await storage.set(key, file)
|
|
32
|
+
|
|
33
|
+
// Then, sometime later...
|
|
34
|
+
let fileFromStorage = await storage.get(key)
|
|
35
|
+
|
|
36
|
+
if (fileFromStorage != null) {
|
|
37
|
+
// All of the original file's metadata is intact
|
|
38
|
+
fileFromStorage.name // 'hello.txt'
|
|
39
|
+
fileFromStorage.type // 'text/plain'
|
|
40
|
+
|
|
41
|
+
// The filesystem backend returns a LazyFile, so you can stream it directly.
|
|
42
|
+
let response = new Response(fileFromStorage.stream())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// To remove from storage
|
|
46
|
+
await storage.remove(key)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Related Packages
|
|
50
|
+
|
|
51
|
+
- [`file-storage-s3`](https://github.com/remix-run/remix/tree/main/packages/file-storage-s3) - S3 backend for `file-storage`
|
|
52
|
+
- [`form-data-parser`](https://github.com/remix-run/remix/tree/main/packages/form-data-parser) - Pairs well with this library for storing `FileUpload` objects received in `multipart/form-data` requests
|
|
53
|
+
- [`lazy-file`](https://github.com/remix-run/remix/tree/main/packages/lazy-file) - The streaming `File` implementation used internally to stream files from storage
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# file-storage-s3
|
|
2
|
+
|
|
3
|
+
S3 backend for [`remix/file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage).
|
|
4
|
+
Use this package when you want the `FileStorage` API backed by AWS S3 or an S3-compatible provider.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **S3-Compatible API** - Works with AWS S3 and S3-compatible APIs (e.g. MinIO, LocalStack)
|
|
9
|
+
- **Metadata Preservation** - Preserves `File` metadata (`name`, `type`, `size`, `lastModified`)
|
|
10
|
+
- **Runtime-Agnostic Signing** - Uses [`aws4fetch`](https://github.com/mhart/aws4fetch) for SigV4 signing
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm i remix
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createS3FileStorage } from 'remix/file-storage/s3'
|
|
22
|
+
|
|
23
|
+
let storage = createS3FileStorage({
|
|
24
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
25
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
26
|
+
bucket: 'my-app-uploads',
|
|
27
|
+
region: 'us-east-1',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
await storage.set(
|
|
31
|
+
'uploads/hello.txt',
|
|
32
|
+
new File(['hello world'], 'hello.txt', { type: 'text/plain' }),
|
|
33
|
+
)
|
|
34
|
+
let file = await storage.get('uploads/hello.txt')
|
|
35
|
+
await storage.remove('uploads/hello.txt')
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For S3-compatible providers such as MinIO and LocalStack, set `endpoint` and `forcePathStyle: true`.
|
|
39
|
+
|
|
40
|
+
## Related Packages
|
|
41
|
+
|
|
42
|
+
- [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) - Core `FileStorage` interface and filesystem/memory backends
|
|
43
|
+
- [`form-data-parser`](https://github.com/remix-run/remix/tree/main/packages/form-data-parser) - Parses `multipart/form-data` uploads into `FileUpload` objects
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# form-data-middleware
|
|
2
|
+
|
|
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.formData` (or `context.get(FormData)`).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Request Form Parsing** - Parses request body form data once per request
|
|
8
|
+
- **File Access** - Uploaded files are available from `context.formData` (or `context.get(FormData)`)
|
|
9
|
+
- **Custom Upload Handling** - Supports pluggable upload handlers for file processing
|
|
10
|
+
- **Error Control** - Optional suppression for malformed form data
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm i remix
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Use the `formData()` middleware at the router level to parse `FormData` from the request body and make it available as `context.formData` (or `context.get(FormData)`).
|
|
21
|
+
|
|
22
|
+
When `formData()` runs successfully it always provides a `FormData` value. Requests that do not contain a form body, including `GET` and `HEAD` requests, receive an empty `FormData`.
|
|
23
|
+
|
|
24
|
+
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)`.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createRouter } from 'remix/router'
|
|
28
|
+
import { formData } from 'remix/middleware/form-data'
|
|
29
|
+
|
|
30
|
+
let router = createRouter({
|
|
31
|
+
middleware: [formData()],
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
router.post('/users', async (context) => {
|
|
35
|
+
let formData = context.formData
|
|
36
|
+
let name = formData.get('name')
|
|
37
|
+
let email = formData.get('email')
|
|
38
|
+
|
|
39
|
+
// Handle file uploads
|
|
40
|
+
let avatar = formData.get('avatar')
|
|
41
|
+
|
|
42
|
+
return Response.json({ name, email, hasAvatar: avatar instanceof File })
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use `context.formData` (or `context.get(FormData)`).
|
|
47
|
+
|
|
48
|
+
### Custom File Upload Handler
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { formData } from 'remix/middleware/form-data'
|
|
54
|
+
import { writeFile } from 'node:fs/promises'
|
|
55
|
+
|
|
56
|
+
let router = createRouter({
|
|
57
|
+
middleware: [
|
|
58
|
+
formData({
|
|
59
|
+
async uploadHandler(upload) {
|
|
60
|
+
// Save to disk and return path
|
|
61
|
+
let path = `./uploads/${upload.name}`
|
|
62
|
+
await writeFile(path, Buffer.from(await upload.arrayBuffer()))
|
|
63
|
+
return path
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Limit Multipart Growth
|
|
71
|
+
|
|
72
|
+
`formData()` forwards multipart limit options to `parseFormData()`, so you can cap uploads with
|
|
73
|
+
`maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, and `maxTotalSize`.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
let router = createRouter({
|
|
77
|
+
middleware: [
|
|
78
|
+
formData({
|
|
79
|
+
maxFiles: 5,
|
|
80
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
81
|
+
maxParts: 25,
|
|
82
|
+
maxTotalSize: 12 * 1024 * 1024,
|
|
83
|
+
}),
|
|
84
|
+
],
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Suppress Parse Errors
|
|
89
|
+
|
|
90
|
+
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.formData` will be an empty `FormData` object. Multipart limit violations from `maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, or `maxTotalSize` are never suppressed.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
let router = createRouter({
|
|
94
|
+
middleware: [
|
|
95
|
+
formData({
|
|
96
|
+
suppressErrors: true, // Invalid form data won't throw
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Related Packages
|
|
103
|
+
|
|
104
|
+
- [`fetch-router`](https://github.com/remix-run/remix/tree/main/packages/fetch-router) - Router for the web Fetch API
|
|
105
|
+
- [`form-data-parser`](https://github.com/remix-run/remix/tree/main/packages/form-data-parser) - The underlying form data parser
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# form-data-parser
|
|
2
|
+
|
|
3
|
+
A streaming `multipart/form-data` parser that solves memory issues with file uploads in server environments. Built as an enhanced replacement for the native `request.formData()` API, it enables efficient handling of large file uploads by streaming directly to disk or cloud storage services like [AWS S3](https://aws.amazon.com/s3/) or [Cloudflare R2](https://www.cloudflare.com/developer-platform/r2/), preventing server crashes from memory exhaustion.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Drop-in replacement** for `request.formData()` with streaming file upload support
|
|
8
|
+
- **Minimal buffering** - processes file upload streams with minimal memory footprint
|
|
9
|
+
- **Standards-based** - built on the [web Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) and [File API](https://developer.mozilla.org/en-US/docs/Web/API/File)
|
|
10
|
+
- **Smart fallback** - automatically uses native `request.formData()` for non-`multipart/form-data` requests
|
|
11
|
+
- **Storage agnostic** - works with any storage backend (local disk, S3, R2, etc.)
|
|
12
|
+
|
|
13
|
+
## Why You Need This
|
|
14
|
+
|
|
15
|
+
The native [`request.formData()` method](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) has a few major flaws in server environments:
|
|
16
|
+
|
|
17
|
+
- It buffers all file uploads in memory
|
|
18
|
+
- It does not provide fine-grained control over file upload handling
|
|
19
|
+
- It does not prevent DoS attacks from malicious requests
|
|
20
|
+
|
|
21
|
+
In normal usage, this makes it difficult to process requests with large file uploads because they can exhaust your server's RAM and crash the application.
|
|
22
|
+
|
|
23
|
+
For attackers, this creates an attack vector where malicious actors can overwhelm your server's memory by sending large payloads with many files.
|
|
24
|
+
|
|
25
|
+
`form-data-parser` solves this by handling file uploads as they arrive in the request body stream, allowing you to safely store files and use either a) the `File` directly or b) a unique identifier for that file in the returned `FormData` object.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm i remix
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
The `parseFormData` interface allows you to define an "upload handler" function for fine-grained control of handling file uploads.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import * as fsp from 'node:fs/promises'
|
|
39
|
+
import type { FileUpload } from 'remix/form-data-parser'
|
|
40
|
+
import { parseFormData } from 'remix/form-data-parser'
|
|
41
|
+
|
|
42
|
+
// Define how to handle incoming file uploads
|
|
43
|
+
async function uploadHandler(fileUpload: FileUpload) {
|
|
44
|
+
// Is this file upload from the <input type="file" name="user-avatar"> field?
|
|
45
|
+
if (fileUpload.fieldName === 'user-avatar') {
|
|
46
|
+
let filename = `/uploads/user-${user.id}-avatar.bin`
|
|
47
|
+
|
|
48
|
+
// Store the file safely on disk
|
|
49
|
+
await fsp.writeFile(filename, fileUpload.bytes)
|
|
50
|
+
|
|
51
|
+
// Return the file name to use in the FormData object so we don't
|
|
52
|
+
// keep the file contents around in memory.
|
|
53
|
+
return filename
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Ignore unrecognized fields
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Handle form submissions with file uploads
|
|
60
|
+
async function requestHandler(request: Request) {
|
|
61
|
+
// Parse the form data from the request.body stream, passing any files
|
|
62
|
+
// through your upload handler as they are parsed from the stream
|
|
63
|
+
let formData = await parseFormData(request, uploadHandler)
|
|
64
|
+
|
|
65
|
+
let avatarFilename = formData.get('user-avatar')
|
|
66
|
+
|
|
67
|
+
if (avatarFilename != null) {
|
|
68
|
+
console.log(`User avatar uploaded to ${avatarFilename}`)
|
|
69
|
+
} else {
|
|
70
|
+
console.log(`No user avatar file was uploaded`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
To validate the resulting `FormData` object with `remix/data-schema`, use the
|
|
76
|
+
`remix/data-schema/form-data` helpers.
|
|
77
|
+
|
|
78
|
+
To limit the overall shape of multipart requests, use the `maxHeaderSize`, `maxFileSize`, `maxFiles`, `maxParts`, and `maxTotalSize` options. By default, `parseFormData()` uses `maxFiles = 20`, `maxParts = 1000`, and `maxTotalSize = maxFiles * maxFileSize + 1 MiB`.
|
|
79
|
+
|
|
80
|
+
Known limit errors are thrown directly so you can handle them with `instanceof` checks. Other failures while parsing the request body are wrapped in `FormDataParseError`, with the original error available as `error.cause`. Errors thrown or rejected by your `uploadHandler` are not wrapped.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import {
|
|
84
|
+
FormDataParseError,
|
|
85
|
+
MaxFilesExceededError,
|
|
86
|
+
MaxFileSizeExceededError,
|
|
87
|
+
MaxHeaderSizeExceededError,
|
|
88
|
+
MaxPartsExceededError,
|
|
89
|
+
MaxTotalSizeExceededError,
|
|
90
|
+
} from 'remix/form-data-parser'
|
|
91
|
+
|
|
92
|
+
const oneKb = 1024
|
|
93
|
+
const oneMb = 1024 * oneKb
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
let formData = await parseFormData(request, {
|
|
97
|
+
maxFiles: 5,
|
|
98
|
+
maxFileSize: 10 * oneMb,
|
|
99
|
+
maxParts: 25,
|
|
100
|
+
maxTotalSize: 12 * oneMb,
|
|
101
|
+
})
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof MaxFilesExceededError) {
|
|
104
|
+
console.error(`Request may not contain more than 5 files`)
|
|
105
|
+
} else if (error instanceof MaxHeaderSizeExceededError) {
|
|
106
|
+
console.error(`Multipart headers may not exceed the configured size limit`)
|
|
107
|
+
} else if (error instanceof MaxFileSizeExceededError) {
|
|
108
|
+
console.error(`Files may not be larger than 10 MiB`)
|
|
109
|
+
} else if (error instanceof MaxPartsExceededError) {
|
|
110
|
+
console.error(`Request may not contain more than 25 multipart parts`)
|
|
111
|
+
} else if (error instanceof MaxTotalSizeExceededError) {
|
|
112
|
+
console.error(`Multipart request may not exceed 12 MiB of total content`)
|
|
113
|
+
} else if (error instanceof FormDataParseError) {
|
|
114
|
+
console.error(`Could not parse form data:`, error.cause ?? error)
|
|
115
|
+
} else {
|
|
116
|
+
throw error
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
If you're looking for a more flexible storage solution for `FileUpload` objects, this library pairs really well with [the `file-storage` library](https://github.com/remix-run/remix/tree/main/packages/file-storage) for keeping files in various storage backends.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createFsFileStorage } from 'remix/file-storage/fs'
|
|
125
|
+
import type { FileUpload } from 'remix/form-data-parser'
|
|
126
|
+
import { parseFormData } from 'remix/form-data-parser'
|
|
127
|
+
|
|
128
|
+
// Set up storage for uploaded files
|
|
129
|
+
const fileStorage = createFsFileStorage('/uploads/user-avatars')
|
|
130
|
+
|
|
131
|
+
// Define how to handle incoming file uploads
|
|
132
|
+
async function uploadHandler(fileUpload: FileUpload) {
|
|
133
|
+
// Is this file upload from the <input type="file" name="user-avatar"> field?
|
|
134
|
+
if (fileUpload.fieldName === 'user-avatar') {
|
|
135
|
+
let storageKey = `user-${user.id}-avatar`
|
|
136
|
+
|
|
137
|
+
// Put the file in storage and return the stored LazyFile
|
|
138
|
+
return fileStorage.put(storageKey, fileUpload)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Ignore unrecognized fields
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Demos
|
|
146
|
+
|
|
147
|
+
The [`demos` directory](https://github.com/remix-run/remix/tree/main/packages/form-data-parser/demos) contains working demos:
|
|
148
|
+
|
|
149
|
+
- [`demos/node`](https://github.com/remix-run/remix/tree/main/packages/form-data-parser/demos/node) - using form-data-parser with file-storage in Node.js
|
|
150
|
+
|
|
151
|
+
## Related Packages
|
|
152
|
+
|
|
153
|
+
- [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Tiny,
|
|
154
|
+
standards-aligned validation with a `form-data` export for `FormData` and `URLSearchParams`
|
|
155
|
+
- [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) - A simple key/value interface for storing `FileUpload` objects you get from the parser
|
|
156
|
+
- [`multipart-parser`](https://github.com/remix-run/remix/tree/main/packages/multipart-parser) - The parser used internally for parsing `multipart/form-data` HTTP messages
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|
package/src/fs/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# fs
|
|
2
|
+
|
|
3
|
+
Lazy, streaming filesystem utilities for JavaScript. This package provides utilities for working with files on the local filesystem using the [`LazyFile`](https://github.com/remix-run/remix/tree/main/packages/lazy-file)/ native [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Web Standards** - Uses [`LazyFile`](https://github.com/remix-run/remix/tree/main/packages/lazy-file) which matches the native [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) API and provides `.stream()`, `.toFile()`, and `.toBlob()` for converting to native types.
|
|
8
|
+
- **Seamless Node.js Compat** - Works seamlessly with Node.js file descriptors and handles
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm i remix
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### Opening Lazy Files
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { openLazyFile } from 'remix/fs'
|
|
22
|
+
|
|
23
|
+
// Open a file from the filesystem
|
|
24
|
+
let lazyFile = openLazyFile('./path/to/file.json')
|
|
25
|
+
|
|
26
|
+
// The file is lazy - no data is read until you call lazyFile.text(), lazyFile.bytes(), etc.
|
|
27
|
+
let json = JSON.parse(await lazyFile.text())
|
|
28
|
+
|
|
29
|
+
// You can override file metadata
|
|
30
|
+
let customLazyFile = openLazyFile('./image.jpg', {
|
|
31
|
+
name: 'custom-name.jpg',
|
|
32
|
+
type: 'image/jpeg',
|
|
33
|
+
lastModified: Date.now(),
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Writing Files
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { openLazyFile, writeFile } from 'remix/fs'
|
|
41
|
+
|
|
42
|
+
// Read a file and write it elsewhere
|
|
43
|
+
let lazyFile = openLazyFile('./source.txt')
|
|
44
|
+
await writeFile('./destination.txt', lazyFile)
|
|
45
|
+
|
|
46
|
+
// Write to an open file handle
|
|
47
|
+
import * as fsp from 'node:fs/promises'
|
|
48
|
+
let handle = await fsp.open('./destination.txt', 'w')
|
|
49
|
+
await writeFile(handle, lazyFile)
|
|
50
|
+
await handle.close()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Related Packages
|
|
54
|
+
|
|
55
|
+
- [`lazy-file`](https://github.com/remix-run/remix/tree/main/packages/lazy-file) - Lazy, streaming `Blob`/`File` implementation
|
|
56
|
+
- [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) - Storage abstraction for files
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)
|