@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.
Files changed (2) hide show
  1. package/README.md +79 -296
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,14 +1,6 @@
1
1
  # @b9g/filesystem
2
2
 
3
- Universal filesystem abstraction with multiple backend implementations for different runtimes and storage systems.
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
- ## Quick Start
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
- // Create directory
35
- const dir = await fs.getDirectoryHandle('projects', { create: true });
13
+ Each backend is a separate entry point:
36
14
 
37
- // Create file
38
- const file = await dir.getFileHandle('readme.txt', { create: true });
15
+ ### Node.js / Bun local filesystem
39
16
 
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
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 fs = new MemoryFilesystem();
20
+ const dir = new NodeFSDirectory("data", {path: "./data"});
61
21
  ```
62
22
 
63
- ### NodeFilesystem
64
-
65
- Node.js filesystem backend:
23
+ ### In-memory
66
24
 
67
- ```javascript
68
- import { NodeFilesystem } from '@b9g/filesystem';
25
+ ```typescript
26
+ import {MemoryDirectory} from "@b9g/filesystem/memory";
69
27
 
70
- const fs = new NodeFilesystem('/app/data');
28
+ const dir = new MemoryDirectory();
71
29
  ```
72
30
 
73
- ### BunS3Filesystem
74
-
75
- Bun with S3-compatible storage:
31
+ ### Bun S3
76
32
 
77
- ```javascript
78
- import { BunS3Filesystem } from '@b9g/filesystem';
33
+ ```typescript
34
+ import {S3Directory} from "@b9g/filesystem/bun-s3";
79
35
 
80
- const fs = new BunS3Filesystem({
81
- bucket: 'my-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
- ## Registry Usage
89
-
90
- ```javascript
91
- import {
92
- FilesystemRegistry,
93
- MemoryFilesystem,
94
- NodeFilesystem
95
- } from '@b9g/filesystem';
43
+ ## Usage
96
44
 
97
- const registry = new FilesystemRegistry();
45
+ All backends implement the standard `FileSystemDirectoryHandle` interface:
98
46
 
99
- // Register implementations
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 writable stream
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
- // Or write all at once
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
- // Read as JSON
176
- const json = JSON.parse(text);
177
-
178
- // Read as ArrayBuffer
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
- // Read as stream
182
- const stream = fileData.stream();
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`. The built-in names `server`, `public`, and `tmp` have platform defaults. Custom directory names require an explicit `module`:
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. Paths outside the project (e.g. `"../docs"`) are supported — the root path itself is unrestricted, while directory traversal within opened directories is blocked at the filesystem level.
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
- ## Integration Examples
213
-
214
- ### Cache Storage
93
+ ## CustomDirectoryStorage
215
94
 
216
- ```javascript
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
- const registry = new FilesystemRegistry();
220
- registry.register('cache', () => new NodeFilesystem('./cache'));
97
+ ```typescript
98
+ import {CustomDirectoryStorage} from "@b9g/filesystem";
99
+ import {NodeFSDirectory} from "@b9g/filesystem/node-fs";
221
100
 
222
- // Use with cache
223
- const fs = await registry.get('cache');
224
- const cacheDir = await fs.getDirectoryHandle('pages', { create: true });
101
+ const directories = new CustomDirectoryStorage((name) => {
102
+ return new NodeFSDirectory(name, {path: `./data/${name}`});
103
+ });
225
104
 
226
- // Store cached response
227
- const file = await cacheDir.getFileHandle('index.html', { create: true });
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
- ### Asset Pipeline
109
+ ## Custom Backends
234
110
 
235
- ```javascript
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
- const assets = await registry.get('assets');
240
- const staticDir = await assets.getDirectoryHandle('static', { create: true });
241
-
242
- // Copy build assets
243
- for (const asset of buildAssets) {
244
- const file = await staticDir.getFileHandle(asset.name, { create: true });
245
- const writable = await file.createWritable();
246
- await writable.write(asset.content);
247
- await writable.close();
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
- ### Upload Handling
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
- ### Classes
130
+ ### Main (`@b9g/filesystem`)
277
131
 
278
- - `ShovelFileHandle` - FileSystemFileHandle implementation
279
- - `ShovelDirectoryHandle` - FileSystemDirectoryHandle implementation
280
- - `ShovelHandle` - Base handle class
281
- - `CustomDirectoryStorage` - Directory storage with custom backend factories
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
- - `Directory` - Alias for FileSystemDirectoryHandle
286
- - `DirectoryStorage` - Interface for directory storage (`open(name): Promise<FileSystemDirectoryHandle>`)
287
- - `DirectoryFactory` - Factory function type for creating directory backends
288
- - `FileSystemConfig` - Configuration for filesystem backends
289
- - `FileSystemPermissionDescriptor` - Permission descriptor type
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
- ## API Reference
145
+ ### `@b9g/filesystem/node-fs`
293
146
 
294
- ### DirectoryStorage
147
+ - `NodeFSDirectory` - Local filesystem directory (Node.js/Bun)
148
+ - `NodeFSBackend` - Local filesystem backend
295
149
 
296
- ```typescript
297
- interface DirectoryStorage {
298
- open(name: string): Promise<FileSystemDirectoryHandle>;
299
- }
150
+ ### `@b9g/filesystem/memory`
300
151
 
301
- class CustomDirectoryStorage implements DirectoryStorage {
302
- register(name: string, factory: DirectoryFactory): void;
303
- open(name: string): Promise<FileSystemDirectoryHandle>;
304
- }
305
- ```
152
+ - `MemoryDirectory` - In-memory directory
153
+ - `MemoryFileSystemBackend` - In-memory backend
306
154
 
307
- ### FileSystemHandle Interface
155
+ ### `@b9g/filesystem/bun-s3`
308
156
 
309
- ```typescript
310
- interface FileSystemDirectoryHandle {
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/filesystem",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "Universal File System Access API implementations for all platforms",
5
5
  "license": "MIT",
6
6
  "repository": {