opfs-worker 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +51 -37
  2. package/dist/BaseOPFS-Bu7FZtCb.cjs +2 -0
  3. package/dist/BaseOPFS-Bu7FZtCb.cjs.map +1 -0
  4. package/dist/{BaseOPFS-Cylz6ijk.js → BaseOPFS-C3NE4BKr.js} +804 -484
  5. package/dist/BaseOPFS-C3NE4BKr.js.map +1 -0
  6. package/dist/OPFSAsync-C0RsxCX5.cjs +2 -0
  7. package/dist/OPFSAsync-C0RsxCX5.cjs.map +1 -0
  8. package/dist/{OPFSAsync-BKSAJJeh.js → OPFSAsync-aeLUdIqd.js} +19 -19
  9. package/dist/OPFSAsync-aeLUdIqd.js.map +1 -0
  10. package/dist/OPFSFacade-BJv2yuWU.cjs +2 -0
  11. package/dist/OPFSFacade-BJv2yuWU.cjs.map +1 -0
  12. package/dist/OPFSFacade-DiogtQoq.js +308 -0
  13. package/dist/OPFSFacade-DiogtQoq.js.map +1 -0
  14. package/dist/OPFSSync-3qlSBYmz.cjs +2 -0
  15. package/dist/OPFSSync-3qlSBYmz.cjs.map +1 -0
  16. package/dist/{OPFSSync-CsSQkpk5.js → OPFSSync-CvSOswhW.js} +57 -57
  17. package/dist/OPFSSync-CvSOswhW.js.map +1 -0
  18. package/dist/assets/dedicated.worker-Bqqr9UBA.js.map +1 -0
  19. package/dist/core/BaseOPFS.d.ts +60 -9
  20. package/dist/core/BaseOPFS.d.ts.map +1 -1
  21. package/dist/core/OPFSAsync.d.ts.map +1 -1
  22. package/dist/core/OPFSSync.d.ts.map +1 -1
  23. package/dist/createOPFSAsync-B611B63X.js +10 -0
  24. package/dist/{createOPFSAsync-Y4Mg6qte.js.map → createOPFSAsync-B611B63X.js.map} +1 -1
  25. package/dist/createOPFSAsync-BATjJg8k.cjs +2 -0
  26. package/dist/{createOPFSAsync-BwKDmHsy.cjs.map → createOPFSAsync-BATjJg8k.cjs.map} +1 -1
  27. package/dist/createOPFSDedicated-DgudPTB8.js +2676 -0
  28. package/dist/createOPFSDedicated-DgudPTB8.js.map +1 -0
  29. package/dist/createOPFSDedicated-EmIwluQo.cjs +2597 -0
  30. package/dist/createOPFSDedicated-EmIwluQo.cjs.map +1 -0
  31. package/dist/{createOPFSShared-CGdd__PH.js → createOPFSShared-BWYln8jJ.js} +2 -2
  32. package/dist/{createOPFSShared-CGdd__PH.js.map → createOPFSShared-BWYln8jJ.js.map} +1 -1
  33. package/dist/{createOPFSShared-CskROt7U.cjs → createOPFSShared-DPJpCd7z.cjs} +2 -2
  34. package/dist/{createOPFSShared-CskROt7U.cjs.map → createOPFSShared-DPJpCd7z.cjs.map} +1 -1
  35. package/dist/dedicated.worker.js +1104 -790
  36. package/dist/dedicated.worker.js.map +1 -1
  37. package/dist/facade/OPFSFacade.d.ts +21 -3
  38. package/dist/facade/OPFSFacade.d.ts.map +1 -1
  39. package/dist/facade/createOPFSDedicated.d.ts +4 -2
  40. package/dist/facade/createOPFSDedicated.d.ts.map +1 -1
  41. package/dist/index.async.cjs +1 -1
  42. package/dist/index.async.js +33 -32
  43. package/dist/index.cjs +1 -1
  44. package/dist/index.d.ts +1 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +44 -43
  47. package/dist/index.pure.cjs +1 -1
  48. package/dist/index.pure.js +38 -37
  49. package/dist/index.sharedworker.cjs +1 -1
  50. package/dist/index.sharedworker.js +34 -33
  51. package/dist/index.sync.cjs +1 -1
  52. package/dist/index.sync.d.ts +2 -0
  53. package/dist/index.sync.d.ts.map +1 -1
  54. package/dist/index.sync.js +34 -32
  55. package/dist/shared.worker.js +1101 -791
  56. package/dist/shared.worker.js.map +1 -1
  57. package/dist/types.d.ts +38 -5
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/utils/helpers.d.ts +13 -3
  60. package/dist/utils/helpers.d.ts.map +1 -1
  61. package/dist/worker/createDedicatedWorker.d.ts +8 -3
  62. package/dist/worker/createDedicatedWorker.d.ts.map +1 -1
  63. package/docs/README.md +13 -25
  64. package/docs/api/README.md +221 -0
  65. package/docs/api/file-descriptors.md +137 -0
  66. package/docs/guides/async.md +4 -4
  67. package/docs/guides/dedicated.md +10 -8
  68. package/docs/guides/hashing.md +3 -3
  69. package/docs/guides/pure.md +4 -4
  70. package/docs/guides/sharedworker.md +2 -2
  71. package/docs/guides/streaming.md +146 -12
  72. package/docs/guides/watching.md +3 -3
  73. package/docs/migration.md +10 -11
  74. package/docs/types.md +93 -155
  75. package/package.json +20 -9
  76. package/src/core/BaseOPFS.ts +194 -29
  77. package/src/core/OPFSAsync.ts +4 -5
  78. package/src/core/OPFSSync.ts +6 -7
  79. package/src/facade/OPFSFacade.ts +51 -4
  80. package/src/facade/createOPFSDedicated.ts +4 -2
  81. package/src/index.sync.ts +2 -0
  82. package/src/index.ts +1 -5
  83. package/src/types.ts +42 -3
  84. package/src/utils/helpers.ts +32 -6
  85. package/src/worker/createDedicatedWorker.ts +85 -7
  86. package/src/worker/dedicated.worker.ts +12 -1
  87. package/dist/BaseOPFS-BPw9AIQ-.cjs +0 -4
  88. package/dist/BaseOPFS-BPw9AIQ-.cjs.map +0 -1
  89. package/dist/BaseOPFS-Cylz6ijk.js.map +0 -1
  90. package/dist/OPFSAsync-BKSAJJeh.js.map +0 -1
  91. package/dist/OPFSAsync-s2P7y3sw.cjs +0 -2
  92. package/dist/OPFSAsync-s2P7y3sw.cjs.map +0 -1
  93. package/dist/OPFSFacade-DePnNRgt.cjs +0 -2
  94. package/dist/OPFSFacade-DePnNRgt.cjs.map +0 -1
  95. package/dist/OPFSFacade-XohWlHuu.js +0 -281
  96. package/dist/OPFSFacade-XohWlHuu.js.map +0 -1
  97. package/dist/OPFSSync-C7KBepe_.cjs +0 -2
  98. package/dist/OPFSSync-C7KBepe_.cjs.map +0 -1
  99. package/dist/OPFSSync-CsSQkpk5.js.map +0 -1
  100. package/dist/assets/dedicated.worker-nY3tK4zi.js.map +0 -1
  101. package/dist/createOPFSAsync-BwKDmHsy.cjs +0 -2
  102. package/dist/createOPFSAsync-Y4Mg6qte.js +0 -10
  103. package/dist/createOPFSDedicated-CsnV6MLP.js +0 -2333
  104. package/dist/createOPFSDedicated-CsnV6MLP.js.map +0 -1
  105. package/dist/createOPFSDedicated-pKTRWLdz.cjs +0 -2283
  106. package/dist/createOPFSDedicated-pKTRWLdz.cjs.map +0 -1
  107. package/docs/api/backend.md +0 -36
  108. package/docs/api/create.md +0 -82
  109. package/docs/api/facade.md +0 -52
  110. package/docs/choosing-a-mode.md +0 -41
  111. package/docs/file-descriptors.md +0 -695
@@ -2,7 +2,7 @@
2
2
 
3
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
4
 
5
- See also [Choosing a mode](../choosing-a-mode.md).
5
+ See also [API](../api/README.md).
6
6
 
7
7
  ## Facade
8
8
 
@@ -16,13 +16,13 @@ await fs.writeFile('/config.json', JSON.stringify({ theme: 'dark' }));
16
16
  const config = await fs.readFile('/config.json'); // string — auto-detected
17
17
  ```
18
18
 
19
- `createOPFS` is the same thing with a shorter name.
19
+ `createOPFS` is the same thing with a shorter name (also from `opfs-worker/sync`).
20
20
 
21
21
  The worker is inlined by default, so you don’t need a special bundler worker setup.
22
22
 
23
23
  ## Raw backend
24
24
 
25
- Bytes in, bytes out — no encoding helpers. Same facade, just talk to `backend`:
25
+ Bytes in, bytes out — no encoding helpers. Same facade, via `backend`:
26
26
 
27
27
  ```typescript
28
28
  const fs = createOPFSDedicated({ root: '/my-app' });
@@ -30,7 +30,7 @@ const fs = createOPFSDedicated({ root: '/my-app' });
30
30
  await fs.backend.writeFile('/config.json', new TextEncoder().encode('{}'));
31
31
  const bytes = await fs.backend.readFile('/config.json');
32
32
 
33
- // Browser Worker, if you ever need it:
33
+ // Browser Worker:
34
34
  fs.worker; // Worker
35
35
 
36
36
  fs.dispose(); // dispose + terminate
@@ -68,9 +68,11 @@ const fs = new OPFSFacade({
68
68
  });
69
69
  ```
70
70
 
71
- ## Heads-up
71
+ ## Notes
72
72
 
73
- - Each call gets its own worker. Tabs don’t share state use [SharedWorker](./sharedworker.md) for that.
74
- - Sync access handles are a dedicated-worker-only browser API.
73
+ - Same `root` (and same `url`) on one page reuses one Worker; different roots get different Workers. `dispose()` drops your port and terminates the Worker only when the last facade for that pool entry is gone.
74
+ - Tabs don’t share a dedicated Worker — use [SharedWorker](./sharedworker.md) for that.
75
+ - Sync access handles only work in a dedicated worker.
76
+ - `setOptions` hits the shared instance for a pooled root — keep options consistent.
75
77
 
76
- More: [file descriptors](../file-descriptors.md), [streaming](./streaming.md), [create helpers](../api/create.md).
78
+ Also: [file descriptors](../api/file-descriptors.md), [streaming](./streaming.md), [API](../api/README.md).
@@ -4,7 +4,7 @@ Set once via `hashAlgorithm` / `maxFileSize` in options or `setOptions()`. Shows
4
4
 
5
5
  | Value | What you get |
6
6
  | ----- | ------------ |
7
- | `'etag'` (default) | Cheap tag from mtime + size — no content read |
7
+ | `'etag'` (default) | Tag from mtime + size — no content read |
8
8
  | `'SHA-1'` … `'SHA-512'` | Real content hash (Web Crypto) |
9
9
  | `null` / `false` | No `hash` field |
10
10
 
@@ -16,7 +16,7 @@ import { createOPFS } from 'opfs-worker';
16
16
  const fs = createOPFS();
17
17
 
18
18
  await fs.writeFile('/data.txt', 'Hello');
19
- console.log((await fs.stat('/data.txt')).hash); // etag-ish, e.g. "m1abc-c"
19
+ console.log((await fs.stat('/data.txt')).hash); // e.g. "m1abc-c"
20
20
 
21
21
  await fs.setOptions({ hashAlgorithm: 'SHA-256' });
22
22
  console.log((await fs.stat('/data.txt')).hash); // hex digest
@@ -31,4 +31,4 @@ const fs = createOPFS({
31
31
  });
32
32
  ```
33
33
 
34
- Full options table: [`create` helpers](../api/create.md#options).
34
+ Full options table: [API → Options](../api/README.md#options).
@@ -1,6 +1,6 @@
1
1
  # Pure classes
2
2
 
3
- Use the classes directly inside a worker (or thread) you already own. No nested worker, no Comlink `expose()` from this package wiring to the main thread is on you.
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
4
 
5
5
  ```typescript
6
6
  import { OPFSSync, OPFSAsync } from 'opfs-worker/pure';
@@ -26,7 +26,7 @@ const fs = new OPFSSync({
26
26
  await fs.writeFile('/config.json', new TextEncoder().encode('{}'));
27
27
  ```
28
28
 
29
- `/pure` won’t hijack your worker’s message port.
29
+ `/pure` does not take over your worker’s message port.
30
30
 
31
31
  ## OPFSAsync in a SharedWorker you write
32
32
 
@@ -41,6 +41,6 @@ onconnect = (e) => {
41
41
  };
42
42
  ```
43
43
 
44
- Or skip the boilerplate and use the [SharedWorker guide](./sharedworker.md).
44
+ Or use the [SharedWorker guide](./sharedworker.md).
45
45
 
46
- Also: [backend API](../api/backend.md), [dedicated](./dedicated.md), [async](./async.md).
46
+ Also: [dedicated](./dedicated.md), [async](./async.md), [API](../api/README.md).
@@ -1,6 +1,6 @@
1
1
  # SharedWorker
2
2
 
3
- One `OPFSAsync` for every tab. Locks live in that single instance, so writes don’t stomp each other across tabs. Watch events go out on `BroadcastChannel` like everywhere else.
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
4
 
5
5
  Same limits as [async](./async.md): Safari 26+ for writes, no FDs.
6
6
 
@@ -54,4 +54,4 @@ fs.dispose(); // closes this tab’s port only
54
54
  - `setOptions` hits the shared instance, so keep options consistent across tabs.
55
55
  - Same script URL + `name` (default `'opfs-worker'`) → same instance.
56
56
 
57
- Also: [async](./async.md), [watching](./watching.md), [create helpers](../api/create.md).
57
+ Also: [async](./async.md), [watching](./watching.md), [API](../api/README.md).
@@ -1,11 +1,11 @@
1
1
  # Streaming
2
2
 
3
- Write big files without holding the whole thing in memory.
3
+ Write large files without buffering the whole payload.
4
4
 
5
- | | Method |
6
- | --- | --- |
7
- | Facade | `importStream(path, source, options?)` |
8
- | Backend / raw | `writeStream(path, stream, onProgress?)` |
5
+ | | Method |
6
+ | ------------- | ------------------------------------------------------------------------------ |
7
+ | Facade | `importStream(path, source, options?)` · `importFiles(entries, options?)` |
8
+ | Backend / raw | `writeStream(path, stream, onProgress?)` · `importFiles(entries, onProgress?)` |
9
9
 
10
10
  Both create or overwrite the file, return bytes written, and fire a watch event when done.
11
11
 
@@ -27,7 +27,120 @@ const res = await fetch('/large.bin');
27
27
  await fs.importStream('/cache/large.bin', res.body!);
28
28
  ```
29
29
 
30
- With a worker backend the facade transfers the stream and proxies `onProgress` — you don’t need Comlink `transfer` yourself.
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/components/FileBrowser/`.
31
144
 
32
145
  ## `writeStream` (raw)
33
146
 
@@ -38,14 +151,35 @@ const fs = createOPFS();
38
151
  await fs.backend.writeStream('/data.bin', someBlob.stream(), (n) => console.log(n));
39
152
  ```
40
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
+
41
174
  ## When to use what
42
175
 
43
- | API | Good for |
44
- | --- | -------- |
45
- | `writeFile` | Stuff already in memory |
46
- | `importStream` / `writeStream` | Large `File` / `Blob` / network bodies |
47
- | `createIndex` | Many small entries (Blobs here are still fully buffered) |
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 |
48
182
 
49
183
  Dedicated path chunks through FDs; async path uses `createWritable()` (Safari 26+ for writes).
50
184
 
51
- Also: [facade](../api/facade.md), [backend](../api/backend.md).
185
+ Also: [facade](../api/README.md#facade).
@@ -28,8 +28,8 @@ stop(); // or fs.unwatch('/')
28
28
 
29
29
  `watch` returns an unsubscribe function. Options: [`WatchOptions`](../types.md#watchoptions). Payload: [`WatchEvent`](../types.md#watchevent).
30
30
 
31
- ## Gotchas
31
+ ## Notes
32
32
 
33
- - Into a worker, pass a **channel name** (string). You can’t post a `BroadcastChannel` instance across the wire. On the main thread the facade accepts either.
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
34
  - SharedWorker: one backend + one channel → every listening tab sees the same events.
35
- - Whether events include `hash` depends on [hashing](./hashing.md) settings.
35
+ - Whether events include `hash` depends on [hashing](./hashing.md).
package/docs/migration.md CHANGED
@@ -2,14 +2,13 @@
2
2
 
3
3
  ## Renames
4
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.createIndex(entries)` |
5
+ | Was | Now |
6
+ | ------------------------- | ---------------------------------------------------- |
7
+ | `createWorker` | `createOPFS` (deprecated alias kept) |
8
+ | `OPFSFileSystem` | `OPFSFacade` (deprecated alias kept) |
9
+ | `OPFSWorker` | `OPFSSync` |
10
+ | `opfs-worker/raw` | `opfs-worker/pure` |
11
+ | `fs.createIndex(entries)` | `fs.importFiles(entries)` (`createIndex` alias kept) |
13
12
 
14
13
  ```diff
15
14
  - import { createWorker, OPFSFileSystem } from 'opfs-worker';
@@ -29,8 +28,8 @@
29
28
  - SharedWorker: `createOPFSShared` / `opfs-worker/sharedworker`
30
29
  - Prebuilt scripts: `dedicated.worker.js`, `shared.worker.js`
31
30
  - Streaming: `importStream` / `writeStream`
32
- - Escape hatch on every facade: `fs.backend` / `fs.worker`
33
- - Clearer names: `createOPFSDedicated`, `createOPFSAsync`, `createOPFSShared`
31
+ - `fs.backend` / `fs.worker` on every facade
32
+ - `createOPFSDedicated`, `createOPFSAsync`, `createOPFSShared`
34
33
 
35
34
  ## Behaviour
36
35
 
@@ -38,4 +37,4 @@
38
37
  - Multiple FDs on one path share one sync handle, each with its own cursor
39
38
  - Dedicated `dispose()` terminates the browser `Worker`
40
39
 
41
- Full entry map: [Choosing a mode](./choosing-a-mode.md).
40
+ Full entry map: [API](./api/README.md).