opfs-worker 2.0.0 → 2.1.1

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.
Files changed (134) hide show
  1. package/README.md +64 -428
  2. package/dist/BaseOPFS-BusBkyJX.js +2114 -0
  3. package/dist/BaseOPFS-BusBkyJX.js.map +1 -0
  4. package/dist/BaseOPFS-CYwrD7-q.cjs +4 -0
  5. package/dist/BaseOPFS-CYwrD7-q.cjs.map +1 -0
  6. package/dist/OPFSAsync-B2dR-2VR.cjs +2 -0
  7. package/dist/OPFSAsync-B2dR-2VR.cjs.map +1 -0
  8. package/dist/OPFSAsync-CO44W23e.js +124 -0
  9. package/dist/OPFSAsync-CO44W23e.js.map +1 -0
  10. package/dist/OPFSFacade-CEaSqTut.js +308 -0
  11. package/dist/OPFSFacade-CEaSqTut.js.map +1 -0
  12. package/dist/OPFSFacade-hrNXCn6w.cjs +2 -0
  13. package/dist/OPFSFacade-hrNXCn6w.cjs.map +1 -0
  14. package/dist/OPFSSync-BPrNtvFS.cjs +2 -0
  15. package/dist/OPFSSync-BPrNtvFS.cjs.map +1 -0
  16. package/dist/OPFSSync-D7obTsl3.js +208 -0
  17. package/dist/OPFSSync-D7obTsl3.js.map +1 -0
  18. package/dist/assets/dedicated.worker-DW6UqFo0.js.map +1 -0
  19. package/dist/{OPFSWorker.d.ts → core/BaseOPFS.d.ts} +53 -292
  20. package/dist/core/BaseOPFS.d.ts.map +1 -0
  21. package/dist/core/OPFSAsync.d.ts +55 -0
  22. package/dist/core/OPFSAsync.d.ts.map +1 -0
  23. package/dist/core/OPFSSync.d.ts +54 -0
  24. package/dist/core/OPFSSync.d.ts.map +1 -0
  25. package/dist/createOPFSAsync-DdhJYoC0.js +10 -0
  26. package/dist/createOPFSAsync-DdhJYoC0.js.map +1 -0
  27. package/dist/createOPFSAsync-DyWpvmL-.cjs +2 -0
  28. package/dist/createOPFSAsync-DyWpvmL-.cjs.map +1 -0
  29. package/dist/createOPFSDedicated-BwTc0NV5.js +2407 -0
  30. package/dist/createOPFSDedicated-BwTc0NV5.js.map +1 -0
  31. package/dist/createOPFSDedicated-CEeRexom.cjs +2357 -0
  32. package/dist/createOPFSDedicated-CEeRexom.cjs.map +1 -0
  33. package/dist/createOPFSShared-BSFWsoLe.js +23 -0
  34. package/dist/createOPFSShared-BSFWsoLe.js.map +1 -0
  35. package/dist/createOPFSShared-BoCft_BS.cjs +2 -0
  36. package/dist/createOPFSShared-BoCft_BS.cjs.map +1 -0
  37. package/dist/dedicated.worker.js +2355 -0
  38. package/dist/dedicated.worker.js.map +1 -0
  39. package/dist/{OPFSFacade.d.ts → facade/OPFSFacade.d.ts} +51 -25
  40. package/dist/facade/OPFSFacade.d.ts.map +1 -0
  41. package/dist/facade/createOPFSAsync.d.ts +16 -0
  42. package/dist/facade/createOPFSAsync.d.ts.map +1 -0
  43. package/dist/facade/createOPFSDedicated.d.ts +17 -0
  44. package/dist/facade/createOPFSDedicated.d.ts.map +1 -0
  45. package/dist/facade/createOPFSShared.d.ts +24 -0
  46. package/dist/facade/createOPFSShared.d.ts.map +1 -0
  47. package/dist/index.async.cjs +2 -0
  48. package/dist/index.async.cjs.map +1 -0
  49. package/dist/index.async.d.ts +10 -0
  50. package/dist/index.async.d.ts.map +1 -0
  51. package/dist/index.async.js +55 -0
  52. package/dist/index.async.js.map +1 -0
  53. package/dist/index.cjs +1 -2477
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.ts +17 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +61 -2828
  58. package/dist/index.js.map +1 -1
  59. package/dist/index.pure.cjs +1 -1
  60. package/dist/index.pure.cjs.map +1 -1
  61. package/dist/index.pure.d.ts +3 -1
  62. package/dist/index.pure.d.ts.map +1 -1
  63. package/dist/index.pure.js +50 -1053
  64. package/dist/index.pure.js.map +1 -1
  65. package/dist/index.sharedworker.cjs +2 -0
  66. package/dist/index.sharedworker.cjs.map +1 -0
  67. package/dist/index.sharedworker.d.ts +11 -0
  68. package/dist/index.sharedworker.d.ts.map +1 -0
  69. package/dist/index.sharedworker.js +55 -0
  70. package/dist/index.sharedworker.js.map +1 -0
  71. package/dist/index.sync.cjs +2 -0
  72. package/dist/index.sync.cjs.map +1 -0
  73. package/dist/index.sync.d.ts +11 -0
  74. package/dist/index.sync.d.ts.map +1 -0
  75. package/dist/index.sync.js +55 -0
  76. package/dist/index.sync.js.map +1 -0
  77. package/dist/shared.worker.js +2240 -0
  78. package/dist/shared.worker.js.map +1 -0
  79. package/dist/types.d.ts +42 -4
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/worker/createDedicatedWorker.d.ts +30 -0
  82. package/dist/worker/createDedicatedWorker.d.ts.map +1 -0
  83. package/dist/worker/createSharedWorker.d.ts +37 -0
  84. package/dist/worker/createSharedWorker.d.ts.map +1 -0
  85. package/dist/worker/dedicated.worker.d.ts +2 -0
  86. package/dist/worker/dedicated.worker.d.ts.map +1 -0
  87. package/dist/worker/shared.worker.d.ts +2 -0
  88. package/dist/worker/shared.worker.d.ts.map +1 -0
  89. package/docs/api/backend.md +36 -0
  90. package/docs/api/create.md +82 -0
  91. package/docs/api/facade.md +53 -0
  92. package/docs/choosing-a-mode.md +41 -0
  93. package/docs/file-descriptors.md +46 -605
  94. package/docs/guides/async.md +36 -0
  95. package/docs/guides/dedicated.md +76 -0
  96. package/docs/guides/hashing.md +34 -0
  97. package/docs/guides/pure.md +46 -0
  98. package/docs/guides/sharedworker.md +57 -0
  99. package/docs/guides/streaming.md +185 -0
  100. package/docs/guides/watching.md +35 -0
  101. package/docs/migration.md +41 -0
  102. package/docs/types.md +105 -153
  103. package/package.json +53 -4
  104. package/src/{OPFSWorker.ts → core/BaseOPFS.ts} +159 -629
  105. package/src/core/OPFSAsync.ts +248 -0
  106. package/src/core/OPFSSync.ts +460 -0
  107. package/src/{OPFSFacade.ts → facade/OPFSFacade.ts} +134 -58
  108. package/src/facade/createOPFSAsync.ts +22 -0
  109. package/src/facade/createOPFSDedicated.ts +23 -0
  110. package/src/facade/createOPFSShared.ts +30 -0
  111. package/src/index.async.ts +11 -0
  112. package/src/index.pure.ts +3 -2
  113. package/src/index.sharedworker.ts +12 -0
  114. package/src/index.sync.ts +12 -0
  115. package/src/index.ts +37 -8
  116. package/src/types.ts +45 -4
  117. package/src/worker/createDedicatedWorker.ts +74 -0
  118. package/src/worker/createSharedWorker.ts +67 -0
  119. package/src/worker/dedicated.worker.ts +13 -0
  120. package/src/worker/shared.worker.ts +17 -0
  121. package/dist/OPFSFacade.d.ts.map +0 -1
  122. package/dist/OPFSWorker.d.ts.map +0 -1
  123. package/dist/assets/worker.entry-DUlEoroc.js.map +0 -1
  124. package/dist/createOPFSWorker.d.ts +0 -17
  125. package/dist/createOPFSWorker.d.ts.map +0 -1
  126. package/dist/helpers-DNj8ZoMu.cjs +0 -4
  127. package/dist/helpers-DNj8ZoMu.cjs.map +0 -1
  128. package/dist/helpers-WY2jfbOT.js +0 -1427
  129. package/dist/helpers-WY2jfbOT.js.map +0 -1
  130. package/dist/worker.entry.d.ts +0 -2
  131. package/dist/worker.entry.d.ts.map +0 -1
  132. package/docs/api-reference.md +0 -815
  133. package/src/createOPFSWorker.ts +0 -57
  134. package/src/worker.entry.ts +0 -6
@@ -0,0 +1,36 @@
1
+ # Async (no worker)
2
+
3
+ `OPFSAsync` on the current thread — `getFile()` / `createWritable()`, no Comlink, no worker in the bundle.
4
+
5
+ Same limits apply as SharedWorker (it’s the same backend).
6
+
7
+ ## Facade
8
+
9
+ ```typescript
10
+ import { createOPFSAsync } from 'opfs-worker/async';
11
+
12
+ const fs = createOPFSAsync({ root: '/my-app' });
13
+
14
+ await fs.writeFile('/note.txt', 'hello');
15
+ await fs.appendText('/note.txt', '!');
16
+ const text = await fs.readFile('/note.txt', 'utf-8');
17
+ ```
18
+
19
+ Import from `opfs-worker/async` to keep the dedicated-worker code out of the bundle.
20
+
21
+ ## Raw class
22
+
23
+ ```typescript
24
+ import { OPFSAsync } from 'opfs-worker/async';
25
+
26
+ const fs = new OPFSAsync({ root: '/my-app' });
27
+ await fs.writeFile('/x.bin', new Uint8Array([1, 2, 3]));
28
+ ```
29
+
30
+ ## Limits
31
+
32
+ - **Writes** need `createWritable()` — Chrome, Firefox, Safari **26+**. Reads work anywhere OPFS does.
33
+ - **No file descriptors** — `open` / `read` / `write` / … throw `ENOTSUP`.
34
+ - Each write goes through a swap file and commits on close — fine for documents, slow for many tiny random writes.
35
+
36
+ Streaming still works: [streaming](./streaming.md). For one fs across tabs: [SharedWorker](./sharedworker.md).
@@ -0,0 +1,76 @@
1
+ # Dedicated worker
2
+
3
+ The default path: `OPFSSync` inside a dedicated Web Worker. You get file descriptors and the fastest write path the browser allows for OPFS.
4
+
5
+ See also [Choosing a mode](../choosing-a-mode.md).
6
+
7
+ ## Facade
8
+
9
+ ```typescript
10
+ import { createOPFSDedicated } from 'opfs-worker';
11
+ // or from 'opfs-worker/sync'
12
+
13
+ const fs = createOPFSDedicated({ root: '/my-app' });
14
+
15
+ await fs.writeFile('/config.json', JSON.stringify({ theme: 'dark' }));
16
+ const config = await fs.readFile('/config.json'); // string — auto-detected
17
+ ```
18
+
19
+ `createOPFS` is the same thing with a shorter name.
20
+
21
+ The worker is inlined by default, so you don’t need a special bundler worker setup.
22
+
23
+ ## Raw backend
24
+
25
+ Bytes in, bytes out — no encoding helpers. Same facade, via `backend`:
26
+
27
+ ```typescript
28
+ const fs = createOPFSDedicated({ root: '/my-app' });
29
+
30
+ await fs.backend.writeFile('/config.json', new TextEncoder().encode('{}'));
31
+ const bytes = await fs.backend.readFile('/config.json');
32
+
33
+ // Browser Worker:
34
+ fs.worker; // Worker
35
+
36
+ fs.dispose(); // dispose + terminate
37
+ ```
38
+
39
+ ## Loading a real worker file
40
+
41
+ Pass `url` or `worker` when you can’t use the inline blob (strict CSP, custom hosting):
42
+
43
+ ```typescript
44
+ import workerUrl from 'opfs-worker/dedicated.worker.js?url'; // Vite
45
+
46
+ const fs = createOPFSDedicated({ root: '/my-app', url: workerUrl });
47
+ ```
48
+
49
+ ## DIY prebuilt script
50
+
51
+ `opfs-worker/dedicated.worker.js` is a self-contained Worker (Comlink + `OPFSSync` already inside):
52
+
53
+ ```typescript
54
+ import { wrap } from 'comlink';
55
+ import { OPFSFacade } from 'opfs-worker';
56
+ import workerUrl from 'opfs-worker/dedicated.worker.js?url';
57
+
58
+ const worker = new Worker(workerUrl, { type: 'module' });
59
+ const remote = wrap(worker);
60
+
61
+ const fs = new OPFSFacade({
62
+ fs: remote,
63
+ worker,
64
+ dispose: () => {
65
+ void remote.dispose();
66
+ worker.terminate();
67
+ },
68
+ });
69
+ ```
70
+
71
+ ## Notes
72
+
73
+ - Each call gets its own worker. Tabs don’t share state — use [SharedWorker](./sharedworker.md) for that.
74
+ - Sync access handles only work in a dedicated worker.
75
+
76
+ Also: [file descriptors](../file-descriptors.md), [streaming](./streaming.md), [create helpers](../api/create.md).
@@ -0,0 +1,34 @@
1
+ # Hashing
2
+
3
+ Set once via `hashAlgorithm` / `maxFileSize` in options or `setOptions()`. Shows up on `stat()`, `index()`, and [watch](./watching.md) events (files only).
4
+
5
+ | Value | What you get |
6
+ | ----- | ------------ |
7
+ | `'etag'` (default) | Tag from mtime + size — no content read |
8
+ | `'SHA-1'` … `'SHA-512'` | Real content hash (Web Crypto) |
9
+ | `null` / `false` | No `hash` field |
10
+
11
+ `maxFileSize` (default 50MB) only limits SHA-\* hashing. Oversized files skip the hash.
12
+
13
+ ```typescript
14
+ import { createOPFS } from 'opfs-worker';
15
+
16
+ const fs = createOPFS();
17
+
18
+ await fs.writeFile('/data.txt', 'Hello');
19
+ console.log((await fs.stat('/data.txt')).hash); // e.g. "m1abc-c"
20
+
21
+ await fs.setOptions({ hashAlgorithm: 'SHA-256' });
22
+ console.log((await fs.stat('/data.txt')).hash); // hex digest
23
+ ```
24
+
25
+ `root` scopes you under a subdirectory of OPFS. `namespace` is stamped on every watch event:
26
+
27
+ ```typescript
28
+ const fs = createOPFS({
29
+ root: '/my-app',
30
+ namespace: 'my-app:fs',
31
+ });
32
+ ```
33
+
34
+ Full options table: [`create` helpers](../api/create.md#options).
@@ -0,0 +1,46 @@
1
+ # Pure classes
2
+
3
+ Use the classes inside a worker (or thread) you already own. This package does not spawn a nested worker or call Comlink `expose()` — main-thread wiring is yours.
4
+
5
+ ```typescript
6
+ import { OPFSSync, OPFSAsync } from 'opfs-worker/pure';
7
+ ```
8
+
9
+ | Class | Runs where | FD |
10
+ | ----- | ---------- | -- |
11
+ | `OPFSSync` | Dedicated worker only | yes |
12
+ | `OPFSAsync` | Main thread, dedicated, or SharedWorker | no |
13
+ | `BaseOPFS` | Shared base — usually you don’t instantiate it | — |
14
+
15
+ ## OPFSSync in your dedicated worker
16
+
17
+ ```typescript
18
+ // my-app.worker.ts
19
+ import { OPFSSync } from 'opfs-worker/pure';
20
+
21
+ const fs = new OPFSSync({
22
+ root: '/my-app',
23
+ broadcastChannel: 'my-app-events', // name only — instances can’t cross the wire
24
+ });
25
+
26
+ await fs.writeFile('/config.json', new TextEncoder().encode('{}'));
27
+ ```
28
+
29
+ `/pure` does not take over your worker’s message port.
30
+
31
+ ## OPFSAsync in a SharedWorker you write
32
+
33
+ ```typescript
34
+ import { expose } from 'comlink';
35
+ import { OPFSAsync } from 'opfs-worker/pure';
36
+
37
+ const fs = new OPFSAsync({ root: '/my-app' });
38
+
39
+ onconnect = (e) => {
40
+ expose(fs, e.ports[0]);
41
+ };
42
+ ```
43
+
44
+ Or use the [SharedWorker guide](./sharedworker.md).
45
+
46
+ Also: [backend API](../api/backend.md), [dedicated](./dedicated.md), [async](./async.md).
@@ -0,0 +1,57 @@
1
+ # SharedWorker
2
+
3
+ One `OPFSAsync` for every tab. Locks live in that instance, so writes across tabs don’t collide. Watch events use `BroadcastChannel` as usual.
4
+
5
+ Same limits as [async](./async.md): Safari 26+ for writes, no FDs.
6
+
7
+ SharedWorkers are keyed by **script URL**, so we can’t inline the worker. Use the shipped file: `opfs-worker/shared.worker.js`.
8
+
9
+ ## With a bundler (Vite example)
10
+
11
+ ```typescript
12
+ import workerUrl from 'opfs-worker/shared.worker.js?url';
13
+ import { createOPFSShared } from 'opfs-worker/sharedworker';
14
+
15
+ const fs = createOPFSShared({ root: '/my-app', url: workerUrl });
16
+
17
+ await fs.writeText('/shared-config.json', '{}');
18
+ ```
19
+
20
+ ## Without a bundler
21
+
22
+ Default URL looks for `./shared.worker.js` next to the package files:
23
+
24
+ ```typescript
25
+ import { createOPFSShared } from 'opfs-worker/sharedworker';
26
+
27
+ const fs = createOPFSShared({ root: '/my-app' });
28
+ ```
29
+
30
+ Or pass your own instance:
31
+
32
+ ```typescript
33
+ const worker = new SharedWorker(
34
+ new URL('opfs-worker/shared.worker.js', import.meta.url),
35
+ { type: 'module', name: 'opfs-worker' }
36
+ );
37
+ const fs = createOPFSShared({ root: '/my-app', worker });
38
+ ```
39
+
40
+ ## Raw backend
41
+
42
+ ```typescript
43
+ const fs = createOPFSShared({ root: '/my-app', url: workerUrl });
44
+
45
+ await fs.backend.writeFile('/a.bin', bytes);
46
+ fs.worker; // SharedWorker
47
+
48
+ fs.dispose(); // closes this tab’s port only
49
+ ```
50
+
51
+ ## Notes
52
+
53
+ - `dispose()` does **not** kill the worker for other tabs — only your port.
54
+ - `setOptions` hits the shared instance, so keep options consistent across tabs.
55
+ - Same script URL + `name` (default `'opfs-worker'`) → same instance.
56
+
57
+ Also: [async](./async.md), [watching](./watching.md), [create helpers](../api/create.md).
@@ -0,0 +1,185 @@
1
+ # Streaming
2
+
3
+ Write large files without buffering the whole payload.
4
+
5
+ | | Method |
6
+ | ------------- | ------------------------------------------------------------------------------ |
7
+ | Facade | `importStream(path, source, options?)` · `importFiles(entries, options?)` |
8
+ | Backend / raw | `writeStream(path, stream, onProgress?)` · `importFiles(entries, onProgress?)` |
9
+
10
+ Both create or overwrite the file, return bytes written, and fire a watch event when done.
11
+
12
+ ## `importStream` (facade)
13
+
14
+ Takes a `ReadableStream<Uint8Array>`, or a `Blob` / `File` (we call `.stream()` for you):
15
+
16
+ ```typescript
17
+ import { createOPFS } from 'opfs-worker';
18
+
19
+ const fs = createOPFS({ root: '/my-app' });
20
+
21
+ const file = input.files[0];
22
+ await fs.importStream(`/uploads/${file.name}`, file, {
23
+ onProgress: (n) => console.log(`wrote ${n} bytes`),
24
+ });
25
+
26
+ const res = await fetch('/large.bin');
27
+ await fs.importStream('/cache/large.bin', res.body!);
28
+ ```
29
+
30
+ With a worker backend the facade transfers the stream and proxies `onProgress`.
31
+
32
+ ## `importFiles` (bulk)
33
+
34
+ Same streaming path for many entries — strings, bytes, Blobs, or Files.
35
+
36
+ First argument is any iterable of `[path, data]` pairs: an array of tuples, a `Map`, etc.
37
+
38
+ `onProgress` receives an object (not a bare number):
39
+
40
+ | Field | Meaning |
41
+ | ---------------------------------- | ----------------------------------------- |
42
+ | `path` | File currently being written |
43
+ | `index` / `count` | 0-based index and total number of entries |
44
+ | `bytesWritten` / `bytesTotal` | Progress within the current file |
45
+ | `totalBytesWritten` / `totalBytes` | Progress across the whole import |
46
+
47
+ ```typescript
48
+ await fs.importFiles(
49
+ [
50
+ ['/a.txt', 'hello'],
51
+ ['/upload.bin', fileFromInput],
52
+ ],
53
+ {
54
+ onProgress: ({ path, bytesWritten, bytesTotal, totalBytesWritten, totalBytes }) => {
55
+ console.log(`${path}: ${bytesWritten}/${bytesTotal} (all ${totalBytesWritten}/${totalBytes})`);
56
+ },
57
+ }
58
+ );
59
+ // → { paths: ['/a.txt', '/upload.bin'], count: 2, bytesWritten: … }
60
+
61
+ // Map works too
62
+ await fs.importFiles(new Map([
63
+ ['/a.txt', 'hello'],
64
+ ['/b.txt', 'world'],
65
+ ]));
66
+ ```
67
+
68
+ `createIndex` is kept as a deprecated alias.
69
+
70
+ ## Uploading files and folders from disk
71
+
72
+ Getting `File` objects out of the browser is the DOM's job; once you have them, `importFiles` does the rest.
73
+
74
+ ### File / folder picker
75
+
76
+ A plain `<input type="file">` gives you files. Add `webkitdirectory` and the picker selects a whole folder — each file then carries its relative path in `webkitRelativePath`:
77
+
78
+ ```html
79
+ <input type="file" id="files" multiple />
80
+ <input type="file" id="folder" webkitdirectory multiple />
81
+ ```
82
+
83
+ ```typescript
84
+ input.addEventListener('change', async () => {
85
+ const entries = [...input.files].map((file) => [
86
+ `/uploads/${file.webkitRelativePath || file.name}`,
87
+ file,
88
+ ] as [string, File]);
89
+
90
+ await fs.importFiles(entries, {
91
+ onProgress: ({ path, totalBytesWritten, totalBytes }) => {
92
+ console.log(`${path}: ${totalBytesWritten}/${totalBytes}`);
93
+ },
94
+ });
95
+ });
96
+ ```
97
+
98
+ ### Drag and drop
99
+
100
+ Dropped folders are only reachable through the non-standard (but universally supported) `webkitGetAsEntry` API, which has to be walked recursively:
101
+
102
+ ```typescript
103
+ async function collect(entry: FileSystemEntry, prefix = ''): Promise<[string, File][]> {
104
+ if (entry.isFile) {
105
+ const file = await new Promise<File>((res, rej) => (entry as FileSystemFileEntry).file(res, rej));
106
+
107
+ return [[`${prefix}${entry.name}`, file]];
108
+ }
109
+
110
+ const reader = (entry as FileSystemDirectoryEntry).createReader();
111
+ const children: FileSystemEntry[] = [];
112
+
113
+ // readEntries returns partial batches — keep calling until it's empty
114
+ for (;;) {
115
+ const batch = await new Promise<FileSystemEntry[]>((res, rej) => reader.readEntries(res, rej));
116
+
117
+ if (batch.length === 0) break;
118
+ children.push(...batch);
119
+ }
120
+
121
+ const nested = await Promise.all(children.map((c) => collect(c, `${prefix}${entry.name}/`)));
122
+
123
+ return nested.flat();
124
+ }
125
+
126
+ dropZone.addEventListener('drop', async (e) => {
127
+ e.preventDefault();
128
+
129
+ const entries = [...e.dataTransfer!.items]
130
+ .map((item) => item.webkitGetAsEntry())
131
+ .filter((entry): entry is FileSystemEntry => entry !== null);
132
+
133
+ const files = (await Promise.all(entries.map((entry) => collect(entry, '/uploads/')))).flat();
134
+
135
+ await fs.importFiles(files, {
136
+ onProgress: ({ path, index, count, bytesWritten, bytesTotal }) => {
137
+ console.log(`[${index + 1}/${count}] ${path}: ${bytesWritten}/${bytesTotal}`);
138
+ },
139
+ });
140
+ });
141
+ ```
142
+
143
+ The [demo](https://kachurun.github.io/opfs-worker/) implements exactly this flow — see `demo/FileBrowser.tsx`.
144
+
145
+ ## `writeStream` (raw)
146
+
147
+ ```typescript
148
+ import { createOPFS } from 'opfs-worker';
149
+
150
+ const fs = createOPFS();
151
+ await fs.backend.writeStream('/data.bin', someBlob.stream(), (n) => console.log(n));
152
+ ```
153
+
154
+ ## Reading back without buffering: `readBlob`
155
+
156
+ `readFile` copies the whole file into a `Uint8Array`, which is wasteful for media. `readBlob` returns the disk-backed `Blob` that OPFS already has, so nothing is read until something asks for bytes:
157
+
158
+ ```typescript
159
+ const blob = await fs.readBlob('/media/clip.mp4');
160
+
161
+ video.src = URL.createObjectURL(blob);
162
+ ```
163
+
164
+ The browser then seeks and buffers only the ranges it plays — a 2 GB video costs no memory up front. The same `Blob` works for `<img>`, `<audio>`, `<iframe>` (PDF), `fetch()` bodies, and `showSaveFilePicker` downloads.
165
+
166
+ Reading a small header stays cheap too, since `slice()` does not touch the rest of the file:
167
+
168
+ ```typescript
169
+ const magic = new Uint8Array(await blob.slice(0, 4).arrayBuffer());
170
+ ```
171
+
172
+ When the file system runs in a worker, the `Blob` crosses the boundary by reference — structured clone does not copy its contents.
173
+
174
+ ## When to use what
175
+
176
+ | API | Good for |
177
+ | ------------------------------ | ------------------------------------------------------------ |
178
+ | `writeFile` | Data already in memory |
179
+ | `importStream` / `writeStream` | One large `File` / `Blob` / network body |
180
+ | `importFiles` | Many files / folder uploads (streamed, with total progress) |
181
+ | `readBlob` | Media previews, downloads, anything you can hand a `Blob` to |
182
+
183
+ Dedicated path chunks through FDs; async path uses `createWritable()` (Safari 26+ for writes).
184
+
185
+ Also: [facade](../api/facade.md), [backend](../api/backend.md).
@@ -0,0 +1,35 @@
1
+ # Watching
2
+
3
+ Changes go out on a `BroadcastChannel` (default name `opfs-worker`). Nothing is emitted unless you `watch` a path.
4
+
5
+ ```typescript
6
+ import { createOPFS } from 'opfs-worker';
7
+
8
+ const channel = new BroadcastChannel('opfs-worker');
9
+ channel.onmessage = (e) => console.log(e.data);
10
+
11
+ const fs = createOPFS({
12
+ root: '/my-app',
13
+ namespace: 'my-app:fs',
14
+ broadcastChannel: channel, // or just the string 'opfs-worker'
15
+ });
16
+
17
+ const stop = fs.watch('/', {
18
+ recursive: true,
19
+ include: ['**/*.json'],
20
+ exclude: ['tmp/**'],
21
+ });
22
+
23
+ await fs.writeFile('/config.json', '{}');
24
+ // → { namespace, path, type: 'added' | 'changed' | 'removed', isDirectory, timestamp, hash? }
25
+
26
+ stop(); // or fs.unwatch('/')
27
+ ```
28
+
29
+ `watch` returns an unsubscribe function. Options: [`WatchOptions`](../types.md#watchoptions). Payload: [`WatchEvent`](../types.md#watchevent).
30
+
31
+ ## Notes
32
+
33
+ - Pass a **channel name** (string) into a worker — a `BroadcastChannel` instance can’t cross the wire. On the main thread the facade accepts either.
34
+ - SharedWorker: one backend + one channel → every listening tab sees the same events.
35
+ - Whether events include `hash` depends on [hashing](./hashing.md).
@@ -0,0 +1,41 @@
1
+ # Migration (1.x → 2.x)
2
+
3
+ ## Renames
4
+
5
+ | Was | Now |
6
+ | ------------------------ | ------------------------------------------------------------------------------------ |
7
+ | `createWorker` | `createOPFSDedicated` (`createOPFS` alias; deprecated `createWorker` still exported) |
8
+ | `OPFSFileSystem` | `OPFSFacade` (deprecated alias kept) |
9
+ | `OPFSWorker` | `OPFSSync` |
10
+ | `opfs-worker/raw` | `opfs-worker/pure` |
11
+ | `opfs-worker/raw?worker` | `createOPFSDedicated()` + `fs.backend` / `fs.worker`, or `dedicated.worker.js` |
12
+ | `fs.sync(entries)` | `fs.importFiles(entries)` (`createIndex` alias kept, deprecated) |
13
+
14
+ ```diff
15
+ - import { createWorker, OPFSFileSystem } from 'opfs-worker';
16
+ - const fs: OPFSFileSystem = createWorker({ root: '/my-app' });
17
+ + import { createOPFS, OPFSFacade } from 'opfs-worker';
18
+ + const fs: OPFSFacade = createOPFS({ root: '/my-app' });
19
+ ```
20
+
21
+ ```diff
22
+ - import { OPFSWorker } from 'opfs-worker/raw';
23
+ + import { OPFSSync } from 'opfs-worker/pure';
24
+ ```
25
+
26
+ ## New in 2.x
27
+
28
+ - Async: `createOPFSAsync` / `opfs-worker/async`
29
+ - SharedWorker: `createOPFSShared` / `opfs-worker/sharedworker`
30
+ - Prebuilt scripts: `dedicated.worker.js`, `shared.worker.js`
31
+ - Streaming: `importStream` / `writeStream`
32
+ - `fs.backend` / `fs.worker` on every facade
33
+ - `createOPFSDedicated`, `createOPFSAsync`, `createOPFSShared`
34
+
35
+ ## Behaviour
36
+
37
+ - Concurrent `readFile` on one path takes the same exclusive lock as writes
38
+ - Multiple FDs on one path share one sync handle, each with its own cursor
39
+ - Dedicated `dispose()` terminates the browser `Worker`
40
+
41
+ Full entry map: [Choosing a mode](./choosing-a-mode.md).