opfs-worker 0.2.5 → 0.3.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.
package/dist/raw.js CHANGED
@@ -1,22 +1,25 @@
1
- import { expose as p } from "comlink";
2
- import { d as S, O as n, b as l, s as w, P as g, i as I, o as v, F as f, w as F, j as T, e as y, f as u, k as M, r as A, n as d, l as $ } from "./helpers-3TGPHd1h.js";
3
- class O {
1
+ import { expose as E } from "comlink";
2
+ import { d as S, O as n, b as l, s as w, P as g, i as v, o as I, F as f, w as F, j as C, e as d, f as y, k as T, r as M, n as u, l as b } from "./helpers-C0nyU6hv.js";
3
+ class A {
4
4
  /** Root directory handle for the file system */
5
5
  root = null;
6
- /** Watch event callback */
7
- watchCallback = null;
8
6
  /** Map of watched paths to their last known state */
9
7
  watchers = /* @__PURE__ */ new Map();
10
8
  /** Interval handle for polling watched paths */
11
9
  watchTimer = null;
12
- /** Polling interval in milliseconds */
13
- watchInterval = 1e3;
14
10
  /** Flag to avoid concurrent scans */
15
11
  scanning = !1;
16
12
  /** Promise to prevent concurrent mount operations */
17
13
  mountingPromise = null;
18
- /** Hash algorithm for file hashing (null means no hashing) */
19
- hashAlgorithm = null;
14
+ /** BroadcastChannel instance for sending events */
15
+ broadcastChannel = null;
16
+ /** Configuration options */
17
+ options = {
18
+ watchInterval: 1e3,
19
+ maxFileSize: 50 * 1024 * 1024,
20
+ hashAlgorithm: null,
21
+ broadcastChannel: "opfs-worker"
22
+ };
20
23
  /**
21
24
  * Notify about internal changes to the file system
22
25
  *
@@ -27,33 +30,39 @@ class O {
27
30
  * @param type - The type of change (create, change, delete)
28
31
  */
29
32
  async notifyChange(t) {
30
- if (!this.watchCallback)
33
+ if (!this.options.broadcastChannel)
31
34
  return;
32
35
  let e;
33
- if (this.hashAlgorithm && !t.isDirectory && t.type !== "removed")
36
+ if (this.options.hashAlgorithm && !t.isDirectory && t.type !== "removed")
34
37
  try {
35
38
  const i = await this.stat(t.path);
36
39
  i.isFile && i.hash && (e = i.hash);
37
40
  } catch (i) {
38
41
  console.warn(`Failed to calculate hash for ${t.path}:`, i);
39
42
  }
40
- this.watchCallback({
41
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
42
- ...t,
43
- ...e && { hash: e }
44
- });
43
+ try {
44
+ this.broadcastChannel || (this.broadcastChannel = new BroadcastChannel(this.options.broadcastChannel));
45
+ const i = {
46
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
47
+ ...t,
48
+ ...e && { hash: e }
49
+ };
50
+ this.broadcastChannel.postMessage(i);
51
+ } catch (i) {
52
+ console.warn("Failed to send event via BroadcastChannel:", i);
53
+ }
45
54
  }
46
55
  /**
47
56
  * Creates a new OPFSFileSystem instance
48
57
  *
49
- * @param watchCallback - Optional callback for file change events
50
58
  * @param options - Optional configuration options
51
59
  * @param options.watchInterval - Polling interval in milliseconds for file watching
52
60
  * @param options.hashAlgorithm - Hash algorithm for file hashing
61
+ * @param options.maxFileSize - Maximum file size for hashing in bytes (default: 50MB)
53
62
  * @throws {OPFSError} If OPFS is not supported in the current browser
54
63
  */
55
- constructor(t, e) {
56
- S(), t && (this.watchCallback = t), e && this.setOptions(e), this.mount("/");
64
+ constructor(t) {
65
+ S(), t && this.setOptions(t), this.mount("/");
57
66
  }
58
67
  /**
59
68
  * Initialize the file system within a given directory
@@ -80,32 +89,26 @@ class O {
80
89
  return this.mountingPromise && await this.mountingPromise, this.mountingPromise = new Promise(async (e, i) => {
81
90
  this.root = null;
82
91
  try {
83
- const r = await navigator.storage.getDirectory();
84
- t === "/" ? this.root = r : this.root = await this.getDirectoryHandle(t, !0, r), e(!0);
85
- } catch (r) {
86
- console.error(r), i(new n("Failed to initialize OPFS", "INIT_FAILED"));
92
+ const a = await navigator.storage.getDirectory();
93
+ t === "/" ? this.root = a : this.root = await this.getDirectoryHandle(t, !0, a), e(!0);
94
+ } catch (a) {
95
+ console.error(a), i(new n("Failed to initialize OPFS", "INIT_FAILED"));
87
96
  } finally {
88
97
  this.mountingPromise = null;
89
98
  }
90
99
  }), this.mountingPromise;
91
100
  }
92
101
  /**
93
- * Set the watch callback for file change events
94
- *
95
- * @param callback - The callback function to invoke when files change
96
- */
97
- setWatchCallback(t) {
98
- this.watchCallback = t;
99
- }
100
- /**
101
- * Set configuration options for the file system
102
+ * Update configuration options
102
103
  *
103
104
  * @param options - Configuration options to update
104
105
  * @param options.watchInterval - Polling interval in milliseconds for file watching
105
106
  * @param options.hashAlgorithm - Hash algorithm for file hashing
107
+ * @param options.maxFileSize - Maximum file size for hashing in bytes
108
+ * @param options.broadcastChannel - Custom name for the broadcast channel
106
109
  */
107
110
  setOptions(t) {
108
- t.watchInterval !== void 0 && (this.watchInterval = t.watchInterval), t.hashAlgorithm !== void 0 && (this.hashAlgorithm = t.hashAlgorithm);
111
+ t.watchInterval !== void 0 && (this.options.watchInterval = t.watchInterval), t.hashAlgorithm !== void 0 && (this.options.hashAlgorithm = t.hashAlgorithm), t.maxFileSize !== void 0 && (this.options.maxFileSize = t.maxFileSize), t.broadcastChannel !== void 0 && (this.broadcastChannel && this.options.broadcastChannel !== t.broadcastChannel && (this.broadcastChannel.close(), this.broadcastChannel = null), this.options.broadcastChannel = t.broadcastChannel);
109
112
  }
110
113
  /**
111
114
  * Automatically mount the OPFS root if not already mounted
@@ -146,11 +149,11 @@ class O {
146
149
  async getDirectoryHandle(t, e = !1, i = this.root) {
147
150
  if (!i)
148
151
  throw new l();
149
- const r = Array.isArray(t) ? t : w(t);
150
- let a = i;
151
- for (const s of r)
152
- a = await a.getDirectoryHandle(s, { create: e });
153
- return a;
152
+ const a = Array.isArray(t) ? t : w(t);
153
+ let r = i;
154
+ for (const s of a)
155
+ r = await r.getDirectoryHandle(s, { create: e });
156
+ return r;
154
157
  }
155
158
  /**
156
159
  * Get a file handle from a path
@@ -174,11 +177,11 @@ class O {
174
177
  async getFileHandle(t, e = !1, i = this.root) {
175
178
  if (!i)
176
179
  throw new l();
177
- const r = w(t);
178
- if (r.length === 0)
180
+ const a = w(t);
181
+ if (a.length === 0)
179
182
  throw new g("Path must not be empty", Array.isArray(t) ? t.join("/") : t);
180
- const a = r.pop();
181
- return (await this.getDirectoryHandle(r, e, i)).getFileHandle(a, { create: e });
183
+ const r = a.pop();
184
+ return (await this.getDirectoryHandle(a, e, i)).getFileHandle(r, { create: e });
182
185
  }
183
186
  /**
184
187
  * Get a complete index of all files and directories in the file system
@@ -201,9 +204,9 @@ class O {
201
204
  */
202
205
  async index() {
203
206
  const t = /* @__PURE__ */ new Map(), e = async (i) => {
204
- const r = await this.readdir(i, { withFileTypes: !0 });
205
- for (const a of r) {
206
- const s = `${i === "/" ? "" : i}/${a.name}`;
207
+ const a = await this.readdir(i, { withFileTypes: !0 });
208
+ for (const r of a) {
209
+ const s = `${i === "/" ? "" : i}/${r.name}`;
207
210
  try {
208
211
  const o = await this.stat(s);
209
212
  t.set(s, o), o.isDirectory && await e(s);
@@ -224,8 +227,8 @@ class O {
224
227
  async readFile(t, e = "utf-8") {
225
228
  await this.ensureMounted();
226
229
  try {
227
- const i = await this.getFileHandle(t, !1), r = await I(i);
228
- return e === "binary" ? r : v(r, e);
230
+ const i = await this.getFileHandle(t, !1), a = await v(i);
231
+ return e === "binary" ? a : I(a, e);
229
232
  } catch (i) {
230
233
  throw console.error(i), new f(t);
231
234
  }
@@ -257,8 +260,8 @@ class O {
257
260
  */
258
261
  async writeFile(t, e, i) {
259
262
  await this.ensureMounted();
260
- const r = await this.getFileHandle(t, !0);
261
- await F(r, e, i, { truncate: !0 }), await this.notifyChange({ path: t, type: "changed", isDirectory: !1 });
263
+ const a = await this.getFileHandle(t, !0);
264
+ await F(a, e, i, { truncate: !0 }), await this.notifyChange({ path: t, type: "changed", isDirectory: !1 });
262
265
  }
263
266
  /**
264
267
  * Append data to a file
@@ -284,8 +287,8 @@ class O {
284
287
  */
285
288
  async appendFile(t, e, i) {
286
289
  await this.ensureMounted();
287
- const r = await this.getFileHandle(t, !0);
288
- await F(r, e, i, { append: !0 }), await this.notifyChange({ path: t, type: "changed", isDirectory: !1 });
290
+ const a = await this.getFileHandle(t, !0);
291
+ await F(a, e, i, { append: !0 }), await this.notifyChange({ path: t, type: "changed", isDirectory: !1 });
289
292
  }
290
293
  /**
291
294
  * Create a directory
@@ -311,15 +314,15 @@ class O {
311
314
  async mkdir(t, e) {
312
315
  if (await this.ensureMounted(), !this.root)
313
316
  throw new l();
314
- const i = e?.recursive ?? !1, r = w(t);
315
- let a = this.root;
316
- for (let s = 0; s < r.length; s++) {
317
- const o = r[s];
317
+ const i = e?.recursive ?? !1, a = w(t);
318
+ let r = this.root;
319
+ for (let s = 0; s < a.length; s++) {
320
+ const o = a[s];
318
321
  try {
319
- a = await a.getDirectoryHandle(o, { create: i || s === r.length - 1 });
322
+ r = await r.getDirectoryHandle(o, { create: i || s === a.length - 1 });
320
323
  } catch (c) {
321
324
  throw c.name === "NotFoundError" ? new n(
322
- `Parent directory does not exist: ${T(r.slice(0, s + 1))}`,
325
+ `Parent directory does not exist: ${C(a.slice(0, s + 1))}`,
323
326
  "ENOENT"
324
327
  ) : c.name === "TypeMismatchError" ? new n(`Path segment is not a directory: ${o}`, "ENOTDIR") : new n("Failed to create directory", "MKDIR_FAILED");
325
328
  }
@@ -358,7 +361,7 @@ class O {
358
361
  isFile: !1,
359
362
  isDirectory: !0
360
363
  };
361
- const e = y(t), i = await this.getDirectoryHandle(u(t), !1), r = this.hashAlgorithm !== null;
364
+ const e = d(t), i = await this.getDirectoryHandle(y(t), !1), a = this.options.hashAlgorithm !== null;
362
365
  try {
363
366
  const s = await (await i.getFileHandle(e, { create: !1 })).getFile(), o = {
364
367
  kind: "file",
@@ -368,16 +371,16 @@ class O {
368
371
  isFile: !0,
369
372
  isDirectory: !1
370
373
  };
371
- if (r && this.hashAlgorithm)
374
+ if (a && this.options.hashAlgorithm)
372
375
  try {
373
- const c = await M(s, this.hashAlgorithm);
376
+ const c = await T(s, this.options.hashAlgorithm, this.options.maxFileSize);
374
377
  o.hash = c;
375
378
  } catch (c) {
376
379
  console.warn(`Failed to calculate hash for ${t}:`, c);
377
380
  }
378
381
  return o;
379
- } catch (a) {
380
- if (a.name !== "TypeMismatchError" && a.name !== "NotFoundError")
382
+ } catch (r) {
383
+ if (r.name !== "TypeMismatchError" && r.name !== "NotFoundError")
381
384
  throw new n("Failed to stat (file)", "STAT_FAILED");
382
385
  }
383
386
  try {
@@ -389,30 +392,30 @@ class O {
389
392
  isFile: !1,
390
393
  isDirectory: !0
391
394
  };
392
- } catch (a) {
393
- throw a.name === "NotFoundError" ? new n(`No such file or directory: ${t}`, "ENOENT") : new n("Failed to stat (directory)", "STAT_FAILED");
395
+ } catch (r) {
396
+ throw r.name === "NotFoundError" ? new n(`No such file or directory: ${t}`, "ENOENT") : new n("Failed to stat (directory)", "STAT_FAILED");
394
397
  }
395
398
  }
396
399
  async readdir(t, e) {
397
400
  await this.ensureMounted();
398
- const i = e?.withFileTypes ?? !1, r = await this.getDirectoryHandle(t, !1);
401
+ const i = e?.withFileTypes ?? !1, a = await this.getDirectoryHandle(t, !1);
399
402
  if (i) {
400
- const a = [];
401
- for await (const [s, o] of r.entries()) {
403
+ const r = [];
404
+ for await (const [s, o] of a.entries()) {
402
405
  const c = o.kind === "file";
403
- a.push({
406
+ r.push({
404
407
  name: s,
405
408
  kind: o.kind,
406
409
  isFile: c,
407
410
  isDirectory: !c
408
411
  });
409
412
  }
410
- return a;
413
+ return r;
411
414
  } else {
412
- const a = [];
413
- for await (const [s] of r.entries())
414
- a.push(s);
415
- return a;
415
+ const r = [];
416
+ for await (const [s] of a.entries())
417
+ r.push(s);
418
+ return r;
416
419
  }
417
420
  }
418
421
  /**
@@ -432,26 +435,26 @@ class O {
432
435
  async exists(t) {
433
436
  if (await this.ensureMounted(), t === "/")
434
437
  return !0;
435
- const e = y(t);
438
+ const e = d(t);
436
439
  let i = null;
437
440
  try {
438
- i = await this.getDirectoryHandle(u(t), !1);
439
- } catch (r) {
440
- throw (r.name === "NotFoundError" || r.name === "TypeMismatchError") && (i = null), r;
441
+ i = await this.getDirectoryHandle(y(t), !1);
442
+ } catch (a) {
443
+ throw (a.name === "NotFoundError" || a.name === "TypeMismatchError") && (i = null), a;
441
444
  }
442
445
  if (!i || !e)
443
446
  return !1;
444
447
  try {
445
448
  return await i.getFileHandle(e, { create: !1 }), !0;
446
- } catch (r) {
447
- if (r.name !== "NotFoundError" && r.name !== "TypeMismatchError")
448
- throw r;
449
+ } catch (a) {
450
+ if (a.name !== "NotFoundError" && a.name !== "TypeMismatchError")
451
+ throw a;
449
452
  }
450
453
  try {
451
454
  return await i.getDirectoryHandle(e, { create: !1 }), !0;
452
- } catch (r) {
453
- if (r.name !== "NotFoundError" && r.name !== "TypeMismatchError")
454
- throw r;
455
+ } catch (a) {
456
+ if (a.name !== "NotFoundError" && a.name !== "TypeMismatchError")
457
+ throw a;
455
458
  }
456
459
  return !1;
457
460
  }
@@ -479,8 +482,8 @@ class O {
479
482
  try {
480
483
  const e = await this.readdir(t, { withFileTypes: !0 });
481
484
  for (const i of e) {
482
- const r = `${t === "/" ? "" : t}/${i.name}`;
483
- await this.remove(r, { recursive: !0 });
485
+ const a = `${t === "/" ? "" : t}/${i.name}`;
486
+ await this.remove(a, { recursive: !0 });
484
487
  }
485
488
  await this.notifyChange({ path: t, type: "changed", isDirectory: !0 });
486
489
  } catch (e) {
@@ -513,18 +516,18 @@ class O {
513
516
  */
514
517
  async remove(t, e) {
515
518
  await this.ensureMounted();
516
- const i = e?.recursive ?? !1, r = e?.force ?? !1;
519
+ const i = e?.recursive ?? !1, a = e?.force ?? !1;
517
520
  if (t === "/")
518
521
  throw new n("Cannot remove root directory", "EROOT");
519
- const a = y(t);
520
- if (!a)
522
+ const r = d(t);
523
+ if (!r)
521
524
  throw new g("Invalid path", t);
522
- const s = await this.getDirectoryHandle(u(t), !1);
525
+ const s = await this.getDirectoryHandle(y(t), !1);
523
526
  try {
524
- await s.removeEntry(a, { recursive: i });
527
+ await s.removeEntry(r, { recursive: i });
525
528
  } catch (o) {
526
529
  if (o.name === "NotFoundError") {
527
- if (!r)
530
+ if (!a)
528
531
  throw new n(`No such file or directory: ${t}`, "ENOENT");
529
532
  } else throw o.name === "InvalidModificationError" ? new n(`Directory not empty: ${t}. Use recursive option to force removal.`, "ENOTEMPTY") : o.name === "TypeMismatchError" && !i ? new n(`Cannot remove directory without recursive option: ${t}`, "EISDIR") : new n(`Failed to remove path: ${t}`, "RM_FAILED");
530
533
  }
@@ -551,7 +554,7 @@ class O {
551
554
  async realpath(t) {
552
555
  await this.ensureMounted();
553
556
  try {
554
- const e = A(t);
557
+ const e = M(t);
555
558
  if (!await this.exists(e))
556
559
  throw new f(e);
557
560
  return e;
@@ -613,27 +616,27 @@ class O {
613
616
  async copy(t, e, i) {
614
617
  await this.ensureMounted();
615
618
  try {
616
- const r = i?.recursive ?? !1, a = i?.force ?? !0;
619
+ const a = i?.recursive ?? !1, r = i?.force ?? !0;
617
620
  if (!await this.exists(t))
618
621
  throw new n(`Source does not exist: ${t}`, "ENOENT");
619
- if (await this.exists(e) && !a)
622
+ if (await this.exists(e) && !r)
620
623
  throw new n(`Destination already exists: ${e}`, "EEXIST");
621
624
  if ((await this.stat(t)).isFile) {
622
625
  const h = await this.readFile(t, "binary");
623
626
  await this.writeFile(e, h);
624
627
  } else {
625
- if (!r)
628
+ if (!a)
626
629
  throw new n(`Cannot copy directory without recursive option: ${t}`, "EISDIR");
627
630
  await this.mkdir(e, { recursive: !0 });
628
631
  const h = await this.readdir(t, { withFileTypes: !0 });
629
632
  for (const m of h) {
630
- const D = `${t}/${m.name}`, E = `${e}/${m.name}`;
631
- await this.copy(D, E, { recursive: !0, force: a });
633
+ const D = `${t}/${m.name}`, p = `${e}/${m.name}`;
634
+ await this.copy(D, p, { recursive: !0, force: r });
632
635
  }
633
636
  }
634
637
  await this.notifyChange({ path: e, type: "added", isDirectory: !1 });
635
- } catch (r) {
636
- throw r instanceof n ? r : new n(`Failed to copy from ${t} to ${e}`, "CP_FAILED");
638
+ } catch (a) {
639
+ throw a instanceof n ? a : new n(`Failed to copy from ${t} to ${e}`, "CP_FAILED");
637
640
  }
638
641
  }
639
642
  /**
@@ -641,25 +644,34 @@ class O {
641
644
  */
642
645
  async watch(t) {
643
646
  await this.ensureMounted();
644
- const e = d(t), i = await this.buildSnapshot(e);
647
+ const e = u(t), i = await this.buildSnapshot(e);
645
648
  this.watchers.set(e, i), this.watchTimer || (this.watchTimer = setInterval(() => {
646
649
  this.scanWatches();
647
- }, this.watchInterval));
650
+ }, this.options.watchInterval));
648
651
  }
649
652
  /**
650
653
  * Stop watching a previously watched path
651
654
  */
652
655
  unwatch(t) {
653
- const e = d(t);
656
+ const e = u(t);
654
657
  this.watchers.delete(e), this.watchers.size === 0 && this.watchTimer && (clearInterval(this.watchTimer), this.watchTimer = null);
655
658
  }
659
+ /**
660
+ * Dispose of resources and clean up the file system instance
661
+ *
662
+ * This method should be called when the file system instance is no longer needed
663
+ * to properly clean up resources like the broadcast channel and watch timers.
664
+ */
665
+ dispose() {
666
+ this.broadcastChannel && (this.broadcastChannel.close(), this.broadcastChannel = null), this.watchTimer && (clearInterval(this.watchTimer), this.watchTimer = null), this.watchers.clear();
667
+ }
656
668
  async buildSnapshot(t) {
657
- const e = /* @__PURE__ */ new Map(), i = async (r) => {
658
- const a = await this.stat(r);
659
- if (e.set(r, a), a.isDirectory) {
660
- const s = await this.readdir(r, { withFileTypes: !0 });
669
+ const e = /* @__PURE__ */ new Map(), i = async (a) => {
670
+ const r = await this.stat(a);
671
+ if (e.set(a, r), r.isDirectory) {
672
+ const s = await this.readdir(a, { withFileTypes: !0 });
661
673
  for (const o of s) {
662
- const c = `${r === "/" ? "" : r}/${o.name}`;
674
+ const c = `${a === "/" ? "" : a}/${o.name}`;
663
675
  await i(c);
664
676
  }
665
677
  }
@@ -678,12 +690,15 @@ class O {
678
690
  } catch {
679
691
  i = /* @__PURE__ */ new Map();
680
692
  }
681
- for (const [r, a] of i) {
682
- const s = e.get(r);
683
- s ? (s.mtime !== a.mtime || s.size !== a.size) && await this.notifyChange({ path: r, type: "changed", isDirectory: !1 }) : await this.notifyChange({ path: r, type: "added", isDirectory: !1 });
693
+ for (const [a, r] of i) {
694
+ const s = e.get(a);
695
+ s ? (s.mtime !== r.mtime || s.size !== r.size) && await this.notifyChange({ path: a, type: "changed", isDirectory: r.isDirectory }) : await this.notifyChange({ path: a, type: "added", isDirectory: r.isDirectory });
684
696
  }
685
- for (const r of e.keys())
686
- i.has(r) || await this.notifyChange({ path: r, type: "removed", isDirectory: !1 });
697
+ for (const a of e.keys())
698
+ if (!i.has(a)) {
699
+ const r = e.get(a);
700
+ await this.notifyChange({ path: a, type: "removed", isDirectory: r?.isDirectory ?? !1 });
701
+ }
687
702
  this.watchers.set(t, i);
688
703
  })
689
704
  );
@@ -724,10 +739,10 @@ class O {
724
739
  await this.ensureMounted();
725
740
  try {
726
741
  (e?.cleanBefore ?? !1) && await this.clear("/");
727
- for (const [r, a] of t) {
728
- const s = d(r);
742
+ for (const [a, r] of t) {
743
+ const s = u(a);
729
744
  let o;
730
- a instanceof Blob ? o = await $(a) : o = a, await this.writeFile(s, o);
745
+ r instanceof Blob ? o = await b(r) : o = r, await this.writeFile(s, o);
731
746
  }
732
747
  await this.notifyChange({ path: "/", type: "changed", isDirectory: !0 });
733
748
  } catch (i) {
@@ -735,8 +750,8 @@ class O {
735
750
  }
736
751
  }
737
752
  }
738
- typeof self < "u" && self.constructor.name === "DedicatedWorkerGlobalScope" && p(new O());
753
+ typeof self < "u" && self.constructor.name === "DedicatedWorkerGlobalScope" && E(new A());
739
754
  export {
740
- O as OPFSWorker
755
+ A as OPFSWorker
741
756
  };
742
757
  //# sourceMappingURL=raw.js.map