@remix-run/file-storage 0.8.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Michael Jackson
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.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # file-storage
2
+
3
+ `file-storage` is a key/value interface for storing [`File` objects](https://developer.mozilla.org/en-US/docs/Web/API/File) in JavaScript. Similar to how `localStorage` allows you to store key/value pairs of strings in the browser, `file-storage` allows you to store key/value pairs of files on the server.
4
+
5
+ ## Features
6
+
7
+ - Simple, 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
+ - A generic `FileStorage` interface that works for various large object storage backends (can be adapted to AWS S3, Cloudflare R2, etc.)
9
+ - Support streaming file content to and from storage
10
+ - Preserves all `File` metadata including `file.name`, `file.type`, and `file.lastModified`
11
+
12
+ ## Installation
13
+
14
+ Install from [npm](https://www.npmjs.com/):
15
+
16
+ ```sh
17
+ npm install @remix-run/file-storage
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ import { LocalFileStorage } from '@remix-run/file-storage/local';
24
+
25
+ let storage = new LocalFileStorage('./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
+ // All of the original file's metadata is intact
36
+ fileFromStorage.name; // 'hello.txt'
37
+ fileFromStorage.type; // 'text/plain'
38
+
39
+ // To remove from storage
40
+ await storage.remove(key);
41
+ ```
42
+
43
+ The `FileStorage` interface allows you to implement your own file storage for custom storage backends:
44
+
45
+ ```ts
46
+ import { type FileStorage } from '@remix-run/file-storage';
47
+
48
+ class CustomFileStorage implements FileStorage {
49
+ /**
50
+ * Returns `true` if a file with the given key exists, `false` otherwise.
51
+ */
52
+ has(key: string): boolean | Promise<boolean> {
53
+ // ...
54
+ }
55
+ /**
56
+ * Puts a file in storage at the given key.
57
+ */
58
+ set(key: string, file: File): void | Promise<void> {
59
+ // ...
60
+ }
61
+ /**
62
+ * Returns the file with the given key, or `null` if no such key exists.
63
+ */
64
+ get(key: string): File | null | Promise<File | null> {
65
+ // ...
66
+ }
67
+ /**
68
+ * Removes the file with the given key from storage.
69
+ */
70
+ remove(key: string): void | Promise<void> {
71
+ // ...
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Related Packages
77
+
78
+ - [`form-data-parser`](https://github.com/remix-run/remix/tree/v3/packages/form-data-parser) - Pairs well with this library for storing `FileUpload` objects received in `multipart/form-data` requests
79
+ - [`lazy-file`](https://github.com/remix-run/remix/tree/v3/packages/lazy-file) - The streaming `File` implementation used internally to stream files from storage
80
+
81
+ ## License
82
+
83
+ See [LICENSE](https://github.com/remix-run/remix/blob/v3/LICENSE)
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/file-storage.ts
17
+ var file_storage_exports = {};
18
+ module.exports = __toCommonJS(file_storage_exports);
19
+ //# sourceMappingURL=file-storage.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/file-storage.ts"],
4
+ "sourcesContent": ["export type {\n FileStorage,\n FileKey,\n FileMetadata,\n ListOptions,\n ListResult,\n} from './lib/file-storage.ts';\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export type { FileStorage, FileKey, FileMetadata, ListOptions, ListResult, } from './lib/file-storage.ts';
2
+ //# sourceMappingURL=file-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../src/file-storage.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,OAAO,EACP,YAAY,EACZ,WAAW,EACX,UAAU,GACX,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=file-storage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * A key/value interface for storing `File` objects.
3
+ */
4
+ export interface FileStorage {
5
+ /**
6
+ * Get a [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) at the given key.
7
+ * @param key The key to look up
8
+ * @returns The file with the given key, or `null` if no such key exists
9
+ */
10
+ get(key: string): File | null | Promise<File | null>;
11
+ /**
12
+ * Check if a file with the given key exists.
13
+ * @param key The key to look up
14
+ * @returns `true` if a file with the given key exists, `false` otherwise
15
+ */
16
+ has(key: string): boolean | Promise<boolean>;
17
+ /**
18
+ * List the files in storage.
19
+ *
20
+ * The following `options` are available:
21
+ *
22
+ * - `cursor`: An opaque string that allows you to paginate over the keys in storage
23
+ * - `includeMetadata`: If `true`, include file metadata in the result
24
+ * - `limit`: The maximum number of files to return
25
+ * - `prefix`: Only return keys that start with this string
26
+ *
27
+ * For example, to list all files under keys that start with `user123/`:
28
+ *
29
+ * ```ts
30
+ * let result = await storage.list({ prefix: 'user123/' });
31
+ * console.log(result.files);
32
+ * // [
33
+ * // { key: "user123/..." },
34
+ * // { key: "user123/..." },
35
+ * // ...
36
+ * // ]
37
+ * ```
38
+ *
39
+ * `result.files` will be an array of `{ key: string }` objects. To include metadata about each
40
+ * file, use `includeMetadata: true`.
41
+ *
42
+ * ```ts
43
+ * let result = await storage.list({ prefix: 'user123/', includeMetadata: true });
44
+ * console.log(result.files);
45
+ * // [
46
+ * // {
47
+ * // key: "user123/...",
48
+ * // lastModified: 1737955705270,
49
+ * // name: "hello.txt",
50
+ * // size: 16,
51
+ * // type: "text/plain"
52
+ * // },
53
+ * // ...
54
+ * // ]
55
+ * ```
56
+ *
57
+ * Pagination is done via an opaque `cursor` property in the list result object. If it is not
58
+ * `undefined`, there are more files to list. You can list them by passing the `cursor` back in
59
+ * the `options` object on the next call.
60
+ *
61
+ * ```ts
62
+ * let result = await storage.list();
63
+ *
64
+ * console.log(result.files);
65
+ *
66
+ * if (result.cursor !== undefined) {
67
+ * let result2 = await storage.list({ cursor: result.cursor });
68
+ * }
69
+ * ```
70
+ *
71
+ * Use the `limit` option to limit how many results you get back in the `files` array.
72
+ *
73
+ * @param options Options for the list operation
74
+ * @returns An object with an array of `files` and an optional `cursor` property
75
+ */
76
+ list<T extends ListOptions>(options?: T): ListResult<T> | Promise<ListResult<T>>;
77
+ /**
78
+ * Put a [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) in storage and return
79
+ * a new file backed by this storage.
80
+ * @param key The key to store the file under
81
+ * @param file The file to store
82
+ * @returns A new File object backed by this storage
83
+ */
84
+ put(key: string, file: File): File | Promise<File>;
85
+ /**
86
+ * Remove the file with the given key from storage.
87
+ * @param key The key to remove
88
+ * @returns A promise that resolves when the file has been removed
89
+ */
90
+ remove(key: string): void | Promise<void>;
91
+ /**
92
+ * Put a [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) in storage at the given
93
+ * key.
94
+ * @param key The key to store the file under
95
+ * @param file The file to store
96
+ * @returns A promise that resolves when the file has been stored
97
+ */
98
+ set(key: string, file: File): void | Promise<void>;
99
+ }
100
+ export interface FileKey {
101
+ /**
102
+ * The key of the file in storage.
103
+ */
104
+ key: string;
105
+ }
106
+ /**
107
+ * Metadata about a file in storage.
108
+ */
109
+ export interface FileMetadata extends FileKey {
110
+ /**
111
+ * The last modified time of the file (in ms since the Unix epoch).
112
+ */
113
+ lastModified: number;
114
+ /**
115
+ * The name of the file.
116
+ */
117
+ name: string;
118
+ /**
119
+ * The size of the file in bytes.
120
+ */
121
+ size: number;
122
+ /**
123
+ * The MIME type of the file.
124
+ */
125
+ type: string;
126
+ }
127
+ export interface ListOptions {
128
+ /**
129
+ * An opaque string that allows you to paginate over the keys in storage.
130
+ */
131
+ cursor?: string;
132
+ /**
133
+ * If `true`, include file metadata in the result.
134
+ */
135
+ includeMetadata?: boolean;
136
+ /**
137
+ * The maximum number of files to return.
138
+ */
139
+ limit?: number;
140
+ /**
141
+ * Only return files with keys that start with this prefix.
142
+ */
143
+ prefix?: string;
144
+ }
145
+ export interface ListResult<T extends ListOptions> {
146
+ /**
147
+ * An opaque string that allows you to paginate over the keys in storage. Pass this back in the
148
+ * `options` object on the next `list()` call to get the next page of results.
149
+ */
150
+ cursor?: string;
151
+ /**
152
+ * A list of the files in storage.
153
+ */
154
+ files: (T extends {
155
+ includeMetadata: true;
156
+ } ? FileMetadata : FileKey)[];
157
+ }
158
+ //# sourceMappingURL=file-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/file-storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAErD;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA0DG;IACH,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjF;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnD;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1C;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,OAAO;IAC3C;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,WAAW;IAC/C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC,SAAS;QAAE,eAAe,EAAE,IAAI,CAAA;KAAE,GAAG,YAAY,GAAG,OAAO,CAAC,EAAE,CAAC;CACzE"}
@@ -0,0 +1,26 @@
1
+ import type { FileStorage, ListOptions, ListResult } from './file-storage.ts';
2
+ /**
3
+ * A `FileStorage` that is backed by a directory on the local filesystem.
4
+ *
5
+ * Important: No attempt is made to avoid overwriting existing files, so the directory used should
6
+ * be a new directory solely dedicated to this storage object.
7
+ *
8
+ * Note: Keys have no correlation to file names on disk, so they may be any string including
9
+ * characters that are not valid in file names. Additionally, individual `File` names have no
10
+ * correlation to names of files on disk, so multiple files with the same name may be stored in the
11
+ * same storage object.
12
+ */
13
+ export declare class LocalFileStorage implements FileStorage {
14
+ #private;
15
+ /**
16
+ * @param directory The directory where files are stored
17
+ */
18
+ constructor(directory: string);
19
+ get(key: string): Promise<File | null>;
20
+ has(key: string): Promise<boolean>;
21
+ list<T extends ListOptions>(options?: T): Promise<ListResult<T>>;
22
+ put(key: string, file: File): Promise<File>;
23
+ remove(key: string): Promise<void>;
24
+ set(key: string, file: File): Promise<void>;
25
+ }
26
+ //# sourceMappingURL=local-file-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/local-file-storage.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAgB,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAI5F;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,YAAW,WAAW;;IAGlD;;OAEG;gBACS,SAAS,EAAE,MAAM;IAkBvB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAoBtC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWlC,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAgDhE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CA2BlD"}
@@ -0,0 +1,17 @@
1
+ import type { FileStorage, ListOptions, ListResult } from './file-storage.ts';
2
+ /**
3
+ * A simple, in-memory implementation of the `FileStorage` interface.
4
+ *
5
+ * Note: Any files you put in storage will have their entire contents buffered in memory, so this is not suitable for large files
6
+ * in production scenarios.
7
+ */
8
+ export declare class MemoryFileStorage implements FileStorage {
9
+ #private;
10
+ get(key: string): File | null;
11
+ has(key: string): boolean;
12
+ list<T extends ListOptions>(options?: T): ListResult<T>;
13
+ put(key: string, file: File): Promise<File>;
14
+ remove(key: string): void;
15
+ set(key: string, file: File): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=memory-file-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-file-storage.d.ts","sourceRoot":"","sources":["../../src/lib/memory-file-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,WAAW;;IAGnD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAwCjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAInB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CASlD"}