@componentor/fs 1.2.8 โ†’ 2.0.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/README.md CHANGED
@@ -1,738 +1,509 @@
1
1
  # @componentor/fs
2
2
 
3
- > ๐Ÿš€ A blazing-fast, Node.js-compatible filesystem interface for the browser using the Origin Private File System API
3
+ **Battle-tested OPFS-based Node.js `fs` polyfill with sync and async APIs**
4
4
 
5
- [![npm version](https://badge.fury.io/js/@componentor%2Ffs.svg)](https://www.npmjs.com/package/@componentor/fs)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ A high-performance browser filesystem with native OPFS backend and synchronous API support.
7
6
 
8
- ## โœจ Features
7
+ ```typescript
8
+ import { fs } from '@componentor/fs';
9
9
 
10
- - ๐Ÿ”ฅ **Lightning Fast** - Leverages sync access handles for optimal performance
11
- - ๐ŸŒ **Browser Native** - Built on the modern Origin Private File System API
12
- - ๐Ÿ”„ **Drop-in Replacement** - Compatible with Node.js fs/promises API
13
- - โšก **Isomorphic Git Ready** - Perfect companion for browser-based Git operations
14
- - ๐Ÿ”— **Symlink Support** - Full symbolic link emulation for advanced file operations
15
- - ๐Ÿ“ฆ **Zero Dependencies** - Lightweight and efficient
16
- - โœ… **Fully Tested** - 214 comprehensive tests with 100% pass rate
17
- - ๐Ÿ“ **Full fs Compatibility** - access, appendFile, copyFile, cp, rm, truncate, open, opendir, streams, and more
18
- - ๐Ÿš€ **Hybrid Mode** - Optimal performance with reads on main thread and writes on worker
10
+ // Sync API (requires crossOriginIsolated)
11
+ fs.writeFileSync('/hello.txt', 'Hello World!');
12
+ const data = fs.readFileSync('/hello.txt', 'utf8');
19
13
 
20
- ## ๐Ÿš€ Installation
21
-
22
- ```bash
23
- npm install @componentor/fs
14
+ // Async API (always available)
15
+ await fs.promises.writeFile('/async.txt', 'Async data');
16
+ const content = await fs.promises.readFile('/async.txt', 'utf8');
24
17
  ```
25
18
 
26
- ```bash
27
- yarn add @componentor/fs
28
- ```
19
+ ## Features
20
+
21
+ - **Node.js Compatible** - Drop-in replacement for `fs` module
22
+ - **Sync API** - `readFileSync`, `writeFileSync`, etc. (requires COOP/COEP)
23
+ - **Async API** - `promises.readFile`, `promises.writeFile`, etc.
24
+ - **Cross-tab Safe** - Uses `navigator.locks` for multi-tab coordination
25
+ - **isomorphic-git Ready** - Full compatibility with git operations
26
+ - **Zero Config** - Works out of the box, no worker files needed
27
+ - **TypeScript First** - Complete type definitions included
28
+
29
+ ## Installation
29
30
 
30
31
  ```bash
31
- pnpm add @componentor/fs
32
+ npm install @componentor/fs
32
33
  ```
33
34
 
34
- ## ๐Ÿ”ง Quick Start
35
+ ## Quick Start
35
36
 
36
- ```javascript
37
- import OPFS from '@componentor/fs'
37
+ ```typescript
38
+ import { fs, path } from '@componentor/fs';
38
39
 
39
- const fs = new OPFS()
40
+ // Create a directory
41
+ await fs.promises.mkdir('/projects/my-app', { recursive: true });
40
42
 
41
43
  // Write a file
42
- await fs.writeFile('hello.txt', 'Hello, OPFS World!')
43
-
44
- // Read it back
45
- const content = await fs.readFile('hello.txt', { encoding: 'utf8' })
46
- console.log(content) // "Hello, OPFS World!"
44
+ await fs.promises.writeFile('/projects/my-app/index.js', 'console.log("Hello!");');
47
45
 
48
- // Create directories
49
- await fs.mkdir('projects/my-app', { recursive: true })
46
+ // Read a file
47
+ const code = await fs.promises.readFile('/projects/my-app/index.js', 'utf8');
48
+ console.log(code); // 'console.log("Hello!");'
50
49
 
51
50
  // List directory contents
52
- const files = await fs.readdir('.')
53
- console.log(files) // ['hello.txt', 'projects']
54
- ```
55
-
56
- ## ๐Ÿ’ก Why It's Fast
57
-
58
- The Origin Private File System API provides **direct access to the device's storage** with significantly better performance characteristics than traditional browser storage solutions:
59
-
60
- ### ๐ŸŽ๏ธ Performance Advantages
61
-
62
- - **Sync Access Handles**: When available, operations bypass the async overhead for read/write operations
63
- - **Native File System**: Direct integration with the operating system's file system
64
- - **Optimized I/O**: Reduced serialization overhead compared to IndexedDB or localStorage
65
- - **Streaming Support**: Efficient handling of large files without memory constraints
66
-
67
- ### ๐Ÿ“Š Performance vs LightningFS
68
-
69
- Benchmarked against [LightningFS](https://github.com/isomorphic-git/lightning-fs) (100 iterations):
70
-
71
- | Operation | LightningFS | OPFS-FS (Hybrid) | Speedup |
72
- |-----------|-------------|------------------|---------|
73
- | Batch Writes | 25.57ms | 1.73ms | **14.8x faster** |
74
- | Batch Reads | 12.64ms | 1.56ms | **8.1x faster** |
75
- | Single Writes | 66.45ms | 71.37ms | ~1x |
76
- | Single Reads | 66.76ms | 66.93ms | ~1x |
77
- | **Total** | **172.85ms** | **144.30ms** | **1.20x faster** |
78
-
79
- > **Note:** This package was previously published as `@componentor/opfs-fs`. If you're upgrading, simply change your imports from `@componentor/opfs-fs` to `@componentor/fs`.
80
-
81
- ## ๐Ÿ“š API Reference
82
-
83
- ### Constructor
84
-
85
- #### `new OPFS(options?)`
86
-
87
- Creates a new OPFS filesystem instance.
88
-
89
- **Parameters:**
90
- - `options.useSync` (boolean, default: `true`) - Use synchronous access handles when available
91
- - `options.workerUrl` (URL | string, optional) - Worker script URL. When provided, enables **hybrid mode** for optimal performance
92
- - `options.read` ('main' | 'worker', default: 'main') - Backend for read operations in hybrid mode
93
- - `options.write` ('main' | 'worker', default: 'worker') - Backend for write operations in hybrid mode
94
- - `options.verbose` (boolean, default: `false`) - Enable verbose logging
95
- - `options.useCompression` (boolean, default: `false`) - Enable gzip compression for batch writes. Can improve performance for text-heavy workloads.
96
- - `options.useChecksum` (boolean, default: `true`) - Enable CRC32 checksum for batch writes. Disable for maximum performance if data integrity verification is not needed.
97
-
98
- **Example:**
99
- ```javascript
100
- // Use sync handles (recommended for workers)
101
- const fs = new OPFS({ useSync: true })
51
+ const files = await fs.promises.readdir('/projects/my-app');
52
+ console.log(files); // ['index.js']
102
53
 
103
- // Force async mode
104
- const fsAsync = new OPFS({ useSync: false })
54
+ // Get file stats
55
+ const stats = await fs.promises.stat('/projects/my-app/index.js');
56
+ console.log(stats.size); // 23
105
57
 
106
- // Use hybrid mode (recommended for main thread - best performance!)
107
- const fs = new OPFS({
108
- workerUrl: new URL('./opfs-worker.js', import.meta.url)
109
- })
110
- await fs.ready() // Wait for worker to initialize
111
-
112
- // Don't forget to terminate when done
113
- fs.terminate()
58
+ // Use path utilities
59
+ console.log(path.join('/projects', 'my-app', 'src')); // '/projects/my-app/src'
60
+ console.log(path.dirname('/projects/my-app/index.js')); // '/projects/my-app'
61
+ console.log(path.basename('/projects/my-app/index.js')); // 'index.js'
114
62
  ```
115
63
 
116
- ### Hybrid Mode (Recommended)
117
-
118
- Hybrid mode provides the **best performance** by routing operations to optimal backends:
119
- - **Reads on main thread**: No message passing overhead
120
- - **Writes on worker**: Sync access handles are faster
64
+ ## Performance Tiers
121
65
 
122
- ```javascript
123
- import OPFS from '@componentor/fs'
66
+ @componentor/fs operates in two performance tiers based on browser capabilities:
124
67
 
125
- // Create with hybrid mode
126
- const fs = new OPFS({
127
- workerUrl: new URL('@componentor/fs/worker-script', import.meta.url)
128
- })
68
+ ### Tier 1: Sync (Fastest)
129
69
 
130
- // Wait for worker to be ready
131
- await fs.ready()
70
+ **Requirements:** `crossOriginIsolated` context (COOP/COEP headers)
132
71
 
133
- // Use like normal - hybrid routing happens automatically
134
- await fs.writeFile('test.txt', 'Hello World') // Routed to worker
135
- const data = await fs.readFile('test.txt') // Routed to main thread
72
+ Uses `SharedArrayBuffer` + `Atomics` for zero-copy data transfer between main thread and worker. Enables **synchronous** filesystem operations.
136
73
 
137
- // Clean up when done
138
- fs.terminate()
74
+ ```typescript
75
+ // Tier 1 unlocks sync APIs
76
+ fs.writeFileSync('/file.txt', 'data');
77
+ const data = fs.readFileSync('/file.txt', 'utf8');
78
+ fs.mkdirSync('/dir', { recursive: true });
79
+ fs.existsSync('/file.txt'); // true
139
80
  ```
140
81
 
141
- ### File Operations
142
-
143
- #### `readFile(path, options?)`
82
+ ### Tier 2: Async (Always Available)
144
83
 
145
- Reads the entire contents of a file.
84
+ Works in any browser context without special headers. Uses Web Worker with `postMessage` for async operations.
146
85
 
147
- **Parameters:**
148
- - `path` (string) - File path
149
- - `options.encoding` (string, optional) - Text encoding ('utf8' for string output)
150
-
151
- **Returns:** `Promise<Uint8Array | string>`
152
-
153
- **Examples:**
154
- ```javascript
155
- // Read as binary
156
- const buffer = await fs.readFile('image.png')
157
-
158
- // Read as text
159
- const text = await fs.readFile('config.json', { encoding: 'utf8' })
160
-
161
- // Parse JSON
162
- const config = JSON.parse(await fs.readFile('config.json', { encoding: 'utf8' }))
86
+ ```typescript
87
+ // Tier 2 - promises API always works
88
+ await fs.promises.writeFile('/file.txt', 'data');
89
+ const data = await fs.promises.readFile('/file.txt', 'utf8');
90
+ await fs.promises.mkdir('/dir', { recursive: true });
91
+ await fs.promises.exists('/file.txt'); // true
163
92
  ```
164
93
 
165
- #### `writeFile(path, data, options?)`
166
-
167
- Writes data to a file, creating it if it doesn't exist.
168
-
169
- **Parameters:**
170
- - `path` (string) - File path
171
- - `data` (string | Uint8Array) - Data to write
172
- - `options` (object, optional) - Write options
173
-
174
- **Returns:** `Promise<void>`
175
-
176
- **Examples:**
177
- ```javascript
178
- // Write text
179
- await fs.writeFile('note.txt', 'Hello World')
94
+ ## COOP/COEP Headers (Required for Tier 1)
180
95
 
181
- // Write binary data
182
- await fs.writeFile('data.bin', new Uint8Array([1, 2, 3, 4]))
96
+ To enable Tier 1 (sync) performance, your server must send these headers:
183
97
 
184
- // Write JSON
185
- await fs.writeFile('config.json', JSON.stringify({ theme: 'dark' }))
186
98
  ```
187
-
188
- #### `unlink(path)`
189
-
190
- Deletes a file.
191
-
192
- **Parameters:**
193
- - `path` (string) - File path to delete
194
-
195
- **Returns:** `Promise<void>`
196
-
197
- **Example:**
198
- ```javascript
199
- await fs.unlink('temp.txt')
99
+ Cross-Origin-Opener-Policy: same-origin
100
+ Cross-Origin-Embedder-Policy: require-corp
200
101
  ```
201
102
 
202
- #### `rename(oldPath, newPath)`
103
+ ### Vite Configuration
203
104
 
204
- Moves/renames a file.
205
-
206
- **Parameters:**
207
- - `oldPath` (string) - Current file path
208
- - `newPath` (string) - New file path
209
-
210
- **Returns:** `Promise<void>`
211
-
212
- **Example:**
213
- ```javascript
214
- await fs.rename('old-name.txt', 'new-name.txt')
215
- await fs.rename('file.txt', 'backup/file.txt')
105
+ ```typescript
106
+ // vite.config.ts
107
+ export default defineConfig({
108
+ server: {
109
+ headers: {
110
+ 'Cross-Origin-Opener-Policy': 'same-origin',
111
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
112
+ },
113
+ },
114
+ });
216
115
  ```
217
116
 
218
- #### `stat(path)`
219
-
220
- Gets file statistics (follows symlinks).
221
-
222
- **Parameters:**
223
- - `path` (string) - File path
117
+ ### Express/Node.js
224
118
 
225
- **Returns:** `Promise<FileStats>`
226
-
227
- **Example:**
228
119
  ```javascript
229
- const stats = await fs.stat('large-file.zip')
230
- console.log(`Size: ${stats.size} bytes`)
231
- console.log(`Modified: ${new Date(stats.mtimeMs)}`)
232
- console.log(`Is file: ${stats.isFile()}`)
120
+ app.use((req, res, next) => {
121
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
122
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
123
+ next();
124
+ });
233
125
  ```
234
126
 
235
- #### `lstat(path)`
236
-
237
- Gets file statistics without following symlinks.
238
-
239
- **Parameters:**
240
- - `path` (string) - File path
241
-
242
- **Returns:** `Promise<FileStats>`
127
+ ### Vercel
243
128
 
244
- **Example:**
245
- ```javascript
246
- const stats = await fs.lstat('link.txt')
247
- if (stats.isSymbolicLink()) {
248
- console.log(`Symlink pointing to: ${stats.target}`)
129
+ ```json
130
+ // vercel.json
131
+ {
132
+ "headers": [
133
+ {
134
+ "source": "/(.*)",
135
+ "headers": [
136
+ { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
137
+ { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
138
+ ]
139
+ }
140
+ ]
249
141
  }
250
142
  ```
251
143
 
252
- ### Symlink Operations
253
-
254
- #### `symlink(target, path)`
255
-
256
- Creates a symbolic link.
257
-
258
- **Parameters:**
259
- - `target` (string) - Target path the symlink points to
260
- - `path` (string) - Path where the symlink will be created
261
-
262
- **Returns:** `Promise<void>`
263
-
264
- **Example:**
265
- ```javascript
266
- await fs.writeFile('config.json', '{"key": "value"}')
267
- await fs.symlink('config.json', 'current-config.json')
268
-
269
- // Read through symlink
270
- const content = await fs.readFile('current-config.json', { encoding: 'utf8' })
271
- ```
272
-
273
- #### `readlink(path)`
274
-
275
- Reads the target of a symbolic link.
276
-
277
- **Parameters:**
278
- - `path` (string) - Symlink path
279
-
280
- **Returns:** `Promise<string>` - The target path
281
-
282
- **Example:**
283
- ```javascript
284
- const target = await fs.readlink('my-link.txt')
285
- console.log(`Link points to: ${target}`)
286
- ```
287
-
288
- #### `symlinkBatch(links)`
289
-
290
- Creates multiple symbolic links efficiently in a single operation.
291
-
292
- **Parameters:**
293
- - `links` (Array<{target: string, path: string}>) - Array of symlink definitions
294
-
295
- **Returns:** `Promise<void>`
296
-
297
- **Example:**
298
- ```javascript
299
- // Create multiple symlinks with a single metadata write
300
- await fs.symlinkBatch([
301
- { target: '/configs/prod.json', path: '/current-config.json' },
302
- { target: '/data/latest.db', path: '/current-db.db' },
303
- { target: '/logs/today.log', path: '/current.log' }
304
- ])
305
-
306
- // 60-70% faster than individual symlink() calls
307
- ```
308
-
309
- ### Directory Operations
310
-
311
- #### `mkdir(path, options?)`
312
-
313
- Creates a directory.
314
-
315
- **Parameters:**
316
- - `path` (string) - Directory path
317
- - `options.recursive` (boolean, optional) - Create parent directories
318
-
319
- **Returns:** `Promise<void>`
320
-
321
- **Examples:**
322
- ```javascript
323
- // Create single directory
324
- await fs.mkdir('uploads')
325
-
326
- // Create nested directories
327
- await fs.mkdir('projects/webapp/src', { recursive: true })
328
- ```
329
-
330
- #### `rmdir(path)`
331
-
332
- Removes a directory and all its contents.
333
-
334
- **Parameters:**
335
- - `path` (string) - Directory path
336
-
337
- **Returns:** `Promise<void>`
338
-
339
- **Example:**
340
- ```javascript
341
- await fs.rmdir('temp-folder')
342
- ```
343
-
344
- #### `readdir(path)`
345
-
346
- Lists directory contents.
347
-
348
- **Parameters:**
349
- - `path` (string) - Directory path
350
-
351
- **Returns:** `Promise<string[]>`
352
-
353
- **Example:**
354
- ```javascript
355
- const files = await fs.readdir('documents')
356
- console.log('Files:', files)
357
-
358
- // List root directory
359
- const rootFiles = await fs.readdir('.')
360
- ```
144
+ ### Check if Tier 1 is Available
361
145
 
362
- ### Additional File Operations
363
-
364
- #### `access(path, mode?)`
365
-
366
- Tests file accessibility. Throws if the file doesn't exist.
367
-
368
- ```javascript
369
- await fs.access('/path/to/file') // Throws if not accessible
370
- ```
371
-
372
- #### `appendFile(path, data)`
373
-
374
- Appends data to a file, creating it if it doesn't exist.
375
-
376
- ```javascript
377
- await fs.appendFile('log.txt', 'New log entry\n')
378
- ```
379
-
380
- #### `copyFile(src, dest, mode?)`
381
-
382
- Copies a file from source to destination.
383
-
384
- ```javascript
385
- await fs.copyFile('original.txt', 'backup.txt')
386
- // With COPYFILE_EXCL flag to fail if dest exists
387
- await fs.copyFile('src.txt', 'dest.txt', fs.constants.COPYFILE_EXCL)
388
- ```
389
-
390
- #### `cp(src, dest, options?)`
391
-
392
- Copies files or directories recursively.
393
-
394
- ```javascript
395
- // Copy single file
396
- await fs.cp('file.txt', 'copy.txt')
397
-
398
- // Copy directory recursively
399
- await fs.cp('source-dir', 'dest-dir', { recursive: true })
400
- ```
401
-
402
- #### `exists(path)`
403
-
404
- Returns true if the path exists, false otherwise (doesn't throw).
405
-
406
- ```javascript
407
- if (await fs.exists('config.json')) {
408
- // File exists
146
+ ```typescript
147
+ if (crossOriginIsolated) {
148
+ console.log('Tier 1 (sync) available!');
149
+ fs.writeFileSync('/fast.txt', 'blazing fast');
150
+ } else {
151
+ console.log('Tier 2 (async) only');
152
+ await fs.promises.writeFile('/fast.txt', 'still fast');
409
153
  }
410
154
  ```
411
155
 
412
- #### `realpath(path)`
156
+ ## Benchmarks
413
157
 
414
- Resolves symlinks to get the real path.
158
+ Tested against LightningFS (IndexedDB-based filesystem) in Chrome with Tier 1 enabled:
415
159
 
416
- ```javascript
417
- const realPath = await fs.realpath('my-symlink')
418
- ```
419
-
420
- #### `rm(path, options?)`
421
-
422
- Removes files or directories.
423
-
424
- ```javascript
425
- await fs.rm('file.txt')
426
- await fs.rm('directory', { recursive: true })
427
- await fs.rm('maybe-exists', { force: true }) // No error if doesn't exist
428
- ```
429
-
430
- #### `truncate(path, len?)`
431
-
432
- Truncates a file to the specified length.
433
-
434
- ```javascript
435
- await fs.truncate('file.txt', 100) // Truncate to 100 bytes
436
- await fs.truncate('file.txt') // Truncate to 0 bytes
437
- ```
438
-
439
- #### `mkdtemp(prefix)`
160
+ | Operation | @componentor/fs | LightningFS | Winner |
161
+ |-----------|-----------------|-------------|--------|
162
+ | Write 100 x 1KB | 131ms (763 ops/s) | 317ms (316 ops/s) | **OPFS 2.4x** |
163
+ | Write 100 x 4KB | 145ms (690 ops/s) | 49ms (2061 ops/s) | LightningFS |
164
+ | Read 100 x 1KB | 11ms (9170 ops/s) | 17ms (5824 ops/s) | **OPFS 1.6x** |
165
+ | Read 100 x 4KB | 10ms (10493 ops/s) | 16ms (6431 ops/s) | **OPFS 1.6x** |
166
+ | Large 10 x 1MB | 19ms (538 ops/s) | 11ms (910 ops/s) | LightningFS |
167
+ | Batch Write 500 | 416ms (1202 ops/s) | 125ms (4014 ops/s) | LightningFS |
168
+ | Batch Read 500 | 311ms (1608 ops/s) | 74ms (6736 ops/s) | LightningFS |
169
+ | **Git Clone** | 427ms | 1325ms | **OPFS 3.1x** |
170
+ | Git Status 10x | 53ms | 39ms | LightningFS |
440
171
 
441
- Creates a unique temporary directory.
442
-
443
- ```javascript
444
- const tempDir = await fs.mkdtemp('/tmp/myapp-')
445
- console.log(tempDir) // e.g., "/tmp/myapp-1234567890-abc123"
446
- ```
447
-
448
- #### `open(path, flags?, mode?)`
449
-
450
- Opens a file and returns a FileHandle.
451
-
452
- ```javascript
453
- const handle = await fs.open('file.txt', 'r')
454
- const buffer = new Uint8Array(100)
455
- await handle.read(buffer)
456
- await handle.close()
457
- ```
458
-
459
- #### `opendir(path)`
460
-
461
- Opens a directory for iteration.
462
-
463
- ```javascript
464
- const dir = await fs.opendir('/my-dir')
465
- for await (const entry of dir) {
466
- console.log(entry.name, entry.isFile(), entry.isDirectory())
467
- }
468
- ```
172
+ **Key takeaways:**
173
+ - **Git clone is 2-3x faster** - the most important real-world operation
174
+ - **Reads are 1.6x faster** - OPFS excels at read operations
175
+ - **Small writes (1KB) are 2.4x faster** - great for config files and metadata
176
+ - LightningFS wins on batch operations and larger sequential writes
469
177
 
470
- #### `createReadStream(path, options?)`
178
+ *Results from Chrome 120+ with crossOriginIsolated enabled. Performance varies by browser and hardware.*
471
179
 
472
- Creates a readable stream for a file.
180
+ Run benchmarks yourself:
473
181
 
474
- ```javascript
475
- const stream = fs.createReadStream('large-file.bin')
476
- const reader = stream.getReader()
477
- // Read chunks...
182
+ ```bash
183
+ npm run benchmark:open
478
184
  ```
479
185
 
480
- #### `createWriteStream(path, options?)`
186
+ ## API Reference
187
+
188
+ ### Sync API (Tier 1 Only)
481
189
 
482
- Creates a writable stream for a file.
483
-
484
- ```javascript
485
- const stream = fs.createWriteStream('output.txt')
486
- const writer = stream.getWriter()
487
- await writer.write(new TextEncoder().encode('data'))
488
- await writer.close()
489
- ```
190
+ ```typescript
191
+ // Read/Write
192
+ fs.readFileSync(path: string, options?: { encoding?: string }): Uint8Array | string
193
+ fs.writeFileSync(path: string, data: Uint8Array | string, options?: { flush?: boolean }): void
194
+ fs.appendFileSync(path: string, data: Uint8Array | string): void
490
195
 
491
- #### `watch(path, options?)`
196
+ // Directories
197
+ fs.mkdirSync(path: string, options?: { recursive?: boolean }): void
198
+ fs.rmdirSync(path: string, options?: { recursive?: boolean }): void
199
+ fs.readdirSync(path: string): string[]
492
200
 
493
- Watches for file/directory changes (basic implementation).
201
+ // File Operations
202
+ fs.unlinkSync(path: string): void
203
+ fs.renameSync(oldPath: string, newPath: string): void
204
+ fs.copyFileSync(src: string, dest: string): void
205
+ fs.truncateSync(path: string, len?: number): void
494
206
 
495
- ```javascript
496
- const watcher = fs.watch('/my-dir')
497
- for await (const event of watcher) {
498
- console.log(event.eventType, event.filename)
499
- }
207
+ // Info
208
+ fs.statSync(path: string): Stats
209
+ fs.existsSync(path: string): boolean
210
+ fs.accessSync(path: string, mode?: number): void
500
211
  ```
501
212
 
502
- ### Compatibility Methods (No-ops for OPFS)
503
-
504
- The following methods are implemented for API compatibility but are no-ops since OPFS doesn't support these features:
213
+ ### Async API (Always Available)
505
214
 
506
- - `chmod(path, mode)` - File modes not supported
507
- - `chown(path, uid, gid)` - File ownership not supported
508
- - `utimes(path, atime, mtime)` - Timestamps are read-only
509
- - `lutimes(path, atime, mtime)` - Symlink timestamps are read-only
215
+ ```typescript
216
+ // Read/Write
217
+ fs.promises.readFile(path: string, options?: ReadOptions): Promise<Uint8Array | string>
218
+ fs.promises.writeFile(path: string, data: Uint8Array | string, options?: WriteOptions): Promise<void>
219
+ fs.promises.appendFile(path: string, data: Uint8Array | string): Promise<void>
510
220
 
511
- ### Lifecycle Methods (Hybrid Mode)
221
+ // Directories
222
+ fs.promises.mkdir(path: string, options?: { recursive?: boolean }): Promise<void>
223
+ fs.promises.rmdir(path: string, options?: { recursive?: boolean }): Promise<void>
224
+ fs.promises.readdir(path: string, options?: { withFileTypes?: boolean }): Promise<string[] | Dirent[]>
512
225
 
513
- These methods are used when running in hybrid mode (with `workerUrl`):
226
+ // File Operations
227
+ fs.promises.unlink(path: string): Promise<void>
228
+ fs.promises.rename(oldPath: string, newPath: string): Promise<void>
229
+ fs.promises.copyFile(src: string, dest: string): Promise<void>
230
+ fs.promises.truncate(path: string, len?: number): Promise<void>
231
+ fs.promises.rm(path: string, options?: { recursive?: boolean, force?: boolean }): Promise<void>
514
232
 
515
- #### `ready()`
233
+ // Info
234
+ fs.promises.stat(path: string): Promise<Stats>
235
+ fs.promises.lstat(path: string): Promise<Stats>
236
+ fs.promises.exists(path: string): Promise<boolean>
237
+ fs.promises.access(path: string, mode?: number): Promise<void>
238
+ fs.promises.realpath(path: string): Promise<string>
516
239
 
517
- Wait for the worker to be initialized. Call this before performing any operations.
240
+ // Advanced
241
+ fs.promises.open(path: string, flags?: string, mode?: number): Promise<FileHandle>
242
+ fs.promises.opendir(path: string): Promise<Dir>
243
+ fs.promises.mkdtemp(prefix: string): Promise<string>
244
+ fs.promises.symlink(target: string, path: string): Promise<void>
245
+ fs.promises.readlink(path: string): Promise<string>
246
+ fs.promises.link(existingPath: string, newPath: string): Promise<void>
518
247
 
519
- ```javascript
520
- const fs = new OPFS({ workerUrl: '...' })
521
- await fs.ready() // Wait for worker
248
+ // Cache Management
249
+ fs.promises.flush(): Promise<void> // Flush pending writes
250
+ fs.promises.purge(): Promise<void> // Clear all caches
522
251
  ```
523
252
 
524
- #### `terminate()`
253
+ ### Path Utilities
525
254
 
526
- Terminate the background worker. Call this when you're done using the filesystem.
255
+ ```typescript
256
+ import { path } from '@componentor/fs';
527
257
 
528
- ```javascript
529
- fs.terminate() // Clean up worker
258
+ path.join('/foo', 'bar', 'baz') // '/foo/bar/baz'
259
+ path.resolve('foo', 'bar') // '/foo/bar'
260
+ path.dirname('/foo/bar/baz.txt') // '/foo/bar'
261
+ path.basename('/foo/bar/baz.txt') // 'baz.txt'
262
+ path.extname('/foo/bar/baz.txt') // '.txt'
263
+ path.normalize('/foo//bar/../baz') // '/foo/baz'
264
+ path.isAbsolute('/foo') // true
265
+ path.relative('/foo/bar', '/foo/baz') // '../baz'
266
+ path.parse('/foo/bar/baz.txt') // { root, dir, base, ext, name }
267
+ path.format({ dir: '/foo', name: 'bar', ext: '.txt' }) // '/foo/bar.txt'
530
268
  ```
531
269
 
532
- #### `gc()`
533
-
534
- Force garbage collection by reinitializing the worker's OPFS instance. Use this for long-running applications to prevent memory leaks.
270
+ ### Constants
535
271
 
536
- ```javascript
537
- // Periodically call gc() in long-running apps
538
- await fs.gc()
539
- ```
272
+ ```typescript
273
+ import { constants } from '@componentor/fs';
540
274
 
541
- #### `resetCache()`
275
+ constants.F_OK // 0 - File exists
276
+ constants.R_OK // 4 - File is readable
277
+ constants.W_OK // 2 - File is writable
278
+ constants.X_OK // 1 - File is executable
542
279
 
543
- Reset internal caches (symlinks, directory handles). Lighter than `gc()`.
280
+ constants.COPYFILE_EXCL // 1 - Fail if dest exists
544
281
 
545
- ```javascript
546
- fs.resetCache()
282
+ constants.O_RDONLY // 0
283
+ constants.O_WRONLY // 1
284
+ constants.O_RDWR // 2
285
+ constants.O_CREAT // 64
286
+ constants.O_EXCL // 128
287
+ constants.O_TRUNC // 512
288
+ constants.O_APPEND // 1024
547
289
  ```
548
290
 
549
- ## ๐ŸŽฏ Real-World Examples
550
-
551
- ### Working with Isomorphic Git
552
-
553
- ```javascript
554
- import git from 'isomorphic-git'
555
- import OPFS from '@componentor/fs'
556
-
557
- // Use hybrid mode for best performance with git operations
558
- const fs = new OPFS({
559
- workerUrl: new URL('@componentor/fs/worker-script', import.meta.url)
560
- })
561
- await fs.ready()
291
+ ## isomorphic-git Integration
292
+
293
+ @componentor/fs works seamlessly with isomorphic-git:
294
+
295
+ ```typescript
296
+ import { fs } from '@componentor/fs';
297
+ import git from 'isomorphic-git';
298
+ import http from 'isomorphic-git/http/web';
562
299
 
563
300
  // Clone a repository
564
301
  await git.clone({
565
302
  fs,
566
- http: fetch,
567
- dir: '/my-repo',
568
- url: 'https://github.com/user/repo.git'
569
- })
570
-
571
- // Read a file from the repo
572
- const readme = await fs.readFile('/my-repo/README.md', { encoding: 'utf8' })
573
- console.log(readme)
574
-
575
- // Clean up when done
576
- fs.terminate()
577
- ```
578
-
579
- ### Building a Code Editor
580
-
581
- ```javascript
582
- import OPFS from '@componentor/fs'
583
-
584
- class CodeEditor {
585
- constructor(workerUrl) {
586
- // Use hybrid mode for optimal performance
587
- this.fs = new OPFS({ workerUrl })
588
- }
589
-
590
- async init() {
591
- await this.fs.ready()
592
- }
593
-
594
- destroy() {
595
- this.fs.terminate()
596
- }
597
-
598
- async createProject(name) {
599
- await this.fs.mkdir(`projects/${name}/src`)
600
- await this.fs.writeFile(`projects/${name}/package.json`, JSON.stringify({
601
- name,
602
- version: '1.0.0',
603
- main: 'src/index.js'
604
- }, null, 2))
605
- await this.fs.writeFile(`projects/${name}/src/index.js`, '// Your code here\n')
606
- }
607
-
608
- async saveFile(path, content) {
609
- await this.fs.writeFile(path, content)
610
- }
611
-
612
- async loadFile(path) {
613
- return await this.fs.readFile(path, { encoding: 'utf8' })
614
- }
615
-
616
- async getProjectFiles(projectName) {
617
- return await this.fs.readdir(`projects/${projectName}`)
618
- }
619
- }
620
- ```
303
+ http,
304
+ dir: '/repo',
305
+ url: 'https://github.com/user/repo',
306
+ corsProxy: 'https://cors.isomorphic-git.org',
307
+ });
308
+
309
+ // Check status
310
+ const status = await git.statusMatrix({ fs, dir: '/repo' });
311
+
312
+ // Stage and commit
313
+ await git.add({ fs, dir: '/repo', filepath: '.' });
314
+ await git.commit({
315
+ fs,
316
+ dir: '/repo',
317
+ message: 'Initial commit',
318
+ author: { name: 'User', email: 'user@example.com' },
319
+ });
320
+ ```
321
+
322
+ ## Architecture
323
+
324
+ ```
325
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
326
+ โ”‚ Main Thread โ”‚
327
+ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
328
+ โ”‚ โ”‚ Sync API โ”‚ โ”‚ Async API โ”‚ โ”‚ Path Utilities โ”‚ โ”‚
329
+ โ”‚ โ”‚ readFileSyncโ”‚ โ”‚ promises. โ”‚ โ”‚ join, dirname, etc. โ”‚ โ”‚
330
+ โ”‚ โ”‚writeFileSyncโ”‚ โ”‚ readFile โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
331
+ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
332
+ โ”‚ โ”‚ โ”‚ โ”‚
333
+ โ”‚ โ”‚ Atomics.wait โ”‚ postMessage โ”‚
334
+ โ”‚ โ”‚ (Tier 1) โ”‚ (Tier 2) โ”‚
335
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
336
+ โ”‚ โ”‚
337
+ โ–ผ โ–ผ
338
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
339
+ โ”‚ Web Worker โ”‚
340
+ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
341
+ โ”‚ โ”‚ OPFS Kernel โ”‚ โ”‚
342
+ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚
343
+ โ”‚ โ”‚ โ”‚ Sync Handle โ”‚ โ”‚ Directory โ”‚ โ”‚ navigator โ”‚ โ”‚ โ”‚
344
+ โ”‚ โ”‚ โ”‚ Cache โ”‚ โ”‚ Cache โ”‚ โ”‚ .locks โ”‚ โ”‚ โ”‚
345
+ โ”‚ โ”‚ โ”‚ (100 max) โ”‚ โ”‚ โ”‚ โ”‚ (cross-tab) โ”‚ โ”‚ โ”‚
346
+ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚
347
+ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
348
+ โ”‚ โ”‚ โ”‚
349
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
350
+ โ”‚
351
+ โ–ผ
352
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
353
+ โ”‚ OPFS โ”‚
354
+ โ”‚ Origin Private File System โ”‚
355
+ โ”‚ (Browser Storage API) โ”‚
356
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
357
+ ```
358
+
359
+ ## Feature Comparison
360
+
361
+ ### API Compatibility
362
+
363
+ | Feature | Node.js fs | @componentor/fs v2 | @componentor/fs v1 | LightningFS |
364
+ |---------|------------|--------------------|--------------------|-------------|
365
+ | `readFile` | โœ… | โœ… | โœ… | โœ… |
366
+ | `writeFile` | โœ… | โœ… | โœ… | โœ… |
367
+ | `readFileSync` | โœ… | โœ… Tier 1 | โŒ | โŒ |
368
+ | `writeFileSync` | โœ… | โœ… Tier 1 | โŒ | โŒ |
369
+ | `mkdir` / `mkdirSync` | โœ… | โœ… | โœ… | โœ… |
370
+ | `readdir` / `readdirSync` | โœ… | โœ… | โœ… | โœ… |
371
+ | `stat` / `statSync` | โœ… | โœ… | โœ… | โœ… |
372
+ | `unlink` / `unlinkSync` | โœ… | โœ… | โœ… | โœ… |
373
+ | `rename` / `renameSync` | โœ… | โœ… | โœ… | โœ… |
374
+ | `rm` (recursive) | โœ… | โœ… | โœ… | โŒ |
375
+ | `copyFile` | โœ… | โœ… | โœ… | โŒ |
376
+ | `symlink` / `readlink` | โœ… | โœ… | โœ… | โœ… |
377
+ | `watch` / `watchFile` | โœ… | โœ… | โŒ | โŒ |
378
+ | `open` / `FileHandle` | โœ… | โœ… | โŒ | โŒ |
379
+ | `opendir` / `Dir` | โœ… | โœ… | โŒ | โŒ |
380
+ | `mkdtemp` | โœ… | โœ… | โŒ | โŒ |
381
+ | Streams | โœ… | โŒ | โŒ | โŒ |
382
+
383
+ ### Performance Tiers
384
+
385
+ | Capability | Tier 1 Sync | Tier 1 Promises | Tier 2 | Legacy v1 | LightningFS |
386
+ |------------|-------------|-----------------|--------|-----------|-------------|
387
+ | **Sync API** | โœ… | โŒ | โŒ | โŒ | โŒ |
388
+ | **Async API** | โœ… | โœ… | โœ… | โœ… | โœ… |
389
+ | **Requires COOP/COEP** | โœ… | โœ… | โŒ | โŒ | โŒ |
390
+ | **SharedArrayBuffer** | โœ… | โœ… | โŒ | โŒ | โŒ |
391
+ | **Handle Caching** | โœ… | โœ… | โŒ | โŒ | N/A |
392
+ | **Zero-copy Transfer** | โœ… | โŒ | โŒ | โŒ | โŒ |
393
+ | **Cross-tab Safety** | โœ… | โœ… | โœ… | โœ… | โŒ |
394
+ | **Storage Backend** | OPFS | OPFS | OPFS | OPFS | IndexedDB |
395
+
396
+ ### Architecture Comparison
397
+
398
+ | Aspect | @componentor/fs v2 | @componentor/fs v1 | LightningFS |
399
+ |--------|--------------------|--------------------|-------------|
400
+ | **Storage** | OPFS (native FS) | OPFS | IndexedDB |
401
+ | **Worker** | Dedicated kernel | Shared worker | None |
402
+ | **Sync Method** | Atomics.wait | N/A | N/A |
403
+ | **Handle Strategy** | Cached (100 max) | Per-operation | N/A |
404
+ | **Locking** | navigator.locks | navigator.locks | None |
405
+ | **Bundle Size** | ~16KB | ~12KB | ~25KB |
406
+ | **TypeScript** | Full | Full | Partial |
407
+
408
+ ## Browser Support
409
+
410
+ | Browser | Tier 1 (Sync) | Tier 2 (Async) |
411
+ |---------|---------------|----------------|
412
+ | Chrome 102+ | Yes | Yes |
413
+ | Edge 102+ | Yes | Yes |
414
+ | Firefox 111+ | Yes* | Yes |
415
+ | Safari 15.2+ | No** | Yes |
416
+ | Opera 88+ | Yes | Yes |
417
+
418
+ \* Firefox requires `dom.workers.modules.enabled` flag
419
+ \** Safari doesn't support `createSyncAccessHandle` in workers
420
+
421
+ ## Troubleshooting
422
+
423
+ ### "SharedArrayBuffer is not defined"
424
+
425
+ Your page is not crossOriginIsolated. Add COOP/COEP headers:
426
+
427
+ ```
428
+ Cross-Origin-Opener-Policy: same-origin
429
+ Cross-Origin-Embedder-Policy: require-corp
430
+ ```
431
+
432
+ ### "Atomics.wait cannot be called in this context"
433
+
434
+ `Atomics.wait` can only be called from a Worker. The library handles this automatically - use the async API on the main thread.
435
+
436
+ ### "NotAllowedError: Access handle is already open"
437
+
438
+ Another tab or operation has the file open. The library uses `navigator.locks` to prevent this, but if you're using multiple filesystem instances, ensure they coordinate.
439
+
440
+ ### Slow Performance
441
+
442
+ 1. Check if Tier 1 is enabled: `console.log(crossOriginIsolated)`
443
+ 2. Use batch operations when possible
444
+ 3. Disable flush for bulk writes: `{ flush: false }`
445
+ 4. Call `fs.promises.flush()` after bulk operations
446
+
447
+ ## Changelog
621
448
 
622
- ### File Upload Handler
449
+ ### v2.0.1 (2025)
623
450
 
624
- ```javascript
625
- import OPFS from '@componentor/fs'
626
-
627
- const fs = new OPFS()
628
-
629
- async function handleFileUpload(file) {
630
- // Create uploads directory
631
- await fs.mkdir('uploads', { recursive: true })
632
-
633
- // Save uploaded file
634
- const buffer = new Uint8Array(await file.arrayBuffer())
635
- const filename = `uploads/${Date.now()}-${file.name}`
636
- await fs.writeFile(filename, buffer)
637
-
638
- // Get file info
639
- const stats = await fs.stat(filename)
640
- console.log(`Saved ${file.name} (${stats.size} bytes)`)
641
-
642
- return filename
643
- }
644
- ```
451
+ **Bug Fixes:**
452
+ - Fixed mtime not updating correctly when files are modified
453
+ - `stat()` now always returns accurate `lastModified` from OPFS instead of approximation
454
+ - Ensures git status and other mtime-dependent operations work correctly
645
455
 
646
- ## ๐ŸŒ Browser Support
456
+ ### v2.0.0 (2025)
647
457
 
648
- @componentor/fs requires browsers that support the Origin Private File System API:
458
+ **Major rewrite with sync API support and performance tiers.**
649
459
 
650
- - โœ… Chrome 86+
651
- - โœ… Edge 86+
652
- - โœ… Firefox 111+
653
- - โœ… Safari 15.2+
460
+ **New Features:**
461
+ - Synchronous API (`readFileSync`, `writeFileSync`, etc.) via Atomics
462
+ - Performance tiers (Tier 1 Sync, Tier 1 Promises, Tier 2)
463
+ - Dedicated worker kernel with handle caching (100 max)
464
+ - `watch()` and `watchFile()` for file change notifications
465
+ - `FileHandle` API (`fs.promises.open()`)
466
+ - `Dir` API (`fs.promises.opendir()`)
467
+ - `mkdtemp()` for temporary directories
468
+ - `flush()` and `purge()` for cache management
469
+ - Full `Dirent` support with `withFileTypes` option
654
470
 
655
- ### Feature Detection
471
+ **Performance:**
472
+ - 2-3x faster git clone vs LightningFS
473
+ - 1.6x faster reads
474
+ - Handle caching eliminates repeated open/close overhead
475
+ - Zero-copy data transfer with SharedArrayBuffer (Tier 1)
656
476
 
657
- ```javascript
658
- if ('storage' in navigator && 'getDirectory' in navigator.storage) {
659
- const fs = new OPFS()
660
- // OPFS is supported
661
- } else {
662
- console.warn('OPFS not supported in this browser')
663
- // Fallback to other storage solutions
664
- }
665
- ```
477
+ **Breaking Changes:**
478
+ - Requires `crossOriginIsolated` for Tier 1 (sync) features
479
+ - New architecture - not backwards compatible with v1 internals
480
+ - Minimum browser versions increased
666
481
 
667
- ## ๐Ÿšฆ Error Handling
482
+ ### v1.2.8 (2024)
668
483
 
669
- OPFS-FS throws standard filesystem errors:
484
+ - Final release of v1 branch
485
+ - OPFS-based async filesystem
486
+ - Basic isomorphic-git compatibility
487
+ - Cross-tab locking with `navigator.locks`
670
488
 
671
- ```javascript
672
- try {
673
- await fs.readFile('nonexistent.txt')
674
- } catch (error) {
675
- if (error.message.includes('ENOENT')) {
676
- console.log('File not found')
677
- }
678
- }
489
+ ### v1.0.0 (2024)
679
490
 
680
- try {
681
- await fs.mkdir('existing-dir')
682
- } catch (error) {
683
- if (error.message.includes('EEXIST')) {
684
- console.log('Directory already exists')
685
- }
686
- }
687
- ```
688
-
689
- ## ๐Ÿงช Testing
491
+ - Initial release
492
+ - Async-only OPFS filesystem
493
+ - Node.js `fs.promises` compatible API
494
+ - Basic directory and file operations
690
495
 
691
- @componentor/fs comes with a comprehensive test suite covering all functionality:
496
+ ## Contributing
692
497
 
693
498
  ```bash
694
- # Run all tests
695
- npm test
696
-
697
- # Run tests in watch mode
698
- npm run test:watch
499
+ git clone https://github.com/componentor/fs
500
+ cd fs
501
+ npm install
502
+ npm run dev # Watch mode
503
+ npm test # Run tests
504
+ npm run benchmark:open # Run benchmarks
699
505
  ```
700
506
 
701
- **Test Coverage:**
702
- - โœ… 214 tests with 100% pass rate
703
- - โœ… File read/write operations (text and binary)
704
- - โœ… Directory operations (create, remove, list)
705
- - โœ… File metadata and statistics
706
- - โœ… Path normalization and edge cases
707
- - โœ… Symlink operations and resolution
708
- - โœ… Error handling and edge cases
709
- - โœ… Concurrent operations
710
- - โœ… Large file handling
711
- - โœ… Performance benchmarks
712
- - โœ… Git integration with symlinks (isomorphic-git compatibility)
713
- - โœ… Node.js fs compatibility (access, appendFile, copyFile, cp, rm, truncate, open, opendir, streams)
714
-
715
- See [SYMLINK_IMPLEMENTATION.md](SYMLINK_IMPLEMENTATION.md) for details on symlink support and [PERFORMANCE.md](PERFORMANCE.md) for performance analysis.
716
-
717
- ## ๐Ÿค Contributing
718
-
719
- Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
720
-
721
- When contributing, please ensure:
722
- - All tests pass (`npm test`)
723
- - New features include corresponding tests
724
- - Code follows the existing style
725
-
726
- ## ๐Ÿ“„ License
727
-
728
- MIT ยฉ Componentor
729
-
730
- ## ๐Ÿ™ Acknowledgments
731
-
732
- - Built on the powerful [Origin Private File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)
733
- - Inspired by Node.js fs/promises module
734
- - Perfect companion for [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git)
735
-
736
- ---
507
+ ## License
737
508
 
738
- **Made with โค๏ธ for the modern web**
509
+ MIT