@mikestools/usefilesystem 0.0.1 → 0.0.3

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,187 +1,589 @@
1
1
  # @mikestools/usefilesystem
2
2
 
3
- Vue 3 composables for in-memory virtual filesystem and ZIP archive operations.
3
+ Vue 3 composable for an in-memory virtual filesystem with reactive state and familiar Node.js-like API.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Virtual Filesystem** - In-memory filesystem with familiar Node.js-like API
8
- - **ZIP Archives** - Create and extract ZIP files using native browser compression
9
- - **Reactive State** - Vue 3 composables with reactive stats and progress tracking
10
- - **Zero Dependencies** - Uses native browser APIs (CompressionStream/DecompressionStream)
11
- - **TypeScript** - Full type safety with exported types
12
- - **Glob Search** - Find files with patterns like `**/*.ts`
13
- - **File Watchers** - Subscribe to create, modify, delete events
7
+ - 🎯 **Composable-First** - Pure Vue 3 Composition API, no components
8
+ - 📦 **TypeScript First** - Full type safety with IntelliSense
9
+ - 🗂️ **Auto Directories** - Parent directories created automatically on file write
10
+ - **Reactive State** - File counts and sizes update automatically
11
+
12
+ ### Complete Feature Set
13
+
14
+ | Feature | Description |
15
+ |---------------------------|--------------------------------------------------|
16
+ | 📁 **File Operations** | Write, read, copy, move, delete files |
17
+ | 📂 **Directory Ops** | Create, list, remove directories |
18
+ | 🔍 **Glob Search** | Find files with patterns like `**/*.ts` |
19
+ | 👀 **File Watchers** | Subscribe to events with filtering & debouncing |
20
+ | ⚡ **Reactive Stats** | Auto-updating file count and total size |
21
+ | 💾 **Serialization** | Export/import filesystem state as JSON |
22
+ | 🗄️ **Persistence** | OPFS adapter + custom adapters with auto-persist |
23
+ | 📝 **MIME Types** | Automatic inference from file extensions |
24
+ | 🔄 **Path Normalization** | Handles mixed separators and relative paths |
25
+ | 📤 **Upload/Download** | Import Files, export Blobs, trigger downloads |
26
+ | 🌊 **Web Streams** | ReadableStream/WritableStream for large files |
27
+ | 🔐 **Checksums** | SHA-256 file hashing |
28
+ | 💿 **Storage Quotas** | Max size, file count limits with warnings |
29
+ | ⚛️ **Transactions** | Atomic multi-file operations with rollback |
14
30
 
15
31
  ## Installation
16
32
 
17
33
  ```bash
18
- npm install @mikestools/usefilesystem
34
+ npm install @mikestools/usefilesystem vue
19
35
  ```
20
36
 
21
37
  ## Quick Start
22
38
 
23
39
  ```typescript
24
- import { useFileSystem, useZip } from '@mikestools/usefilesystem'
40
+ import { ref, onMounted } from 'vue'
41
+ import { useFileSystem } from '@mikestools/usefilesystem'
25
42
 
26
- const fs = useFileSystem()
43
+ onMounted(() => {
44
+ const fs = useFileSystem()
45
+
46
+ // Write files - directories auto-created
47
+ fs.writeFile('/src/index.ts', 'export const version = "1.0.0"')
48
+ fs.writeFile('/readme.md', '# My Project')
49
+
50
+ // Reactive stats (update automatically)
51
+ console.log(fs.fileCount.value) // 2
52
+ console.log(fs.totalSize.value) // bytes
27
53
 
28
- // Write files - directories auto-created
29
- fs.writeFile('/src/index.ts', 'export const version = "1.0.0"')
30
- fs.writeFile('/readme.md', '# My Project')
54
+ // Read content
55
+ const content = fs.readFile('/src/index.ts')
31
56
 
32
- // Reactive stats
33
- console.log(fs.fileCount.value) // 2
34
- console.log(fs.totalSize.value) // bytes
57
+ // Search with glob patterns
58
+ const tsFiles = fs.find('/src/**/*.ts')
35
59
 
36
- // Export as ZIP
37
- const zip = useZip()
38
- await zip.download(
39
- fs.getAllFiles().map(f => ({ path: f.path, content: f.content })),
40
- 'project.zip'
41
- )
60
+ // Watch for changes
61
+ const unsubscribe = fs.watch((event, path, type) => {
62
+ console.log(`${event} ${type}: ${path}`)
63
+ })
64
+ })
42
65
  ```
43
66
 
44
- ## useFileSystem
67
+ ## API Reference
68
+
69
+ ### Core Properties
70
+
71
+ | Property | Type | Description |
72
+ |--------------|---------------------------------|--------------------------------|
73
+ | `stats` | `ComputedRef<FileSystemStats>` | Reactive stats object |
74
+ | `fileCount` | `ComputedRef<number>` | Number of files |
75
+ | `totalSize` | `ComputedRef<number>` | Total size in bytes |
45
76
 
46
- In-memory virtual filesystem with reactive stats.
77
+ ### File Methods
47
78
 
79
+ #### Write Operations
48
80
  ```typescript
49
- const fs = useFileSystem()
81
+ fs.writeFile('/path/file.txt', 'content')
82
+ fs.writeFile('/path/file.txt', 'content', { mimeType: 'text/plain', overwrite: false })
83
+ fs.appendFile('/path/file.txt', 'more content')
84
+ ```
85
+
86
+ #### Read Operations
87
+ ```typescript
88
+ const text = fs.readFile('/path/file.txt') // Returns string
89
+ const binary = fs.readFile('/image.png', { encoding: 'binary' }) // Returns Uint8Array
90
+ const meta = fs.stat('/path/file.txt') // Returns FileMetadata
91
+ ```
50
92
 
51
- // File operations
52
- fs.writeFile('/path/to/file.txt', 'content')
53
- fs.appendFile('/path/to/file.txt', 'more content')
54
- const content = fs.readFile('/path/to/file.txt')
55
- const binary = fs.readFile('/image.png', { encoding: 'binary' })
56
- fs.remove('/path/to/file.txt')
93
+ #### File Management
94
+ ```typescript
57
95
  fs.copy('/source.txt', '/dest.txt')
96
+ fs.copy('/source.txt', '/dest.txt', { overwrite: false })
58
97
  fs.move('/old.txt', '/new.txt')
98
+ fs.move('/old.txt', '/new.txt', { overwrite: false })
99
+ fs.rename('/path/old.txt', 'new.txt')
100
+ fs.remove('/path/file.txt')
101
+ ```
102
+
103
+ #### Path Checks
104
+ ```typescript
105
+ fs.exists('/path') // true if file or directory exists
106
+ fs.isFile('/path') // true if path is a file
107
+ fs.isDirectory('/path') // true if path is a directory
108
+ ```
109
+
110
+ ### Directory Methods
111
+
112
+ #### Directory Operations
113
+ ```typescript
114
+ fs.mkdir('/a/b/c') // Creates all parent directories
115
+ fs.rmdir('/empty') // Removes empty directory only
116
+ fs.rmdirRecursive('/folder') // Removes directory and all contents
117
+ const meta = fs.statDirectory('/path') // Returns DirectoryMetadata
118
+ ```
119
+
120
+ #### List Contents
121
+ ```typescript
122
+ const entries = fs.list('/')
123
+ const recursive = fs.list('/', { recursive: true })
124
+ const filesOnly = fs.list('/', { filesOnly: true })
125
+ const dirsOnly = fs.list('/', { directoriesOnly: true })
126
+ ```
127
+
128
+ ### Search Methods
59
129
 
60
- // Directory operations
61
- fs.mkdir('/a/b/c') // Creates all parent directories
62
- fs.rmdir('/empty') // Removes empty directory
63
- fs.rmdirRecursive('/folder') // Removes with contents
64
- const entries = fs.list('/src', { recursive: true })
130
+ #### Glob Patterns
131
+ ```typescript
132
+ const tsFiles = fs.find('/src/**/*.ts') // Recursive TypeScript files
133
+ const configs = fs.find('/**/config.*') // Any config file
134
+ const rootFiles = fs.find('/*.txt') // Root-level text files
135
+ ```
65
136
 
66
- // Search
67
- const tsFiles = fs.find('/src/**/*.ts')
137
+ #### Extension Search
138
+ ```typescript
68
139
  const jsonFiles = fs.findByExtension('.json')
140
+ const images = fs.findByExtension('png') // Works with or without dot
141
+ ```
69
142
 
70
- // Metadata
71
- const meta = fs.stat('/file.txt') // { name, path, size, mimeType, createdAt, modifiedAt }
72
- fs.exists('/path')
73
- fs.isFile('/path')
74
- fs.isDirectory('/path')
143
+ #### Bulk Access
144
+ ```typescript
145
+ const allFiles = fs.getAllFiles() // Returns VirtualFile[]
146
+ const allPaths = fs.getAllPaths() // Returns string[]
147
+ const stats = fs.getStats() // Returns FileSystemStats
148
+ ```
75
149
 
76
- // Reactive stats
77
- console.log(fs.stats.value) // { totalFiles, totalDirectories, totalSize }
78
- console.log(fs.fileCount.value)
79
- console.log(fs.totalSize.value)
150
+ ### Watcher Methods
80
151
 
81
- // Watchers
152
+ ```typescript
153
+ // Simple watch for all changes
82
154
  const unsubscribe = fs.watch((event, path, type) => {
83
155
  console.log(`${event} ${type}: ${path}`)
156
+ // event: 'create' | 'modify' | 'delete'
157
+ // type: 'file' | 'directory'
158
+ })
159
+
160
+ // Watch with options (receives WatchEvent objects)
161
+ const unsubscribe = fs.watch((event) => {
162
+ console.log(`${event.event}: ${event.path}`)
163
+ console.log('Metadata:', event.metadata) // File size, timestamps, etc.
164
+ }, {
165
+ pattern: '/*.txt', // Glob pattern filter
166
+ events: ['create', 'modify'], // Event type filter
167
+ type: 'file', // Entry type filter ('file' | 'directory' | 'all')
168
+ debounce: 100, // Debounce rapid changes (ms)
169
+ path: '/src', // Watch specific directory
170
+ recursive: true // Include subdirectories
84
171
  })
85
172
 
86
- // Serialization
173
+ // Triggers: "create file: /new.txt"
174
+ fs.writeFile('/new.txt', 'content')
175
+
176
+ // Triggers: "modify file: /new.txt"
177
+ fs.writeFile('/new.txt', 'updated')
178
+
179
+ // Triggers: "delete file: /new.txt"
180
+ fs.remove('/new.txt')
181
+
182
+ // Stop watching
183
+ unsubscribe()
184
+ ```
185
+
186
+ ### Serialization Methods
187
+
188
+ ```typescript
189
+ // Export to JSON (base64 encoded content)
87
190
  const json = fs.toJSON()
88
- fs.fromJSON(json)
191
+ localStorage.setItem('filesystem', JSON.stringify(json))
192
+
193
+ // Import from JSON
194
+ const data = JSON.parse(localStorage.getItem('filesystem'))
195
+ fs.fromJSON(data)
196
+
197
+ // Clear all files and directories
89
198
  fs.clear()
90
199
  ```
91
200
 
92
- ## useZip
201
+ ### Persistence Methods
202
+
203
+ ```typescript
204
+ import { useFileSystem, type StorageAdapter } from '@mikestools/usefilesystem'
205
+
206
+ // Create custom adapter
207
+ const adapter: StorageAdapter = {
208
+ async save(key, data) { /* save Uint8Array */ },
209
+ async load(key) { /* return Uint8Array | undefined */ },
210
+ async remove(key) { /* delete key */ },
211
+ async list() { /* return string[] of keys */ },
212
+ async clear() { /* clear all */ }
213
+ }
214
+
215
+ // Use with composable
216
+ const fs = useFileSystem({
217
+ adapter,
218
+ autoPersist: true // Auto-save on every change
219
+ })
220
+
221
+ // Manual persist/restore
222
+ await fs.persist()
223
+ await fs.restore()
224
+ ```
225
+
226
+ ### OPFS Storage Adapter
93
227
 
94
- ZIP archive operations with reactive state.
228
+ Built-in high-performance adapter using Origin Private File System:
95
229
 
96
230
  ```typescript
97
- const zip = useZip()
231
+ import { useFileSystem, createOPFSAdapter } from '@mikestools/usefilesystem'
98
232
 
99
- // Create ZIP
100
- const blob = await zip.create([
101
- { path: 'readme.txt', content: 'Hello!' },
102
- { path: 'data/config.json', content: '{}' }
103
- ])
233
+ const adapter = createOPFSAdapter()
234
+ const fs = useFileSystem({ adapter, autoPersist: true })
235
+ ```
236
+
237
+ ### Upload/Download Methods
238
+
239
+ ```typescript
240
+ // Upload native File objects
241
+ const fileInput = document.querySelector('input[type="file"]')
242
+ await fs.upload(fileInput.files[0]) // Upload to root
243
+ await fs.upload(file, '/custom/path/file.txt') // Custom path
244
+ await fs.uploads(fileInput.files, '/documents') // Upload multiple
245
+
246
+ // Export files
247
+ const blob = fs.toBlob('/image.png') // Get as Blob
248
+ const file = fs.toFile('/data.json') // Get as File object
249
+ const url = fs.createObjectURL('/photo.jpg') // Object URL
250
+ fs.revokeObjectURL(url) // Clean up URL
251
+
252
+ // Download to user's device
253
+ fs.download('/report.pdf') // Uses original filename
254
+ fs.download('/report.pdf', 'annual-report.pdf') // Custom filename
255
+ fs.downloads(['/file1.txt', '/file2.txt']) // Multiple downloads
256
+ ```
257
+
258
+ ### Web Streams API
259
+
260
+ ```typescript
261
+ // Create ReadableStream from file
262
+ const stream = fs.createReadStream('/large-file.bin')
263
+ const response = new Response(stream)
264
+
265
+ // Create WritableStream to file
266
+ const writable = fs.createWriteStream('/output.bin', { mimeType: 'application/octet-stream' })
267
+ await someReadable.pipeTo(writable)
268
+
269
+ // Write from any ReadableStream
270
+ await fs.writeFromStream('/download.zip', response.body)
271
+
272
+ // Create Response for Service Workers
273
+ const response = fs.toResponse('/api/data.json', { status: 200 })
274
+ ```
104
275
 
105
- // Download ZIP
106
- await zip.download(files, 'archive.zip')
276
+ ### Checksum
107
277
 
108
- // Extract ZIP
109
- const entries = await zip.extract(zipBlob)
110
- const filtered = await zip.extract(zipBlob, {
111
- filter: entry => entry.path.endsWith('.txt')
278
+ ```typescript
279
+ const hash = await fs.computeChecksum('/important-file.bin')
280
+ console.log(hash) // SHA-256 hex string
281
+ ```
282
+
283
+ ### Storage Quotas
284
+
285
+ ```typescript
286
+ const fs = useFileSystem({
287
+ maxSize: 10 * 1024 * 1024, // 10MB total limit
288
+ maxFiles: 1000, // Max file count
289
+ maxFileSize: 5 * 1024 * 1024, // 5MB per file
290
+ warnAtPercentage: 80 // Warn at 80% capacity
112
291
  })
113
292
 
114
- // Extract single file
115
- const content = await zip.extractSingle(zipBlob, 'readme.txt')
293
+ // Reactive storage estimate
294
+ console.log(fs.storageEstimate.value)
295
+ // { quota: 10485760, usage: 1234567, available: 9251193, percentage: 11.8 }
296
+
297
+ // Check if near capacity
298
+ if (fs.isNearCapacity.value) {
299
+ console.warn('Storage is almost full!')
300
+ }
116
301
 
117
- // List without extracting
118
- const metadata = await zip.list(zipBlob)
302
+ // Subscribe to warnings
303
+ const unsubscribe = fs.onStorageWarning((estimate) => {
304
+ console.warn(`Storage at ${estimate.percentage}%`)
305
+ })
306
+ ```
119
307
 
120
- // Reactive state
121
- console.log(zip.isProcessing.value) // boolean
122
- console.log(zip.progress.value) // 0-100
123
- console.log(zip.error.value) // string | undefined
308
+ ### Transactions
124
309
 
125
- // Compression utilities
126
- const compressed = await zip.compress(data)
127
- const decompressed = await zip.decompress(data)
128
- const checksum = zip.computeCrc32(data)
310
+ ```typescript
311
+ // Atomic multi-file operations
312
+ const transaction = fs.beginTransaction()
313
+ .writeFile('/config.json', JSON.stringify(config))
314
+ .writeFile('/backup.json', JSON.stringify(backup))
315
+ .copy('/template.txt', '/output.txt')
316
+ .remove('/temp.txt')
317
+
318
+ // All operations succeed or none do
319
+ const result = transaction.commit()
320
+ if (!result.success) {
321
+ console.error('Transaction failed:', result.error)
322
+ // All changes have been rolled back automatically
323
+ }
324
+
325
+ // Or explicitly discard
326
+ transaction.rollback()
129
327
  ```
130
328
 
131
- ## Standalone Functions
329
+ ### Standalone Function
132
330
 
133
- Core functions are also available without Vue:
331
+ For use without Vue reactivity:
134
332
 
135
333
  ```typescript
136
- import {
137
- createFileSystem,
138
- createZip,
139
- extractZip,
140
- listZip,
141
- compress,
142
- decompress,
143
- computeCrc32,
144
- downloadBlob,
145
- downloadAsZip
146
- } from '@mikestools/usefilesystem'
334
+ import { createFileSystem } from '@mikestools/usefilesystem'
147
335
 
148
336
  // Pure filesystem (no Vue reactivity)
149
337
  const fs = createFileSystem()
338
+ fs.writeFile('/file.txt', 'Hello')
339
+ const content = fs.readFile('/file.txt')
340
+ ```
341
+
342
+ ### Types
150
343
 
151
- // Direct ZIP operations
152
- const blob = await createZip([{ path: 'file.txt', content: 'Hello' }])
153
- const entries = await extractZip(blob)
344
+ ```typescript
345
+ // File types
346
+ interface VirtualFile {
347
+ readonly name: string
348
+ readonly path: string
349
+ readonly content: Uint8Array
350
+ readonly size: number
351
+ readonly mimeType: string
352
+ readonly createdAt: number
353
+ readonly modifiedAt: number
354
+ }
355
+
356
+ interface FileMetadata {
357
+ readonly name: string
358
+ readonly path: string
359
+ readonly size: number
360
+ readonly mimeType: string
361
+ readonly createdAt: number
362
+ readonly modifiedAt: number
363
+ readonly extension: string // e.g., '.txt'
364
+ readonly isText: boolean // MIME type is text-based
365
+ readonly isBinary: boolean // MIME type is binary
366
+ readonly parentPath: string // Parent directory
367
+ readonly depth: number // Directory depth from root
368
+ }
369
+
370
+ // Directory types
371
+ interface DirectoryMetadata {
372
+ readonly name: string
373
+ readonly path: string
374
+ readonly createdAt: number
375
+ readonly parentPath: string
376
+ readonly depth: number
377
+ readonly fileCount: number // Direct children files
378
+ readonly directoryCount: number // Direct children dirs
379
+ readonly totalSize: number // Recursive total size
380
+ }
381
+
382
+ interface DirectoryEntry {
383
+ readonly name: string
384
+ readonly path: string
385
+ readonly type: 'file' | 'directory'
386
+ readonly size?: number // Only for files
387
+ }
388
+
389
+ // Operation result
390
+ interface FileSystemResult {
391
+ readonly success: boolean
392
+ readonly error?: string
393
+ }
394
+
395
+ // Stats
396
+ interface FileSystemStats {
397
+ readonly totalFiles: number
398
+ readonly totalDirectories: number
399
+ readonly totalSize: number
400
+ }
401
+
402
+ // Watch options for filtering and debouncing
403
+ interface WatchOptions {
404
+ readonly pattern?: string // Glob pattern filter
405
+ readonly events?: readonly ('create' | 'modify' | 'delete')[]
406
+ readonly type?: 'file' | 'directory' | 'all'
407
+ readonly debounce?: number // Debounce ms
408
+ readonly path?: string // Watch directory
409
+ readonly recursive?: boolean // Include subdirs
410
+ }
411
+
412
+ // Watch event (when using options)
413
+ interface WatchEvent {
414
+ readonly event: 'create' | 'modify' | 'delete'
415
+ readonly path: string
416
+ readonly type: 'file' | 'directory'
417
+ readonly timestamp: number
418
+ readonly metadata?: FileMetadata | DirectoryMetadata
419
+ }
420
+
421
+ // Watcher callback (without options: simple params, with options: WatchEvent)
422
+ type FileSystemWatcher =
423
+ | ((event: WatchEvent) => void)
424
+ | ((event: 'create' | 'modify' | 'delete', path: string, type: 'file' | 'directory') => void)
425
+
426
+ // Storage estimate
427
+ interface StorageEstimate {
428
+ readonly quota: number // Max allowed bytes
429
+ readonly usage: number // Current usage bytes
430
+ readonly available: number // Remaining bytes
431
+ readonly percentage: number // Usage percentage (0-100)
432
+ }
433
+
434
+ // Transaction for atomic operations
435
+ interface Transaction {
436
+ writeFile(path: string, content: string | Uint8Array, options?: WriteFileOptions): Transaction
437
+ remove(path: string): Transaction
438
+ mkdir(path: string): Transaction
439
+ copy(source: string, destination: string): Transaction
440
+ move(source: string, destination: string): Transaction
441
+ commit(): FileSystemResult
442
+ rollback(): void
443
+ }
444
+
445
+ // Options
446
+ interface WriteFileOptions {
447
+ readonly mimeType?: string
448
+ readonly overwrite?: boolean
449
+ }
450
+
451
+ interface ReadFileOptions {
452
+ readonly encoding?: 'utf8' | 'binary'
453
+ }
454
+
455
+ interface ListOptions {
456
+ readonly recursive?: boolean
457
+ readonly filesOnly?: boolean
458
+ readonly directoriesOnly?: boolean
459
+ }
460
+
461
+ interface CopyOptions {
462
+ readonly overwrite?: boolean
463
+ }
464
+
465
+ interface MoveOptions {
466
+ readonly overwrite?: boolean
467
+ }
468
+
469
+ // Persistence
470
+ interface StorageAdapter {
471
+ save(key: string, data: Uint8Array): Promise<void>
472
+ load(key: string): Promise<Uint8Array | undefined>
473
+ remove(key: string): Promise<void>
474
+ list(): Promise<readonly string[]>
475
+ clear(): Promise<void>
476
+ }
477
+
478
+ interface UseFileSystemOptions {
479
+ readonly adapter?: StorageAdapter
480
+ readonly autoPersist?: boolean
481
+ readonly maxSize?: number // Max total bytes
482
+ readonly maxFiles?: number // Max file count
483
+ readonly maxFileSize?: number // Max bytes per file
484
+ readonly warnAtPercentage?: number // Warning threshold (default: 90)
485
+ }
154
486
  ```
155
487
 
156
- ## Types
488
+ ## Examples
157
489
 
158
- All types are exported:
490
+ ### Project Scaffolding
159
491
 
160
492
  ```typescript
161
- import type {
162
- VirtualFile,
163
- FileMetadata,
164
- DirectoryMetadata,
165
- DirectoryEntry,
166
- FileSystemStats,
167
- FileSystemWatcher,
168
- StorageAdapter,
169
- ZipEntry,
170
- ZipEntryMetadata,
171
- ZipInput,
172
- CreateZipOptions,
173
- ExtractZipOptions
174
- } from '@mikestools/usefilesystem'
493
+ const fs = useFileSystem()
494
+
495
+ // Generate project structure
496
+ fs.writeFile('/package.json', JSON.stringify({
497
+ name: 'my-app',
498
+ version: '1.0.0'
499
+ }, null, 2))
500
+
501
+ fs.writeFile('/src/index.ts', 'export const app = () => console.log("Hello!")')
502
+ fs.writeFile('/src/utils/helpers.ts', 'export const add = (a: number, b: number) => a + b')
503
+ fs.writeFile('/readme.md', '# My App\n\nGenerated project.')
504
+
505
+ console.log(fs.stats.value)
506
+ // { totalFiles: 4, totalDirectories: 2, totalSize: ... }
507
+ ```
508
+
509
+ ### File Browser
510
+
511
+ ```typescript
512
+ const fs = useFileSystem()
513
+ const currentPath = ref('/')
514
+
515
+ // Navigate directories
516
+ function navigate(path: string) {
517
+ if (fs.isDirectory(path)) {
518
+ currentPath.value = path
519
+ }
520
+ }
521
+
522
+ // Get current directory contents
523
+ const entries = computed(() => {
524
+ return fs.list(currentPath.value).sort((a, b) => {
525
+ if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
526
+ return a.name.localeCompare(b.name)
527
+ })
528
+ })
529
+ ```
530
+
531
+ ### Auto-Save with localStorage
532
+
533
+ ```typescript
534
+ const adapter: StorageAdapter = {
535
+ async save(key, data) {
536
+ localStorage.setItem(key, btoa(String.fromCharCode(...data)))
537
+ },
538
+ async load(key) {
539
+ const base64 = localStorage.getItem(key)
540
+ if (!base64) return undefined
541
+ return Uint8Array.from(atob(base64), c => c.charCodeAt(0))
542
+ },
543
+ async remove(key) {
544
+ localStorage.removeItem(key)
545
+ },
546
+ async list() {
547
+ return Object.keys(localStorage)
548
+ },
549
+ async clear() {
550
+ localStorage.clear()
551
+ }
552
+ }
553
+
554
+ const fs = useFileSystem({ adapter, autoPersist: true })
555
+
556
+ // Restore on startup
557
+ await fs.restore()
558
+
559
+ // All changes now auto-save!
560
+ fs.writeFile('/notes.txt', 'This will persist')
561
+ ```
562
+
563
+ ### Change Tracking
564
+
565
+ ```typescript
566
+ const fs = useFileSystem()
567
+ const changes = ref<string[]>([])
568
+
569
+ fs.watch((event, path, type) => {
570
+ changes.value.push(`${new Date().toISOString()}: ${event} ${type} ${path}`)
571
+ })
572
+
573
+ // Track all operations
574
+ fs.writeFile('/file.txt', 'content')
575
+ fs.writeFile('/file.txt', 'updated')
576
+ fs.remove('/file.txt')
577
+
578
+ console.log(changes.value)
579
+ // ["...: create file /file.txt", "...: modify file /file.txt", "...: delete file /file.txt"]
175
580
  ```
176
581
 
177
582
  ## Browser Support
178
583
 
179
- Requires browsers with support for:
180
- - CompressionStream/DecompressionStream (Chrome 80+, Firefox 113+, Safari 16.4+)
181
- - TextEncoder/TextDecoder
182
- - Blob, ArrayBuffer, Uint8Array
584
+ Works in all modern browsers that support ES2020+.
183
585
 
184
586
  ## License
185
587
 
186
- MIT
588
+ MIT © Mike Garcia
187
589