@b9g/filesystem 0.1.10 → 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 +93 -283
- package/package.json +2 -2
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,337 +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');
|
|
33
|
-
|
|
34
|
-
// Create directory
|
|
35
|
-
const dir = await fs.getDirectoryHandle('projects', { create: true });
|
|
36
|
-
|
|
37
|
-
// Create file
|
|
38
|
-
const file = await dir.getFileHandle('readme.txt', { create: true });
|
|
39
|
-
|
|
40
|
-
// Write content
|
|
41
|
-
const writable = await file.createWritable();
|
|
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
|
|
11
|
+
## Backends
|
|
52
12
|
|
|
53
|
-
|
|
13
|
+
Each backend is a separate entry point:
|
|
54
14
|
|
|
55
|
-
|
|
15
|
+
### Node.js / Bun local filesystem
|
|
56
16
|
|
|
57
|
-
```
|
|
58
|
-
import {
|
|
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
|
-
###
|
|
23
|
+
### In-memory
|
|
64
24
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```javascript
|
|
68
|
-
import { NodeFilesystem } from '@b9g/filesystem';
|
|
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
|
-
##
|
|
43
|
+
## Usage
|
|
89
44
|
|
|
90
|
-
|
|
91
|
-
import {
|
|
92
|
-
FilesystemRegistry,
|
|
93
|
-
MemoryFilesystem,
|
|
94
|
-
NodeFilesystem
|
|
95
|
-
} from '@b9g/filesystem';
|
|
45
|
+
All backends implement the standard `FileSystemDirectoryHandle` interface:
|
|
96
46
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
registry.register('memory', () => new MemoryFilesystem());
|
|
101
|
-
registry.register('local', () => new NodeFilesystem('./data'));
|
|
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' }));
|
|
156
|
-
await writable.close();
|
|
157
|
-
|
|
158
|
-
// Or write all at once
|
|
159
|
-
await writable.write(new Blob(['Hello World']));
|
|
54
|
+
await writable.write("Hello World!");
|
|
160
55
|
await writable.close();
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Reading Files
|
|
164
|
-
|
|
165
|
-
```javascript
|
|
166
|
-
// Get file handle
|
|
167
|
-
const file = await dir.getFileHandle('config.json');
|
|
168
56
|
|
|
169
|
-
//
|
|
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();
|
|
180
|
-
|
|
181
|
-
// Read as stream
|
|
182
|
-
const stream = fileData.stream();
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Integration Examples
|
|
186
|
-
|
|
187
|
-
### Cache Storage
|
|
188
|
-
|
|
189
|
-
```javascript
|
|
190
|
-
import { FilesystemRegistry, NodeFilesystem } from '@b9g/filesystem';
|
|
191
|
-
|
|
192
|
-
const registry = new FilesystemRegistry();
|
|
193
|
-
registry.register('cache', () => new NodeFilesystem('./cache'));
|
|
194
|
-
|
|
195
|
-
// Use with cache
|
|
196
|
-
const fs = await registry.get('cache');
|
|
197
|
-
const cacheDir = await fs.getDirectoryHandle('pages', { create: true });
|
|
61
|
+
// List entries
|
|
62
|
+
for await (const [name, handle] of dir.entries()) {
|
|
63
|
+
console.log(name, handle.kind); // "file" or "directory"
|
|
64
|
+
}
|
|
198
65
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
await writable.write('<html>...</html>');
|
|
203
|
-
await writable.close();
|
|
66
|
+
// Remove entries
|
|
67
|
+
await dir.removeEntry("old-file.txt");
|
|
68
|
+
await dir.removeEntry("old-dir", {recursive: true});
|
|
204
69
|
```
|
|
205
70
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
```javascript
|
|
209
|
-
// Static assets filesystem
|
|
210
|
-
registry.register('assets', () => new NodeFilesystem('./dist/assets'));
|
|
71
|
+
## Shovel Configuration
|
|
211
72
|
|
|
212
|
-
|
|
213
|
-
const staticDir = await assets.getDirectoryHandle('static', { create: true });
|
|
73
|
+
When used with Shovel, directories are configured in `shovel.json`:
|
|
214
74
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"directories": {
|
|
78
|
+
"uploads": {
|
|
79
|
+
"module": "@b9g/filesystem/node-fs",
|
|
80
|
+
"export": "NodeFSDirectory",
|
|
81
|
+
"path": "./uploads"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
221
84
|
}
|
|
222
85
|
```
|
|
223
86
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
const formData = await request.formData();
|
|
229
|
-
const file = formData.get('file');
|
|
230
|
-
|
|
231
|
-
if (file) {
|
|
232
|
-
const uploads = await registry.get('uploads');
|
|
233
|
-
const uploadsDir = await uploads.getDirectoryHandle('files', { create: true });
|
|
234
|
-
|
|
235
|
-
const fileHandle = await uploadsDir.getFileHandle(file.name, { create: true });
|
|
236
|
-
const writable = await fileHandle.createWritable();
|
|
237
|
-
await writable.write(await file.arrayBuffer());
|
|
238
|
-
await writable.close();
|
|
239
|
-
|
|
240
|
-
return Response.json({ success: true, filename: file.name });
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return BadRequest('No file provided');
|
|
244
|
-
});
|
|
87
|
+
The `path` field is resolved relative to the project root. Access configured directories via `self.directories`:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const uploads = await self.directories.open("uploads");
|
|
245
91
|
```
|
|
246
92
|
|
|
247
|
-
##
|
|
93
|
+
## CustomDirectoryStorage
|
|
248
94
|
|
|
249
|
-
|
|
95
|
+
The main entry point exports `CustomDirectoryStorage`, a registry for named directories with lazy instantiation:
|
|
250
96
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
- `CustomDirectoryStorage` - Directory storage with custom backend factories
|
|
97
|
+
```typescript
|
|
98
|
+
import {CustomDirectoryStorage} from "@b9g/filesystem";
|
|
99
|
+
import {NodeFSDirectory} from "@b9g/filesystem/node-fs";
|
|
255
100
|
|
|
256
|
-
|
|
101
|
+
const directories = new CustomDirectoryStorage((name) => {
|
|
102
|
+
return new NodeFSDirectory(name, {path: `./data/${name}`});
|
|
103
|
+
});
|
|
257
104
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
- `FileSystemConfig` - Configuration for filesystem backends
|
|
262
|
-
- `FileSystemPermissionDescriptor` - Permission descriptor type
|
|
263
|
-
- `FileSystemBackend` - Backend interface for filesystem implementations
|
|
105
|
+
const uploads = await directories.open("uploads");
|
|
106
|
+
const tmp = await directories.open("tmp");
|
|
107
|
+
```
|
|
264
108
|
|
|
265
|
-
##
|
|
109
|
+
## Custom Backends
|
|
266
110
|
|
|
267
|
-
|
|
111
|
+
Implement the `FileSystemBackend` interface to create custom storage backends:
|
|
268
112
|
|
|
269
113
|
```typescript
|
|
270
|
-
|
|
271
|
-
|
|
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) { /* ... */ }
|
|
272
123
|
}
|
|
273
124
|
|
|
274
|
-
|
|
275
|
-
register(name: string, factory: DirectoryFactory): void;
|
|
276
|
-
open(name: string): Promise<FileSystemDirectoryHandle>;
|
|
277
|
-
}
|
|
125
|
+
const dir = new ShovelDirectoryHandle(new MyBackend(), "/");
|
|
278
126
|
```
|
|
279
127
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
interface FileSystemDirectoryHandle {
|
|
284
|
-
kind: 'directory';
|
|
285
|
-
name: string;
|
|
286
|
-
getDirectoryHandle(name: string, options?: { create?: boolean }): Promise<FileSystemDirectoryHandle>;
|
|
287
|
-
getFileHandle(name: string, options?: { create?: boolean }): Promise<FileSystemFileHandle>;
|
|
288
|
-
removeEntry(name: string, options?: { recursive?: boolean }): Promise<void>;
|
|
289
|
-
entries(): AsyncIterableIterator<[string, FileSystemDirectoryHandle | FileSystemFileHandle]>;
|
|
290
|
-
keys(): AsyncIterableIterator<string>;
|
|
291
|
-
values(): AsyncIterableIterator<FileSystemDirectoryHandle | FileSystemFileHandle>;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
interface FileSystemFileHandle {
|
|
295
|
-
kind: 'file';
|
|
296
|
-
name: string;
|
|
297
|
-
getFile(): Promise<File>;
|
|
298
|
-
createWritable(): Promise<FileSystemWritableFileStream>;
|
|
299
|
-
}
|
|
300
|
-
```
|
|
128
|
+
## Exports
|
|
301
129
|
|
|
302
|
-
|
|
130
|
+
### Main (`@b9g/filesystem`)
|
|
303
131
|
|
|
304
|
-
|
|
132
|
+
- `ShovelHandle` - Abstract base handle class
|
|
133
|
+
- `ShovelFileHandle` - `FileSystemFileHandle` implementation
|
|
134
|
+
- `ShovelDirectoryHandle` - `FileSystemDirectoryHandle` implementation
|
|
135
|
+
- `CustomDirectoryStorage` - Named directory registry
|
|
305
136
|
|
|
306
|
-
|
|
307
|
-
import { BunS3Filesystem } from '@b9g/filesystem';
|
|
137
|
+
### Types
|
|
308
138
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
315
|
-
pathStyle: false,
|
|
316
|
-
prefix: 'app-data/'
|
|
317
|
-
});
|
|
318
|
-
```
|
|
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`
|
|
319
144
|
|
|
320
|
-
###
|
|
145
|
+
### `@b9g/filesystem/node-fs`
|
|
321
146
|
|
|
322
|
-
|
|
323
|
-
|
|
147
|
+
- `NodeFSDirectory` - Local filesystem directory (Node.js/Bun)
|
|
148
|
+
- `NodeFSBackend` - Local filesystem backend
|
|
324
149
|
|
|
325
|
-
|
|
326
|
-
maxSize: 100 * 1024 * 1024, // 100MB limit
|
|
327
|
-
caseSensitive: true
|
|
328
|
-
});
|
|
329
|
-
```
|
|
150
|
+
### `@b9g/filesystem/memory`
|
|
330
151
|
|
|
331
|
-
|
|
152
|
+
- `MemoryDirectory` - In-memory directory
|
|
153
|
+
- `MemoryFileSystemBackend` - In-memory backend
|
|
332
154
|
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
const file = await dir.getFileHandle('missing.txt');
|
|
336
|
-
} catch (error) {
|
|
337
|
-
if (error.name === 'NotFoundError') {
|
|
338
|
-
console.log('File not found');
|
|
339
|
-
} else {
|
|
340
|
-
console.error('Unexpected error:', error);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
155
|
+
### `@b9g/filesystem/bun-s3`
|
|
343
156
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
.then(() => true)
|
|
347
|
-
.catch(() => false);
|
|
348
|
-
```
|
|
157
|
+
- `S3Directory` - S3-compatible storage directory
|
|
158
|
+
- `S3FileSystemBackend` - S3 storage backend
|
|
349
159
|
|
|
350
160
|
## License
|
|
351
161
|
|
|
352
|
-
MIT
|
|
162
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/filesystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Universal File System Access API implementations for all platforms",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/bikeshaving/shovel.git",
|
|
8
|
+
"url": "git+https://github.com/bikeshaving/shovel.git",
|
|
9
9
|
"directory": "packages/filesystem"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|