@mikestools/usefilesystem 0.0.2 → 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
@@ -11,17 +11,22 @@ Vue 3 composable for an in-memory virtual filesystem with reactive state and fam
11
11
 
12
12
  ### Complete Feature Set
13
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 create, modify, delete events |
20
- | ⚡ **Reactive Stats** | Auto-updating file count and total size |
21
- | 💾 **Serialization** | Export/import filesystem state as JSON |
22
- | 🗄️ **Persistence** | Storage adapter support with auto-persist |
23
- | 📝 **MIME Types** | Automatic inference from file extensions |
24
- | 🔄 **Path Normalization** | Handles mixed separators and relative paths |
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 |
25
30
 
26
31
  ## Installation
27
32
 
@@ -145,13 +150,26 @@ const stats = fs.getStats() // Returns FileSystemStats
145
150
  ### Watcher Methods
146
151
 
147
152
  ```typescript
148
- // Watch for changes
153
+ // Simple watch for all changes
149
154
  const unsubscribe = fs.watch((event, path, type) => {
150
155
  console.log(`${event} ${type}: ${path}`)
151
156
  // event: 'create' | 'modify' | 'delete'
152
157
  // type: 'file' | 'directory'
153
158
  })
154
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
171
+ })
172
+
155
173
  // Triggers: "create file: /new.txt"
156
174
  fs.writeFile('/new.txt', 'content')
157
175
 
@@ -205,6 +223,109 @@ await fs.persist()
205
223
  await fs.restore()
206
224
  ```
207
225
 
226
+ ### OPFS Storage Adapter
227
+
228
+ Built-in high-performance adapter using Origin Private File System:
229
+
230
+ ```typescript
231
+ import { useFileSystem, createOPFSAdapter } from '@mikestools/usefilesystem'
232
+
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
+ ```
275
+
276
+ ### Checksum
277
+
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
291
+ })
292
+
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
+ }
301
+
302
+ // Subscribe to warnings
303
+ const unsubscribe = fs.onStorageWarning((estimate) => {
304
+ console.warn(`Storage at ${estimate.percentage}%`)
305
+ })
306
+ ```
307
+
308
+ ### Transactions
309
+
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()
327
+ ```
328
+
208
329
  ### Standalone Function
209
330
 
210
331
  For use without Vue reactivity:
@@ -239,6 +360,11 @@ interface FileMetadata {
239
360
  readonly mimeType: string
240
361
  readonly createdAt: number
241
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
242
368
  }
243
369
 
244
370
  // Directory types
@@ -246,6 +372,11 @@ interface DirectoryMetadata {
246
372
  readonly name: string
247
373
  readonly path: string
248
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
249
380
  }
250
381
 
251
382
  interface DirectoryEntry {
@@ -268,12 +399,48 @@ interface FileSystemStats {
268
399
  readonly totalSize: number
269
400
  }
270
401
 
271
- // Watcher callback
272
- type FileSystemWatcher = (
273
- event: 'create' | 'modify' | 'delete',
274
- path: string,
275
- type: 'file' | 'directory'
276
- ) => void
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
+ }
277
444
 
278
445
  // Options
279
446
  interface WriteFileOptions {
@@ -311,6 +478,10 @@ interface StorageAdapter {
311
478
  interface UseFileSystemOptions {
312
479
  readonly adapter?: StorageAdapter
313
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)
314
485
  }
315
486
  ```
316
487
 
package/dist/index.d.ts CHANGED
@@ -61,7 +61,7 @@ export declare function createFileSystem(): {
61
61
  clear: () => void;
62
62
  find: (pattern: string) => readonly string[];
63
63
  findByExtension: (extension: string) => readonly string[];
64
- watch: (callback: FileSystemWatcher) => () => void;
64
+ watch: (callback: FileSystemWatcher, options?: WatchOptions) => () => void;
65
65
  toJSON: () => Record<string, {
66
66
  content: string;
67
67
  mimeType: string;
@@ -72,6 +72,21 @@ export declare function createFileSystem(): {
72
72
  }>) => void;
73
73
  };
74
74
 
75
+ /**
76
+ * Built-in OPFS (Origin Private File System) storage adapter.
77
+ * Provides high-performance persistence using the browser's native file system.
78
+ *
79
+ * OPFS is available in modern browsers and provides fast, synchronous-like
80
+ * access to a private file system sandboxed to the origin.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const adapter = createOPFSAdapter()
85
+ * const filesystem = useFileSystem({ adapter, autoPersist: true })
86
+ * ```
87
+ */
88
+ export declare function createOPFSAdapter(): StorageAdapter;
89
+
75
90
  /**
76
91
  * Directory entry for listing.
77
92
  */
@@ -89,6 +104,16 @@ export declare interface DirectoryMetadata {
89
104
  readonly name: string;
90
105
  readonly path: string;
91
106
  readonly createdAt: number;
107
+ /** Parent directory path */
108
+ readonly parentPath: string;
109
+ /** Directory depth from root (root = 0) */
110
+ readonly depth: number;
111
+ /** Count of direct children files */
112
+ readonly fileCount: number;
113
+ /** Count of direct children directories */
114
+ readonly directoryCount: number;
115
+ /** Recursive total size of all contents in bytes */
116
+ readonly totalSize: number;
92
117
  }
93
118
 
94
119
  /**
@@ -101,6 +126,16 @@ export declare interface FileMetadata {
101
126
  readonly mimeType: string;
102
127
  readonly createdAt: number;
103
128
  readonly modifiedAt: number;
129
+ /** File extension including leading dot, or empty string if none */
130
+ readonly extension: string;
131
+ /** True if MIME type indicates text content */
132
+ readonly isText: boolean;
133
+ /** True if MIME type indicates binary content */
134
+ readonly isBinary: boolean;
135
+ /** Parent directory path */
136
+ readonly parentPath: string;
137
+ /** Directory depth from root (root = 0) */
138
+ readonly depth: number;
104
139
  }
105
140
 
106
141
  /**
@@ -129,8 +164,10 @@ export declare interface FileSystemStats {
129
164
 
130
165
  /**
131
166
  * Watcher callback for filesystem changes.
167
+ * When using watch options, receives a WatchEvent object.
168
+ * When using simple watch (no options), receives individual parameters.
132
169
  */
133
- export declare type FileSystemWatcher = (event: 'create' | 'modify' | 'delete', path: string, type: 'file' | 'directory') => void;
170
+ export declare type FileSystemWatcher = ((event: WatchEvent) => void) | ((event: 'create' | 'modify' | 'delete', path: string, type: 'file' | 'directory') => void);
134
171
 
135
172
  /**
136
173
  * Options for listing directory contents.
@@ -166,6 +203,51 @@ export declare interface StorageAdapter {
166
203
  clear(): Promise<void>;
167
204
  }
168
205
 
206
+ /**
207
+ * Storage usage estimate.
208
+ */
209
+ declare interface StorageEstimate_2 {
210
+ /** Estimated available space (virtual limit) */
211
+ readonly quota: number;
212
+ /** Current usage in bytes */
213
+ readonly usage: number;
214
+ /** Available space (quota - usage) */
215
+ readonly available: number;
216
+ /** Usage percentage (0-100) */
217
+ readonly percentage: number;
218
+ }
219
+ export { StorageEstimate_2 as StorageEstimate }
220
+
221
+ /**
222
+ * Options for Web Streams API operations.
223
+ */
224
+ export declare interface StreamOptions {
225
+ /** Chunk size in bytes for streaming (default: 64KB) */
226
+ readonly chunkSize?: number;
227
+ /** High water mark for backpressure (default: 3 chunks) */
228
+ readonly highWaterMark?: number;
229
+ }
230
+
231
+ /**
232
+ * Transaction for atomic multi-file operations.
233
+ */
234
+ export declare interface Transaction {
235
+ /** Queue a file write operation */
236
+ writeFile(path: string, content: string | Uint8Array, options?: WriteFileOptions): Transaction;
237
+ /** Queue a file removal */
238
+ remove(path: string): Transaction;
239
+ /** Queue a directory creation */
240
+ mkdir(path: string): Transaction;
241
+ /** Queue a file copy operation */
242
+ copy(source: string, destination: string): Transaction;
243
+ /** Queue a file move operation */
244
+ move(source: string, destination: string): Transaction;
245
+ /** Execute all operations atomically (all or nothing) */
246
+ commit(): FileSystemResult;
247
+ /** Discard all pending operations */
248
+ rollback(): void;
249
+ }
250
+
169
251
  /**
170
252
  * Vue composable wrapper for the virtual filesystem with reactivity.
171
253
  *
@@ -189,6 +271,14 @@ export declare function useFileSystem(options?: UseFileSystemOptions): UseFileSy
189
271
  export declare interface UseFileSystemOptions {
190
272
  readonly adapter?: StorageAdapter;
191
273
  readonly autoPersist?: boolean;
274
+ /** Maximum total size in bytes (default: unlimited) */
275
+ readonly maxSize?: number;
276
+ /** Maximum number of files (default: unlimited) */
277
+ readonly maxFiles?: number;
278
+ /** Maximum single file size in bytes (default: unlimited) */
279
+ readonly maxFileSize?: number;
280
+ /** Trigger warning at this usage percentage (default: 90) */
281
+ readonly warnAtPercentage?: number;
192
282
  }
193
283
 
194
284
  /**
@@ -205,13 +295,82 @@ export declare interface UseFileSystemReturn extends FileSystem_2 {
205
295
  persist: () => Promise<void>;
206
296
  /** Restore from storage adapter */
207
297
  restore: () => Promise<void>;
298
+ /** Import a native File object into the virtual filesystem */
299
+ upload: (file: File, targetPath?: string) => Promise<FileSystemResult>;
300
+ /** Import multiple files from a FileList (e.g., from <input type="file">) */
301
+ uploads: (files: FileList, targetDirectory?: string) => Promise<FileSystemResult>;
302
+ /** Export a virtual file as a native Blob */
303
+ toBlob: (path: string) => Blob | undefined;
304
+ /** Export a virtual file as a native File object */
305
+ toFile: (path: string) => File | undefined;
306
+ /** Create a downloadable object URL for a file */
307
+ createObjectURL: (path: string) => string | undefined;
308
+ /** Revoke a previously created object URL */
309
+ revokeObjectURL: (url: string) => void;
310
+ /** Download a virtual file to the user's device */
311
+ download: (path: string, filename?: string) => boolean;
312
+ /** Download multiple files as individual downloads */
313
+ downloads: (paths: readonly string[]) => void;
314
+ /** Create a Web Streams API ReadableStream for a file */
315
+ createReadStream: (path: string, options?: StreamOptions) => ReadableStream<Uint8Array> | undefined;
316
+ /** Create a Web Streams API WritableStream for a file */
317
+ createWriteStream: (path: string, options?: WriteFileOptions & StreamOptions) => WritableStream<Uint8Array>;
318
+ /** Write from any ReadableStream (fetch response, Blob.stream(), etc.) */
319
+ writeFromStream: (path: string, stream: ReadableStream<Uint8Array>, options?: WriteFileOptions) => Promise<FileSystemResult>;
320
+ /** Create a Response object from a file (for Service Worker integration) */
321
+ toResponse: (path: string, init?: ResponseInit) => Response | undefined;
322
+ /** Compute SHA-256 checksum of file content */
323
+ computeChecksum: (path: string) => Promise<string | undefined>;
324
+ /** Get storage usage estimate */
325
+ readonly storageEstimate: ComputedRef<StorageEstimate_2>;
326
+ /** Check if storage is near capacity (above warning threshold) */
327
+ readonly isNearCapacity: ComputedRef<boolean>;
328
+ /** Register callback when storage exceeds warning threshold */
329
+ onStorageWarning: (callback: (estimate: StorageEstimate_2) => void) => () => void;
330
+ /** Begin a transaction for atomic operations */
331
+ beginTransaction: () => Transaction;
208
332
  }
209
333
 
210
334
  /**
211
335
  * Represents a complete file with content.
212
336
  */
213
- export declare interface VirtualFile extends FileMetadata {
337
+ export declare interface VirtualFile {
338
+ readonly name: string;
339
+ readonly path: string;
214
340
  readonly content: Uint8Array;
341
+ readonly size: number;
342
+ readonly mimeType: string;
343
+ readonly createdAt: number;
344
+ readonly modifiedAt: number;
345
+ }
346
+
347
+ /**
348
+ * Enhanced watch event with metadata.
349
+ */
350
+ export declare interface WatchEvent {
351
+ readonly event: 'create' | 'modify' | 'delete';
352
+ readonly path: string;
353
+ readonly type: 'file' | 'directory';
354
+ readonly timestamp: number;
355
+ readonly metadata?: FileMetadata | DirectoryMetadata;
356
+ }
357
+
358
+ /**
359
+ * Options for enhanced file watching.
360
+ */
361
+ export declare interface WatchOptions {
362
+ /** Filter by path pattern (glob) */
363
+ readonly pattern?: string;
364
+ /** Filter by event types */
365
+ readonly events?: readonly ('create' | 'modify' | 'delete')[];
366
+ /** Filter by entry type */
367
+ readonly type?: 'file' | 'directory' | 'all';
368
+ /** Debounce rapid changes (ms) */
369
+ readonly debounce?: number;
370
+ /** Only watch specific directory (non-recursive by default) */
371
+ readonly path?: string;
372
+ /** Watch recursively when path is specified */
373
+ readonly recursive?: boolean;
215
374
  }
216
375
 
217
376
  /**