@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.
Files changed (2) hide show
  1. package/README.md +93 -283
  2. package/package.json +2 -2
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,337 +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');
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
- ### MemoryFilesystem
13
+ Each backend is a separate entry point:
54
14
 
55
- In-memory filesystem for testing and temporary storage:
15
+ ### Node.js / Bun local filesystem
56
16
 
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
23
+ ### In-memory
64
24
 
65
- Node.js filesystem backend:
66
-
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
43
+ ## Usage
89
44
 
90
- ```javascript
91
- import {
92
- FilesystemRegistry,
93
- MemoryFilesystem,
94
- NodeFilesystem
95
- } from '@b9g/filesystem';
45
+ All backends implement the standard `FileSystemDirectoryHandle` interface:
96
46
 
97
- const registry = new FilesystemRegistry();
98
-
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' }));
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
- // 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();
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
- // Store cached response
200
- const file = await cacheDir.getFileHandle('index.html', { create: true });
201
- const writable = await file.createWritable();
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
- ### Asset Pipeline
207
-
208
- ```javascript
209
- // Static assets filesystem
210
- registry.register('assets', () => new NodeFilesystem('./dist/assets'));
71
+ ## Shovel Configuration
211
72
 
212
- const assets = await registry.get('assets');
213
- const staticDir = await assets.getDirectoryHandle('static', { create: true });
73
+ When used with Shovel, directories are configured in `shovel.json`:
214
74
 
215
- // Copy build assets
216
- for (const asset of buildAssets) {
217
- const file = await staticDir.getFileHandle(asset.name, { create: true });
218
- const writable = await file.createWritable();
219
- await writable.write(asset.content);
220
- await writable.close();
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
- ### Upload Handling
225
-
226
- ```javascript
227
- router.post('/upload', async (request) => {
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
- ## Exports
93
+ ## CustomDirectoryStorage
248
94
 
249
- ### Classes
95
+ The main entry point exports `CustomDirectoryStorage`, a registry for named directories with lazy instantiation:
250
96
 
251
- - `ShovelFileHandle` - FileSystemFileHandle implementation
252
- - `ShovelDirectoryHandle` - FileSystemDirectoryHandle implementation
253
- - `ShovelHandle` - Base handle class
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
- ### Types
101
+ const directories = new CustomDirectoryStorage((name) => {
102
+ return new NodeFSDirectory(name, {path: `./data/${name}`});
103
+ });
257
104
 
258
- - `Directory` - Alias for FileSystemDirectoryHandle
259
- - `DirectoryStorage` - Interface for directory storage (`open(name): Promise<FileSystemDirectoryHandle>`)
260
- - `DirectoryFactory` - Factory function type for creating directory backends
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
- ## API Reference
109
+ ## Custom Backends
266
110
 
267
- ### DirectoryStorage
111
+ Implement the `FileSystemBackend` interface to create custom storage backends:
268
112
 
269
113
  ```typescript
270
- interface DirectoryStorage {
271
- open(name: string): Promise<FileSystemDirectoryHandle>;
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
- class CustomDirectoryStorage implements DirectoryStorage {
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
- ### FileSystemHandle Interface
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
- ## Backend-Specific Options
130
+ ### Main (`@b9g/filesystem`)
303
131
 
304
- ### S3 Configuration
132
+ - `ShovelHandle` - Abstract base handle class
133
+ - `ShovelFileHandle` - `FileSystemFileHandle` implementation
134
+ - `ShovelDirectoryHandle` - `FileSystemDirectoryHandle` implementation
135
+ - `CustomDirectoryStorage` - Named directory registry
305
136
 
306
- ```javascript
307
- import { BunS3Filesystem } from '@b9g/filesystem';
137
+ ### Types
308
138
 
309
- const s3fs = new BunS3Filesystem({
310
- bucket: 'my-bucket',
311
- region: 'us-west-2',
312
- endpoint: 'https://s3.us-west-2.amazonaws.com',
313
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
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
- ### Memory Options
145
+ ### `@b9g/filesystem/node-fs`
321
146
 
322
- ```javascript
323
- import { MemoryFilesystem } from '@b9g/filesystem';
147
+ - `NodeFSDirectory` - Local filesystem directory (Node.js/Bun)
148
+ - `NodeFSBackend` - Local filesystem backend
324
149
 
325
- const memfs = new MemoryFilesystem({
326
- maxSize: 100 * 1024 * 1024, // 100MB limit
327
- caseSensitive: true
328
- });
329
- ```
150
+ ### `@b9g/filesystem/memory`
330
151
 
331
- ## Error Handling
152
+ - `MemoryDirectory` - In-memory directory
153
+ - `MemoryFileSystemBackend` - In-memory backend
332
154
 
333
- ```javascript
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
- // Check before accessing
345
- const exists = await dir.getFileHandle('data.txt')
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.1.10",
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": {