@remix-run/file-storage 0.9.0 → 0.10.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.
@@ -1,11 +1,11 @@
1
- import * as fs from 'node:fs';
2
- import * as fsp from 'node:fs/promises';
3
- import * as path from 'node:path';
4
- import { openFile, writeFile } from '@remix-run/lazy-file/fs';
1
+ import * as fs from 'node:fs'
2
+ import * as fsp from 'node:fs/promises'
3
+ import * as path from 'node:path'
4
+ import { openFile, writeFile } from '@remix-run/lazy-file/fs'
5
5
 
6
- import type { FileStorage, FileMetadata, ListOptions, ListResult } from './file-storage.ts';
6
+ import type { FileStorage, FileMetadata, ListOptions, ListResult } from './file-storage.ts'
7
7
 
8
- type MetadataJson = Omit<FileMetadata, 'size'>;
8
+ type MetadataJson = Omit<FileMetadata, 'size'>
9
9
 
10
10
  /**
11
11
  * A `FileStorage` that is backed by a directory on the local filesystem.
@@ -19,171 +19,171 @@ type MetadataJson = Omit<FileMetadata, 'size'>;
19
19
  * same storage object.
20
20
  */
21
21
  export class LocalFileStorage implements FileStorage {
22
- #dirname: string;
22
+ #dirname: string
23
23
 
24
24
  /**
25
25
  * @param directory The directory where files are stored
26
26
  */
27
27
  constructor(directory: string) {
28
- this.#dirname = path.resolve(directory);
28
+ this.#dirname = path.resolve(directory)
29
29
 
30
30
  try {
31
- let stats = fs.statSync(this.#dirname);
31
+ let stats = fs.statSync(this.#dirname)
32
32
 
33
33
  if (!stats.isDirectory()) {
34
- throw new Error(`Path "${this.#dirname}" is not a directory`);
34
+ throw new Error(`Path "${this.#dirname}" is not a directory`)
35
35
  }
36
36
  } catch (error) {
37
37
  if (!isNoEntityError(error)) {
38
- throw error;
38
+ throw error
39
39
  }
40
40
 
41
- fs.mkdirSync(this.#dirname, { recursive: true });
41
+ fs.mkdirSync(this.#dirname, { recursive: true })
42
42
  }
43
43
  }
44
44
 
45
45
  async get(key: string): Promise<File | null> {
46
- let { filePath, metaPath } = await this.#getPaths(key);
46
+ let { filePath, metaPath } = await this.#getPaths(key)
47
47
 
48
48
  try {
49
- let meta = await readMetadata(metaPath);
49
+ let meta = await readMetadata(metaPath)
50
50
 
51
51
  return openFile(filePath, {
52
52
  lastModified: meta.lastModified,
53
53
  name: meta.name,
54
54
  type: meta.type,
55
- });
55
+ })
56
56
  } catch (error) {
57
57
  if (!isNoEntityError(error)) {
58
- throw error;
58
+ throw error
59
59
  }
60
60
 
61
- return null;
61
+ return null
62
62
  }
63
63
  }
64
64
 
65
65
  async has(key: string): Promise<boolean> {
66
- let { metaPath } = await this.#getPaths(key);
66
+ let { metaPath } = await this.#getPaths(key)
67
67
 
68
68
  try {
69
- await fsp.access(metaPath);
70
- return true;
69
+ await fsp.access(metaPath)
70
+ return true
71
71
  } catch {
72
- return false;
72
+ return false
73
73
  }
74
74
  }
75
75
 
76
76
  async list<T extends ListOptions>(options?: T): Promise<ListResult<T>> {
77
- let { cursor, includeMetadata = false, limit = 32, prefix } = options ?? {};
77
+ let { cursor, includeMetadata = false, limit = 32, prefix } = options ?? {}
78
78
 
79
- let files: any[] = [];
80
- let foundCursor = cursor === undefined;
81
- let nextCursor: string | undefined;
82
- let lastHash: string | undefined;
79
+ let files: any[] = []
80
+ let foundCursor = cursor === undefined
81
+ let nextCursor: string | undefined
82
+ let lastHash: string | undefined
83
83
 
84
84
  outerLoop: for await (let subdir of await fsp.opendir(this.#dirname)) {
85
- if (!subdir.isDirectory()) continue;
85
+ if (!subdir.isDirectory()) continue
86
86
 
87
87
  for await (let file of await fsp.opendir(path.join(this.#dirname, subdir.name))) {
88
- if (!file.isFile() || !file.name.endsWith('.meta.json')) continue;
88
+ if (!file.isFile() || !file.name.endsWith('.meta.json')) continue
89
89
 
90
- let hash = file.name.slice(0, -10); // Remove ".meta.json"
90
+ let hash = file.name.slice(0, -10) // Remove ".meta.json"
91
91
 
92
92
  if (foundCursor) {
93
- let meta = await readMetadata(path.join(this.#dirname, subdir.name, file.name));
93
+ let meta = await readMetadata(path.join(this.#dirname, subdir.name, file.name))
94
94
 
95
95
  if (prefix != null && !meta.key.startsWith(prefix)) {
96
- continue;
96
+ continue
97
97
  }
98
98
 
99
99
  if (files.length >= limit) {
100
- nextCursor = lastHash;
101
- break outerLoop;
100
+ nextCursor = lastHash
101
+ break outerLoop
102
102
  }
103
103
 
104
104
  if (includeMetadata) {
105
- let size = (await fsp.stat(path.join(this.#dirname, subdir.name, `${hash}.dat`))).size;
106
- files.push({ ...meta, size });
105
+ let size = (await fsp.stat(path.join(this.#dirname, subdir.name, `${hash}.dat`))).size
106
+ files.push({ ...meta, size })
107
107
  } else {
108
- files.push({ key: meta.key });
108
+ files.push({ key: meta.key })
109
109
  }
110
110
  } else if (hash === cursor) {
111
- foundCursor = true;
111
+ foundCursor = true
112
112
  }
113
113
 
114
- lastHash = hash;
114
+ lastHash = hash
115
115
  }
116
116
  }
117
117
 
118
118
  return {
119
119
  cursor: nextCursor,
120
120
  files,
121
- };
121
+ }
122
122
  }
123
123
 
124
124
  async put(key: string, file: File): Promise<File> {
125
- await this.set(key, file);
126
- return (await this.get(key))!;
125
+ await this.set(key, file)
126
+ return (await this.get(key))!
127
127
  }
128
128
 
129
129
  async remove(key: string): Promise<void> {
130
- let { directory, filePath, metaPath } = await this.#getPaths(key);
130
+ let { directory, filePath, metaPath } = await this.#getPaths(key)
131
131
 
132
132
  try {
133
- await Promise.all([fsp.unlink(filePath), fsp.unlink(metaPath)]);
133
+ await Promise.all([fsp.unlink(filePath), fsp.unlink(metaPath)])
134
134
 
135
135
  // Check if directory is empty and remove it if so
136
- let files = await fsp.readdir(directory);
136
+ let files = await fsp.readdir(directory)
137
137
  if (files.length === 0) {
138
- await fsp.rmdir(directory);
138
+ await fsp.rmdir(directory)
139
139
  }
140
140
  } catch (error) {
141
141
  if (!isNoEntityError(error)) {
142
- throw error;
142
+ throw error
143
143
  }
144
144
  }
145
145
  }
146
146
 
147
147
  async set(key: string, file: File): Promise<void> {
148
- let { directory, filePath, metaPath } = await this.#getPaths(key);
148
+ let { directory, filePath, metaPath } = await this.#getPaths(key)
149
149
 
150
150
  // Ensure directory exists
151
- await fsp.mkdir(directory, { recursive: true });
151
+ await fsp.mkdir(directory, { recursive: true })
152
152
 
153
- await writeFile(filePath, file);
153
+ await writeFile(filePath, file)
154
154
 
155
155
  let meta: MetadataJson = {
156
156
  key,
157
157
  lastModified: file.lastModified,
158
158
  name: file.name,
159
159
  type: file.type,
160
- };
161
- await fsp.writeFile(metaPath, JSON.stringify(meta));
160
+ }
161
+ await fsp.writeFile(metaPath, JSON.stringify(meta))
162
162
  }
163
163
 
164
164
  async #getPaths(key: string): Promise<{ directory: string; filePath: string; metaPath: string }> {
165
- let hash = await computeHash(key);
166
- let directory = path.join(this.#dirname, hash.slice(0, 2));
165
+ let hash = await computeHash(key)
166
+ let directory = path.join(this.#dirname, hash.slice(0, 2))
167
167
 
168
168
  return {
169
169
  directory,
170
170
  filePath: path.join(directory, `${hash}.dat`),
171
171
  metaPath: path.join(directory, `${hash}.meta.json`),
172
- };
172
+ }
173
173
  }
174
174
  }
175
175
 
176
176
  async function readMetadata(metaPath: string): Promise<MetadataJson> {
177
- return JSON.parse(await fsp.readFile(metaPath, 'utf-8'));
177
+ return JSON.parse(await fsp.readFile(metaPath, 'utf-8'))
178
178
  }
179
179
 
180
180
  async function computeHash(key: string, algorithm = 'SHA-256'): Promise<string> {
181
- let digest = await crypto.subtle.digest(algorithm, new TextEncoder().encode(key));
181
+ let digest = await crypto.subtle.digest(algorithm, new TextEncoder().encode(key))
182
182
  return Array.from(new Uint8Array(digest))
183
183
  .map((b) => b.toString(16).padStart(2, '0'))
184
- .join('');
184
+ .join('')
185
185
  }
186
186
 
187
187
  function isNoEntityError(obj: unknown): obj is NodeJS.ErrnoException & { code: 'ENOENT' } {
188
- return obj instanceof Error && 'code' in obj && (obj as NodeJS.ErrnoException).code === 'ENOENT';
188
+ return obj instanceof Error && 'code' in obj && (obj as NodeJS.ErrnoException).code === 'ENOENT'
189
189
  }
@@ -1,35 +1,35 @@
1
- import type { FileStorage, ListOptions, ListResult } from './file-storage.ts';
1
+ import type { FileStorage, ListOptions, ListResult } from './file-storage.ts'
2
2
 
3
3
  /**
4
4
  * A simple, in-memory implementation of the `FileStorage` interface.
5
5
  */
6
6
  export class MemoryFileStorage implements FileStorage {
7
- #map = new Map<string, File>();
7
+ #map = new Map<string, File>()
8
8
 
9
9
  get(key: string): File | null {
10
- return this.#map.get(key) ?? null;
10
+ return this.#map.get(key) ?? null
11
11
  }
12
12
 
13
13
  has(key: string): boolean {
14
- return this.#map.has(key);
14
+ return this.#map.has(key)
15
15
  }
16
16
 
17
17
  list<T extends ListOptions>(options?: T): ListResult<T> {
18
- let { cursor, includeMetadata = false, limit = Infinity, prefix } = options ?? {};
18
+ let { cursor, includeMetadata = false, limit = Infinity, prefix } = options ?? {}
19
19
 
20
- let files: any[] = [];
21
- let foundCursor = cursor === undefined;
22
- let nextCursor: string | undefined;
20
+ let files: any[] = []
21
+ let foundCursor = cursor === undefined
22
+ let nextCursor: string | undefined
23
23
 
24
24
  for (let [key, file] of this.#map.entries()) {
25
25
  if (foundCursor) {
26
26
  if (prefix != null && !key.startsWith(prefix)) {
27
- continue;
27
+ continue
28
28
  }
29
29
 
30
30
  if (files.length >= limit) {
31
- nextCursor = files[files.length - 1]?.key;
32
- break;
31
+ nextCursor = files[files.length - 1]?.key
32
+ break
33
33
  }
34
34
 
35
35
  if (includeMetadata) {
@@ -39,37 +39,37 @@ export class MemoryFileStorage implements FileStorage {
39
39
  name: file.name,
40
40
  size: file.size,
41
41
  type: file.type,
42
- });
42
+ })
43
43
  } else {
44
- files.push({ key });
44
+ files.push({ key })
45
45
  }
46
46
  } else if (key === cursor) {
47
- foundCursor = true;
47
+ foundCursor = true
48
48
  }
49
49
  }
50
50
 
51
51
  return {
52
52
  cursor: nextCursor,
53
53
  files,
54
- };
54
+ }
55
55
  }
56
56
 
57
57
  async put(key: string, file: File): Promise<File> {
58
- await this.set(key, file);
59
- return this.get(key)!;
58
+ await this.set(key, file)
59
+ return this.get(key)!
60
60
  }
61
61
 
62
62
  remove(key: string): void {
63
- this.#map.delete(key);
63
+ this.#map.delete(key)
64
64
  }
65
65
 
66
66
  async set(key: string, file: File): Promise<void> {
67
- let buffer = await file.arrayBuffer();
67
+ let buffer = await file.arrayBuffer()
68
68
  let newFile = new File([buffer], file.name, {
69
69
  lastModified: file.lastModified,
70
70
  type: file.type,
71
- });
71
+ })
72
72
 
73
- this.#map.set(key, newFile);
73
+ this.#map.set(key, newFile)
74
74
  }
75
75
  }
package/src/local.ts CHANGED
@@ -1 +1 @@
1
- export { LocalFileStorage } from './lib/local-file-storage.ts';
1
+ export { LocalFileStorage } from './lib/local-file-storage.ts'
package/src/memory.ts CHANGED
@@ -1 +1 @@
1
- export { MemoryFileStorage } from './lib/memory-file-storage.ts';
1
+ export { MemoryFileStorage } from './lib/memory-file-storage.ts'
@@ -1,19 +0,0 @@
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
@@ -1,7 +0,0 @@
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
- }
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=file-storage.js.map