@componentor/fs 1.1.7

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 ADDED
@@ -0,0 +1,742 @@
1
+ # @componentor/fs
2
+
3
+ > ๐Ÿš€ A blazing-fast, Node.js-compatible filesystem interface for the browser using the Origin Private File System API
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)
7
+
8
+ ## โœจ Features
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** - 199 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
19
+
20
+ ## ๐Ÿš€ Installation
21
+
22
+ ```bash
23
+ npm install @componentor/fs
24
+ ```
25
+
26
+ ```bash
27
+ yarn add @componentor/fs
28
+ ```
29
+
30
+ ```bash
31
+ pnpm add @componentor/fs
32
+ ```
33
+
34
+ ## ๐Ÿ”ง Quick Start
35
+
36
+ ```javascript
37
+ import OPFS from '@componentor/fs'
38
+
39
+ const fs = new OPFS()
40
+
41
+ // 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!"
47
+
48
+ // Create directories
49
+ await fs.mkdir('projects/my-app', { recursive: true })
50
+
51
+ // 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 Comparison
68
+
69
+ | Operation | localStorage | IndexedDB | OPFS-FS |
70
+ |-----------|-------------|-----------|---------|
71
+ | Small Files | ~50ms | ~20ms | **~5ms** |
72
+ | Large Files | Memory limited | ~100ms | **~15ms** |
73
+ | Directory Ops | Not supported | Complex | **Native** |
74
+
75
+ > **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`.
76
+
77
+ ## ๐Ÿ“š API Reference
78
+
79
+ ### Constructor
80
+
81
+ #### `new OPFS(options?)`
82
+
83
+ Creates a new OPFS filesystem instance.
84
+
85
+ **Parameters:**
86
+ - `options.useSync` (boolean, default: `true`) - Use synchronous access handles when available
87
+ - `options.workerUrl` (URL | string, optional) - Worker script URL. When provided, enables **hybrid mode** for optimal performance
88
+ - `options.read` ('main' | 'worker', default: 'main') - Backend for read operations in hybrid mode
89
+ - `options.write` ('main' | 'worker', default: 'worker') - Backend for write operations in hybrid mode
90
+ - `options.verbose` (boolean, default: `false`) - Enable verbose logging
91
+
92
+ **Example:**
93
+ ```javascript
94
+ // Use sync handles (recommended for workers)
95
+ const fs = new OPFS({ useSync: true })
96
+
97
+ // Force async mode
98
+ const fsAsync = new OPFS({ useSync: false })
99
+
100
+ // Use hybrid mode (recommended for main thread - best performance!)
101
+ const fs = new OPFS({
102
+ workerUrl: new URL('./opfs-worker.js', import.meta.url)
103
+ })
104
+ await fs.ready() // Wait for worker to initialize
105
+
106
+ // Don't forget to terminate when done
107
+ fs.terminate()
108
+ ```
109
+
110
+ ### Hybrid Mode (Recommended)
111
+
112
+ Hybrid mode provides the **best performance** by routing operations to optimal backends:
113
+ - **Reads on main thread**: No message passing overhead
114
+ - **Writes on worker**: Sync access handles are faster
115
+
116
+ ```javascript
117
+ import OPFS from '@componentor/fs'
118
+
119
+ // Create with hybrid mode
120
+ const fs = new OPFS({
121
+ workerUrl: new URL('@componentor/fs/worker-script', import.meta.url)
122
+ })
123
+
124
+ // Wait for worker to be ready
125
+ await fs.ready()
126
+
127
+ // Use like normal - hybrid routing happens automatically
128
+ await fs.writeFile('test.txt', 'Hello World') // Routed to worker
129
+ const data = await fs.readFile('test.txt') // Routed to main thread
130
+
131
+ // For long-running apps, periodically call gc() to prevent memory leaks
132
+ await fs.gc()
133
+
134
+ // Clean up when done
135
+ fs.terminate()
136
+ ```
137
+
138
+ **Performance comparison** (100 iterations benchmark):
139
+ | Mode | Average Time |
140
+ |------|-------------|
141
+ | Main Thread | ~335ms |
142
+ | Worker Only | ~274ms |
143
+ | **Hybrid** | **~262ms** |
144
+
145
+ ### File Operations
146
+
147
+ #### `readFile(path, options?)`
148
+
149
+ Reads the entire contents of a file.
150
+
151
+ **Parameters:**
152
+ - `path` (string) - File path
153
+ - `options.encoding` (string, optional) - Text encoding ('utf8' for string output)
154
+
155
+ **Returns:** `Promise<Uint8Array | string>`
156
+
157
+ **Examples:**
158
+ ```javascript
159
+ // Read as binary
160
+ const buffer = await fs.readFile('image.png')
161
+
162
+ // Read as text
163
+ const text = await fs.readFile('config.json', { encoding: 'utf8' })
164
+
165
+ // Parse JSON
166
+ const config = JSON.parse(await fs.readFile('config.json', { encoding: 'utf8' }))
167
+ ```
168
+
169
+ #### `writeFile(path, data, options?)`
170
+
171
+ Writes data to a file, creating it if it doesn't exist.
172
+
173
+ **Parameters:**
174
+ - `path` (string) - File path
175
+ - `data` (string | Uint8Array) - Data to write
176
+ - `options` (object, optional) - Write options
177
+
178
+ **Returns:** `Promise<void>`
179
+
180
+ **Examples:**
181
+ ```javascript
182
+ // Write text
183
+ await fs.writeFile('note.txt', 'Hello World')
184
+
185
+ // Write binary data
186
+ await fs.writeFile('data.bin', new Uint8Array([1, 2, 3, 4]))
187
+
188
+ // Write JSON
189
+ await fs.writeFile('config.json', JSON.stringify({ theme: 'dark' }))
190
+ ```
191
+
192
+ #### `unlink(path)`
193
+
194
+ Deletes a file.
195
+
196
+ **Parameters:**
197
+ - `path` (string) - File path to delete
198
+
199
+ **Returns:** `Promise<void>`
200
+
201
+ **Example:**
202
+ ```javascript
203
+ await fs.unlink('temp.txt')
204
+ ```
205
+
206
+ #### `rename(oldPath, newPath)`
207
+
208
+ Moves/renames a file.
209
+
210
+ **Parameters:**
211
+ - `oldPath` (string) - Current file path
212
+ - `newPath` (string) - New file path
213
+
214
+ **Returns:** `Promise<void>`
215
+
216
+ **Example:**
217
+ ```javascript
218
+ await fs.rename('old-name.txt', 'new-name.txt')
219
+ await fs.rename('file.txt', 'backup/file.txt')
220
+ ```
221
+
222
+ #### `stat(path)`
223
+
224
+ Gets file statistics (follows symlinks).
225
+
226
+ **Parameters:**
227
+ - `path` (string) - File path
228
+
229
+ **Returns:** `Promise<FileStats>`
230
+
231
+ **Example:**
232
+ ```javascript
233
+ const stats = await fs.stat('large-file.zip')
234
+ console.log(`Size: ${stats.size} bytes`)
235
+ console.log(`Modified: ${new Date(stats.mtimeMs)}`)
236
+ console.log(`Is file: ${stats.isFile()}`)
237
+ ```
238
+
239
+ #### `lstat(path)`
240
+
241
+ Gets file statistics without following symlinks.
242
+
243
+ **Parameters:**
244
+ - `path` (string) - File path
245
+
246
+ **Returns:** `Promise<FileStats>`
247
+
248
+ **Example:**
249
+ ```javascript
250
+ const stats = await fs.lstat('link.txt')
251
+ if (stats.isSymbolicLink()) {
252
+ console.log(`Symlink pointing to: ${stats.target}`)
253
+ }
254
+ ```
255
+
256
+ ### Symlink Operations
257
+
258
+ #### `symlink(target, path)`
259
+
260
+ Creates a symbolic link.
261
+
262
+ **Parameters:**
263
+ - `target` (string) - Target path the symlink points to
264
+ - `path` (string) - Path where the symlink will be created
265
+
266
+ **Returns:** `Promise<void>`
267
+
268
+ **Example:**
269
+ ```javascript
270
+ await fs.writeFile('config.json', '{"key": "value"}')
271
+ await fs.symlink('config.json', 'current-config.json')
272
+
273
+ // Read through symlink
274
+ const content = await fs.readFile('current-config.json', { encoding: 'utf8' })
275
+ ```
276
+
277
+ #### `readlink(path)`
278
+
279
+ Reads the target of a symbolic link.
280
+
281
+ **Parameters:**
282
+ - `path` (string) - Symlink path
283
+
284
+ **Returns:** `Promise<string>` - The target path
285
+
286
+ **Example:**
287
+ ```javascript
288
+ const target = await fs.readlink('my-link.txt')
289
+ console.log(`Link points to: ${target}`)
290
+ ```
291
+
292
+ #### `symlinkBatch(links)`
293
+
294
+ Creates multiple symbolic links efficiently in a single operation.
295
+
296
+ **Parameters:**
297
+ - `links` (Array<{target: string, path: string}>) - Array of symlink definitions
298
+
299
+ **Returns:** `Promise<void>`
300
+
301
+ **Example:**
302
+ ```javascript
303
+ // Create multiple symlinks with a single metadata write
304
+ await fs.symlinkBatch([
305
+ { target: '/configs/prod.json', path: '/current-config.json' },
306
+ { target: '/data/latest.db', path: '/current-db.db' },
307
+ { target: '/logs/today.log', path: '/current.log' }
308
+ ])
309
+
310
+ // 60-70% faster than individual symlink() calls
311
+ ```
312
+
313
+ ### Directory Operations
314
+
315
+ #### `mkdir(path, options?)`
316
+
317
+ Creates a directory.
318
+
319
+ **Parameters:**
320
+ - `path` (string) - Directory path
321
+ - `options.recursive` (boolean, optional) - Create parent directories
322
+
323
+ **Returns:** `Promise<void>`
324
+
325
+ **Examples:**
326
+ ```javascript
327
+ // Create single directory
328
+ await fs.mkdir('uploads')
329
+
330
+ // Create nested directories
331
+ await fs.mkdir('projects/webapp/src', { recursive: true })
332
+ ```
333
+
334
+ #### `rmdir(path)`
335
+
336
+ Removes a directory and all its contents.
337
+
338
+ **Parameters:**
339
+ - `path` (string) - Directory path
340
+
341
+ **Returns:** `Promise<void>`
342
+
343
+ **Example:**
344
+ ```javascript
345
+ await fs.rmdir('temp-folder')
346
+ ```
347
+
348
+ #### `readdir(path)`
349
+
350
+ Lists directory contents.
351
+
352
+ **Parameters:**
353
+ - `path` (string) - Directory path
354
+
355
+ **Returns:** `Promise<string[]>`
356
+
357
+ **Example:**
358
+ ```javascript
359
+ const files = await fs.readdir('documents')
360
+ console.log('Files:', files)
361
+
362
+ // List root directory
363
+ const rootFiles = await fs.readdir('.')
364
+ ```
365
+
366
+ ### Additional File Operations
367
+
368
+ #### `access(path, mode?)`
369
+
370
+ Tests file accessibility. Throws if the file doesn't exist.
371
+
372
+ ```javascript
373
+ await fs.access('/path/to/file') // Throws if not accessible
374
+ ```
375
+
376
+ #### `appendFile(path, data)`
377
+
378
+ Appends data to a file, creating it if it doesn't exist.
379
+
380
+ ```javascript
381
+ await fs.appendFile('log.txt', 'New log entry\n')
382
+ ```
383
+
384
+ #### `copyFile(src, dest, mode?)`
385
+
386
+ Copies a file from source to destination.
387
+
388
+ ```javascript
389
+ await fs.copyFile('original.txt', 'backup.txt')
390
+ // With COPYFILE_EXCL flag to fail if dest exists
391
+ await fs.copyFile('src.txt', 'dest.txt', fs.constants.COPYFILE_EXCL)
392
+ ```
393
+
394
+ #### `cp(src, dest, options?)`
395
+
396
+ Copies files or directories recursively.
397
+
398
+ ```javascript
399
+ // Copy single file
400
+ await fs.cp('file.txt', 'copy.txt')
401
+
402
+ // Copy directory recursively
403
+ await fs.cp('source-dir', 'dest-dir', { recursive: true })
404
+ ```
405
+
406
+ #### `exists(path)`
407
+
408
+ Returns true if the path exists, false otherwise (doesn't throw).
409
+
410
+ ```javascript
411
+ if (await fs.exists('config.json')) {
412
+ // File exists
413
+ }
414
+ ```
415
+
416
+ #### `realpath(path)`
417
+
418
+ Resolves symlinks to get the real path.
419
+
420
+ ```javascript
421
+ const realPath = await fs.realpath('my-symlink')
422
+ ```
423
+
424
+ #### `rm(path, options?)`
425
+
426
+ Removes files or directories.
427
+
428
+ ```javascript
429
+ await fs.rm('file.txt')
430
+ await fs.rm('directory', { recursive: true })
431
+ await fs.rm('maybe-exists', { force: true }) // No error if doesn't exist
432
+ ```
433
+
434
+ #### `truncate(path, len?)`
435
+
436
+ Truncates a file to the specified length.
437
+
438
+ ```javascript
439
+ await fs.truncate('file.txt', 100) // Truncate to 100 bytes
440
+ await fs.truncate('file.txt') // Truncate to 0 bytes
441
+ ```
442
+
443
+ #### `mkdtemp(prefix)`
444
+
445
+ Creates a unique temporary directory.
446
+
447
+ ```javascript
448
+ const tempDir = await fs.mkdtemp('/tmp/myapp-')
449
+ console.log(tempDir) // e.g., "/tmp/myapp-1234567890-abc123"
450
+ ```
451
+
452
+ #### `open(path, flags?, mode?)`
453
+
454
+ Opens a file and returns a FileHandle.
455
+
456
+ ```javascript
457
+ const handle = await fs.open('file.txt', 'r')
458
+ const buffer = new Uint8Array(100)
459
+ await handle.read(buffer)
460
+ await handle.close()
461
+ ```
462
+
463
+ #### `opendir(path)`
464
+
465
+ Opens a directory for iteration.
466
+
467
+ ```javascript
468
+ const dir = await fs.opendir('/my-dir')
469
+ for await (const entry of dir) {
470
+ console.log(entry.name, entry.isFile(), entry.isDirectory())
471
+ }
472
+ ```
473
+
474
+ #### `createReadStream(path, options?)`
475
+
476
+ Creates a readable stream for a file.
477
+
478
+ ```javascript
479
+ const stream = fs.createReadStream('large-file.bin')
480
+ const reader = stream.getReader()
481
+ // Read chunks...
482
+ ```
483
+
484
+ #### `createWriteStream(path, options?)`
485
+
486
+ Creates a writable stream for a file.
487
+
488
+ ```javascript
489
+ const stream = fs.createWriteStream('output.txt')
490
+ const writer = stream.getWriter()
491
+ await writer.write(new TextEncoder().encode('data'))
492
+ await writer.close()
493
+ ```
494
+
495
+ #### `watch(path, options?)`
496
+
497
+ Watches for file/directory changes (basic implementation).
498
+
499
+ ```javascript
500
+ const watcher = fs.watch('/my-dir')
501
+ for await (const event of watcher) {
502
+ console.log(event.eventType, event.filename)
503
+ }
504
+ ```
505
+
506
+ ### Compatibility Methods (No-ops for OPFS)
507
+
508
+ The following methods are implemented for API compatibility but are no-ops since OPFS doesn't support these features:
509
+
510
+ - `chmod(path, mode)` - File modes not supported
511
+ - `chown(path, uid, gid)` - File ownership not supported
512
+ - `utimes(path, atime, mtime)` - Timestamps are read-only
513
+ - `lutimes(path, atime, mtime)` - Symlink timestamps are read-only
514
+
515
+ ### Lifecycle Methods (Hybrid Mode)
516
+
517
+ These methods are used when running in hybrid mode (with `workerUrl`):
518
+
519
+ #### `ready()`
520
+
521
+ Wait for the worker to be initialized. Call this before performing any operations.
522
+
523
+ ```javascript
524
+ const fs = new OPFS({ workerUrl: '...' })
525
+ await fs.ready() // Wait for worker
526
+ ```
527
+
528
+ #### `terminate()`
529
+
530
+ Terminate the background worker. Call this when you're done using the filesystem.
531
+
532
+ ```javascript
533
+ fs.terminate() // Clean up worker
534
+ ```
535
+
536
+ #### `gc()`
537
+
538
+ Force garbage collection by reinitializing the worker's OPFS instance. Use this for long-running applications to prevent memory leaks.
539
+
540
+ ```javascript
541
+ // Periodically call gc() in long-running apps
542
+ await fs.gc()
543
+ ```
544
+
545
+ #### `resetCache()`
546
+
547
+ Reset internal caches (symlinks, directory handles). Lighter than `gc()`.
548
+
549
+ ```javascript
550
+ fs.resetCache()
551
+ ```
552
+
553
+ ## ๐ŸŽฏ Real-World Examples
554
+
555
+ ### Working with Isomorphic Git
556
+
557
+ ```javascript
558
+ import git from 'isomorphic-git'
559
+ import OPFS from '@componentor/fs'
560
+
561
+ // Use hybrid mode for best performance with git operations
562
+ const fs = new OPFS({
563
+ workerUrl: new URL('@componentor/fs/worker-script', import.meta.url)
564
+ })
565
+ await fs.ready()
566
+
567
+ // Clone a repository
568
+ await git.clone({
569
+ fs,
570
+ http: fetch,
571
+ dir: '/my-repo',
572
+ url: 'https://github.com/user/repo.git'
573
+ })
574
+
575
+ // Read a file from the repo
576
+ const readme = await fs.readFile('/my-repo/README.md', { encoding: 'utf8' })
577
+ console.log(readme)
578
+
579
+ // Clean up when done
580
+ fs.terminate()
581
+ ```
582
+
583
+ ### Building a Code Editor
584
+
585
+ ```javascript
586
+ import OPFS from '@componentor/fs'
587
+
588
+ class CodeEditor {
589
+ constructor(workerUrl) {
590
+ // Use hybrid mode for optimal performance
591
+ this.fs = new OPFS({ workerUrl })
592
+ }
593
+
594
+ async init() {
595
+ await this.fs.ready()
596
+ }
597
+
598
+ destroy() {
599
+ this.fs.terminate()
600
+ }
601
+
602
+ async createProject(name) {
603
+ await this.fs.mkdir(`projects/${name}/src`)
604
+ await this.fs.writeFile(`projects/${name}/package.json`, JSON.stringify({
605
+ name,
606
+ version: '1.0.0',
607
+ main: 'src/index.js'
608
+ }, null, 2))
609
+ await this.fs.writeFile(`projects/${name}/src/index.js`, '// Your code here\n')
610
+ }
611
+
612
+ async saveFile(path, content) {
613
+ await this.fs.writeFile(path, content)
614
+ }
615
+
616
+ async loadFile(path) {
617
+ return await this.fs.readFile(path, { encoding: 'utf8' })
618
+ }
619
+
620
+ async getProjectFiles(projectName) {
621
+ return await this.fs.readdir(`projects/${projectName}`)
622
+ }
623
+ }
624
+ ```
625
+
626
+ ### File Upload Handler
627
+
628
+ ```javascript
629
+ import OPFS from '@componentor/fs'
630
+
631
+ const fs = new OPFS()
632
+
633
+ async function handleFileUpload(file) {
634
+ // Create uploads directory
635
+ await fs.mkdir('uploads', { recursive: true })
636
+
637
+ // Save uploaded file
638
+ const buffer = new Uint8Array(await file.arrayBuffer())
639
+ const filename = `uploads/${Date.now()}-${file.name}`
640
+ await fs.writeFile(filename, buffer)
641
+
642
+ // Get file info
643
+ const stats = await fs.stat(filename)
644
+ console.log(`Saved ${file.name} (${stats.size} bytes)`)
645
+
646
+ return filename
647
+ }
648
+ ```
649
+
650
+ ## ๐ŸŒ Browser Support
651
+
652
+ @componentor/fs requires browsers that support the Origin Private File System API:
653
+
654
+ - โœ… Chrome 86+
655
+ - โœ… Edge 86+
656
+ - โœ… Firefox 111+
657
+ - โœ… Safari 15.2+
658
+
659
+ ### Feature Detection
660
+
661
+ ```javascript
662
+ if ('storage' in navigator && 'getDirectory' in navigator.storage) {
663
+ const fs = new OPFS()
664
+ // OPFS is supported
665
+ } else {
666
+ console.warn('OPFS not supported in this browser')
667
+ // Fallback to other storage solutions
668
+ }
669
+ ```
670
+
671
+ ## ๐Ÿšฆ Error Handling
672
+
673
+ OPFS-FS throws standard filesystem errors:
674
+
675
+ ```javascript
676
+ try {
677
+ await fs.readFile('nonexistent.txt')
678
+ } catch (error) {
679
+ if (error.message.includes('ENOENT')) {
680
+ console.log('File not found')
681
+ }
682
+ }
683
+
684
+ try {
685
+ await fs.mkdir('existing-dir')
686
+ } catch (error) {
687
+ if (error.message.includes('EEXIST')) {
688
+ console.log('Directory already exists')
689
+ }
690
+ }
691
+ ```
692
+
693
+ ## ๐Ÿงช Testing
694
+
695
+ @componentor/fs comes with a comprehensive test suite covering all functionality:
696
+
697
+ ```bash
698
+ # Run all tests
699
+ npm test
700
+
701
+ # Run tests in watch mode
702
+ npm run test:watch
703
+ ```
704
+
705
+ **Test Coverage:**
706
+ - โœ… 199 tests with 100% pass rate
707
+ - โœ… File read/write operations (text and binary)
708
+ - โœ… Directory operations (create, remove, list)
709
+ - โœ… File metadata and statistics
710
+ - โœ… Path normalization and edge cases
711
+ - โœ… Symlink operations and resolution
712
+ - โœ… Error handling and edge cases
713
+ - โœ… Concurrent operations
714
+ - โœ… Large file handling
715
+ - โœ… Performance benchmarks
716
+ - โœ… Git integration with symlinks (isomorphic-git compatibility)
717
+ - โœ… Node.js fs compatibility (access, appendFile, copyFile, cp, rm, truncate, open, opendir, streams)
718
+
719
+ See [SYMLINK_IMPLEMENTATION.md](SYMLINK_IMPLEMENTATION.md) for details on symlink support and [PERFORMANCE.md](PERFORMANCE.md) for performance analysis.
720
+
721
+ ## ๐Ÿค Contributing
722
+
723
+ 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.
724
+
725
+ When contributing, please ensure:
726
+ - All tests pass (`npm test`)
727
+ - New features include corresponding tests
728
+ - Code follows the existing style
729
+
730
+ ## ๐Ÿ“„ License
731
+
732
+ MIT ยฉ Componentor
733
+
734
+ ## ๐Ÿ™ Acknowledgments
735
+
736
+ - Built on the powerful [Origin Private File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)
737
+ - Inspired by Node.js fs/promises module
738
+ - Perfect companion for [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git)
739
+
740
+ ---
741
+
742
+ **Made with โค๏ธ for the modern web**