@b9g/filesystem 0.1.11 → 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 +79 -296
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
# @b9g/filesystem
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Universal API**: Same interface across Node.js, Bun, browsers, and edge platforms
|
|
8
|
-
- **Multiple Backends**: Memory, Node.js fs, Bun, S3, R2, and more
|
|
9
|
-
- **Registry Pattern**: Register filesystem implementations by name
|
|
10
|
-
- **Async/Await**: Promise-based API throughout
|
|
11
|
-
- **Directory Handles**: File System Access API compatible
|
|
3
|
+
[File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) implementations for server-side runtimes. Provides `FileSystemDirectoryHandle` and `FileSystemFileHandle` backed by local disk, memory, or S3-compatible storage.
|
|
12
4
|
|
|
13
5
|
## Installation
|
|
14
6
|
|
|
@@ -16,364 +8,155 @@ Universal filesystem abstraction with multiple backend implementations for diffe
|
|
|
16
8
|
npm install @b9g/filesystem
|
|
17
9
|
```
|
|
18
10
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
```javascript
|
|
22
|
-
import { FilesystemRegistry, MemoryFilesystem } from '@b9g/filesystem';
|
|
23
|
-
|
|
24
|
-
// Create registry
|
|
25
|
-
const registry = new FilesystemRegistry();
|
|
26
|
-
|
|
27
|
-
// Register backends
|
|
28
|
-
registry.register('memory', () => new MemoryFilesystem());
|
|
29
|
-
registry.register('temp', () => new NodeFilesystem('/tmp'));
|
|
30
|
-
|
|
31
|
-
// Get filesystem
|
|
32
|
-
const fs = await registry.get('memory');
|
|
11
|
+
## Backends
|
|
33
12
|
|
|
34
|
-
|
|
35
|
-
const dir = await fs.getDirectoryHandle('projects', { create: true });
|
|
13
|
+
Each backend is a separate entry point:
|
|
36
14
|
|
|
37
|
-
|
|
38
|
-
const file = await dir.getFileHandle('readme.txt', { create: true });
|
|
15
|
+
### Node.js / Bun local filesystem
|
|
39
16
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await writable.write('Hello World!');
|
|
43
|
-
await writable.close();
|
|
44
|
-
|
|
45
|
-
// Read content
|
|
46
|
-
const fileData = await file.getFile();
|
|
47
|
-
const text = await fileData.text();
|
|
48
|
-
console.log(text); // 'Hello World!'
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Filesystem Implementations
|
|
52
|
-
|
|
53
|
-
### MemoryFilesystem
|
|
54
|
-
|
|
55
|
-
In-memory filesystem for testing and temporary storage:
|
|
56
|
-
|
|
57
|
-
```javascript
|
|
58
|
-
import { MemoryFilesystem } from '@b9g/filesystem';
|
|
17
|
+
```typescript
|
|
18
|
+
import {NodeFSDirectory} from "@b9g/filesystem/node-fs";
|
|
59
19
|
|
|
60
|
-
const
|
|
20
|
+
const dir = new NodeFSDirectory("data", {path: "./data"});
|
|
61
21
|
```
|
|
62
22
|
|
|
63
|
-
###
|
|
64
|
-
|
|
65
|
-
Node.js filesystem backend:
|
|
23
|
+
### In-memory
|
|
66
24
|
|
|
67
|
-
```
|
|
68
|
-
import {
|
|
25
|
+
```typescript
|
|
26
|
+
import {MemoryDirectory} from "@b9g/filesystem/memory";
|
|
69
27
|
|
|
70
|
-
const
|
|
28
|
+
const dir = new MemoryDirectory();
|
|
71
29
|
```
|
|
72
30
|
|
|
73
|
-
###
|
|
74
|
-
|
|
75
|
-
Bun with S3-compatible storage:
|
|
31
|
+
### Bun S3
|
|
76
32
|
|
|
77
|
-
```
|
|
78
|
-
import {
|
|
33
|
+
```typescript
|
|
34
|
+
import {S3Directory} from "@b9g/filesystem/bun-s3";
|
|
79
35
|
|
|
80
|
-
const
|
|
81
|
-
bucket:
|
|
82
|
-
region: 'us-east-1',
|
|
36
|
+
const dir = new S3Directory("uploads", {
|
|
37
|
+
bucket: "my-bucket",
|
|
83
38
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
84
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
39
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
85
40
|
});
|
|
86
41
|
```
|
|
87
42
|
|
|
88
|
-
##
|
|
89
|
-
|
|
90
|
-
```javascript
|
|
91
|
-
import {
|
|
92
|
-
FilesystemRegistry,
|
|
93
|
-
MemoryFilesystem,
|
|
94
|
-
NodeFilesystem
|
|
95
|
-
} from '@b9g/filesystem';
|
|
43
|
+
## Usage
|
|
96
44
|
|
|
97
|
-
|
|
45
|
+
All backends implement the standard `FileSystemDirectoryHandle` interface:
|
|
98
46
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
registry.register('temp', () => new NodeFilesystem('/tmp'));
|
|
103
|
-
|
|
104
|
-
// Use by name
|
|
105
|
-
const memFs = await registry.get('memory');
|
|
106
|
-
const localFs = await registry.get('local');
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## File System Access API
|
|
110
|
-
|
|
111
|
-
Compatible with the File System Access API:
|
|
112
|
-
|
|
113
|
-
```javascript
|
|
114
|
-
// Get directory handle
|
|
115
|
-
const dirHandle = await fs.getDirectoryHandle('uploads', { create: true });
|
|
116
|
-
|
|
117
|
-
// List directory contents
|
|
118
|
-
for await (const [name, handle] of dirHandle.entries()) {
|
|
119
|
-
if (handle.kind === 'file') {
|
|
120
|
-
console.log(`File: ${name}`);
|
|
121
|
-
} else {
|
|
122
|
-
console.log(`Directory: ${name}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Get file handle
|
|
127
|
-
const fileHandle = await dirHandle.getFileHandle('data.json', { create: true });
|
|
128
|
-
|
|
129
|
-
// Check if exists
|
|
130
|
-
try {
|
|
131
|
-
await dirHandle.getFileHandle('missing.txt');
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.log('File does not exist');
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Remove file
|
|
137
|
-
await dirHandle.removeEntry('old-file.txt');
|
|
138
|
-
|
|
139
|
-
// Remove directory
|
|
140
|
-
await dirHandle.removeEntry('old-dir', { recursive: true });
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## File Operations
|
|
144
|
-
|
|
145
|
-
### Writing Files
|
|
146
|
-
|
|
147
|
-
```javascript
|
|
148
|
-
// Get file handle
|
|
149
|
-
const file = await dir.getFileHandle('config.json', { create: true });
|
|
47
|
+
```typescript
|
|
48
|
+
// Create a directory
|
|
49
|
+
const subdir = await dir.getDirectoryHandle("uploads", {create: true});
|
|
150
50
|
|
|
151
|
-
// Create
|
|
51
|
+
// Create and write a file
|
|
52
|
+
const file = await subdir.getFileHandle("readme.txt", {create: true});
|
|
152
53
|
const writable = await file.createWritable();
|
|
153
|
-
|
|
154
|
-
// Write data
|
|
155
|
-
await writable.write(JSON.stringify({ setting: 'value' }));
|
|
54
|
+
await writable.write("Hello World!");
|
|
156
55
|
await writable.close();
|
|
157
56
|
|
|
158
|
-
//
|
|
159
|
-
await writable.write(new Blob(['Hello World']));
|
|
160
|
-
await writable.close();
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Reading Files
|
|
164
|
-
|
|
165
|
-
```javascript
|
|
166
|
-
// Get file handle
|
|
167
|
-
const file = await dir.getFileHandle('config.json');
|
|
168
|
-
|
|
169
|
-
// Get file object
|
|
57
|
+
// Read a file
|
|
170
58
|
const fileData = await file.getFile();
|
|
171
|
-
|
|
172
|
-
// Read as text
|
|
173
59
|
const text = await fileData.text();
|
|
174
60
|
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const buffer = await fileData.arrayBuffer();
|
|
61
|
+
// List entries
|
|
62
|
+
for await (const [name, handle] of dir.entries()) {
|
|
63
|
+
console.log(name, handle.kind); // "file" or "directory"
|
|
64
|
+
}
|
|
180
65
|
|
|
181
|
-
//
|
|
182
|
-
|
|
66
|
+
// Remove entries
|
|
67
|
+
await dir.removeEntry("old-file.txt");
|
|
68
|
+
await dir.removeEntry("old-dir", {recursive: true});
|
|
183
69
|
```
|
|
184
70
|
|
|
185
71
|
## Shovel Configuration
|
|
186
72
|
|
|
187
|
-
When used with Shovel, directories are configured in `shovel.json
|
|
73
|
+
When used with Shovel, directories are configured in `shovel.json`:
|
|
188
74
|
|
|
189
75
|
```json
|
|
190
76
|
{
|
|
191
77
|
"directories": {
|
|
192
78
|
"uploads": {
|
|
193
79
|
"module": "@b9g/filesystem/node-fs",
|
|
80
|
+
"export": "NodeFSDirectory",
|
|
194
81
|
"path": "./uploads"
|
|
195
|
-
},
|
|
196
|
-
"docs": {
|
|
197
|
-
"module": "@b9g/filesystem/node-fs",
|
|
198
|
-
"path": "../docs"
|
|
199
82
|
}
|
|
200
83
|
}
|
|
201
84
|
}
|
|
202
85
|
```
|
|
203
86
|
|
|
204
|
-
The `path` field is resolved relative to the project root.
|
|
205
|
-
|
|
206
|
-
Access configured directories via the global `self.directories`:
|
|
87
|
+
The `path` field is resolved relative to the project root. Access configured directories via `self.directories`:
|
|
207
88
|
|
|
208
89
|
```typescript
|
|
209
90
|
const uploads = await self.directories.open("uploads");
|
|
210
91
|
```
|
|
211
92
|
|
|
212
|
-
##
|
|
213
|
-
|
|
214
|
-
### Cache Storage
|
|
93
|
+
## CustomDirectoryStorage
|
|
215
94
|
|
|
216
|
-
|
|
217
|
-
import { FilesystemRegistry, NodeFilesystem } from '@b9g/filesystem';
|
|
95
|
+
The main entry point exports `CustomDirectoryStorage`, a registry for named directories with lazy instantiation:
|
|
218
96
|
|
|
219
|
-
|
|
220
|
-
|
|
97
|
+
```typescript
|
|
98
|
+
import {CustomDirectoryStorage} from "@b9g/filesystem";
|
|
99
|
+
import {NodeFSDirectory} from "@b9g/filesystem/node-fs";
|
|
221
100
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
101
|
+
const directories = new CustomDirectoryStorage((name) => {
|
|
102
|
+
return new NodeFSDirectory(name, {path: `./data/${name}`});
|
|
103
|
+
});
|
|
225
104
|
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const writable = await file.createWritable();
|
|
229
|
-
await writable.write('<html>...</html>');
|
|
230
|
-
await writable.close();
|
|
105
|
+
const uploads = await directories.open("uploads");
|
|
106
|
+
const tmp = await directories.open("tmp");
|
|
231
107
|
```
|
|
232
108
|
|
|
233
|
-
|
|
109
|
+
## Custom Backends
|
|
234
110
|
|
|
235
|
-
|
|
236
|
-
// Static assets filesystem
|
|
237
|
-
registry.register('assets', () => new NodeFilesystem('./dist/assets'));
|
|
111
|
+
Implement the `FileSystemBackend` interface to create custom storage backends:
|
|
238
112
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
113
|
+
```typescript
|
|
114
|
+
import {type FileSystemBackend, ShovelDirectoryHandle} from "@b9g/filesystem";
|
|
115
|
+
|
|
116
|
+
class MyBackend implements FileSystemBackend {
|
|
117
|
+
async stat(path: string) { /* ... */ }
|
|
118
|
+
async readFile(path: string) { /* ... */ }
|
|
119
|
+
async writeFile(path: string, data: Uint8Array) { /* ... */ }
|
|
120
|
+
async listDir(path: string) { /* ... */ }
|
|
121
|
+
async createDir?(path: string) { /* ... */ }
|
|
122
|
+
async remove?(path: string, recursive?: boolean) { /* ... */ }
|
|
248
123
|
}
|
|
249
|
-
```
|
|
250
124
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
```javascript
|
|
254
|
-
router.post('/upload', async (request) => {
|
|
255
|
-
const formData = await request.formData();
|
|
256
|
-
const file = formData.get('file');
|
|
257
|
-
|
|
258
|
-
if (file) {
|
|
259
|
-
const uploads = await registry.get('uploads');
|
|
260
|
-
const uploadsDir = await uploads.getDirectoryHandle('files', { create: true });
|
|
261
|
-
|
|
262
|
-
const fileHandle = await uploadsDir.getFileHandle(file.name, { create: true });
|
|
263
|
-
const writable = await fileHandle.createWritable();
|
|
264
|
-
await writable.write(await file.arrayBuffer());
|
|
265
|
-
await writable.close();
|
|
266
|
-
|
|
267
|
-
return Response.json({ success: true, filename: file.name });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return BadRequest('No file provided');
|
|
271
|
-
});
|
|
125
|
+
const dir = new ShovelDirectoryHandle(new MyBackend(), "/");
|
|
272
126
|
```
|
|
273
127
|
|
|
274
128
|
## Exports
|
|
275
129
|
|
|
276
|
-
###
|
|
130
|
+
### Main (`@b9g/filesystem`)
|
|
277
131
|
|
|
278
|
-
- `
|
|
279
|
-
- `
|
|
280
|
-
- `
|
|
281
|
-
- `CustomDirectoryStorage` -
|
|
132
|
+
- `ShovelHandle` - Abstract base handle class
|
|
133
|
+
- `ShovelFileHandle` - `FileSystemFileHandle` implementation
|
|
134
|
+
- `ShovelDirectoryHandle` - `FileSystemDirectoryHandle` implementation
|
|
135
|
+
- `CustomDirectoryStorage` - Named directory registry
|
|
282
136
|
|
|
283
137
|
### Types
|
|
284
138
|
|
|
285
|
-
- `
|
|
286
|
-
- `
|
|
287
|
-
- `
|
|
288
|
-
- `
|
|
289
|
-
- `
|
|
290
|
-
- `FileSystemBackend` - Backend interface for filesystem implementations
|
|
139
|
+
- `FileSystemBackend` - Backend interface for custom implementations
|
|
140
|
+
- `FileSystemConfig` - Configuration interface
|
|
141
|
+
- `FileSystemPermissionDescriptor` - Permission descriptor
|
|
142
|
+
- `DirectoryStorage` - Directory storage interface (`open`, `has`, `delete`, `keys`)
|
|
143
|
+
- `DirectoryFactory` - Factory function type `(name: string) => FileSystemDirectoryHandle`
|
|
291
144
|
|
|
292
|
-
|
|
145
|
+
### `@b9g/filesystem/node-fs`
|
|
293
146
|
|
|
294
|
-
|
|
147
|
+
- `NodeFSDirectory` - Local filesystem directory (Node.js/Bun)
|
|
148
|
+
- `NodeFSBackend` - Local filesystem backend
|
|
295
149
|
|
|
296
|
-
|
|
297
|
-
interface DirectoryStorage {
|
|
298
|
-
open(name: string): Promise<FileSystemDirectoryHandle>;
|
|
299
|
-
}
|
|
150
|
+
### `@b9g/filesystem/memory`
|
|
300
151
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
open(name: string): Promise<FileSystemDirectoryHandle>;
|
|
304
|
-
}
|
|
305
|
-
```
|
|
152
|
+
- `MemoryDirectory` - In-memory directory
|
|
153
|
+
- `MemoryFileSystemBackend` - In-memory backend
|
|
306
154
|
|
|
307
|
-
###
|
|
155
|
+
### `@b9g/filesystem/bun-s3`
|
|
308
156
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
kind: 'directory';
|
|
312
|
-
name: string;
|
|
313
|
-
getDirectoryHandle(name: string, options?: { create?: boolean }): Promise<FileSystemDirectoryHandle>;
|
|
314
|
-
getFileHandle(name: string, options?: { create?: boolean }): Promise<FileSystemFileHandle>;
|
|
315
|
-
removeEntry(name: string, options?: { recursive?: boolean }): Promise<void>;
|
|
316
|
-
entries(): AsyncIterableIterator<[string, FileSystemDirectoryHandle | FileSystemFileHandle]>;
|
|
317
|
-
keys(): AsyncIterableIterator<string>;
|
|
318
|
-
values(): AsyncIterableIterator<FileSystemDirectoryHandle | FileSystemFileHandle>;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
interface FileSystemFileHandle {
|
|
322
|
-
kind: 'file';
|
|
323
|
-
name: string;
|
|
324
|
-
getFile(): Promise<File>;
|
|
325
|
-
createWritable(): Promise<FileSystemWritableFileStream>;
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
## Backend-Specific Options
|
|
330
|
-
|
|
331
|
-
### S3 Configuration
|
|
332
|
-
|
|
333
|
-
```javascript
|
|
334
|
-
import { BunS3Filesystem } from '@b9g/filesystem';
|
|
335
|
-
|
|
336
|
-
const s3fs = new BunS3Filesystem({
|
|
337
|
-
bucket: 'my-bucket',
|
|
338
|
-
region: 'us-west-2',
|
|
339
|
-
endpoint: 'https://s3.us-west-2.amazonaws.com',
|
|
340
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
341
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
342
|
-
pathStyle: false,
|
|
343
|
-
prefix: 'app-data/'
|
|
344
|
-
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### Memory Options
|
|
348
|
-
|
|
349
|
-
```javascript
|
|
350
|
-
import { MemoryFilesystem } from '@b9g/filesystem';
|
|
351
|
-
|
|
352
|
-
const memfs = new MemoryFilesystem({
|
|
353
|
-
maxSize: 100 * 1024 * 1024, // 100MB limit
|
|
354
|
-
caseSensitive: true
|
|
355
|
-
});
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
## Error Handling
|
|
359
|
-
|
|
360
|
-
```javascript
|
|
361
|
-
try {
|
|
362
|
-
const file = await dir.getFileHandle('missing.txt');
|
|
363
|
-
} catch (error) {
|
|
364
|
-
if (error.name === 'NotFoundError') {
|
|
365
|
-
console.log('File not found');
|
|
366
|
-
} else {
|
|
367
|
-
console.error('Unexpected error:', error);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Check before accessing
|
|
372
|
-
const exists = await dir.getFileHandle('data.txt')
|
|
373
|
-
.then(() => true)
|
|
374
|
-
.catch(() => false);
|
|
375
|
-
```
|
|
157
|
+
- `S3Directory` - S3-compatible storage directory
|
|
158
|
+
- `S3FileSystemBackend` - S3 storage backend
|
|
376
159
|
|
|
377
160
|
## License
|
|
378
161
|
|
|
379
|
-
MIT
|
|
162
|
+
MIT
|