@mikestools/usefilesystem 0.0.1 → 0.0.2

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,418 @@
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 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
25
 
15
26
  ## Installation
16
27
 
17
28
  ```bash
18
- npm install @mikestools/usefilesystem
29
+ npm install @mikestools/usefilesystem vue
19
30
  ```
20
31
 
21
32
  ## Quick Start
22
33
 
23
34
  ```typescript
24
- import { useFileSystem, useZip } from '@mikestools/usefilesystem'
35
+ import { ref, onMounted } from 'vue'
36
+ import { useFileSystem } from '@mikestools/usefilesystem'
25
37
 
26
- const fs = useFileSystem()
38
+ onMounted(() => {
39
+ const fs = useFileSystem()
40
+
41
+ // Write files - directories auto-created
42
+ fs.writeFile('/src/index.ts', 'export const version = "1.0.0"')
43
+ fs.writeFile('/readme.md', '# My Project')
44
+
45
+ // Reactive stats (update automatically)
46
+ console.log(fs.fileCount.value) // 2
47
+ console.log(fs.totalSize.value) // bytes
27
48
 
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')
49
+ // Read content
50
+ const content = fs.readFile('/src/index.ts')
31
51
 
32
- // Reactive stats
33
- console.log(fs.fileCount.value) // 2
34
- console.log(fs.totalSize.value) // bytes
52
+ // Search with glob patterns
53
+ const tsFiles = fs.find('/src/**/*.ts')
35
54
 
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
- )
55
+ // Watch for changes
56
+ const unsubscribe = fs.watch((event, path, type) => {
57
+ console.log(`${event} ${type}: ${path}`)
58
+ })
59
+ })
42
60
  ```
43
61
 
44
- ## useFileSystem
62
+ ## API Reference
63
+
64
+ ### Core Properties
45
65
 
46
- In-memory virtual filesystem with reactive stats.
66
+ | Property | Type | Description |
67
+ |--------------|---------------------------------|--------------------------------|
68
+ | `stats` | `ComputedRef<FileSystemStats>` | Reactive stats object |
69
+ | `fileCount` | `ComputedRef<number>` | Number of files |
70
+ | `totalSize` | `ComputedRef<number>` | Total size in bytes |
47
71
 
72
+ ### File Methods
73
+
74
+ #### Write Operations
48
75
  ```typescript
49
- const fs = useFileSystem()
76
+ fs.writeFile('/path/file.txt', 'content')
77
+ fs.writeFile('/path/file.txt', 'content', { mimeType: 'text/plain', overwrite: false })
78
+ fs.appendFile('/path/file.txt', 'more content')
79
+ ```
50
80
 
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')
81
+ #### Read Operations
82
+ ```typescript
83
+ const text = fs.readFile('/path/file.txt') // Returns string
84
+ const binary = fs.readFile('/image.png', { encoding: 'binary' }) // Returns Uint8Array
85
+ const meta = fs.stat('/path/file.txt') // Returns FileMetadata
86
+ ```
87
+
88
+ #### File Management
89
+ ```typescript
57
90
  fs.copy('/source.txt', '/dest.txt')
91
+ fs.copy('/source.txt', '/dest.txt', { overwrite: false })
58
92
  fs.move('/old.txt', '/new.txt')
93
+ fs.move('/old.txt', '/new.txt', { overwrite: false })
94
+ fs.rename('/path/old.txt', 'new.txt')
95
+ fs.remove('/path/file.txt')
96
+ ```
97
+
98
+ #### Path Checks
99
+ ```typescript
100
+ fs.exists('/path') // true if file or directory exists
101
+ fs.isFile('/path') // true if path is a file
102
+ fs.isDirectory('/path') // true if path is a directory
103
+ ```
104
+
105
+ ### Directory Methods
59
106
 
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 })
107
+ #### Directory Operations
108
+ ```typescript
109
+ fs.mkdir('/a/b/c') // Creates all parent directories
110
+ fs.rmdir('/empty') // Removes empty directory only
111
+ fs.rmdirRecursive('/folder') // Removes directory and all contents
112
+ const meta = fs.statDirectory('/path') // Returns DirectoryMetadata
113
+ ```
114
+
115
+ #### List Contents
116
+ ```typescript
117
+ const entries = fs.list('/')
118
+ const recursive = fs.list('/', { recursive: true })
119
+ const filesOnly = fs.list('/', { filesOnly: true })
120
+ const dirsOnly = fs.list('/', { directoriesOnly: true })
121
+ ```
122
+
123
+ ### Search Methods
124
+
125
+ #### Glob Patterns
126
+ ```typescript
127
+ const tsFiles = fs.find('/src/**/*.ts') // Recursive TypeScript files
128
+ const configs = fs.find('/**/config.*') // Any config file
129
+ const rootFiles = fs.find('/*.txt') // Root-level text files
130
+ ```
65
131
 
66
- // Search
67
- const tsFiles = fs.find('/src/**/*.ts')
132
+ #### Extension Search
133
+ ```typescript
68
134
  const jsonFiles = fs.findByExtension('.json')
135
+ const images = fs.findByExtension('png') // Works with or without dot
136
+ ```
69
137
 
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')
138
+ #### Bulk Access
139
+ ```typescript
140
+ const allFiles = fs.getAllFiles() // Returns VirtualFile[]
141
+ const allPaths = fs.getAllPaths() // Returns string[]
142
+ const stats = fs.getStats() // Returns FileSystemStats
143
+ ```
75
144
 
76
- // Reactive stats
77
- console.log(fs.stats.value) // { totalFiles, totalDirectories, totalSize }
78
- console.log(fs.fileCount.value)
79
- console.log(fs.totalSize.value)
145
+ ### Watcher Methods
80
146
 
81
- // Watchers
147
+ ```typescript
148
+ // Watch for changes
82
149
  const unsubscribe = fs.watch((event, path, type) => {
83
150
  console.log(`${event} ${type}: ${path}`)
151
+ // event: 'create' | 'modify' | 'delete'
152
+ // type: 'file' | 'directory'
84
153
  })
85
154
 
86
- // Serialization
155
+ // Triggers: "create file: /new.txt"
156
+ fs.writeFile('/new.txt', 'content')
157
+
158
+ // Triggers: "modify file: /new.txt"
159
+ fs.writeFile('/new.txt', 'updated')
160
+
161
+ // Triggers: "delete file: /new.txt"
162
+ fs.remove('/new.txt')
163
+
164
+ // Stop watching
165
+ unsubscribe()
166
+ ```
167
+
168
+ ### Serialization Methods
169
+
170
+ ```typescript
171
+ // Export to JSON (base64 encoded content)
87
172
  const json = fs.toJSON()
88
- fs.fromJSON(json)
173
+ localStorage.setItem('filesystem', JSON.stringify(json))
174
+
175
+ // Import from JSON
176
+ const data = JSON.parse(localStorage.getItem('filesystem'))
177
+ fs.fromJSON(data)
178
+
179
+ // Clear all files and directories
89
180
  fs.clear()
90
181
  ```
91
182
 
92
- ## useZip
93
-
94
- ZIP archive operations with reactive state.
183
+ ### Persistence Methods
95
184
 
96
185
  ```typescript
97
- const zip = useZip()
186
+ import { useFileSystem, type StorageAdapter } from '@mikestools/usefilesystem'
187
+
188
+ // Create custom adapter
189
+ const adapter: StorageAdapter = {
190
+ async save(key, data) { /* save Uint8Array */ },
191
+ async load(key) { /* return Uint8Array | undefined */ },
192
+ async remove(key) { /* delete key */ },
193
+ async list() { /* return string[] of keys */ },
194
+ async clear() { /* clear all */ }
195
+ }
196
+
197
+ // Use with composable
198
+ const fs = useFileSystem({
199
+ adapter,
200
+ autoPersist: true // Auto-save on every change
201
+ })
98
202
 
99
- // Create ZIP
100
- const blob = await zip.create([
101
- { path: 'readme.txt', content: 'Hello!' },
102
- { path: 'data/config.json', content: '{}' }
103
- ])
203
+ // Manual persist/restore
204
+ await fs.persist()
205
+ await fs.restore()
206
+ ```
104
207
 
105
- // Download ZIP
106
- await zip.download(files, 'archive.zip')
208
+ ### Standalone Function
107
209
 
108
- // Extract ZIP
109
- const entries = await zip.extract(zipBlob)
110
- const filtered = await zip.extract(zipBlob, {
111
- filter: entry => entry.path.endsWith('.txt')
112
- })
210
+ For use without Vue reactivity:
113
211
 
114
- // Extract single file
115
- const content = await zip.extractSingle(zipBlob, 'readme.txt')
212
+ ```typescript
213
+ import { createFileSystem } from '@mikestools/usefilesystem'
116
214
 
117
- // List without extracting
118
- const metadata = await zip.list(zipBlob)
215
+ // Pure filesystem (no Vue reactivity)
216
+ const fs = createFileSystem()
217
+ fs.writeFile('/file.txt', 'Hello')
218
+ const content = fs.readFile('/file.txt')
219
+ ```
119
220
 
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
221
+ ### Types
124
222
 
125
- // Compression utilities
126
- const compressed = await zip.compress(data)
127
- const decompressed = await zip.decompress(data)
128
- const checksum = zip.computeCrc32(data)
223
+ ```typescript
224
+ // File types
225
+ interface VirtualFile {
226
+ readonly name: string
227
+ readonly path: string
228
+ readonly content: Uint8Array
229
+ readonly size: number
230
+ readonly mimeType: string
231
+ readonly createdAt: number
232
+ readonly modifiedAt: number
233
+ }
234
+
235
+ interface FileMetadata {
236
+ readonly name: string
237
+ readonly path: string
238
+ readonly size: number
239
+ readonly mimeType: string
240
+ readonly createdAt: number
241
+ readonly modifiedAt: number
242
+ }
243
+
244
+ // Directory types
245
+ interface DirectoryMetadata {
246
+ readonly name: string
247
+ readonly path: string
248
+ readonly createdAt: number
249
+ }
250
+
251
+ interface DirectoryEntry {
252
+ readonly name: string
253
+ readonly path: string
254
+ readonly type: 'file' | 'directory'
255
+ readonly size?: number // Only for files
256
+ }
257
+
258
+ // Operation result
259
+ interface FileSystemResult {
260
+ readonly success: boolean
261
+ readonly error?: string
262
+ }
263
+
264
+ // Stats
265
+ interface FileSystemStats {
266
+ readonly totalFiles: number
267
+ readonly totalDirectories: number
268
+ readonly totalSize: number
269
+ }
270
+
271
+ // Watcher callback
272
+ type FileSystemWatcher = (
273
+ event: 'create' | 'modify' | 'delete',
274
+ path: string,
275
+ type: 'file' | 'directory'
276
+ ) => void
277
+
278
+ // Options
279
+ interface WriteFileOptions {
280
+ readonly mimeType?: string
281
+ readonly overwrite?: boolean
282
+ }
283
+
284
+ interface ReadFileOptions {
285
+ readonly encoding?: 'utf8' | 'binary'
286
+ }
287
+
288
+ interface ListOptions {
289
+ readonly recursive?: boolean
290
+ readonly filesOnly?: boolean
291
+ readonly directoriesOnly?: boolean
292
+ }
293
+
294
+ interface CopyOptions {
295
+ readonly overwrite?: boolean
296
+ }
297
+
298
+ interface MoveOptions {
299
+ readonly overwrite?: boolean
300
+ }
301
+
302
+ // Persistence
303
+ interface StorageAdapter {
304
+ save(key: string, data: Uint8Array): Promise<void>
305
+ load(key: string): Promise<Uint8Array | undefined>
306
+ remove(key: string): Promise<void>
307
+ list(): Promise<readonly string[]>
308
+ clear(): Promise<void>
309
+ }
310
+
311
+ interface UseFileSystemOptions {
312
+ readonly adapter?: StorageAdapter
313
+ readonly autoPersist?: boolean
314
+ }
129
315
  ```
130
316
 
131
- ## Standalone Functions
317
+ ## Examples
132
318
 
133
- Core functions are also available without Vue:
319
+ ### Project Scaffolding
134
320
 
135
321
  ```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'
322
+ const fs = useFileSystem()
147
323
 
148
- // Pure filesystem (no Vue reactivity)
149
- const fs = createFileSystem()
324
+ // Generate project structure
325
+ fs.writeFile('/package.json', JSON.stringify({
326
+ name: 'my-app',
327
+ version: '1.0.0'
328
+ }, null, 2))
329
+
330
+ fs.writeFile('/src/index.ts', 'export const app = () => console.log("Hello!")')
331
+ fs.writeFile('/src/utils/helpers.ts', 'export const add = (a: number, b: number) => a + b')
332
+ fs.writeFile('/readme.md', '# My App\n\nGenerated project.')
333
+
334
+ console.log(fs.stats.value)
335
+ // { totalFiles: 4, totalDirectories: 2, totalSize: ... }
336
+ ```
150
337
 
151
- // Direct ZIP operations
152
- const blob = await createZip([{ path: 'file.txt', content: 'Hello' }])
153
- const entries = await extractZip(blob)
338
+ ### File Browser
339
+
340
+ ```typescript
341
+ const fs = useFileSystem()
342
+ const currentPath = ref('/')
343
+
344
+ // Navigate directories
345
+ function navigate(path: string) {
346
+ if (fs.isDirectory(path)) {
347
+ currentPath.value = path
348
+ }
349
+ }
350
+
351
+ // Get current directory contents
352
+ const entries = computed(() => {
353
+ return fs.list(currentPath.value).sort((a, b) => {
354
+ if (a.type !== b.type) return a.type === 'directory' ? -1 : 1
355
+ return a.name.localeCompare(b.name)
356
+ })
357
+ })
154
358
  ```
155
359
 
156
- ## Types
360
+ ### Auto-Save with localStorage
157
361
 
158
- All types are exported:
362
+ ```typescript
363
+ const adapter: StorageAdapter = {
364
+ async save(key, data) {
365
+ localStorage.setItem(key, btoa(String.fromCharCode(...data)))
366
+ },
367
+ async load(key) {
368
+ const base64 = localStorage.getItem(key)
369
+ if (!base64) return undefined
370
+ return Uint8Array.from(atob(base64), c => c.charCodeAt(0))
371
+ },
372
+ async remove(key) {
373
+ localStorage.removeItem(key)
374
+ },
375
+ async list() {
376
+ return Object.keys(localStorage)
377
+ },
378
+ async clear() {
379
+ localStorage.clear()
380
+ }
381
+ }
382
+
383
+ const fs = useFileSystem({ adapter, autoPersist: true })
384
+
385
+ // Restore on startup
386
+ await fs.restore()
387
+
388
+ // All changes now auto-save!
389
+ fs.writeFile('/notes.txt', 'This will persist')
390
+ ```
391
+
392
+ ### Change Tracking
159
393
 
160
394
  ```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'
395
+ const fs = useFileSystem()
396
+ const changes = ref<string[]>([])
397
+
398
+ fs.watch((event, path, type) => {
399
+ changes.value.push(`${new Date().toISOString()}: ${event} ${type} ${path}`)
400
+ })
401
+
402
+ // Track all operations
403
+ fs.writeFile('/file.txt', 'content')
404
+ fs.writeFile('/file.txt', 'updated')
405
+ fs.remove('/file.txt')
406
+
407
+ console.log(changes.value)
408
+ // ["...: create file /file.txt", "...: modify file /file.txt", "...: delete file /file.txt"]
175
409
  ```
176
410
 
177
411
  ## Browser Support
178
412
 
179
- Requires browsers with support for:
180
- - CompressionStream/DecompressionStream (Chrome 80+, Firefox 113+, Safari 16.4+)
181
- - TextEncoder/TextDecoder
182
- - Blob, ArrayBuffer, Uint8Array
413
+ Works in all modern browsers that support ES2020+.
183
414
 
184
415
  ## License
185
416
 
186
- MIT
417
+ MIT © Mike Garcia
187
418