@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 +516 -114
- package/dist/index.d.ts +150 -191
- package/dist/usefilesystem.js +595 -425
- package/dist/usefilesystem.umd.cjs +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,187 +1,589 @@
|
|
|
1
1
|
# @mikestools/usefilesystem
|
|
2
2
|
|
|
3
|
-
Vue 3
|
|
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
|
-
-
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 {
|
|
40
|
+
import { ref, onMounted } from 'vue'
|
|
41
|
+
import { useFileSystem } from '@mikestools/usefilesystem'
|
|
25
42
|
|
|
26
|
-
|
|
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
|
-
//
|
|
29
|
-
fs.
|
|
30
|
-
fs.writeFile('/readme.md', '# My Project')
|
|
54
|
+
// Read content
|
|
55
|
+
const content = fs.readFile('/src/index.ts')
|
|
31
56
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
console.log(fs.totalSize.value) // bytes
|
|
57
|
+
// Search with glob patterns
|
|
58
|
+
const tsFiles = fs.find('/src/**/*.ts')
|
|
35
59
|
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
77
|
+
### File Methods
|
|
47
78
|
|
|
79
|
+
#### Write Operations
|
|
48
80
|
```typescript
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
fs.
|
|
63
|
-
fs.
|
|
64
|
-
const
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
fs.
|
|
73
|
-
fs.
|
|
74
|
-
fs.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
+
Built-in high-performance adapter using Origin Private File System:
|
|
95
229
|
|
|
96
230
|
```typescript
|
|
97
|
-
|
|
231
|
+
import { useFileSystem, createOPFSAdapter } from '@mikestools/usefilesystem'
|
|
98
232
|
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
106
|
-
await zip.download(files, 'archive.zip')
|
|
276
|
+
### Checksum
|
|
107
277
|
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
//
|
|
115
|
-
|
|
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
|
-
//
|
|
118
|
-
const
|
|
302
|
+
// Subscribe to warnings
|
|
303
|
+
const unsubscribe = fs.onStorageWarning((estimate) => {
|
|
304
|
+
console.warn(`Storage at ${estimate.percentage}%`)
|
|
305
|
+
})
|
|
306
|
+
```
|
|
119
307
|
|
|
120
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
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
|
-
|
|
329
|
+
### Standalone Function
|
|
132
330
|
|
|
133
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
##
|
|
488
|
+
## Examples
|
|
157
489
|
|
|
158
|
-
|
|
490
|
+
### Project Scaffolding
|
|
159
491
|
|
|
160
492
|
```typescript
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|