opfs-worker 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Maksim Kachurin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,500 @@
1
+ # OPFS Worker
2
+
3
+ A robust TypeScript library for working with Origin Private File System (OPFS) through Web Workers, providing a Node.js-like file system API for browser environments.
4
+
5
+ ## Features
6
+
7
+ - 🌐 **Cross-browser compatible**: Works in all modern browsers including Safari, Firefox, Chrome
8
+ - 🚀 **Web Worker-based**: Runs in a separate thread for better performance
9
+ - 📁 **Node.js-like API**: Familiar file system operations (`readFile`, `writeFile`, `mkdir`, etc.)
10
+ - 🔒 **Type-safe**: Full TypeScript support with comprehensive type definitions
11
+ - 🎯 **Comlink-powered**: Seamless communication between main thread and worker
12
+ - 🔐 **Hash support**: Built-in file hash calculation (SHA-1, SHA-256, SHA-384, SHA-512)
13
+ - 📊 **File indexing**: Complete file system indexing with metadata
14
+ - 🔄 **Sync operations**: Bulk file synchronization from external data
15
+ - 🛡️ **Error handling**: Comprehensive error types and handling
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install opfs-worker
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Basic Usage
26
+
27
+ ```typescript
28
+ import { createWorker } from 'opfs-worker/inline';
29
+
30
+ async function example() {
31
+ // Create a file system instance
32
+ const fs = await createWorker();
33
+
34
+ // Mount the file system
35
+ await fs.mount('/my-app');
36
+
37
+ // Write a file
38
+ await fs.writeFile('/config.json', JSON.stringify({ theme: 'dark' }));
39
+
40
+ // Read the file back
41
+ const config = await fs.readFile('/config.json');
42
+ console.log(JSON.parse(config));
43
+ }
44
+ ```
45
+
46
+ ### Advanced Usage
47
+
48
+ ```typescript
49
+ import { createWorker } from 'opfs-worker/inline';
50
+
51
+ async function advancedExample() {
52
+ const fs = await createWorker();
53
+ await fs.mount('/my-app');
54
+
55
+ // Create directories
56
+ await fs.mkdir('/data/logs', { recursive: true });
57
+
58
+ // Write multiple files
59
+ await fs.writeFile('/data/config.json', JSON.stringify({ version: '1.0' }));
60
+ await fs.writeFile('/data/logs/app.log', 'Application started\n');
61
+
62
+ // Append to a file
63
+ await fs.appendFile('/data/logs/app.log', `${new Date().toISOString()}: User logged in\n`);
64
+
65
+ // Get file statistics with hash
66
+ const stats = await fs.stat('/data/config.json', {
67
+ includeHash: true,
68
+ hashAlgorithm: 'SHA-1'
69
+ });
70
+ console.log(`File size: ${stats.size} bytes, Hash: ${stats.hash}`);
71
+
72
+ // List directory contents
73
+ const files = await fs.readdir('/data', { withFileTypes: true });
74
+ files.forEach(item => {
75
+ console.log(`${item.name} - ${item.isFile ? 'file' : 'directory'}`);
76
+ });
77
+ }
78
+ ```
79
+
80
+ ## API Reference
81
+
82
+ ### Entry Points
83
+
84
+ #### `createWorker()`
85
+
86
+ Creates a new file system instance with an inline worker.
87
+
88
+ ```typescript
89
+ import { createWorker } from 'opfs-worker/inline';
90
+
91
+ const fs = await createWorker();
92
+ ```
93
+
94
+ **Returns:** `Promise<RemoteOPFSWorker>` - A remote file system interface
95
+
96
+ ### Core Methods
97
+
98
+ #### `mount(root?: string): Promise<boolean>`
99
+
100
+ Initialize the file system within a given directory.
101
+
102
+ ```typescript
103
+ await fs.mount('/my-app');
104
+ ```
105
+
106
+ **Parameters:**
107
+
108
+ - `root` (optional): The root path for the file system (default: '/')
109
+
110
+ **Returns:** `Promise<boolean>` - True if initialization was successful
111
+
112
+ **Throws:** `OPFSError` if initialization fails
113
+
114
+ #### `readFile(path: string, encoding?: BufferEncoding | 'binary'): Promise<string | Uint8Array>`
115
+
116
+ Read a file from the file system.
117
+
118
+ ```typescript
119
+ // Read as text (default)
120
+ const content = await fs.readFile('/config/settings.json');
121
+
122
+ // Read as binary
123
+ const binaryData = await fs.readFile('/images/logo.png', 'binary');
124
+
125
+ // Read with specific encoding
126
+ const utf8Content = await fs.readFile('/data/utf8.txt', 'utf-8');
127
+ ```
128
+
129
+ **Parameters:**
130
+
131
+ - `path`: The path to the file to read
132
+ - `encoding` (optional): The encoding to use ('utf-8', 'binary', etc.)
133
+
134
+ **Returns:** `Promise<string | Uint8Array>` - File contents
135
+
136
+ **Throws:** `FileNotFoundError` if the file doesn't exist
137
+
138
+ #### `writeFile(path: string, data: string | Uint8Array | ArrayBuffer, encoding?: BufferEncoding): Promise<void>`
139
+
140
+ Write data to a file, creating or overwriting it.
141
+
142
+ ```typescript
143
+ // Write text data
144
+ await fs.writeFile('/config/settings.json', JSON.stringify({ theme: 'dark' }));
145
+
146
+ // Write binary data
147
+ const binaryData = new Uint8Array([1, 2, 3, 4, 5]);
148
+ await fs.writeFile('/data/binary.dat', binaryData);
149
+
150
+ // Write with specific encoding
151
+ await fs.writeFile('/data/utf16.txt', 'Hello World', 'utf-16le');
152
+ ```
153
+
154
+ **Parameters:**
155
+
156
+ - `path`: The path to the file to write
157
+ - `data`: The data to write (string, Uint8Array, or ArrayBuffer)
158
+ - `encoding` (optional): The encoding to use when writing string data
159
+
160
+ #### `appendFile(path: string, data: string | Uint8Array | ArrayBuffer, encoding?: BufferEncoding): Promise<void>`
161
+
162
+ Append data to the end of a file.
163
+
164
+ ```typescript
165
+ // Append text to a log file
166
+ await fs.appendFile('/logs/app.log', `[${new Date().toISOString()}] User logged in\n`);
167
+
168
+ // Append binary data
169
+ const additionalData = new Uint8Array([6, 7, 8]);
170
+ await fs.appendFile('/data/binary.dat', additionalData);
171
+ ```
172
+
173
+ #### `mkdir(path: string, options?: { recursive?: boolean }): Promise<void>`
174
+
175
+ Create a directory.
176
+
177
+ ```typescript
178
+ // Create a single directory
179
+ await fs.mkdir('/users/john');
180
+
181
+ // Create nested directories
182
+ await fs.mkdir('/users/john/documents/projects', { recursive: true });
183
+ ```
184
+
185
+ **Parameters:**
186
+
187
+ - `path`: The path where the directory should be created
188
+ - `options.recursive` (optional): Whether to create parent directories if they don't exist
189
+
190
+ #### `readdir(path: string, options?: { withFileTypes?: boolean }): Promise<string[] | DirentData[]>`
191
+
192
+ Read a directory's contents.
193
+
194
+ ```typescript
195
+ // Get simple list of names
196
+ const files = await fs.readdir('/users/john/documents');
197
+ console.log('Files:', files); // ['readme.txt', 'config.json', 'images']
198
+
199
+ // Get detailed information
200
+ const detailed = await fs.readdir('/users/john/documents', { withFileTypes: true });
201
+ detailed.forEach(item => {
202
+ console.log(`${item.name} - ${item.isFile ? 'file' : 'directory'}`);
203
+ });
204
+ ```
205
+
206
+ **Returns:**
207
+
208
+ - `Promise<string[]>` when `withFileTypes` is false or undefined
209
+ - `Promise<DirentData[]>` when `withFileTypes` is true
210
+
211
+ #### `stat(path: string, options?: { includeHash?: boolean; hashAlgorithm?: string }): Promise<FileStat>`
212
+
213
+ Get file or directory statistics.
214
+
215
+ ```typescript
216
+ // Basic stats
217
+ const stats = await fs.stat('/config/settings.json');
218
+ console.log(`File size: ${stats.size} bytes`);
219
+ console.log(`Is file: ${stats.isFile}`);
220
+ console.log(`Modified: ${stats.mtime}`);
221
+
222
+ // Stats with hash
223
+ const statsWithHash = await fs.stat('/config/settings.json', {
224
+ includeHash: true,
225
+ hashAlgorithm: 'SHA-1'
226
+ });
227
+ console.log(`Hash: ${statsWithHash.hash}`);
228
+ ```
229
+
230
+ **Parameters:**
231
+
232
+ - `path`: The path to the file or directory
233
+ - `options.includeHash` (optional): Whether to calculate file hash (default: false)
234
+ - `options.hashAlgorithm` (optional): Hash algorithm to use ('SHA-1', 'SHA-256', 'SHA-384', 'SHA-512', default: 'SHA-1')
235
+
236
+ **Returns:** `Promise<FileStat>` - File/directory statistics
237
+
238
+ #### `exists(path: string): Promise<boolean>`
239
+
240
+ Check if a file or directory exists.
241
+
242
+ ```typescript
243
+ const exists = await fs.exists('/config/settings.json');
244
+ console.log(`File exists: ${exists}`);
245
+ ```
246
+
247
+ **Returns:** `Promise<boolean>` - True if the file or directory exists
248
+
249
+ #### `remove(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>`
250
+
251
+ Remove files and directories.
252
+
253
+ ```typescript
254
+ // Remove a file
255
+ await fs.remove('/path/to/file.txt');
256
+
257
+ // Remove a directory and all its contents
258
+ await fs.remove('/path/to/directory', { recursive: true });
259
+
260
+ // Remove with force (ignore if doesn't exist)
261
+ await fs.remove('/maybe/exists', { force: true });
262
+ ```
263
+
264
+ **Parameters:**
265
+
266
+ - `path`: The path to remove
267
+ - `options.recursive` (optional): Whether to remove directories recursively (default: false)
268
+ - `options.force` (optional): Whether to ignore errors if the path doesn't exist (default: false)
269
+
270
+ #### `copy(source: string, destination: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>`
271
+
272
+ Copy files and directories.
273
+
274
+ ```typescript
275
+ // Copy a file
276
+ await fs.copy('/source/file.txt', '/dest/file.txt');
277
+
278
+ // Copy a directory and all its contents
279
+ await fs.copy('/source/dir', '/dest/dir', { recursive: true });
280
+
281
+ // Copy without overwriting existing files
282
+ await fs.copy('/source', '/dest', { recursive: true, force: false });
283
+ ```
284
+
285
+ **Parameters:**
286
+
287
+ - `source`: The source path to copy from
288
+ - `destination`: The destination path to copy to
289
+ - `options.recursive` (optional): Whether to copy directories recursively (default: false)
290
+ - `options.force` (optional): Whether to overwrite existing files (default: true)
291
+
292
+ #### `rename(oldPath: string, newPath: string): Promise<void>`
293
+
294
+ Rename a file or directory.
295
+
296
+ ```typescript
297
+ await fs.rename('/old/path/file.txt', '/new/path/renamed.txt');
298
+ ```
299
+
300
+ **Parameters:**
301
+
302
+ - `oldPath`: The current path of the file or directory
303
+ - `newPath`: The new path for the file or directory
304
+
305
+ #### `clear(path?: string): Promise<void>`
306
+
307
+ Clear all contents of a directory without removing the directory itself.
308
+
309
+ ```typescript
310
+ // Clear root directory contents
311
+ await fs.clear('/');
312
+
313
+ // Clear specific directory contents
314
+ await fs.clear('/data');
315
+ ```
316
+
317
+ **Parameters:**
318
+
319
+ - `path` (optional): The path to the directory to clear (default: '/')
320
+
321
+ #### `index(options?: { includeHash?: boolean; hashAlgorithm?: string }): Promise<Map<string, FileStat>>`
322
+
323
+ Recursively list all files and directories with their stats.
324
+
325
+ ```typescript
326
+ // Basic index without hash
327
+ const index = await fs.index();
328
+
329
+ // Index with file hash
330
+ const indexWithHash = await fs.index({
331
+ includeHash: true,
332
+ hashAlgorithm: 'SHA-1'
333
+ });
334
+
335
+ // Iterate through all files and directories
336
+ for (const [path, stat] of index) {
337
+ console.log(`${path}: ${stat.isFile ? 'file' : 'directory'} (${stat.size} bytes)`);
338
+ if (stat.hash) console.log(` Hash: ${stat.hash}`);
339
+ }
340
+
341
+ // Get specific file stats
342
+ const fileStats = index.get('/data/config.json');
343
+ if (fileStats) {
344
+ console.log(`File size: ${fileStats.size} bytes`);
345
+ if (fileStats.hash) console.log(`Hash: ${fileStats.hash}`);
346
+ }
347
+ ```
348
+
349
+ **Parameters:**
350
+
351
+ - `options.includeHash` (optional): Whether to calculate file hash (default: false)
352
+ - `options.hashAlgorithm` (optional): Hash algorithm to use (default: 'SHA-1')
353
+
354
+ **Returns:** `Promise<Map<string, FileStat>>` - Map of path => FileStat
355
+
356
+ #### `sync(entries: [string, string | Uint8Array | Blob][], options?: { cleanBefore?: boolean }): Promise<void>`
357
+
358
+ Synchronize the file system with external data.
359
+
360
+ ```typescript
361
+ // Sync with external data
362
+ const entries: [string, string | Uint8Array | Blob][] = [
363
+ ['/config.json', JSON.stringify({ theme: 'dark' })],
364
+ ['/data/binary.dat', new Uint8Array([1, 2, 3, 4])],
365
+ ['/upload.txt', new Blob(['file content'], { type: 'text/plain' })]
366
+ ];
367
+
368
+ // Sync without clearing existing files
369
+ await fs.sync(entries);
370
+
371
+ // Clean file system and then sync
372
+ await fs.sync(entries, { cleanBefore: true });
373
+ ```
374
+
375
+ **Parameters:**
376
+
377
+ - `entries`: Array of [path, data] tuples to sync
378
+ - `options.cleanBefore` (optional): Whether to clear the file system before syncing (default: false)
379
+
380
+ #### `realpath(path: string): Promise<string>`
381
+
382
+ Resolve a path to an absolute path.
383
+
384
+ ```typescript
385
+ // Resolve relative path
386
+ const absolute = await fs.realpath('./config/../data/file.txt');
387
+ console.log(absolute); // '/data/file.txt'
388
+ ```
389
+
390
+ **Returns:** `Promise<string>` - The absolute normalized path
391
+
392
+ **Throws:** `FileNotFoundError` if the path doesn't exist
393
+
394
+ ## Types
395
+
396
+ ### `FileStat`
397
+
398
+ File or directory statistics.
399
+
400
+ ```typescript
401
+ interface FileStat {
402
+ kind: 'file' | 'directory';
403
+ size: number;
404
+ mtime: string; // ISO string
405
+ ctime: string; // ISO string
406
+ isFile: boolean;
407
+ isDirectory: boolean;
408
+ hash?: string; // Hash of file content (only for files)
409
+ }
410
+ ```
411
+
412
+ ### `DirentData`
413
+
414
+ Directory entry information.
415
+
416
+ ```typescript
417
+ interface DirentData {
418
+ name: string;
419
+ kind: 'file' | 'directory';
420
+ isFile: boolean;
421
+ isDirectory: boolean;
422
+ }
423
+ ```
424
+
425
+ ### `RemoteOPFSWorker`
426
+
427
+ Remote file system interface type.
428
+
429
+ ```typescript
430
+ type RemoteOPFSWorker = Remote<OPFSWorker>;
431
+ ```
432
+
433
+ ## Error Types
434
+
435
+ The library provides comprehensive error handling with specific error types:
436
+
437
+ - `OPFSError` - Base error class for all OPFS-related errors
438
+ - `OPFSNotSupportedError` - Thrown when OPFS is not supported in the browser
439
+ - `OPFSNotMountedError` - Thrown when OPFS is not mounted
440
+ - `PathError` - Thrown for invalid paths or path traversal attempts
441
+ - `FileNotFoundError` - Thrown when a requested file doesn't exist
442
+ - `DirectoryNotFoundError` - Thrown when a requested directory doesn't exist
443
+ - `PermissionError` - Thrown when permission is denied for an operation
444
+ - `StorageError` - Thrown when an operation fails due to insufficient storage
445
+ - `TimeoutError` - Thrown when an operation times out
446
+
447
+ ## Browser Support
448
+
449
+ This library works in **all modern browsers**, including Safari, Firefox, Chrome, and Edge.
450
+
451
+ **Requirements:**
452
+
453
+ - Web Workers support (available in all modern browsers)
454
+ - File System Access API (for OPFS functionality)
455
+
456
+ **Browser Compatibility:**
457
+
458
+ - ✅ Chrome 86+
459
+ - ✅ Edge 86+
460
+ - ✅ Firefox 111+
461
+ - ✅ Safari 15.2+
462
+ - ✅ Opera 72+
463
+
464
+ **Note:** The library gracefully handles browsers that don't support OPFS by providing appropriate error messages and fallback behavior.
465
+
466
+ ## Development
467
+
468
+ ### Building
469
+
470
+ ```bash
471
+ npm run build
472
+ ```
473
+
474
+ ### Development Server
475
+
476
+ ```bash
477
+ npm run dev
478
+ ```
479
+
480
+ ### Testing
481
+
482
+ ```bash
483
+ npm test
484
+ npm run test:coverage
485
+ ```
486
+
487
+ ### Linting
488
+
489
+ ```bash
490
+ npm run lint
491
+ npm run lint:fix
492
+ ```
493
+
494
+ ## License
495
+
496
+ MIT
497
+
498
+ ## Contributing
499
+
500
+ Contributions are welcome! Please feel free to submit a Pull Request.