@componentor/fs 1.2.7 → 2.0.0

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.
@@ -1,2923 +0,0 @@
1
- // src/constants.ts
2
- var constants = {
3
- // File access modes
4
- F_OK: 0,
5
- R_OK: 4,
6
- W_OK: 2,
7
- X_OK: 1,
8
- // Copy file flags
9
- COPYFILE_EXCL: 1,
10
- COPYFILE_FICLONE: 2,
11
- COPYFILE_FICLONE_FORCE: 4,
12
- // File open flags
13
- O_RDONLY: 0,
14
- O_WRONLY: 1,
15
- O_RDWR: 2,
16
- O_CREAT: 64,
17
- O_EXCL: 128,
18
- O_TRUNC: 512,
19
- O_APPEND: 1024,
20
- // File type masks
21
- S_IFMT: 61440,
22
- S_IFREG: 32768,
23
- S_IFDIR: 16384,
24
- S_IFLNK: 40960
25
- };
26
- function flagsToString(flags) {
27
- if (typeof flags === "string") return flags;
28
- const map = {
29
- [constants.O_RDONLY]: "r",
30
- [constants.O_WRONLY]: "w",
31
- [constants.O_RDWR]: "r+",
32
- [constants.O_CREAT | constants.O_WRONLY]: "w",
33
- [constants.O_CREAT | constants.O_WRONLY | constants.O_TRUNC]: "w",
34
- [constants.O_CREAT | constants.O_RDWR]: "w+",
35
- [constants.O_APPEND | constants.O_WRONLY]: "a",
36
- [constants.O_APPEND | constants.O_RDWR]: "a+"
37
- };
38
- return map[flags] || "r";
39
- }
40
-
41
- // src/errors.ts
42
- var FSError = class extends Error {
43
- code;
44
- syscall;
45
- path;
46
- original;
47
- constructor(message, code, options) {
48
- super(message);
49
- this.name = "FSError";
50
- this.code = code;
51
- this.syscall = options?.syscall;
52
- this.path = options?.path;
53
- this.original = options?.original;
54
- }
55
- };
56
- function createENOENT(path) {
57
- return new FSError(`ENOENT: No such file or directory, '${path}'`, "ENOENT", { path });
58
- }
59
- function createEEXIST(path, operation) {
60
- const message = `EEXIST: File exists, '${path}'`;
61
- return new FSError(message, "EEXIST", { path });
62
- }
63
- function createEACCES(path, syscall) {
64
- return new FSError(`EACCES: permission denied, access '${path}'`, "EACCES", { syscall, path });
65
- }
66
- function createEISDIR(path, operation = "operation") {
67
- return new FSError(`EISDIR: illegal operation on a directory, ${operation} '${path}'`, "EISDIR", { path });
68
- }
69
- function createELOOP(path) {
70
- return new FSError(`ELOOP: Too many symbolic links, '${path}'`, "ELOOP", { path });
71
- }
72
- function createEINVAL(path) {
73
- return new FSError(`EINVAL: Invalid argument, '${path}'`, "EINVAL", { path });
74
- }
75
- function createECORRUPTED(path) {
76
- return new FSError(`ECORRUPTED: Pack file integrity check failed, '${path}'`, "ECORRUPTED", { path });
77
- }
78
- function wrapError(err) {
79
- if (err instanceof FSError) return err;
80
- const error = err;
81
- if (typeof error.code === "string") {
82
- const fsErr = new FSError(error.message, error.code);
83
- fsErr.original = error;
84
- return fsErr;
85
- }
86
- const wrapped = new FSError(error.message || "Unknown error", "UNKNOWN");
87
- wrapped.original = error;
88
- return wrapped;
89
- }
90
-
91
- // src/path-utils.ts
92
- var normalizeCache = /* @__PURE__ */ new Map();
93
- var CACHE_MAX_SIZE = 1e3;
94
- function normalize(path) {
95
- if (path === void 0 || path === null) {
96
- throw new TypeError("Path cannot be undefined or null");
97
- }
98
- if (typeof path !== "string") {
99
- throw new TypeError(`Expected string path, got ${typeof path}`);
100
- }
101
- if (path === "") {
102
- return "/";
103
- }
104
- const cached = normalizeCache.get(path);
105
- if (cached !== void 0) {
106
- return cached;
107
- }
108
- const parts = path.split("/");
109
- const stack = [];
110
- for (const part of parts) {
111
- if (part === "" || part === ".") {
112
- continue;
113
- } else if (part === "..") {
114
- if (stack.length > 0) stack.pop();
115
- } else {
116
- stack.push(part);
117
- }
118
- }
119
- const result = "/" + stack.join("/");
120
- if (normalizeCache.size >= CACHE_MAX_SIZE) {
121
- const deleteCount = CACHE_MAX_SIZE / 4;
122
- let count = 0;
123
- for (const key of normalizeCache.keys()) {
124
- if (count++ >= deleteCount) break;
125
- normalizeCache.delete(key);
126
- }
127
- }
128
- normalizeCache.set(path, result);
129
- return result;
130
- }
131
- function dirname(path) {
132
- const normalized = normalize(path);
133
- const parts = normalized.split("/").filter(Boolean);
134
- if (parts.length < 2) return "/";
135
- return "/" + parts.slice(0, -1).join("/");
136
- }
137
- function isRoot(path) {
138
- const normalized = normalize(path);
139
- return normalized === "/" || normalized === "";
140
- }
141
- function segments(path) {
142
- return normalize(path).split("/").filter(Boolean);
143
- }
144
-
145
- // src/handle-manager.ts
146
- var FILE_HANDLE_POOL_SIZE = 50;
147
- var DIR_CACHE_MAX_SIZE = 200;
148
- var FileLock = class {
149
- active = /* @__PURE__ */ new Set();
150
- queues = /* @__PURE__ */ new Map();
151
- async acquire(path) {
152
- if (!this.active.has(path)) {
153
- this.active.add(path);
154
- return this.createRelease(path);
155
- }
156
- await new Promise((resolve) => {
157
- let queue = this.queues.get(path);
158
- if (!queue) {
159
- queue = [];
160
- this.queues.set(path, queue);
161
- }
162
- queue.push(resolve);
163
- });
164
- return this.createRelease(path);
165
- }
166
- createRelease(path) {
167
- return () => {
168
- const queue = this.queues.get(path);
169
- if (queue && queue.length > 0) {
170
- const next = queue.shift();
171
- if (queue.length === 0) {
172
- this.queues.delete(path);
173
- }
174
- next();
175
- } else {
176
- this.active.delete(path);
177
- }
178
- };
179
- }
180
- };
181
- var fileLock = new FileLock();
182
- var HandleManager = class {
183
- rootPromise;
184
- dirCache = /* @__PURE__ */ new Map();
185
- fileHandlePool = /* @__PURE__ */ new Map();
186
- constructor() {
187
- this.rootPromise = navigator.storage.getDirectory();
188
- }
189
- /**
190
- * Get the root directory handle
191
- */
192
- async getRoot() {
193
- return this.rootPromise;
194
- }
195
- /**
196
- * Cache a directory handle with LRU eviction
197
- */
198
- cacheDirHandle(path, handle) {
199
- if (this.dirCache.size >= DIR_CACHE_MAX_SIZE) {
200
- const firstKey = this.dirCache.keys().next().value;
201
- if (firstKey) this.dirCache.delete(firstKey);
202
- }
203
- this.dirCache.set(path, handle);
204
- }
205
- /**
206
- * Clear directory cache for a path and its children
207
- */
208
- clearCache(path = "") {
209
- const normalizedPath = normalize(path);
210
- if (normalizedPath === "/" || normalizedPath === "") {
211
- this.dirCache.clear();
212
- this.fileHandlePool.clear();
213
- return;
214
- }
215
- if (this.dirCache.size > 0) {
216
- for (const key of this.dirCache.keys()) {
217
- if (key === normalizedPath || key.startsWith(normalizedPath + "/")) {
218
- this.dirCache.delete(key);
219
- }
220
- }
221
- }
222
- if (this.fileHandlePool.size > 0) {
223
- for (const key of this.fileHandlePool.keys()) {
224
- if (key === normalizedPath || key.startsWith(normalizedPath + "/")) {
225
- this.fileHandlePool.delete(key);
226
- }
227
- }
228
- }
229
- }
230
- /**
231
- * Get a file handle from the pool or create a new one
232
- */
233
- async getPooledFileHandle(path, create = false) {
234
- const normalizedPath = normalize(path);
235
- const pooled = this.fileHandlePool.get(normalizedPath);
236
- if (pooled) {
237
- return pooled;
238
- }
239
- const { fileHandle } = await this.getHandle(normalizedPath, { create });
240
- if (!fileHandle) return null;
241
- if (this.fileHandlePool.size >= FILE_HANDLE_POOL_SIZE) {
242
- const firstKey = this.fileHandlePool.keys().next().value;
243
- if (firstKey) this.fileHandlePool.delete(firstKey);
244
- }
245
- this.fileHandlePool.set(normalizedPath, fileHandle);
246
- return fileHandle;
247
- }
248
- /**
249
- * Invalidate a specific file handle from the pool
250
- */
251
- invalidateFileHandle(path) {
252
- const normalizedPath = normalize(path);
253
- this.fileHandlePool.delete(normalizedPath);
254
- }
255
- /**
256
- * Get file or directory handle for a path
257
- */
258
- async getHandle(path, opts = {}) {
259
- const parts = segments(path);
260
- if (parts.length === 0) {
261
- const root = await this.rootPromise;
262
- return { dir: root, name: "", fileHandle: null, dirHandle: root };
263
- }
264
- let dir = await this.rootPromise;
265
- let currentPath = "";
266
- for (let i = 0; i < parts.length - 1; i++) {
267
- currentPath += "/" + parts[i];
268
- if (this.dirCache.has(currentPath)) {
269
- dir = this.dirCache.get(currentPath);
270
- continue;
271
- }
272
- try {
273
- dir = await dir.getDirectoryHandle(parts[i], { create: opts.create });
274
- this.cacheDirHandle(currentPath, dir);
275
- } catch {
276
- throw createENOENT(path);
277
- }
278
- }
279
- const name = parts[parts.length - 1];
280
- try {
281
- if (opts.kind === "directory") {
282
- const dirHandle = await dir.getDirectoryHandle(name, { create: opts.create });
283
- return { dir, name, fileHandle: null, dirHandle };
284
- } else {
285
- const fileHandle = await dir.getFileHandle(name, { create: opts.create });
286
- return { dir, name, fileHandle, dirHandle: null };
287
- }
288
- } catch {
289
- if (!opts.create) {
290
- return { dir, name, fileHandle: null, dirHandle: null };
291
- }
292
- throw createENOENT(path);
293
- }
294
- }
295
- /**
296
- * Get directory handle with caching
297
- */
298
- async getDirectoryHandle(path) {
299
- const normalizedPath = normalize(path);
300
- if (normalizedPath === "/" || normalizedPath === "") {
301
- return this.rootPromise;
302
- }
303
- if (this.dirCache.has(normalizedPath)) {
304
- return this.dirCache.get(normalizedPath);
305
- }
306
- const parts = segments(normalizedPath);
307
- let dir = await this.rootPromise;
308
- let currentPath = "";
309
- for (const part of parts) {
310
- currentPath += "/" + part;
311
- if (this.dirCache.has(currentPath)) {
312
- dir = this.dirCache.get(currentPath);
313
- continue;
314
- }
315
- dir = await dir.getDirectoryHandle(part);
316
- this.cacheDirHandle(currentPath, dir);
317
- }
318
- return dir;
319
- }
320
- /**
321
- * Ensure parent directory exists
322
- */
323
- async ensureParentDir(path) {
324
- const parentPath = dirname(path);
325
- if (parentPath === "/" || parentPath === "") return;
326
- const parts = segments(parentPath);
327
- let dir = await this.rootPromise;
328
- let currentPath = "";
329
- for (const part of parts) {
330
- currentPath += "/" + part;
331
- if (this.dirCache.has(currentPath)) {
332
- dir = this.dirCache.get(currentPath);
333
- continue;
334
- }
335
- dir = await dir.getDirectoryHandle(part, { create: true });
336
- this.cacheDirHandle(currentPath, dir);
337
- }
338
- }
339
- /**
340
- * Create directory (with automatic parent creation)
341
- */
342
- async mkdir(path) {
343
- const normalizedPath = normalize(path);
344
- this.clearCache(normalizedPath);
345
- const parts = segments(normalizedPath);
346
- let dir = await this.rootPromise;
347
- for (let i = 0; i < parts.length; i++) {
348
- const part = parts[i];
349
- const subPath = "/" + parts.slice(0, i + 1).join("/");
350
- if (this.dirCache.has(subPath)) {
351
- dir = this.dirCache.get(subPath);
352
- } else {
353
- dir = await dir.getDirectoryHandle(part, { create: true });
354
- this.cacheDirHandle(subPath, dir);
355
- }
356
- }
357
- }
358
- };
359
-
360
- // src/symlink-manager.ts
361
- var SYMLINK_FILE = "/.opfs-symlinks.json";
362
- var MAX_SYMLINK_DEPTH = 10;
363
- var SymlinkManager = class {
364
- cache = null;
365
- cacheCount = 0;
366
- // Track count to avoid Object.keys() calls
367
- resolvedCache = /* @__PURE__ */ new Map();
368
- // Cache resolved paths
369
- dirty = false;
370
- handleManager;
371
- useSync;
372
- loadPromise = null;
373
- // Avoid multiple concurrent loads
374
- diskLoaded = false;
375
- // Track if we've loaded from disk
376
- constructor(handleManager, useSync) {
377
- this.handleManager = handleManager;
378
- this.useSync = useSync;
379
- this.cache = {};
380
- this.cacheCount = 0;
381
- }
382
- /**
383
- * Reset all symlink state (called when root directory is cleared)
384
- */
385
- reset() {
386
- this.cache = {};
387
- this.cacheCount = 0;
388
- this.resolvedCache.clear();
389
- this.dirty = false;
390
- this.loadPromise = null;
391
- this.diskLoaded = false;
392
- }
393
- /**
394
- * Load symlinks from metadata file
395
- * Uses loadPromise to avoid multiple concurrent disk reads
396
- */
397
- async load() {
398
- if (this.diskLoaded) return this.cache;
399
- if (this.loadPromise) return this.loadPromise;
400
- this.loadPromise = this.loadFromDisk();
401
- const result = await this.loadPromise;
402
- this.loadPromise = null;
403
- return result;
404
- }
405
- /**
406
- * Actually read from disk
407
- */
408
- async loadFromDisk() {
409
- try {
410
- const { fileHandle } = await this.handleManager.getHandle(SYMLINK_FILE);
411
- if (!fileHandle) {
412
- this.diskLoaded = true;
413
- return this.cache;
414
- }
415
- const file = await fileHandle.getFile();
416
- const text = await file.text();
417
- this.cache = JSON.parse(text);
418
- this.cacheCount = Object.keys(this.cache).length;
419
- this.diskLoaded = true;
420
- } catch {
421
- if (!this.cache) {
422
- this.cache = {};
423
- this.cacheCount = 0;
424
- }
425
- this.diskLoaded = true;
426
- }
427
- return this.cache;
428
- }
429
- /**
430
- * Save symlinks to metadata file
431
- */
432
- async save() {
433
- if (!this.cache) return;
434
- const data = JSON.stringify(this.cache);
435
- const { fileHandle } = await this.handleManager.getHandle(SYMLINK_FILE, { create: true });
436
- if (!fileHandle) return;
437
- const buffer = new TextEncoder().encode(data);
438
- if (this.useSync) {
439
- const release = await fileLock.acquire(SYMLINK_FILE);
440
- try {
441
- const access = await fileHandle.createSyncAccessHandle();
442
- try {
443
- access.truncate(0);
444
- let written = 0;
445
- while (written < buffer.length) {
446
- written += access.write(buffer.subarray(written), { at: written });
447
- }
448
- } finally {
449
- access.close();
450
- }
451
- } finally {
452
- release();
453
- }
454
- } else {
455
- const writable = await fileHandle.createWritable();
456
- await writable.write(buffer);
457
- await writable.close();
458
- }
459
- this.dirty = false;
460
- }
461
- /**
462
- * Flush pending changes if dirty
463
- */
464
- async flush() {
465
- if (this.dirty) {
466
- await this.save();
467
- }
468
- }
469
- /**
470
- * Resolve a path through symlinks
471
- * Fast synchronous path when cache is already loaded
472
- * Uses resolved cache for O(1) repeated lookups
473
- *
474
- * OPTIMIZATION: If we haven't loaded from disk yet AND no symlinks have been
475
- * created in this session, we skip the disk check entirely. This makes pure
476
- * file operations (no symlinks) very fast.
477
- */
478
- async resolve(path, maxDepth = MAX_SYMLINK_DEPTH) {
479
- if (this.cacheCount === 0) {
480
- return path;
481
- }
482
- const cached = this.resolvedCache.get(path);
483
- if (cached !== void 0) {
484
- return cached;
485
- }
486
- return this.resolveSync(path, this.cache, maxDepth);
487
- }
488
- /**
489
- * Synchronous resolution helper - caches the result
490
- */
491
- resolveSync(path, symlinks, maxDepth) {
492
- let currentPath = path;
493
- let depth = 0;
494
- while (symlinks[currentPath] && depth < maxDepth) {
495
- currentPath = symlinks[currentPath];
496
- depth++;
497
- }
498
- if (depth >= maxDepth) {
499
- throw createELOOP(path);
500
- }
501
- if (currentPath !== path) {
502
- this.resolvedCache.set(path, currentPath);
503
- }
504
- return currentPath;
505
- }
506
- /**
507
- * Clear the resolved path cache (called when symlinks change)
508
- */
509
- clearResolvedCache() {
510
- this.resolvedCache.clear();
511
- }
512
- /**
513
- * Check if a path is a symlink
514
- */
515
- async isSymlink(path) {
516
- const symlinks = await this.load();
517
- return !!symlinks[path];
518
- }
519
- /**
520
- * Get symlink target
521
- */
522
- async readlink(path) {
523
- const normalizedPath = normalize(path);
524
- const symlinks = await this.load();
525
- if (!symlinks[normalizedPath]) {
526
- throw createEINVAL(path);
527
- }
528
- return symlinks[normalizedPath];
529
- }
530
- /**
531
- * Create a symlink
532
- */
533
- async symlink(target, path, checkExists) {
534
- const normalizedPath = normalize(path);
535
- const normalizedTarget = normalize(target);
536
- const symlinks = await this.load();
537
- if (symlinks[normalizedPath]) {
538
- throw createEEXIST(normalizedPath);
539
- }
540
- await checkExists();
541
- symlinks[normalizedPath] = normalizedTarget;
542
- this.cacheCount++;
543
- this.clearResolvedCache();
544
- this.dirty = true;
545
- await this.flush();
546
- }
547
- /**
548
- * Create multiple symlinks efficiently
549
- */
550
- async symlinkBatch(links, checkExists) {
551
- const symlinks = await this.load();
552
- const normalizedLinks = links.map(({ target, path }) => ({
553
- normalizedPath: normalize(path),
554
- normalizedTarget: normalize(target)
555
- }));
556
- for (const { normalizedPath } of normalizedLinks) {
557
- if (symlinks[normalizedPath]) {
558
- throw createEEXIST(normalizedPath);
559
- }
560
- }
561
- await Promise.all(normalizedLinks.map(({ normalizedPath }) => checkExists(normalizedPath)));
562
- for (const { normalizedPath, normalizedTarget } of normalizedLinks) {
563
- symlinks[normalizedPath] = normalizedTarget;
564
- }
565
- this.cacheCount += links.length;
566
- this.clearResolvedCache();
567
- this.dirty = true;
568
- await this.flush();
569
- }
570
- /**
571
- * Remove a symlink
572
- */
573
- async unlink(path) {
574
- const symlinks = await this.load();
575
- if (symlinks[path]) {
576
- delete symlinks[path];
577
- this.cacheCount--;
578
- this.clearResolvedCache();
579
- this.dirty = true;
580
- await this.flush();
581
- return true;
582
- }
583
- return false;
584
- }
585
- /**
586
- * Rename/move a symlink
587
- */
588
- async rename(oldPath, newPath) {
589
- const symlinks = await this.load();
590
- if (symlinks[oldPath]) {
591
- const target = symlinks[oldPath];
592
- delete symlinks[oldPath];
593
- symlinks[newPath] = target;
594
- this.clearResolvedCache();
595
- this.dirty = true;
596
- await this.flush();
597
- return true;
598
- }
599
- return false;
600
- }
601
- /**
602
- * Get all symlinks in a directory
603
- */
604
- async getSymlinksInDir(dirPath) {
605
- const symlinks = await this.load();
606
- const result = [];
607
- for (const symlinkPath of Object.keys(symlinks)) {
608
- const parts = symlinkPath.split("/").filter(Boolean);
609
- const parentPath = "/" + parts.slice(0, -1).join("/");
610
- if (parentPath === dirPath || dirPath === "/" && parts.length === 1) {
611
- result.push(parts[parts.length - 1]);
612
- }
613
- }
614
- return result;
615
- }
616
- /**
617
- * Check if path is the symlink metadata file
618
- */
619
- isMetadataFile(name) {
620
- return name === SYMLINK_FILE.replace(/^\/+/, "");
621
- }
622
- };
623
-
624
- // src/packed-storage.ts
625
- async function compress(data) {
626
- if (data.length < 100) return data;
627
- try {
628
- const stream = new CompressionStream("gzip");
629
- const writer = stream.writable.getWriter();
630
- writer.write(data);
631
- writer.close();
632
- const chunks = [];
633
- const reader = stream.readable.getReader();
634
- let totalSize = 0;
635
- while (true) {
636
- const { done, value } = await reader.read();
637
- if (done) break;
638
- chunks.push(value);
639
- totalSize += value.length;
640
- }
641
- if (totalSize >= data.length) return data;
642
- const result = new Uint8Array(totalSize);
643
- let offset = 0;
644
- for (const chunk of chunks) {
645
- result.set(chunk, offset);
646
- offset += chunk.length;
647
- }
648
- return result;
649
- } catch {
650
- return data;
651
- }
652
- }
653
- async function decompress(data) {
654
- const stream = new DecompressionStream("gzip");
655
- const writer = stream.writable.getWriter();
656
- writer.write(data);
657
- writer.close();
658
- const chunks = [];
659
- const reader = stream.readable.getReader();
660
- let totalSize = 0;
661
- while (true) {
662
- const { done, value } = await reader.read();
663
- if (done) break;
664
- chunks.push(value);
665
- totalSize += value.length;
666
- }
667
- const result = new Uint8Array(totalSize);
668
- let offset = 0;
669
- for (const chunk of chunks) {
670
- result.set(chunk, offset);
671
- offset += chunk.length;
672
- }
673
- return result;
674
- }
675
- var CRC32_TABLE = new Uint32Array(256);
676
- for (let i = 0; i < 256; i++) {
677
- let c = i;
678
- for (let j = 0; j < 8; j++) {
679
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
680
- }
681
- CRC32_TABLE[i] = c;
682
- }
683
- function crc32(data) {
684
- let crc = 4294967295;
685
- for (let i = 0; i < data.length; i++) {
686
- crc = CRC32_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
687
- }
688
- return (crc ^ 4294967295) >>> 0;
689
- }
690
- var PACK_FILE = "/.opfs-pack";
691
- var PackedStorage = class {
692
- handleManager;
693
- useSync;
694
- useCompression;
695
- useChecksum;
696
- index = null;
697
- indexLoaded = false;
698
- constructor(handleManager, useSync, useCompression = false, useChecksum = true) {
699
- this.handleManager = handleManager;
700
- this.useSync = useSync;
701
- this.useCompression = useCompression && typeof CompressionStream !== "undefined";
702
- this.useChecksum = useChecksum;
703
- }
704
- /**
705
- * Reset pack storage state (memory only)
706
- */
707
- reset() {
708
- this.index = null;
709
- this.indexLoaded = false;
710
- }
711
- /**
712
- * Clear pack storage completely (deletes pack file from disk)
713
- */
714
- async clear() {
715
- this.index = null;
716
- this.indexLoaded = false;
717
- try {
718
- const root = await this.handleManager.getRoot();
719
- await root.removeEntry(PACK_FILE.replace(/^\//, ""));
720
- } catch {
721
- }
722
- }
723
- /**
724
- * Load pack index from disk (always reloads to support hybrid mode)
725
- * Verifies CRC32 checksum for integrity
726
- * Note: Caller must hold the lock
727
- */
728
- async loadIndex() {
729
- try {
730
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
731
- if (!fileHandle) {
732
- return {};
733
- }
734
- if (this.useSync) {
735
- const release = await fileLock.acquire(PACK_FILE);
736
- try {
737
- const access = await fileHandle.createSyncAccessHandle();
738
- try {
739
- const size = access.getSize();
740
- if (size < 8) {
741
- return {};
742
- }
743
- const header = new Uint8Array(8);
744
- access.read(header, { at: 0 });
745
- const view = new DataView(header.buffer);
746
- const indexLen = view.getUint32(0, true);
747
- const storedCrc = view.getUint32(4, true);
748
- const contentSize = size - 8;
749
- const content = new Uint8Array(contentSize);
750
- access.read(content, { at: 8 });
751
- if (this.useChecksum && storedCrc !== 0) {
752
- const calculatedCrc = crc32(content);
753
- if (calculatedCrc !== storedCrc) {
754
- throw createECORRUPTED(PACK_FILE);
755
- }
756
- }
757
- const indexJson = new TextDecoder().decode(content.subarray(0, indexLen));
758
- return JSON.parse(indexJson);
759
- } finally {
760
- access.close();
761
- }
762
- } finally {
763
- release();
764
- }
765
- } else {
766
- const file = await fileHandle.getFile();
767
- const data = new Uint8Array(await file.arrayBuffer());
768
- if (data.length < 8) {
769
- return {};
770
- }
771
- const view = new DataView(data.buffer);
772
- const indexLen = view.getUint32(0, true);
773
- const storedCrc = view.getUint32(4, true);
774
- const content = data.subarray(8);
775
- if (this.useChecksum && storedCrc !== 0) {
776
- const calculatedCrc = crc32(content);
777
- if (calculatedCrc !== storedCrc) {
778
- throw createECORRUPTED(PACK_FILE);
779
- }
780
- }
781
- const indexJson = new TextDecoder().decode(content.subarray(0, indexLen));
782
- return JSON.parse(indexJson);
783
- }
784
- } catch {
785
- return {};
786
- }
787
- }
788
- /**
789
- * Check if a path exists in the pack
790
- */
791
- async has(path) {
792
- const index = await this.loadIndex();
793
- return path in index;
794
- }
795
- /**
796
- * Get file size from pack (for stat)
797
- * Returns originalSize if compressed, otherwise size
798
- */
799
- async getSize(path) {
800
- const index = await this.loadIndex();
801
- const entry = index[path];
802
- if (!entry) return null;
803
- return entry.originalSize ?? entry.size;
804
- }
805
- /**
806
- * Read a file from the pack
807
- * Handles decompression if file was stored compressed
808
- */
809
- async read(path) {
810
- const index = await this.loadIndex();
811
- const entry = index[path];
812
- if (!entry) return null;
813
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
814
- if (!fileHandle) return null;
815
- let buffer;
816
- if (this.useSync) {
817
- const release = await fileLock.acquire(PACK_FILE);
818
- try {
819
- const access = await fileHandle.createSyncAccessHandle();
820
- try {
821
- buffer = new Uint8Array(entry.size);
822
- access.read(buffer, { at: entry.offset });
823
- } finally {
824
- access.close();
825
- }
826
- } finally {
827
- release();
828
- }
829
- } else {
830
- const file = await fileHandle.getFile();
831
- const data = new Uint8Array(await file.arrayBuffer());
832
- buffer = data.slice(entry.offset, entry.offset + entry.size);
833
- }
834
- if (entry.originalSize !== void 0) {
835
- return decompress(buffer);
836
- }
837
- return buffer;
838
- }
839
- /**
840
- * Read multiple files from the pack in a single operation
841
- * Loads index once, reads all data in parallel
842
- * Handles decompression if files were stored compressed
843
- */
844
- async readBatch(paths) {
845
- const results = /* @__PURE__ */ new Map();
846
- if (paths.length === 0) return results;
847
- const index = await this.loadIndex();
848
- const toRead = [];
849
- for (const path of paths) {
850
- const entry = index[path];
851
- if (entry) {
852
- toRead.push({ path, offset: entry.offset, size: entry.size, originalSize: entry.originalSize });
853
- } else {
854
- results.set(path, null);
855
- }
856
- }
857
- if (toRead.length === 0) return results;
858
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
859
- if (!fileHandle) {
860
- for (const { path } of toRead) {
861
- results.set(path, null);
862
- }
863
- return results;
864
- }
865
- const decompressPromises = [];
866
- if (this.useSync) {
867
- const release = await fileLock.acquire(PACK_FILE);
868
- try {
869
- const access = await fileHandle.createSyncAccessHandle();
870
- try {
871
- for (const { path, offset, size, originalSize } of toRead) {
872
- const buffer = new Uint8Array(size);
873
- access.read(buffer, { at: offset });
874
- if (originalSize !== void 0) {
875
- decompressPromises.push({ path, promise: decompress(buffer) });
876
- } else {
877
- results.set(path, buffer);
878
- }
879
- }
880
- } finally {
881
- access.close();
882
- }
883
- } finally {
884
- release();
885
- }
886
- } else {
887
- const file = await fileHandle.getFile();
888
- const data = new Uint8Array(await file.arrayBuffer());
889
- for (const { path, offset, size, originalSize } of toRead) {
890
- const buffer = data.slice(offset, offset + size);
891
- if (originalSize !== void 0) {
892
- decompressPromises.push({ path, promise: decompress(buffer) });
893
- } else {
894
- results.set(path, buffer);
895
- }
896
- }
897
- }
898
- for (const { path, promise } of decompressPromises) {
899
- results.set(path, await promise);
900
- }
901
- return results;
902
- }
903
- /**
904
- * Write multiple files to the pack in a single operation
905
- * This is the key optimization - 100 files become 1 write!
906
- * Includes CRC32 checksum for integrity verification.
907
- * Optionally compresses data for smaller storage.
908
- * Note: This replaces the entire pack with the new entries
909
- */
910
- async writeBatch(entries) {
911
- if (entries.length === 0) return;
912
- const encoder = new TextEncoder();
913
- let processedEntries;
914
- if (this.useCompression) {
915
- processedEntries = await Promise.all(
916
- entries.map(async ({ path, data }) => {
917
- const compressed = await compress(data);
918
- if (compressed.length < data.length) {
919
- return { path, data: compressed, originalSize: data.length };
920
- }
921
- return { path, data };
922
- })
923
- );
924
- } else {
925
- processedEntries = entries;
926
- }
927
- let totalDataSize = 0;
928
- for (const { data } of processedEntries) {
929
- totalDataSize += data.length;
930
- }
931
- const newIndex = {};
932
- let headerSize = 8;
933
- let prevHeaderSize = 0;
934
- while (headerSize !== prevHeaderSize) {
935
- prevHeaderSize = headerSize;
936
- let currentOffset = headerSize;
937
- for (const { path, data, originalSize } of processedEntries) {
938
- const entry = { offset: currentOffset, size: data.length };
939
- if (originalSize !== void 0) {
940
- entry.originalSize = originalSize;
941
- }
942
- newIndex[path] = entry;
943
- currentOffset += data.length;
944
- }
945
- const indexBuf = encoder.encode(JSON.stringify(newIndex));
946
- headerSize = 8 + indexBuf.length;
947
- }
948
- const finalIndexBuf = encoder.encode(JSON.stringify(newIndex));
949
- const totalSize = headerSize + totalDataSize;
950
- const packBuffer = new Uint8Array(totalSize);
951
- const view = new DataView(packBuffer.buffer);
952
- packBuffer.set(finalIndexBuf, 8);
953
- for (const { path, data } of processedEntries) {
954
- const entry = newIndex[path];
955
- packBuffer.set(data, entry.offset);
956
- }
957
- const content = packBuffer.subarray(8);
958
- const checksum = this.useChecksum ? crc32(content) : 0;
959
- view.setUint32(0, finalIndexBuf.length, true);
960
- view.setUint32(4, checksum, true);
961
- await this.writePackFile(packBuffer);
962
- this.index = newIndex;
963
- }
964
- /**
965
- * Write the pack file to OPFS
966
- * Note: Caller must hold the lock
967
- */
968
- async writePackFile(data) {
969
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE, { create: true });
970
- if (!fileHandle) return;
971
- if (this.useSync) {
972
- const release = await fileLock.acquire(PACK_FILE);
973
- try {
974
- const access = await fileHandle.createSyncAccessHandle();
975
- try {
976
- access.truncate(data.length);
977
- access.write(data, { at: 0 });
978
- } finally {
979
- access.close();
980
- }
981
- } finally {
982
- release();
983
- }
984
- } else {
985
- const writable = await fileHandle.createWritable();
986
- await writable.write(data);
987
- await writable.close();
988
- }
989
- }
990
- /**
991
- * Remove a path from the pack index
992
- * Note: Doesn't reclaim space, just removes from index and recalculates CRC32
993
- */
994
- async remove(path) {
995
- const index = await this.loadIndex();
996
- if (!(path in index)) return false;
997
- delete index[path];
998
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
999
- if (!fileHandle) return true;
1000
- const encoder = new TextEncoder();
1001
- const newIndexBuf = encoder.encode(JSON.stringify(index));
1002
- if (this.useSync) {
1003
- const release = await fileLock.acquire(PACK_FILE);
1004
- try {
1005
- const access = await fileHandle.createSyncAccessHandle();
1006
- try {
1007
- const size = access.getSize();
1008
- const oldHeader = new Uint8Array(8);
1009
- access.read(oldHeader, { at: 0 });
1010
- const oldIndexLen = new DataView(oldHeader.buffer).getUint32(0, true);
1011
- const dataStart = 8 + oldIndexLen;
1012
- const dataSize = size - dataStart;
1013
- const dataPortion = new Uint8Array(dataSize);
1014
- if (dataSize > 0) {
1015
- access.read(dataPortion, { at: dataStart });
1016
- }
1017
- const newContent = new Uint8Array(newIndexBuf.length + dataSize);
1018
- newContent.set(newIndexBuf, 0);
1019
- if (dataSize > 0) {
1020
- newContent.set(dataPortion, newIndexBuf.length);
1021
- }
1022
- const checksum = this.useChecksum ? crc32(newContent) : 0;
1023
- const newHeader = new Uint8Array(8);
1024
- const view = new DataView(newHeader.buffer);
1025
- view.setUint32(0, newIndexBuf.length, true);
1026
- view.setUint32(4, checksum, true);
1027
- const newFile = new Uint8Array(8 + newContent.length);
1028
- newFile.set(newHeader, 0);
1029
- newFile.set(newContent, 8);
1030
- access.truncate(newFile.length);
1031
- access.write(newFile, { at: 0 });
1032
- } finally {
1033
- access.close();
1034
- }
1035
- } finally {
1036
- release();
1037
- }
1038
- } else {
1039
- const file = await fileHandle.getFile();
1040
- const oldData = new Uint8Array(await file.arrayBuffer());
1041
- if (oldData.length < 8) return true;
1042
- const oldIndexLen = new DataView(oldData.buffer).getUint32(0, true);
1043
- const dataStart = 8 + oldIndexLen;
1044
- const dataPortion = oldData.subarray(dataStart);
1045
- const newContent = new Uint8Array(newIndexBuf.length + dataPortion.length);
1046
- newContent.set(newIndexBuf, 0);
1047
- newContent.set(dataPortion, newIndexBuf.length);
1048
- const checksum = this.useChecksum ? crc32(newContent) : 0;
1049
- const newFile = new Uint8Array(8 + newContent.length);
1050
- const view = new DataView(newFile.buffer);
1051
- view.setUint32(0, newIndexBuf.length, true);
1052
- view.setUint32(4, checksum, true);
1053
- newFile.set(newContent, 8);
1054
- const writable = await fileHandle.createWritable();
1055
- await writable.write(newFile);
1056
- await writable.close();
1057
- }
1058
- return true;
1059
- }
1060
- /**
1061
- * Check if pack file is being used (has entries)
1062
- */
1063
- async isEmpty() {
1064
- const index = await this.loadIndex();
1065
- return Object.keys(index).length === 0;
1066
- }
1067
- };
1068
-
1069
- // src/file-handle.ts
1070
- function createFileHandle(resolvedPath, initialPosition, context) {
1071
- let position = initialPosition;
1072
- return {
1073
- fd: Math.floor(Math.random() * 1e6),
1074
- async read(buffer, offset = 0, length = buffer.length, pos = null) {
1075
- const readPos = pos !== null ? pos : position;
1076
- const data = await context.readFile(resolvedPath);
1077
- const bytesToRead = Math.min(length, data.length - readPos);
1078
- buffer.set(data.subarray(readPos, readPos + bytesToRead), offset);
1079
- if (pos === null) position += bytesToRead;
1080
- return { bytesRead: bytesToRead, buffer };
1081
- },
1082
- async write(buffer, offset = 0, length = buffer.length, pos = null) {
1083
- const writePos = pos !== null ? pos : position;
1084
- let existingData = new Uint8Array(0);
1085
- try {
1086
- existingData = await context.readFile(resolvedPath);
1087
- } catch (e) {
1088
- if (e.code !== "ENOENT") throw e;
1089
- }
1090
- const dataToWrite = buffer.subarray(offset, offset + length);
1091
- const newSize = Math.max(existingData.length, writePos + length);
1092
- const newData = new Uint8Array(newSize);
1093
- newData.set(existingData, 0);
1094
- newData.set(dataToWrite, writePos);
1095
- await context.writeFile(resolvedPath, newData);
1096
- if (pos === null) position += length;
1097
- return { bytesWritten: length, buffer };
1098
- },
1099
- async close() {
1100
- },
1101
- async stat() {
1102
- return context.stat(resolvedPath);
1103
- },
1104
- async truncate(len = 0) {
1105
- return context.truncate(resolvedPath, len);
1106
- },
1107
- async sync() {
1108
- },
1109
- async datasync() {
1110
- },
1111
- async readFile(options) {
1112
- return context.readFile(resolvedPath, options);
1113
- },
1114
- async writeFile(data, options) {
1115
- return context.writeFile(resolvedPath, data, options);
1116
- },
1117
- async appendFile(data, options) {
1118
- return context.appendFile(resolvedPath, data, options);
1119
- },
1120
- [Symbol.asyncDispose]: async function() {
1121
- }
1122
- };
1123
- }
1124
-
1125
- // src/streams.ts
1126
- function createReadStream(path, options, context) {
1127
- const { start = 0, end = Infinity, highWaterMark = 64 * 1024 } = options;
1128
- let position = start;
1129
- let closed = false;
1130
- let cachedData = null;
1131
- return new ReadableStream({
1132
- async pull(controller) {
1133
- if (closed) {
1134
- controller.close();
1135
- return;
1136
- }
1137
- try {
1138
- if (cachedData === null) {
1139
- cachedData = await context.readFile(path);
1140
- }
1141
- const endPos = Math.min(end, cachedData.length);
1142
- const chunk = cachedData.subarray(position, Math.min(position + highWaterMark, endPos));
1143
- if (chunk.length === 0 || position >= endPos) {
1144
- controller.close();
1145
- closed = true;
1146
- cachedData = null;
1147
- return;
1148
- }
1149
- position += chunk.length;
1150
- controller.enqueue(chunk);
1151
- } catch (err) {
1152
- controller.error(err);
1153
- }
1154
- },
1155
- cancel() {
1156
- closed = true;
1157
- cachedData = null;
1158
- }
1159
- });
1160
- }
1161
- function createWriteStream(path, options, context) {
1162
- const { flags = "w", start = 0 } = options;
1163
- const chunks = [];
1164
- let position = start;
1165
- return new WritableStream({
1166
- async write(chunk) {
1167
- chunks.push({ data: chunk, position });
1168
- position += chunk.length;
1169
- },
1170
- async close() {
1171
- let existingData = new Uint8Array(0);
1172
- if (!flags.includes("w")) {
1173
- try {
1174
- existingData = await context.readFile(path);
1175
- } catch (e) {
1176
- if (e.code !== "ENOENT") throw e;
1177
- }
1178
- }
1179
- let maxSize = existingData.length;
1180
- for (const { data, position: position2 } of chunks) {
1181
- maxSize = Math.max(maxSize, position2 + data.length);
1182
- }
1183
- const finalData = new Uint8Array(maxSize);
1184
- if (!flags.includes("w")) {
1185
- finalData.set(existingData, 0);
1186
- }
1187
- for (const { data, position: position2 } of chunks) {
1188
- finalData.set(data, position2);
1189
- }
1190
- await context.writeFile(path, finalData);
1191
- }
1192
- });
1193
- }
1194
-
1195
- // src/opfs-worker-proxy.ts
1196
- var OPFSWorker = class {
1197
- worker = null;
1198
- pendingRequests = /* @__PURE__ */ new Map();
1199
- nextId = 1;
1200
- readyPromise;
1201
- readyResolve;
1202
- /** File system constants */
1203
- constants = constants;
1204
- constructor(options = {}) {
1205
- this.readyPromise = new Promise((resolve) => {
1206
- this.readyResolve = resolve;
1207
- });
1208
- this.initWorker(options);
1209
- }
1210
- initWorker(options) {
1211
- const { workerUrl, workerOptions = { type: "module" } } = options;
1212
- if (workerUrl) {
1213
- this.worker = new Worker(workerUrl, workerOptions);
1214
- } else {
1215
- throw new Error(
1216
- 'OPFSWorker requires a workerUrl option pointing to the worker script. Example: new OPFSWorker({ workerUrl: new URL("./opfs-worker.js", import.meta.url) })'
1217
- );
1218
- }
1219
- this.worker.onmessage = (event) => {
1220
- const { id, type, result, error } = event.data;
1221
- if (type === "ready") {
1222
- this.readyResolve();
1223
- return;
1224
- }
1225
- if (id !== void 0) {
1226
- const pending = this.pendingRequests.get(id);
1227
- if (pending) {
1228
- this.pendingRequests.delete(id);
1229
- if (error) {
1230
- const fsError = new FSError(error.message, error.code || "UNKNOWN");
1231
- pending.reject(fsError);
1232
- } else {
1233
- pending.resolve(result);
1234
- }
1235
- }
1236
- }
1237
- };
1238
- this.worker.onerror = (event) => {
1239
- console.error("[OPFSWorker] Worker error:", event);
1240
- };
1241
- }
1242
- /**
1243
- * Wait for the worker to be ready
1244
- */
1245
- async ready() {
1246
- return this.readyPromise;
1247
- }
1248
- /**
1249
- * Terminate the worker
1250
- */
1251
- terminate() {
1252
- if (this.worker) {
1253
- this.worker.terminate();
1254
- this.worker = null;
1255
- for (const [, pending] of this.pendingRequests) {
1256
- pending.reject(new Error("Worker terminated"));
1257
- }
1258
- this.pendingRequests.clear();
1259
- }
1260
- }
1261
- call(method, args, transfer) {
1262
- return new Promise((resolve, reject) => {
1263
- if (!this.worker) {
1264
- reject(new Error("Worker not initialized or terminated"));
1265
- return;
1266
- }
1267
- const id = this.nextId++;
1268
- this.pendingRequests.set(id, {
1269
- resolve,
1270
- reject
1271
- });
1272
- const message = { id, method, args };
1273
- if (transfer && transfer.length > 0) {
1274
- this.worker.postMessage(message, transfer);
1275
- } else {
1276
- this.worker.postMessage(message);
1277
- }
1278
- });
1279
- }
1280
- // File operations
1281
- async readFile(path, options) {
1282
- const result = await this.call("readFile", [path, options]);
1283
- return result;
1284
- }
1285
- async writeFile(path, data, options) {
1286
- await this.call("writeFile", [path, data, options]);
1287
- }
1288
- async readFileBatch(paths) {
1289
- return this.call("readFileBatch", [paths]);
1290
- }
1291
- async writeFileBatch(entries) {
1292
- await this.call("writeFileBatch", [entries]);
1293
- }
1294
- async appendFile(path, data, options) {
1295
- await this.call("appendFile", [path, data, options]);
1296
- }
1297
- async copyFile(src, dest, mode) {
1298
- await this.call("copyFile", [src, dest, mode]);
1299
- }
1300
- async unlink(path) {
1301
- await this.call("unlink", [path]);
1302
- }
1303
- async truncate(path, len) {
1304
- await this.call("truncate", [path, len]);
1305
- }
1306
- // Directory operations
1307
- async mkdir(path) {
1308
- await this.call("mkdir", [path]);
1309
- }
1310
- async rmdir(path) {
1311
- await this.call("rmdir", [path]);
1312
- }
1313
- async readdir(path, options) {
1314
- const result = await this.call("readdir", [path, options]);
1315
- if (options?.withFileTypes && Array.isArray(result)) {
1316
- return result.map((item) => {
1317
- if (typeof item === "object" && "name" in item) {
1318
- const entry = item;
1319
- return {
1320
- name: entry.name,
1321
- isFile: () => entry._isFile ?? false,
1322
- isDirectory: () => entry._isDir ?? false,
1323
- isSymbolicLink: () => entry._isSymlink ?? false
1324
- };
1325
- }
1326
- return item;
1327
- });
1328
- }
1329
- return result;
1330
- }
1331
- async cp(src, dest, options) {
1332
- await this.call("cp", [src, dest, options]);
1333
- }
1334
- async rm(path, options) {
1335
- await this.call("rm", [path, options]);
1336
- }
1337
- // Stat operations
1338
- async stat(path) {
1339
- const result = await this.call("stat", [path]);
1340
- return this.deserializeStats(result);
1341
- }
1342
- async lstat(path) {
1343
- const result = await this.call("lstat", [path]);
1344
- return this.deserializeStats(result);
1345
- }
1346
- deserializeStats(data) {
1347
- const ctime = new Date(data.ctime);
1348
- const mtime = new Date(data.mtime);
1349
- return {
1350
- type: data.type,
1351
- size: data.size,
1352
- mode: data.mode,
1353
- ctime,
1354
- ctimeMs: data.ctimeMs,
1355
- mtime,
1356
- mtimeMs: data.mtimeMs,
1357
- target: data.target,
1358
- isFile: () => data.type === "file",
1359
- isDirectory: () => data.type === "dir",
1360
- isSymbolicLink: () => data.type === "symlink"
1361
- };
1362
- }
1363
- async exists(path) {
1364
- return this.call("exists", [path]);
1365
- }
1366
- async access(path, mode) {
1367
- await this.call("access", [path, mode]);
1368
- }
1369
- async statfs(path) {
1370
- return this.call("statfs", [path]);
1371
- }
1372
- async du(path) {
1373
- return this.call("du", [path]);
1374
- }
1375
- // Symlink operations
1376
- async symlink(target, path) {
1377
- await this.call("symlink", [target, path]);
1378
- }
1379
- async readlink(path) {
1380
- return this.call("readlink", [path]);
1381
- }
1382
- async symlinkBatch(links) {
1383
- await this.call("symlinkBatch", [links]);
1384
- }
1385
- async realpath(path) {
1386
- return this.call("realpath", [path]);
1387
- }
1388
- // Other operations
1389
- async rename(oldPath, newPath) {
1390
- await this.call("rename", [oldPath, newPath]);
1391
- }
1392
- async mkdtemp(prefix) {
1393
- return this.call("mkdtemp", [prefix]);
1394
- }
1395
- async chmod(path, mode) {
1396
- await this.call("chmod", [path, mode]);
1397
- }
1398
- async chown(path, uid, gid) {
1399
- await this.call("chown", [path, uid, gid]);
1400
- }
1401
- async utimes(path, atime, mtime) {
1402
- await this.call("utimes", [path, atime, mtime]);
1403
- }
1404
- async lutimes(path, atime, mtime) {
1405
- await this.call("lutimes", [path, atime, mtime]);
1406
- }
1407
- /**
1408
- * Reset internal caches to free memory
1409
- * Useful for long-running benchmarks or after bulk operations
1410
- */
1411
- async resetCache() {
1412
- await this.call("resetCache", []);
1413
- }
1414
- /**
1415
- * Force full garbage collection by reinitializing the OPFS instance in the worker
1416
- * This completely releases all handles and caches, preventing memory leaks in long-running operations
1417
- * More aggressive than resetCache() - use when resetCache() isn't sufficient
1418
- */
1419
- async gc() {
1420
- await this.call("gc", []);
1421
- }
1422
- };
1423
-
1424
- // src/opfs-hybrid.ts
1425
- var OPFSHybrid = class {
1426
- mainFs;
1427
- workerFs = null;
1428
- readBackend;
1429
- writeBackend;
1430
- workerUrl;
1431
- workerReady = null;
1432
- verbose;
1433
- constructor(options = {}) {
1434
- this.readBackend = options.read ?? "main";
1435
- this.writeBackend = options.write ?? "worker";
1436
- this.workerUrl = options.workerUrl;
1437
- this.verbose = options.verbose ?? false;
1438
- this.mainFs = new OPFS({ useSync: false, verbose: this.verbose });
1439
- if (this.readBackend === "worker" || this.writeBackend === "worker") {
1440
- if (!this.workerUrl) {
1441
- throw new Error("workerUrl is required when using worker backend");
1442
- }
1443
- this.workerFs = new OPFSWorker({ workerUrl: this.workerUrl });
1444
- this.workerReady = this.workerFs.ready();
1445
- }
1446
- }
1447
- /**
1448
- * Wait for all backends to be ready
1449
- */
1450
- async ready() {
1451
- if (this.workerReady) {
1452
- await this.workerReady;
1453
- }
1454
- }
1455
- /**
1456
- * Terminate worker if active
1457
- */
1458
- terminate() {
1459
- if (this.workerFs) {
1460
- this.workerFs.terminate();
1461
- this.workerFs = null;
1462
- }
1463
- }
1464
- getReadFs() {
1465
- if (this.readBackend === "worker" && this.workerFs) {
1466
- return this.workerFs;
1467
- }
1468
- return this.mainFs;
1469
- }
1470
- getWriteFs() {
1471
- if (this.writeBackend === "worker" && this.workerFs) {
1472
- return this.workerFs;
1473
- }
1474
- return this.mainFs;
1475
- }
1476
- // ============ Read Operations ============
1477
- async readFile(path, options) {
1478
- return this.getReadFs().readFile(path, options);
1479
- }
1480
- async readFileBatch(paths) {
1481
- return this.getReadFs().readFileBatch(paths);
1482
- }
1483
- async readdir(path, options) {
1484
- return this.getReadFs().readdir(path, options);
1485
- }
1486
- async stat(path) {
1487
- return this.getReadFs().stat(path);
1488
- }
1489
- async lstat(path) {
1490
- return this.getReadFs().lstat(path);
1491
- }
1492
- async exists(path) {
1493
- return this.getReadFs().exists(path);
1494
- }
1495
- async access(path, mode) {
1496
- return this.getReadFs().access(path, mode);
1497
- }
1498
- async readlink(path) {
1499
- return this.getReadFs().readlink(path);
1500
- }
1501
- async realpath(path) {
1502
- return this.getReadFs().realpath(path);
1503
- }
1504
- async statfs(path) {
1505
- return this.getReadFs().statfs(path);
1506
- }
1507
- async du(path) {
1508
- return this.getReadFs().du(path);
1509
- }
1510
- // ============ Write Operations ============
1511
- async writeFile(path, data, options) {
1512
- return this.getWriteFs().writeFile(path, data, options);
1513
- }
1514
- async writeFileBatch(entries) {
1515
- return this.getWriteFs().writeFileBatch(entries);
1516
- }
1517
- async appendFile(path, data, options) {
1518
- return this.getWriteFs().appendFile(path, data, options);
1519
- }
1520
- async mkdir(path) {
1521
- return this.getWriteFs().mkdir(path);
1522
- }
1523
- async rmdir(path) {
1524
- if (this.readBackend !== this.writeBackend && this.workerFs) {
1525
- await this.workerFs.rmdir(path);
1526
- this.mainFs.resetCache();
1527
- } else {
1528
- return this.getWriteFs().rmdir(path);
1529
- }
1530
- }
1531
- async unlink(path) {
1532
- return this.getWriteFs().unlink(path);
1533
- }
1534
- async truncate(path, len) {
1535
- return this.getWriteFs().truncate(path, len);
1536
- }
1537
- async symlink(target, path) {
1538
- if (this.readBackend !== this.writeBackend && this.workerFs) {
1539
- await this.workerFs.symlink(target, path);
1540
- this.mainFs.resetCache();
1541
- } else {
1542
- return this.getWriteFs().symlink(target, path);
1543
- }
1544
- }
1545
- async symlinkBatch(symlinks) {
1546
- if (this.readBackend !== this.writeBackend && this.workerFs) {
1547
- await this.workerFs.symlinkBatch(symlinks);
1548
- this.mainFs.resetCache();
1549
- } else {
1550
- return this.getWriteFs().symlinkBatch(symlinks);
1551
- }
1552
- }
1553
- async rename(oldPath, newPath) {
1554
- return this.getWriteFs().rename(oldPath, newPath);
1555
- }
1556
- async copyFile(src, dest, mode) {
1557
- return this.getWriteFs().copyFile(src, dest, mode);
1558
- }
1559
- async cp(src, dest, options) {
1560
- return this.getWriteFs().cp(src, dest, options);
1561
- }
1562
- async rm(path, options) {
1563
- return this.getWriteFs().rm(path, options);
1564
- }
1565
- async chmod(path, mode) {
1566
- return this.getWriteFs().chmod(path, mode);
1567
- }
1568
- async chown(path, uid, gid) {
1569
- return this.getWriteFs().chown(path, uid, gid);
1570
- }
1571
- async utimes(path, atime, mtime) {
1572
- return this.getWriteFs().utimes(path, atime, mtime);
1573
- }
1574
- async lutimes(path, atime, mtime) {
1575
- return this.getWriteFs().lutimes(path, atime, mtime);
1576
- }
1577
- async mkdtemp(prefix) {
1578
- return this.getWriteFs().mkdtemp(prefix);
1579
- }
1580
- /**
1581
- * Reset internal caches on both backends
1582
- */
1583
- async resetCache() {
1584
- this.mainFs.resetCache();
1585
- if (this.workerFs) {
1586
- await this.workerFs.resetCache();
1587
- }
1588
- }
1589
- /**
1590
- * Force full garbage collection on both backends
1591
- * More aggressive than resetCache() - reinitializes the worker's OPFS instance
1592
- */
1593
- async gc() {
1594
- this.mainFs.resetCache();
1595
- if (this.workerFs) {
1596
- await this.workerFs.gc();
1597
- }
1598
- }
1599
- };
1600
-
1601
- // src/index.ts
1602
- var OPFS = class {
1603
- useSync;
1604
- verbose;
1605
- handleManager;
1606
- symlinkManager;
1607
- packedStorage;
1608
- watchCallbacks = /* @__PURE__ */ new Map();
1609
- tmpCounter = 0;
1610
- /** Hybrid instance when workerUrl is provided */
1611
- hybrid = null;
1612
- /** File system constants */
1613
- constants = constants;
1614
- constructor(options = {}) {
1615
- const { useSync = true, verbose = false, useCompression = false, useChecksum = true, workerUrl, read, write } = options;
1616
- this.verbose = verbose;
1617
- if (workerUrl) {
1618
- this.hybrid = new OPFSHybrid({
1619
- workerUrl,
1620
- read: read ?? "main",
1621
- write: write ?? "worker",
1622
- verbose
1623
- });
1624
- this.useSync = false;
1625
- this.handleManager = new HandleManager();
1626
- this.symlinkManager = new SymlinkManager(this.handleManager, false);
1627
- this.packedStorage = new PackedStorage(this.handleManager, false, useCompression, useChecksum);
1628
- } else {
1629
- this.useSync = useSync && typeof FileSystemFileHandle !== "undefined" && "createSyncAccessHandle" in FileSystemFileHandle.prototype;
1630
- this.handleManager = new HandleManager();
1631
- this.symlinkManager = new SymlinkManager(this.handleManager, this.useSync);
1632
- this.packedStorage = new PackedStorage(this.handleManager, this.useSync, useCompression, useChecksum);
1633
- }
1634
- }
1635
- /**
1636
- * Wait for the filesystem to be ready (only needed for hybrid mode)
1637
- */
1638
- async ready() {
1639
- if (this.hybrid) {
1640
- await this.hybrid.ready();
1641
- }
1642
- }
1643
- /**
1644
- * Terminate any background workers (only needed for hybrid mode)
1645
- */
1646
- terminate() {
1647
- if (this.hybrid) {
1648
- this.hybrid.terminate();
1649
- }
1650
- }
1651
- log(method, ...args) {
1652
- if (this.verbose) {
1653
- console.log(`[OPFS] ${method}:`, ...args);
1654
- }
1655
- }
1656
- logError(method, err) {
1657
- if (this.verbose) {
1658
- console.error(`[OPFS] ${method} error:`, err);
1659
- }
1660
- }
1661
- /**
1662
- * Execute tasks with limited concurrency to avoid overwhelming the system
1663
- * @param items - Array of items to process
1664
- * @param maxConcurrent - Maximum number of concurrent operations (default: 10)
1665
- * @param taskFn - Function to execute for each item
1666
- */
1667
- async limitConcurrency(items, maxConcurrent, taskFn) {
1668
- if (items.length === 0) return;
1669
- if (items.length <= 2) {
1670
- for (const item of items) {
1671
- await taskFn(item);
1672
- }
1673
- return;
1674
- }
1675
- if (items.length <= maxConcurrent) {
1676
- await Promise.all(items.map(taskFn));
1677
- return;
1678
- }
1679
- const queue = [...items];
1680
- const workers = Array.from({ length: maxConcurrent }).map(async () => {
1681
- while (queue.length) {
1682
- const item = queue.shift();
1683
- if (item !== void 0) await taskFn(item);
1684
- }
1685
- });
1686
- await Promise.all(workers);
1687
- }
1688
- /**
1689
- * Read file contents
1690
- */
1691
- async readFile(path, options = {}) {
1692
- if (this.hybrid) {
1693
- return this.hybrid.readFile(path, options);
1694
- }
1695
- this.log("readFile", path, options);
1696
- try {
1697
- const normalizedPath = normalize(path);
1698
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
1699
- let fileHandle = null;
1700
- try {
1701
- fileHandle = await this.handleManager.getPooledFileHandle(resolvedPath);
1702
- } catch {
1703
- }
1704
- if (fileHandle) {
1705
- let buffer;
1706
- if (this.useSync) {
1707
- const access = await fileHandle.createSyncAccessHandle();
1708
- try {
1709
- const size = access.getSize();
1710
- buffer = new Uint8Array(size);
1711
- access.read(buffer);
1712
- } finally {
1713
- access.close();
1714
- }
1715
- } else {
1716
- const file = await fileHandle.getFile();
1717
- buffer = new Uint8Array(await file.arrayBuffer());
1718
- }
1719
- return options.encoding ? new TextDecoder(options.encoding).decode(buffer) : buffer;
1720
- }
1721
- const packedData = await this.packedStorage.read(resolvedPath);
1722
- if (packedData) {
1723
- return options.encoding ? new TextDecoder(options.encoding).decode(packedData) : packedData;
1724
- }
1725
- throw createENOENT(path);
1726
- } catch (err) {
1727
- this.logError("readFile", err);
1728
- throw wrapError(err);
1729
- }
1730
- }
1731
- /**
1732
- * Read multiple files efficiently in a batch operation
1733
- * Uses packed storage batch read (single index load), falls back to individual files
1734
- * Returns results in the same order as input paths
1735
- */
1736
- async readFileBatch(paths) {
1737
- if (this.hybrid) {
1738
- return this.hybrid.readFileBatch(paths);
1739
- }
1740
- this.log("readFileBatch", `${paths.length} files`);
1741
- if (paths.length === 0) return [];
1742
- try {
1743
- const resolvedPaths = await Promise.all(
1744
- paths.map(async (path) => {
1745
- const normalizedPath = normalize(path);
1746
- return this.symlinkManager.resolve(normalizedPath);
1747
- })
1748
- );
1749
- const packedResults = await this.packedStorage.readBatch(resolvedPaths);
1750
- const results = new Array(paths.length);
1751
- const needsIndividualRead = [];
1752
- for (let i = 0; i < paths.length; i++) {
1753
- const packedData = packedResults.get(resolvedPaths[i]);
1754
- if (packedData) {
1755
- results[i] = { path: paths[i], data: packedData };
1756
- } else {
1757
- needsIndividualRead.push({ index: i, resolvedPath: resolvedPaths[i] });
1758
- }
1759
- }
1760
- if (needsIndividualRead.length > 0) {
1761
- await Promise.all(
1762
- needsIndividualRead.map(async ({ index, resolvedPath }) => {
1763
- try {
1764
- const fileHandle = await this.handleManager.getPooledFileHandle(resolvedPath);
1765
- if (!fileHandle) {
1766
- results[index] = { path: paths[index], data: null, error: createENOENT(paths[index]) };
1767
- return;
1768
- }
1769
- let buffer;
1770
- if (this.useSync) {
1771
- const access = await fileHandle.createSyncAccessHandle();
1772
- try {
1773
- const size = access.getSize();
1774
- buffer = new Uint8Array(size);
1775
- access.read(buffer);
1776
- } finally {
1777
- access.close();
1778
- }
1779
- } else {
1780
- const file = await fileHandle.getFile();
1781
- buffer = new Uint8Array(await file.arrayBuffer());
1782
- }
1783
- results[index] = { path: paths[index], data: buffer };
1784
- } catch (err) {
1785
- results[index] = { path: paths[index], data: null, error: err };
1786
- }
1787
- })
1788
- );
1789
- }
1790
- return results;
1791
- } catch (err) {
1792
- this.logError("readFileBatch", err);
1793
- throw wrapError(err);
1794
- }
1795
- }
1796
- /**
1797
- * Write data to a file
1798
- */
1799
- async writeFile(path, data, options = {}) {
1800
- if (this.hybrid) {
1801
- return this.hybrid.writeFile(path, data, options);
1802
- }
1803
- this.log("writeFile", path);
1804
- try {
1805
- const normalizedPath = normalize(path);
1806
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
1807
- const { fileHandle } = await this.handleManager.getHandle(resolvedPath, { create: true });
1808
- const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
1809
- if (this.useSync) {
1810
- const access = await fileHandle.createSyncAccessHandle();
1811
- try {
1812
- access.truncate(buffer.length);
1813
- access.write(buffer, { at: 0 });
1814
- } finally {
1815
- access.close();
1816
- }
1817
- } else {
1818
- const writable = await fileHandle.createWritable();
1819
- await writable.write(buffer);
1820
- await writable.close();
1821
- }
1822
- } catch (err) {
1823
- this.logError("writeFile", err);
1824
- throw wrapError(err);
1825
- }
1826
- }
1827
- /**
1828
- * Write multiple files efficiently in a batch operation
1829
- * Uses packed storage (single file) for maximum performance
1830
- */
1831
- async writeFileBatch(entries) {
1832
- if (this.hybrid) {
1833
- return this.hybrid.writeFileBatch(entries);
1834
- }
1835
- this.log("writeFileBatch", `${entries.length} files`);
1836
- if (entries.length === 0) return;
1837
- try {
1838
- const encoder = new TextEncoder();
1839
- const packEntries = await Promise.all(
1840
- entries.map(async ({ path, data }) => {
1841
- const normalizedPath = normalize(path);
1842
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
1843
- return {
1844
- path: resolvedPath,
1845
- data: typeof data === "string" ? encoder.encode(data) : data
1846
- };
1847
- })
1848
- );
1849
- await this.packedStorage.writeBatch(packEntries);
1850
- } catch (err) {
1851
- this.logError("writeFileBatch", err);
1852
- throw wrapError(err);
1853
- }
1854
- }
1855
- /**
1856
- * Create a directory
1857
- */
1858
- async mkdir(path) {
1859
- if (this.hybrid) {
1860
- return this.hybrid.mkdir(path);
1861
- }
1862
- this.log("mkdir", path);
1863
- try {
1864
- await this.handleManager.mkdir(path);
1865
- } catch (err) {
1866
- this.logError("mkdir", err);
1867
- throw wrapError(err);
1868
- }
1869
- }
1870
- /**
1871
- * Remove a directory
1872
- */
1873
- async rmdir(path) {
1874
- if (this.hybrid) {
1875
- return this.hybrid.rmdir(path);
1876
- }
1877
- this.log("rmdir", path);
1878
- try {
1879
- const normalizedPath = normalize(path);
1880
- this.handleManager.clearCache(normalizedPath);
1881
- if (isRoot(normalizedPath)) {
1882
- const root = await this.handleManager.getRoot();
1883
- const entries = [];
1884
- for await (const [name2] of root.entries()) {
1885
- entries.push(name2);
1886
- }
1887
- await this.limitConcurrency(
1888
- entries,
1889
- 10,
1890
- (name2) => root.removeEntry(name2, { recursive: true })
1891
- );
1892
- this.symlinkManager.reset();
1893
- this.packedStorage.reset();
1894
- return;
1895
- }
1896
- const pathSegments = segments(normalizedPath);
1897
- const name = pathSegments.pop();
1898
- let dir = await this.handleManager.getRoot();
1899
- for (const part of pathSegments) {
1900
- dir = await dir.getDirectoryHandle(part);
1901
- }
1902
- try {
1903
- await dir.removeEntry(name, { recursive: true });
1904
- } catch {
1905
- throw createENOENT(path);
1906
- }
1907
- } catch (err) {
1908
- this.logError("rmdir", err);
1909
- throw wrapError(err);
1910
- }
1911
- }
1912
- /**
1913
- * Remove a file or symlink
1914
- */
1915
- async unlink(path) {
1916
- if (this.hybrid) {
1917
- return this.hybrid.unlink(path);
1918
- }
1919
- this.log("unlink", path);
1920
- try {
1921
- const normalizedPath = normalize(path);
1922
- this.handleManager.clearCache(normalizedPath);
1923
- const isSymlink = await this.symlinkManager.isSymlink(normalizedPath);
1924
- if (isSymlink) {
1925
- await this.symlinkManager.unlink(normalizedPath);
1926
- return;
1927
- }
1928
- const inPack = await this.packedStorage.has(normalizedPath);
1929
- if (inPack) {
1930
- await this.packedStorage.remove(normalizedPath);
1931
- return;
1932
- }
1933
- const { dir, name, fileHandle } = await this.handleManager.getHandle(normalizedPath);
1934
- if (!fileHandle) throw createENOENT(path);
1935
- try {
1936
- await dir.removeEntry(name);
1937
- } catch {
1938
- throw createENOENT(path);
1939
- }
1940
- } catch (err) {
1941
- this.logError("unlink", err);
1942
- throw wrapError(err);
1943
- }
1944
- }
1945
- /**
1946
- * Read directory contents
1947
- */
1948
- async readdir(path, options) {
1949
- if (this.hybrid) {
1950
- return this.hybrid.readdir(path, options);
1951
- }
1952
- this.log("readdir", path, options);
1953
- try {
1954
- const normalizedPath = normalize(path);
1955
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
1956
- const dir = await this.handleManager.getDirectoryHandle(resolvedPath);
1957
- const withFileTypes = options?.withFileTypes === true;
1958
- const symlinksInDir = await this.symlinkManager.getSymlinksInDir(resolvedPath);
1959
- const hasSymlinks = symlinksInDir.length > 0;
1960
- const symlinkSet = hasSymlinks ? new Set(symlinksInDir) : null;
1961
- const entryNames = /* @__PURE__ */ new Set();
1962
- const entries = [];
1963
- for await (const [name, handle] of dir.entries()) {
1964
- if (this.symlinkManager.isMetadataFile(name)) continue;
1965
- entryNames.add(name);
1966
- if (withFileTypes) {
1967
- const isSymlink = hasSymlinks && symlinkSet.has(name);
1968
- entries.push({
1969
- name,
1970
- isFile: () => !isSymlink && handle.kind === "file",
1971
- isDirectory: () => !isSymlink && handle.kind === "directory",
1972
- isSymbolicLink: () => isSymlink
1973
- });
1974
- } else {
1975
- entries.push(name);
1976
- }
1977
- }
1978
- if (hasSymlinks) {
1979
- for (const name of symlinksInDir) {
1980
- if (!entryNames.has(name)) {
1981
- if (withFileTypes) {
1982
- entries.push({
1983
- name,
1984
- isFile: () => false,
1985
- isDirectory: () => false,
1986
- isSymbolicLink: () => true
1987
- });
1988
- } else {
1989
- entries.push(name);
1990
- }
1991
- }
1992
- }
1993
- }
1994
- return entries;
1995
- } catch (err) {
1996
- this.logError("readdir", err);
1997
- throw wrapError(err);
1998
- }
1999
- }
2000
- /**
2001
- * Get file/directory statistics (follows symlinks)
2002
- */
2003
- async stat(path) {
2004
- if (this.hybrid) {
2005
- return this.hybrid.stat(path);
2006
- }
2007
- this.log("stat", path);
2008
- try {
2009
- const normalizedPath = normalize(path);
2010
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
2011
- const defaultDate = /* @__PURE__ */ new Date(0);
2012
- if (isRoot(resolvedPath)) {
2013
- return {
2014
- type: "dir",
2015
- size: 0,
2016
- mode: 16877,
2017
- ctime: defaultDate,
2018
- ctimeMs: 0,
2019
- mtime: defaultDate,
2020
- mtimeMs: 0,
2021
- isFile: () => false,
2022
- isDirectory: () => true,
2023
- isSymbolicLink: () => false
2024
- };
2025
- }
2026
- const pathSegments = segments(resolvedPath);
2027
- const name = pathSegments.pop();
2028
- let dir = await this.handleManager.getRoot();
2029
- for (const part of pathSegments) {
2030
- try {
2031
- dir = await dir.getDirectoryHandle(part);
2032
- } catch {
2033
- throw createENOENT(path);
2034
- }
2035
- }
2036
- const [fileResult, dirResult] = await Promise.allSettled([
2037
- dir.getFileHandle(name),
2038
- dir.getDirectoryHandle(name)
2039
- ]);
2040
- if (fileResult.status === "fulfilled") {
2041
- const fileHandle = fileResult.value;
2042
- const file = await fileHandle.getFile();
2043
- const mtime = file.lastModified ? new Date(file.lastModified) : defaultDate;
2044
- return {
2045
- type: "file",
2046
- size: file.size,
2047
- mode: 33188,
2048
- ctime: mtime,
2049
- ctimeMs: mtime.getTime(),
2050
- mtime,
2051
- mtimeMs: mtime.getTime(),
2052
- isFile: () => true,
2053
- isDirectory: () => false,
2054
- isSymbolicLink: () => false
2055
- };
2056
- }
2057
- if (dirResult.status === "fulfilled") {
2058
- return {
2059
- type: "dir",
2060
- size: 0,
2061
- mode: 16877,
2062
- ctime: defaultDate,
2063
- ctimeMs: 0,
2064
- mtime: defaultDate,
2065
- mtimeMs: 0,
2066
- isFile: () => false,
2067
- isDirectory: () => true,
2068
- isSymbolicLink: () => false
2069
- };
2070
- }
2071
- const packedSize = await this.packedStorage.getSize(resolvedPath);
2072
- if (packedSize !== null) {
2073
- return {
2074
- type: "file",
2075
- size: packedSize,
2076
- mode: 33188,
2077
- ctime: defaultDate,
2078
- ctimeMs: 0,
2079
- mtime: defaultDate,
2080
- mtimeMs: 0,
2081
- isFile: () => true,
2082
- isDirectory: () => false,
2083
- isSymbolicLink: () => false
2084
- };
2085
- }
2086
- throw createENOENT(path);
2087
- } catch (err) {
2088
- this.logError("stat", err);
2089
- throw wrapError(err);
2090
- }
2091
- }
2092
- /**
2093
- * Get file/directory statistics (does not follow symlinks)
2094
- */
2095
- async lstat(path) {
2096
- if (this.hybrid) {
2097
- return this.hybrid.lstat(path);
2098
- }
2099
- this.log("lstat", path);
2100
- try {
2101
- const normalizedPath = normalize(path);
2102
- const isSymlink = await this.symlinkManager.isSymlink(normalizedPath);
2103
- if (isSymlink) {
2104
- const target = await this.symlinkManager.readlink(normalizedPath);
2105
- return {
2106
- type: "symlink",
2107
- target,
2108
- size: target.length,
2109
- mode: 41471,
2110
- ctime: /* @__PURE__ */ new Date(0),
2111
- ctimeMs: 0,
2112
- mtime: /* @__PURE__ */ new Date(0),
2113
- mtimeMs: 0,
2114
- isFile: () => false,
2115
- isDirectory: () => false,
2116
- isSymbolicLink: () => true
2117
- };
2118
- }
2119
- return this.stat(path);
2120
- } catch (err) {
2121
- this.logError("lstat", err);
2122
- throw wrapError(err);
2123
- }
2124
- }
2125
- /**
2126
- * Rename a file or directory
2127
- */
2128
- async rename(oldPath, newPath) {
2129
- if (this.hybrid) {
2130
- return this.hybrid.rename(oldPath, newPath);
2131
- }
2132
- this.log("rename", oldPath, newPath);
2133
- try {
2134
- const normalizedOld = normalize(oldPath);
2135
- const normalizedNew = normalize(newPath);
2136
- this.handleManager.clearCache(normalizedOld);
2137
- this.handleManager.clearCache(normalizedNew);
2138
- const renamed = await this.symlinkManager.rename(normalizedOld, normalizedNew);
2139
- if (renamed) return;
2140
- const stat = await this.stat(normalizedOld);
2141
- if (stat.isFile()) {
2142
- const [data] = await Promise.all([
2143
- this.readFile(normalizedOld),
2144
- this.handleManager.ensureParentDir(normalizedNew)
2145
- ]);
2146
- await this.writeFile(normalizedNew, data);
2147
- await this.unlink(normalizedOld);
2148
- } else if (stat.isDirectory()) {
2149
- await this.mkdir(normalizedNew);
2150
- const entries = await this.readdir(normalizedOld);
2151
- await this.limitConcurrency(
2152
- entries,
2153
- 10,
2154
- (entry) => this.rename(`${normalizedOld}/${entry}`, `${normalizedNew}/${entry}`)
2155
- );
2156
- await this.rmdir(normalizedOld);
2157
- }
2158
- } catch (err) {
2159
- this.logError("rename", err);
2160
- throw wrapError(err);
2161
- }
2162
- }
2163
- /**
2164
- * Create a symbolic link
2165
- */
2166
- async symlink(target, path) {
2167
- if (this.hybrid) {
2168
- return this.hybrid.symlink(target, path);
2169
- }
2170
- this.log("symlink", target, path);
2171
- try {
2172
- const normalizedPath = normalize(path);
2173
- this.handleManager.clearCache(normalizedPath);
2174
- await this.symlinkManager.symlink(target, path, async () => {
2175
- const { fileHandle, dirHandle } = await this.handleManager.getHandle(normalizedPath);
2176
- if (fileHandle || dirHandle) {
2177
- throw createEEXIST(path);
2178
- }
2179
- });
2180
- } catch (err) {
2181
- this.logError("symlink", err);
2182
- throw wrapError(err);
2183
- }
2184
- }
2185
- /**
2186
- * Read symlink target
2187
- */
2188
- async readlink(path) {
2189
- if (this.hybrid) {
2190
- return this.hybrid.readlink(path);
2191
- }
2192
- this.log("readlink", path);
2193
- try {
2194
- return await this.symlinkManager.readlink(path);
2195
- } catch (err) {
2196
- this.logError("readlink", err);
2197
- throw wrapError(err);
2198
- }
2199
- }
2200
- /**
2201
- * Create multiple symlinks efficiently
2202
- */
2203
- async symlinkBatch(links) {
2204
- if (this.hybrid) {
2205
- return this.hybrid.symlinkBatch(links);
2206
- }
2207
- this.log("symlinkBatch", links.length, "links");
2208
- try {
2209
- for (const { path } of links) {
2210
- this.handleManager.clearCache(normalize(path));
2211
- }
2212
- await this.symlinkManager.symlinkBatch(links, async (normalizedPath) => {
2213
- try {
2214
- const { fileHandle, dirHandle } = await this.handleManager.getHandle(normalizedPath);
2215
- if (fileHandle || dirHandle) {
2216
- throw createEEXIST(normalizedPath);
2217
- }
2218
- } catch (err) {
2219
- if (err.code === "ENOENT") return;
2220
- throw err;
2221
- }
2222
- });
2223
- } catch (err) {
2224
- this.logError("symlinkBatch", err);
2225
- throw wrapError(err);
2226
- }
2227
- }
2228
- /**
2229
- * Check file accessibility
2230
- */
2231
- async access(path, mode = constants.F_OK) {
2232
- if (this.hybrid) {
2233
- return this.hybrid.access(path, mode);
2234
- }
2235
- this.log("access", path, mode);
2236
- try {
2237
- const normalizedPath = normalize(path);
2238
- await this.stat(normalizedPath);
2239
- } catch (err) {
2240
- this.logError("access", err);
2241
- throw createEACCES(path);
2242
- }
2243
- }
2244
- /**
2245
- * Append data to a file
2246
- */
2247
- async appendFile(path, data, options = {}) {
2248
- if (this.hybrid) {
2249
- return this.hybrid.appendFile(path, data, options);
2250
- }
2251
- this.log("appendFile", path);
2252
- try {
2253
- const normalizedPath = normalize(path);
2254
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
2255
- let existingData = new Uint8Array(0);
2256
- try {
2257
- const result = await this.readFile(resolvedPath);
2258
- existingData = result instanceof Uint8Array ? result : new TextEncoder().encode(result);
2259
- } catch (err) {
2260
- if (err.code !== "ENOENT") throw err;
2261
- }
2262
- const newData = typeof data === "string" ? new TextEncoder().encode(data) : data;
2263
- const combined = new Uint8Array(existingData.length + newData.length);
2264
- combined.set(existingData, 0);
2265
- combined.set(newData, existingData.length);
2266
- await this.writeFile(resolvedPath, combined, options);
2267
- } catch (err) {
2268
- this.logError("appendFile", err);
2269
- throw wrapError(err);
2270
- }
2271
- }
2272
- /**
2273
- * Copy a file
2274
- */
2275
- async copyFile(src, dest, mode = 0) {
2276
- if (this.hybrid) {
2277
- return this.hybrid.copyFile(src, dest, mode);
2278
- }
2279
- this.log("copyFile", src, dest, mode);
2280
- try {
2281
- const normalizedSrc = normalize(src);
2282
- const normalizedDest = normalize(dest);
2283
- const resolvedSrc = await this.symlinkManager.resolve(normalizedSrc);
2284
- if (mode & constants.COPYFILE_EXCL) {
2285
- try {
2286
- await this.stat(normalizedDest);
2287
- throw createEEXIST(dest);
2288
- } catch (err) {
2289
- if (err.code !== "ENOENT") throw err;
2290
- }
2291
- }
2292
- const [data] = await Promise.all([
2293
- this.readFile(resolvedSrc),
2294
- this.handleManager.ensureParentDir(normalizedDest)
2295
- ]);
2296
- await this.writeFile(normalizedDest, data);
2297
- } catch (err) {
2298
- this.logError("copyFile", err);
2299
- throw wrapError(err);
2300
- }
2301
- }
2302
- /**
2303
- * Copy files/directories recursively
2304
- */
2305
- async cp(src, dest, options = {}) {
2306
- if (this.hybrid) {
2307
- return this.hybrid.cp(src, dest, options);
2308
- }
2309
- this.log("cp", src, dest, options);
2310
- try {
2311
- const normalizedSrc = normalize(src);
2312
- const normalizedDest = normalize(dest);
2313
- const { recursive = false, force = false, errorOnExist = false } = options;
2314
- const srcStat = await this.stat(normalizedSrc);
2315
- if (srcStat.isDirectory()) {
2316
- if (!recursive) {
2317
- throw createEISDIR(src);
2318
- }
2319
- let destExists = false;
2320
- try {
2321
- await this.stat(normalizedDest);
2322
- destExists = true;
2323
- if (errorOnExist && !force) {
2324
- throw createEEXIST(dest);
2325
- }
2326
- } catch (err) {
2327
- if (err.code !== "ENOENT") throw err;
2328
- }
2329
- if (!destExists) {
2330
- await this.mkdir(normalizedDest);
2331
- }
2332
- const entries = await this.readdir(normalizedSrc);
2333
- await this.limitConcurrency(
2334
- entries,
2335
- 10,
2336
- (entry) => this.cp(`${normalizedSrc}/${entry}`, `${normalizedDest}/${entry}`, options)
2337
- );
2338
- } else {
2339
- if (errorOnExist) {
2340
- try {
2341
- await this.stat(normalizedDest);
2342
- throw createEEXIST(dest);
2343
- } catch (err) {
2344
- if (err.code !== "ENOENT") throw err;
2345
- }
2346
- }
2347
- await this.copyFile(normalizedSrc, normalizedDest);
2348
- }
2349
- } catch (err) {
2350
- this.logError("cp", err);
2351
- throw wrapError(err);
2352
- }
2353
- }
2354
- /**
2355
- * Check if path exists
2356
- */
2357
- async exists(path) {
2358
- if (this.hybrid) {
2359
- return this.hybrid.exists(path);
2360
- }
2361
- this.log("exists", path);
2362
- try {
2363
- await this.stat(normalize(path));
2364
- return true;
2365
- } catch {
2366
- return false;
2367
- }
2368
- }
2369
- /**
2370
- * Resolve symlinks to get real path
2371
- */
2372
- async realpath(path) {
2373
- if (this.hybrid) {
2374
- return this.hybrid.realpath(path);
2375
- }
2376
- this.log("realpath", path);
2377
- const normalizedPath = normalize(path);
2378
- return this.symlinkManager.resolve(normalizedPath);
2379
- }
2380
- /**
2381
- * Remove files and directories
2382
- */
2383
- async rm(path, options = {}) {
2384
- if (this.hybrid) {
2385
- return this.hybrid.rm(path, options);
2386
- }
2387
- this.log("rm", path, options);
2388
- try {
2389
- const normalizedPath = normalize(path);
2390
- const { recursive = false, force = false } = options;
2391
- try {
2392
- const stat = await this.lstat(normalizedPath);
2393
- if (stat.isSymbolicLink()) {
2394
- await this.unlink(normalizedPath);
2395
- } else if (stat.isDirectory()) {
2396
- if (!recursive) {
2397
- throw createEISDIR(path);
2398
- }
2399
- await this.rmdir(normalizedPath);
2400
- } else {
2401
- await this.unlink(normalizedPath);
2402
- }
2403
- } catch (err) {
2404
- if (err.code === "ENOENT" && force) {
2405
- return;
2406
- }
2407
- throw err;
2408
- }
2409
- } catch (err) {
2410
- this.logError("rm", err);
2411
- throw wrapError(err);
2412
- }
2413
- }
2414
- /**
2415
- * Truncate file to specified length
2416
- */
2417
- async truncate(path, len = 0) {
2418
- if (this.hybrid) {
2419
- return this.hybrid.truncate(path, len);
2420
- }
2421
- this.log("truncate", path, len);
2422
- try {
2423
- const normalizedPath = normalize(path);
2424
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
2425
- this.handleManager.clearCache(resolvedPath);
2426
- const { fileHandle } = await this.handleManager.getHandle(resolvedPath);
2427
- if (!fileHandle) throw createENOENT(path);
2428
- if (this.useSync) {
2429
- const access = await fileHandle.createSyncAccessHandle();
2430
- try {
2431
- access.truncate(len);
2432
- } finally {
2433
- access.close();
2434
- }
2435
- } else {
2436
- const file = await fileHandle.getFile();
2437
- const data = new Uint8Array(await file.arrayBuffer());
2438
- const finalData = new Uint8Array(len);
2439
- const copyLen = Math.min(len, data.length);
2440
- if (copyLen > 0) {
2441
- finalData.set(data.subarray(0, copyLen), 0);
2442
- }
2443
- const writable = await fileHandle.createWritable();
2444
- await writable.write(finalData);
2445
- await writable.close();
2446
- }
2447
- } catch (err) {
2448
- this.logError("truncate", err);
2449
- throw wrapError(err);
2450
- }
2451
- }
2452
- /**
2453
- * Create a unique temporary directory
2454
- */
2455
- async mkdtemp(prefix) {
2456
- if (this.hybrid) {
2457
- return this.hybrid.mkdtemp(prefix);
2458
- }
2459
- this.log("mkdtemp", prefix);
2460
- try {
2461
- const normalizedPrefix = normalize(prefix);
2462
- const suffix = `${Date.now()}-${++this.tmpCounter}-${Math.random().toString(36).slice(2, 8)}`;
2463
- const path = `${normalizedPrefix}${suffix}`;
2464
- await this.mkdir(path);
2465
- return path;
2466
- } catch (err) {
2467
- this.logError("mkdtemp", err);
2468
- throw wrapError(err);
2469
- }
2470
- }
2471
- /**
2472
- * Change file mode (no-op for OPFS compatibility)
2473
- */
2474
- async chmod(path, mode) {
2475
- if (this.hybrid) {
2476
- return this.hybrid.chmod(path, mode);
2477
- }
2478
- this.log("chmod", path, mode);
2479
- await this.stat(normalize(path));
2480
- }
2481
- /**
2482
- * Change file owner (no-op for OPFS compatibility)
2483
- */
2484
- async chown(path, uid, gid) {
2485
- if (this.hybrid) {
2486
- return this.hybrid.chown(path, uid, gid);
2487
- }
2488
- this.log("chown", path, uid, gid);
2489
- await this.stat(normalize(path));
2490
- }
2491
- /**
2492
- * Update file timestamps (no-op for OPFS compatibility)
2493
- */
2494
- async utimes(path, atime, mtime) {
2495
- if (this.hybrid) {
2496
- return this.hybrid.utimes(path, atime, mtime);
2497
- }
2498
- this.log("utimes", path, atime, mtime);
2499
- await this.stat(normalize(path));
2500
- }
2501
- /**
2502
- * Update symlink timestamps (no-op)
2503
- */
2504
- async lutimes(path, atime, mtime) {
2505
- if (this.hybrid) {
2506
- return this.hybrid.lutimes(path, atime, mtime);
2507
- }
2508
- this.log("lutimes", path, atime, mtime);
2509
- await this.lstat(normalize(path));
2510
- }
2511
- /**
2512
- * Open file and return FileHandle
2513
- */
2514
- async open(path, flags = "r", mode = 438) {
2515
- this.log("open", path, flags, mode);
2516
- try {
2517
- const normalizedPath = normalize(path);
2518
- const flagStr = flagsToString(flags);
2519
- const shouldCreate = flagStr.includes("w") || flagStr.includes("a") || flagStr.includes("+");
2520
- const shouldTruncate = flagStr.includes("w");
2521
- const shouldAppend = flagStr.includes("a");
2522
- if (shouldCreate) {
2523
- await this.handleManager.ensureParentDir(normalizedPath);
2524
- }
2525
- const resolvedPath = await this.symlinkManager.resolve(normalizedPath);
2526
- const { fileHandle } = await this.handleManager.getHandle(resolvedPath, { create: shouldCreate });
2527
- if (!fileHandle && !shouldCreate) {
2528
- throw createENOENT(path);
2529
- }
2530
- if (shouldTruncate && fileHandle) {
2531
- await this.truncate(resolvedPath, 0);
2532
- }
2533
- const initialPosition = shouldAppend ? (await this.stat(resolvedPath)).size : 0;
2534
- return createFileHandle(resolvedPath, initialPosition, {
2535
- readFile: (p, o) => this.readFile(p, o),
2536
- writeFile: (p, d) => this.writeFile(p, d),
2537
- stat: (p) => this.stat(p),
2538
- truncate: (p, l) => this.truncate(p, l),
2539
- appendFile: (p, d, o) => this.appendFile(p, d, o)
2540
- });
2541
- } catch (err) {
2542
- this.logError("open", err);
2543
- throw wrapError(err);
2544
- }
2545
- }
2546
- /**
2547
- * Open directory for iteration
2548
- */
2549
- async opendir(path) {
2550
- this.log("opendir", path);
2551
- try {
2552
- const normalizedPath = normalize(path);
2553
- const entries = await this.readdir(normalizedPath, { withFileTypes: true });
2554
- let index = 0;
2555
- return {
2556
- path: normalizedPath,
2557
- async read() {
2558
- if (index >= entries.length) return null;
2559
- return entries[index++];
2560
- },
2561
- async close() {
2562
- index = entries.length;
2563
- },
2564
- async *[Symbol.asyncIterator]() {
2565
- for (const entry of entries) {
2566
- yield entry;
2567
- }
2568
- }
2569
- };
2570
- } catch (err) {
2571
- this.logError("opendir", err);
2572
- throw wrapError(err);
2573
- }
2574
- }
2575
- /**
2576
- * Watch for file changes
2577
- */
2578
- watch(path, options = {}) {
2579
- this.log("watch", path, options);
2580
- const normalizedPath = normalize(path);
2581
- const { recursive = false, signal } = options;
2582
- const callbacks = /* @__PURE__ */ new Set();
2583
- const id = /* @__PURE__ */ Symbol("watcher");
2584
- this.watchCallbacks.set(id, { path: normalizedPath, callbacks, recursive });
2585
- if (signal) {
2586
- signal.addEventListener("abort", () => {
2587
- this.watchCallbacks.delete(id);
2588
- });
2589
- }
2590
- const self2 = this;
2591
- return {
2592
- close() {
2593
- self2.watchCallbacks.delete(id);
2594
- },
2595
- ref() {
2596
- return this;
2597
- },
2598
- unref() {
2599
- return this;
2600
- },
2601
- [Symbol.asyncIterator]() {
2602
- const queue = [];
2603
- let resolver = null;
2604
- callbacks.add((eventType, filename) => {
2605
- const event = { eventType, filename };
2606
- if (resolver) {
2607
- resolver({ value: event, done: false });
2608
- resolver = null;
2609
- } else {
2610
- queue.push(event);
2611
- }
2612
- });
2613
- return {
2614
- next() {
2615
- if (queue.length > 0) {
2616
- return Promise.resolve({ value: queue.shift(), done: false });
2617
- }
2618
- return new Promise((resolve) => {
2619
- resolver = resolve;
2620
- });
2621
- },
2622
- return() {
2623
- return Promise.resolve({ done: true, value: void 0 });
2624
- }
2625
- };
2626
- }
2627
- };
2628
- }
2629
- /**
2630
- * Create read stream
2631
- */
2632
- createReadStream(path, options = {}) {
2633
- this.log("createReadStream", path, options);
2634
- const normalizedPath = normalize(path);
2635
- return createReadStream(normalizedPath, options, {
2636
- readFile: (p) => this.readFile(p)
2637
- });
2638
- }
2639
- /**
2640
- * Create write stream
2641
- */
2642
- createWriteStream(path, options = {}) {
2643
- this.log("createWriteStream", path, options);
2644
- const normalizedPath = normalize(path);
2645
- return createWriteStream(normalizedPath, options, {
2646
- readFile: (p) => this.readFile(p),
2647
- writeFile: (p, d) => this.writeFile(p, d)
2648
- });
2649
- }
2650
- /**
2651
- * Get file statistics (alias for stat)
2652
- */
2653
- async backFile(path) {
2654
- this.log("backFile", path);
2655
- try {
2656
- return await this.stat(normalize(path));
2657
- } catch (err) {
2658
- if (err.code === "ENOENT") throw err;
2659
- throw createENOENT(path);
2660
- }
2661
- }
2662
- /**
2663
- * Get disk usage for a path
2664
- */
2665
- async du(path) {
2666
- if (this.hybrid) {
2667
- return this.hybrid.du(path);
2668
- }
2669
- this.log("du", path);
2670
- const normalizedPath = normalize(path);
2671
- const stat = await this.stat(normalizedPath);
2672
- return { path: normalizedPath, size: stat.size };
2673
- }
2674
- /**
2675
- * Get filesystem statistics (similar to Node.js fs.statfs)
2676
- * Uses the Storage API to get quota and usage information
2677
- * Note: Values are estimates for the entire origin, not per-path
2678
- */
2679
- async statfs(path) {
2680
- if (this.hybrid) {
2681
- return this.hybrid.statfs(path);
2682
- }
2683
- this.log("statfs", path);
2684
- try {
2685
- if (path) {
2686
- await this.stat(normalize(path));
2687
- }
2688
- if (typeof navigator === "undefined" || !navigator.storage?.estimate) {
2689
- throw new Error("Storage API not available");
2690
- }
2691
- const estimate = await navigator.storage.estimate();
2692
- const usage = estimate.usage ?? 0;
2693
- const quota = estimate.quota ?? 0;
2694
- const bsize = 4096;
2695
- return {
2696
- type: 0,
2697
- bsize,
2698
- blocks: Math.floor(quota / bsize),
2699
- bfree: Math.floor((quota - usage) / bsize),
2700
- bavail: Math.floor((quota - usage) / bsize),
2701
- files: 0,
2702
- ffree: 0,
2703
- usage,
2704
- quota
2705
- };
2706
- } catch (err) {
2707
- this.logError("statfs", err);
2708
- throw wrapError(err);
2709
- }
2710
- }
2711
- /**
2712
- * Reset internal caches
2713
- * Useful when external processes modify the filesystem
2714
- */
2715
- resetCache() {
2716
- if (this.hybrid) {
2717
- this.hybrid.resetCache();
2718
- return;
2719
- }
2720
- this.symlinkManager.reset();
2721
- this.packedStorage.reset();
2722
- this.handleManager.clearCache();
2723
- }
2724
- /**
2725
- * Force full garbage collection
2726
- * Releases all handles and caches, reinitializes the worker in hybrid mode
2727
- * Use this for long-running operations to prevent memory leaks
2728
- */
2729
- async gc() {
2730
- if (this.hybrid) {
2731
- await this.hybrid.gc();
2732
- return;
2733
- }
2734
- this.symlinkManager.reset();
2735
- await this.packedStorage.clear();
2736
- this.handleManager.clearCache();
2737
- }
2738
- };
2739
-
2740
- // src/opfs-worker.ts
2741
- var fs = null;
2742
- function getFS() {
2743
- if (!fs) {
2744
- fs = new OPFS({ useSync: true, verbose: false });
2745
- }
2746
- return fs;
2747
- }
2748
- self.onmessage = async (event) => {
2749
- const { id, method, args } = event.data;
2750
- try {
2751
- const opfs = getFS();
2752
- let result;
2753
- const transfer = [];
2754
- switch (method) {
2755
- // File operations
2756
- case "readFile": {
2757
- const data = await opfs.readFile(args[0], args[1]);
2758
- if (data instanceof Uint8Array) {
2759
- result = data;
2760
- transfer.push(data.buffer);
2761
- } else {
2762
- result = data;
2763
- }
2764
- break;
2765
- }
2766
- case "writeFile":
2767
- await opfs.writeFile(args[0], args[1], args[2]);
2768
- result = void 0;
2769
- break;
2770
- case "readFileBatch": {
2771
- const results = await opfs.readFileBatch(args[0]);
2772
- for (const r of results) {
2773
- if (r.data) {
2774
- transfer.push(r.data.buffer);
2775
- }
2776
- }
2777
- result = results;
2778
- break;
2779
- }
2780
- case "writeFileBatch":
2781
- await opfs.writeFileBatch(args[0]);
2782
- result = void 0;
2783
- break;
2784
- case "appendFile":
2785
- await opfs.appendFile(args[0], args[1], args[2]);
2786
- result = void 0;
2787
- break;
2788
- case "copyFile":
2789
- await opfs.copyFile(args[0], args[1], args[2]);
2790
- result = void 0;
2791
- break;
2792
- case "unlink":
2793
- await opfs.unlink(args[0]);
2794
- result = void 0;
2795
- break;
2796
- case "truncate":
2797
- await opfs.truncate(args[0], args[1]);
2798
- result = void 0;
2799
- break;
2800
- // Directory operations
2801
- case "mkdir":
2802
- await opfs.mkdir(args[0]);
2803
- result = void 0;
2804
- break;
2805
- case "rmdir":
2806
- await opfs.rmdir(args[0]);
2807
- result = void 0;
2808
- break;
2809
- case "readdir":
2810
- result = await opfs.readdir(args[0], args[1]);
2811
- break;
2812
- case "cp":
2813
- await opfs.cp(args[0], args[1], args[2]);
2814
- result = void 0;
2815
- break;
2816
- case "rm":
2817
- await opfs.rm(args[0], args[1]);
2818
- result = void 0;
2819
- break;
2820
- // Stat operations
2821
- case "stat":
2822
- result = serializeStats(await opfs.stat(args[0]));
2823
- break;
2824
- case "lstat":
2825
- result = serializeStats(await opfs.lstat(args[0]));
2826
- break;
2827
- case "exists":
2828
- result = await opfs.exists(args[0]);
2829
- break;
2830
- case "access":
2831
- await opfs.access(args[0], args[1]);
2832
- result = void 0;
2833
- break;
2834
- case "statfs":
2835
- result = await opfs.statfs(args[0]);
2836
- break;
2837
- case "du":
2838
- result = await opfs.du(args[0]);
2839
- break;
2840
- // Symlink operations
2841
- case "symlink":
2842
- await opfs.symlink(args[0], args[1]);
2843
- result = void 0;
2844
- break;
2845
- case "readlink":
2846
- result = await opfs.readlink(args[0]);
2847
- break;
2848
- case "symlinkBatch":
2849
- await opfs.symlinkBatch(args[0]);
2850
- result = void 0;
2851
- break;
2852
- case "realpath":
2853
- result = await opfs.realpath(args[0]);
2854
- break;
2855
- // Other operations
2856
- case "rename":
2857
- await opfs.rename(args[0], args[1]);
2858
- result = void 0;
2859
- break;
2860
- case "mkdtemp":
2861
- result = await opfs.mkdtemp(args[0]);
2862
- break;
2863
- case "chmod":
2864
- await opfs.chmod(args[0], args[1]);
2865
- result = void 0;
2866
- break;
2867
- case "chown":
2868
- await opfs.chown(args[0], args[1], args[2]);
2869
- result = void 0;
2870
- break;
2871
- case "utimes":
2872
- await opfs.utimes(args[0], args[1], args[2]);
2873
- result = void 0;
2874
- break;
2875
- case "lutimes":
2876
- await opfs.lutimes(args[0], args[1], args[2]);
2877
- result = void 0;
2878
- break;
2879
- case "resetCache":
2880
- opfs.resetCache();
2881
- result = void 0;
2882
- break;
2883
- case "gc":
2884
- fs = null;
2885
- fs = new OPFS({ useSync: true, verbose: false });
2886
- result = void 0;
2887
- break;
2888
- default:
2889
- throw new Error(`Unknown method: ${method}`);
2890
- }
2891
- const response = { id, result };
2892
- if (transfer.length > 0) {
2893
- self.postMessage(response, transfer);
2894
- } else {
2895
- self.postMessage(response);
2896
- }
2897
- } catch (err) {
2898
- const error = err;
2899
- const response = {
2900
- id,
2901
- error: {
2902
- message: error.message,
2903
- code: error.code
2904
- }
2905
- };
2906
- self.postMessage(response);
2907
- }
2908
- };
2909
- function serializeStats(stats) {
2910
- return {
2911
- type: stats.type,
2912
- size: stats.size,
2913
- mode: stats.mode,
2914
- ctime: stats.ctime.toISOString(),
2915
- ctimeMs: stats.ctimeMs,
2916
- mtime: stats.mtime.toISOString(),
2917
- mtimeMs: stats.mtimeMs,
2918
- target: stats.target
2919
- };
2920
- }
2921
- self.postMessage({ type: "ready" });
2922
- //# sourceMappingURL=opfs-worker.js.map
2923
- //# sourceMappingURL=opfs-worker.js.map