opfs-worker 2.1.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 (78) hide show
  1. package/README.md +40 -27
  2. package/dist/{BaseOPFS-Cylz6ijk.js → BaseOPFS-BusBkyJX.js} +436 -358
  3. package/dist/BaseOPFS-BusBkyJX.js.map +1 -0
  4. package/dist/{BaseOPFS-BPw9AIQ-.cjs → BaseOPFS-CYwrD7-q.cjs} +4 -4
  5. package/dist/BaseOPFS-CYwrD7-q.cjs.map +1 -0
  6. package/dist/{OPFSAsync-s2P7y3sw.cjs → OPFSAsync-B2dR-2VR.cjs} +2 -2
  7. package/dist/{OPFSAsync-s2P7y3sw.cjs.map → OPFSAsync-B2dR-2VR.cjs.map} +1 -1
  8. package/dist/{OPFSAsync-BKSAJJeh.js → OPFSAsync-CO44W23e.js} +2 -2
  9. package/dist/{OPFSAsync-BKSAJJeh.js.map → OPFSAsync-CO44W23e.js.map} +1 -1
  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-C7KBepe_.cjs → OPFSSync-BPrNtvFS.cjs} +2 -2
  15. package/dist/{OPFSSync-C7KBepe_.cjs.map → OPFSSync-BPrNtvFS.cjs.map} +1 -1
  16. package/dist/{OPFSSync-CsSQkpk5.js → OPFSSync-D7obTsl3.js} +2 -2
  17. package/dist/{OPFSSync-CsSQkpk5.js.map → OPFSSync-D7obTsl3.js.map} +1 -1
  18. package/dist/assets/dedicated.worker-DW6UqFo0.js.map +1 -0
  19. package/dist/core/BaseOPFS.d.ts +38 -7
  20. package/dist/core/BaseOPFS.d.ts.map +1 -1
  21. package/dist/createOPFSAsync-DdhJYoC0.js +10 -0
  22. package/dist/{createOPFSAsync-Y4Mg6qte.js.map → createOPFSAsync-DdhJYoC0.js.map} +1 -1
  23. package/dist/createOPFSAsync-DyWpvmL-.cjs +2 -0
  24. package/dist/{createOPFSAsync-BwKDmHsy.cjs.map → createOPFSAsync-DyWpvmL-.cjs.map} +1 -1
  25. package/dist/{createOPFSDedicated-CsnV6MLP.js → createOPFSDedicated-BwTc0NV5.js} +289 -215
  26. package/dist/{createOPFSDedicated-CsnV6MLP.js.map → createOPFSDedicated-BwTc0NV5.js.map} +1 -1
  27. package/dist/{createOPFSDedicated-pKTRWLdz.cjs → createOPFSDedicated-CEeRexom.cjs} +286 -212
  28. package/dist/{createOPFSDedicated-pKTRWLdz.cjs.map → createOPFSDedicated-CEeRexom.cjs.map} +1 -1
  29. package/dist/{createOPFSShared-CGdd__PH.js → createOPFSShared-BSFWsoLe.js} +2 -2
  30. package/dist/{createOPFSShared-CGdd__PH.js.map → createOPFSShared-BSFWsoLe.js.map} +1 -1
  31. package/dist/{createOPFSShared-CskROt7U.cjs → createOPFSShared-BoCft_BS.cjs} +2 -2
  32. package/dist/{createOPFSShared-CskROt7U.cjs.map → createOPFSShared-BoCft_BS.cjs.map} +1 -1
  33. package/dist/dedicated.worker.js +282 -208
  34. package/dist/dedicated.worker.js.map +1 -1
  35. package/dist/facade/OPFSFacade.d.ts +21 -3
  36. package/dist/facade/OPFSFacade.d.ts.map +1 -1
  37. package/dist/index.async.cjs +1 -1
  38. package/dist/index.async.js +4 -4
  39. package/dist/index.cjs +1 -1
  40. package/dist/index.js +7 -7
  41. package/dist/index.pure.cjs +1 -1
  42. package/dist/index.pure.js +3 -3
  43. package/dist/index.sharedworker.cjs +1 -1
  44. package/dist/index.sharedworker.js +4 -4
  45. package/dist/index.sync.cjs +1 -1
  46. package/dist/index.sync.js +4 -4
  47. package/dist/shared.worker.js +430 -356
  48. package/dist/shared.worker.js.map +1 -1
  49. package/dist/types.d.ts +33 -0
  50. package/dist/types.d.ts.map +1 -1
  51. package/docs/api/backend.md +3 -3
  52. package/docs/api/create.md +1 -1
  53. package/docs/api/facade.md +28 -27
  54. package/docs/choosing-a-mode.md +6 -6
  55. package/docs/file-descriptors.md +46 -604
  56. package/docs/guides/async.md +2 -2
  57. package/docs/guides/dedicated.md +5 -5
  58. package/docs/guides/hashing.md +2 -2
  59. package/docs/guides/pure.md +3 -3
  60. package/docs/guides/sharedworker.md +1 -1
  61. package/docs/guides/streaming.md +145 -11
  62. package/docs/guides/watching.md +3 -3
  63. package/docs/migration.md +3 -3
  64. package/docs/types.md +92 -154
  65. package/package.json +12 -2
  66. package/src/core/BaseOPFS.ts +148 -25
  67. package/src/facade/OPFSFacade.ts +51 -4
  68. package/src/types.ts +37 -0
  69. package/dist/BaseOPFS-BPw9AIQ-.cjs.map +0 -1
  70. package/dist/BaseOPFS-Cylz6ijk.js.map +0 -1
  71. package/dist/OPFSFacade-DePnNRgt.cjs +0 -2
  72. package/dist/OPFSFacade-DePnNRgt.cjs.map +0 -1
  73. package/dist/OPFSFacade-XohWlHuu.js +0 -281
  74. package/dist/OPFSFacade-XohWlHuu.js.map +0 -1
  75. package/dist/assets/dedicated.worker-nY3tK4zi.js.map +0 -1
  76. package/dist/createOPFSAsync-BwKDmHsy.cjs +0 -2
  77. package/dist/createOPFSAsync-Y4Mg6qte.js +0 -10
  78. package/docs/README.md +0 -30
@@ -16,7 +16,7 @@ await fs.appendText('/note.txt', '!');
16
16
  const text = await fs.readFile('/note.txt', 'utf-8');
17
17
  ```
18
18
 
19
- Import from `opfs-worker/async` if you care about bundle size — the main entry also carries the dedicated-worker path.
19
+ Import from `opfs-worker/async` to keep the dedicated-worker code out of the bundle.
20
20
 
21
21
  ## Raw class
22
22
 
@@ -31,6 +31,6 @@ await fs.writeFile('/x.bin', new Uint8Array([1, 2, 3]));
31
31
 
32
32
  - **Writes** need `createWritable()` — Chrome, Firefox, Safari **26+**. Reads work anywhere OPFS does.
33
33
  - **No file descriptors** — `open` / `read` / `write` / … throw `ENOTSUP`.
34
- - Each write goes through a swap file and commits on close — fine for documents, awkward for tons of tiny random writes.
34
+ - Each write goes through a swap file and commits on close — fine for documents, slow for many tiny random writes.
35
35
 
36
36
  Streaming still works: [streaming](./streaming.md). For one fs across tabs: [SharedWorker](./sharedworker.md).
@@ -22,7 +22,7 @@ The worker is inlined by default, so you don’t need a special bundler worker s
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,9 @@ const fs = new OPFSFacade({
68
68
  });
69
69
  ```
70
70
 
71
- ## Heads-up
71
+ ## Notes
72
72
 
73
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.
74
+ - Sync access handles only work in a dedicated worker.
75
75
 
76
- More: [file descriptors](../file-descriptors.md), [streaming](./streaming.md), [create helpers](../api/create.md).
76
+ Also: [file descriptors](../file-descriptors.md), [streaming](./streaming.md), [create helpers](../api/create.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
@@ -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
46
  Also: [backend API](../api/backend.md), [dedicated](./dedicated.md), [async](./async.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
 
@@ -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/FileBrowser.tsx`.
31
144
 
32
145
  ## `writeStream` (raw)
33
146
 
@@ -38,13 +151,34 @@ 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
 
@@ -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
@@ -9,7 +9,7 @@
9
9
  | `OPFSWorker` | `OPFSSync` |
10
10
  | `opfs-worker/raw` | `opfs-worker/pure` |
11
11
  | `opfs-worker/raw?worker` | `createOPFSDedicated()` + `fs.backend` / `fs.worker`, or `dedicated.worker.js` |
12
- | `fs.sync(entries)` | `fs.createIndex(entries)` |
12
+ | `fs.sync(entries)` | `fs.importFiles(entries)` (`createIndex` alias kept, deprecated) |
13
13
 
14
14
  ```diff
15
15
  - import { createWorker, OPFSFileSystem } from 'opfs-worker';
@@ -29,8 +29,8 @@
29
29
  - SharedWorker: `createOPFSShared` / `opfs-worker/sharedworker`
30
30
  - Prebuilt scripts: `dedicated.worker.js`, `shared.worker.js`
31
31
  - Streaming: `importStream` / `writeStream`
32
- - Escape hatch on every facade: `fs.backend` / `fs.worker`
33
- - Clearer names: `createOPFSDedicated`, `createOPFSAsync`, `createOPFSShared`
32
+ - `fs.backend` / `fs.worker` on every facade
33
+ - `createOPFSDedicated`, `createOPFSAsync`, `createOPFSShared`
34
34
 
35
35
  ## Behaviour
36
36
 
package/docs/types.md CHANGED
@@ -1,38 +1,22 @@
1
1
  # Types
2
2
 
3
- TypeScript types shipped with the package.
4
-
5
- ## Core Types
6
-
7
- ### `FileStat`
8
-
9
- File or directory statistics.
3
+ ## `FileStat`
10
4
 
11
5
  ```typescript
12
6
  interface FileStat {
13
7
  kind: 'file' | 'directory';
14
8
  size: number;
15
- mtime: string; // ISO string
16
- ctime: string; // ISO string
9
+ mtime: string; // ISO
10
+ ctime: string; // ISO
17
11
  isFile: boolean;
18
12
  isDirectory: boolean;
19
- hash?: string; // Hash of file content (only for files)
13
+ hash?: string; // files only, when hashing is enabled
20
14
  }
21
15
  ```
22
16
 
23
- **Properties:**
24
-
25
- - `kind`: Type of the file system entry
26
- - `size`: Size in bytes (0 for directories)
27
- - `mtime`: Last modification time as ISO string
28
- - `ctime`: Creation time as ISO string
29
- - `isFile`: True if this is a file
30
- - `isDirectory`: True if this is a directory
31
- - `hash`: Optional hash of file content (only present for files when hashing is enabled)
17
+ `size` is `0` for directories.
32
18
 
33
- ### `DirentData`
34
-
35
- Directory entry information.
19
+ ## `DirentData`
36
20
 
37
21
  ```typescript
38
22
  interface DirentData {
@@ -43,46 +27,61 @@ interface DirentData {
43
27
  }
44
28
  ```
45
29
 
46
- **Properties:**
30
+ ## `WatchOptions`
47
31
 
48
- - `name`: Name of the file or directory
49
- - `kind`: Type of the entry
50
- - `isFile`: True if this is a file
51
- - `isDirectory`: True if this is a directory
32
+ ```typescript
33
+ interface WatchOptions {
34
+ recursive?: boolean; // default: true
35
+ include?: string | string[]; // minimatch, default: ['**']
36
+ exclude?: string | string[]; // minimatch, default: []
37
+ }
38
+ ```
52
39
 
53
- ### `WatchOptions`
40
+ ## `ImportFilesProgress`
54
41
 
55
- Options for file watching operations.
42
+ Fired by `importFiles` / `onProgress` for each chunk written:
56
43
 
57
44
  ```typescript
58
- interface WatchOptions {
59
- recursive?: boolean; // Whether to watch recursively (default: true)
60
- include?: string | string[]; // Glob patterns to include in watching (minimatch syntax, default: ['**'])
61
- exclude?: string | string[]; // Glob patterns to exclude from watching (minimatch syntax, default: [])
45
+ interface ImportFilesProgress {
46
+ path: string; // file currently being written
47
+ index: number; // 0-based
48
+ count: number; // total entries
49
+ bytesWritten: number; // within current file
50
+ bytesTotal: number; // size of current file
51
+ totalBytesWritten: number;
52
+ totalBytes: number; // sum of all entry sizes
62
53
  }
63
54
  ```
64
55
 
65
- **Properties:**
56
+ ## `ImportFilesResult`
57
+
58
+ Returned when `importFiles` finishes:
59
+
60
+ ```typescript
61
+ interface ImportFilesResult {
62
+ paths: string[]; // written paths, in order
63
+ count: number; // paths.length
64
+ bytesWritten: number; // total bytes across all files
65
+ }
66
+ ```
66
67
 
67
- - `recursive`: Whether to watch the entire directory tree (default: true)
68
- - `include`: Glob patterns to include in watching (default: all files)
69
- - `exclude`: Glob patterns to exclude from watching (default: none)
68
+ Entries accept `Iterable<[path, string | Uint8Array | Blob]>` or `Map<string, …>` — see [streaming](./guides/streaming.md).
70
69
 
71
- ### `OPFSApi`
70
+ ## `OPFSApi`
72
71
 
73
- Promise-based fs API surface shared by all backends — a Comlink proxy to `OPFSSync` in a worker, or an in-process `OPFSAsync` instance. This is what `OPFSFacade` talks to (see `OPFSBackend` in [Create helpers](./api/create.md)).
72
+ Promise-based surface shared by all backends — a Comlink proxy to `OPFSSync`, or an in-process `OPFSAsync`. What `OPFSFacade` talks to (see `OPFSBackend` in [Create helpers](./api/create.md)).
74
73
 
75
74
  ```typescript
76
75
  type OPFSApi = { [K in keyof OPFSSync]: OPFSSync[K] };
77
76
  ```
78
77
 
79
- ### `PathLike`
78
+ ## `PathLike`
80
79
 
81
80
  ```typescript
82
81
  type PathLike = string | URL;
83
82
  ```
84
83
 
85
- ### `FileOpenOptions`
84
+ ## `FileOpenOptions`
86
85
 
87
86
  ```typescript
88
87
  interface FileOpenOptions {
@@ -92,40 +91,26 @@ interface FileOpenOptions {
92
91
  }
93
92
  ```
94
93
 
95
- ## Configuration Types
96
-
97
- ### `OPFSOptions`
94
+ ## `OPFSOptions`
98
95
 
99
- Configuration for all backends. Human-readable table: [Create helpers → Options](./api/create.md#options).
96
+ Table: [Create helpers → Options](./api/create.md#options).
100
97
 
101
98
  ```typescript
102
99
  interface OPFSOptions {
103
- /** Root path for the file system (default: '/') */
100
+ /** Root path (default: '/') */
104
101
  root?: string;
105
- /** Namespace for the events (default: 'opfs-worker:${root}') */
102
+ /** Event namespace (default: 'opfs-worker:${root}') */
106
103
  namespace?: string;
107
- /** Hash algorithm for file hashing, or false/null to disable (default: 'etag') */
104
+ /** Hash algorithm, or false/null to disable (default: 'etag') */
108
105
  hashAlgorithm?: null | false | 'etag' | 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';
109
- /** Maximum file size in bytes for SHA-* hashing (default: 50MB); ignored by 'etag' */
106
+ /** Max bytes for SHA-* hashing (default: 50MB); ignored by 'etag' */
110
107
  maxFileSize?: number;
111
- /** Custom name for the broadcast channel (default: 'opfs-worker') */
108
+ /** Broadcast channel name or instance (default: 'opfs-worker') */
112
109
  broadcastChannel?: string | BroadcastChannel | null;
113
110
  }
114
111
  ```
115
112
 
116
- **Properties:**
117
-
118
- - `root`: Root path for the file system
119
- - `namespace`: Namespace for events and isolation
120
- - `hashAlgorithm`: `'etag'` (default, mtime+size), `'SHA-*'`, or `null`/`false` to disable
121
- - `maxFileSize`: Max bytes for SHA-* content hashing (etag ignores this)
122
- - `broadcastChannel`: Custom broadcast channel name or instance
123
-
124
- ## Event Types
125
-
126
- ### `WatchEventType`
127
-
128
- Enumeration of file system change event types.
113
+ ## `WatchEventType`
129
114
 
130
115
  ```typescript
131
116
  enum WatchEventType {
@@ -135,15 +120,7 @@ enum WatchEventType {
135
120
  }
136
121
  ```
137
122
 
138
- **Values:**
139
-
140
- - `Added`: File or directory was created
141
- - `Changed`: File or directory was modified
142
- - `Removed`: File or directory was deleted
143
-
144
- ### `WatchEvent`
145
-
146
- File system change event sent via BroadcastChannel.
123
+ ## `WatchEvent`
147
124
 
148
125
  ```typescript
149
126
  interface WatchEvent {
@@ -156,91 +133,52 @@ interface WatchEvent {
156
133
  }
157
134
  ```
158
135
 
159
- **Properties:**
160
-
161
- - `namespace`: Event namespace for isolation
162
- - `path`: Path of the changed file/directory
163
- - `type`: Type of change (added, changed, removed)
164
- - `isDirectory`: Whether the changed item is a directory
165
- - `timestamp`: ISO timestamp of the change
166
- - `hash`: Optional hash of the file content (if hashing is enabled)
167
-
168
- ## Utility Types
169
-
170
- ### `Kind`
171
-
172
- File system entry type.
136
+ ## Utility types
173
137
 
174
138
  ```typescript
175
139
  type Kind = 'file' | 'directory';
176
- ```
177
-
178
- ### `Encoding`
179
-
180
- Supported text encodings.
181
-
182
- ```typescript
183
140
  type Encoding = 'utf-8' | 'utf-16le' | 'ascii' | 'latin1' | 'base64' | 'hex' | 'binary';
184
141
  ```
185
142
 
186
- ## Error Types
187
-
188
- The library provides comprehensive error handling with specific error types that are Node.js SystemError compatible:
189
-
190
- ### Base Error Class
191
-
192
- - `OPFSError` - Base error class for all OPFS-related errors (Node.js SystemError compatible)
193
-
194
- **Properties:**
195
-
196
- - `errno: number` - Numeric error code (e.g., -2 for ENOENT)
197
- - `syscall?: string` - System call name (e.g., 'open', 'read', 'write')
198
- - `path?: string` - File path when applicable
199
- - `name: string` - Error class name ('OPFSError')
200
- - `message: string` - Human-readable error message
201
- - `cause?: any` - Underlying error (Node.js 16+ feature)
202
-
203
- ### Specialized Error Classes
204
-
205
- - `OPFSNotSupportedError` - Thrown when OPFS is not supported in the browser
206
- - `PathError` - Thrown for invalid paths or path traversal attempts
207
- - `ExistenceError` - Thrown when files or directories don't exist
208
- - Usage: `new ExistenceError('File not found: /path/to/file', 'ENOENT', '/path/to/file')`
209
- - `PermissionError` - Thrown when permission is denied for an operation
210
- - `StorageError` - Thrown when an operation fails due to insufficient storage
211
- - `TimeoutError` - Thrown when an operation times out
212
- - `FileBusyError` - Thrown when a file is busy (locked by another operation)
213
- - `FileTypeError` - Thrown when file/directory type expectations don't match
214
- - Usage: `new FileTypeError('directory', '/path/to/dir')`
215
- - `ValidationError` - Thrown for validation failures (invalid arguments, formats, etc.)
216
- - Usage: `new ValidationError('Invalid size', 'EINVAL', '/path/to/file')`
217
- - `OperationAbortedError` - Thrown when an operation is aborted
218
- - `IOError` - Thrown for I/O operation failures
219
- - `OperationNotSupportedError` - Thrown when an operation is not supported
220
- - `DirectoryOperationError` - Thrown when directory operations fail
221
- - Usage: `new DirectoryOperationError('RM_FAILED', '/path/to/dir')`
222
- - `InitializationFailedError` - Thrown when OPFS initialization fails
223
- - `FileSystemOperationError` - Thrown when file system operations fail
224
- - `PathResolutionFailedError` - Thrown when path resolution fails
225
- - `AlreadyExistsError` - Thrown when a file or directory already exists
226
-
227
- ### Error Code Mapping
228
-
229
- The library uses standard POSIX error codes with numeric errno mapping:
230
-
231
- - `ENOENT` (-2) - No such file or directory
232
- - `EISDIR` (-21) - Is a directory
233
- - `ENOTDIR` (-20) - Not a directory
234
- - `EACCES` (-13) - Permission denied
235
- - `EEXIST` (-17) - File exists
236
- - `ENOTEMPTY` (-39) - Directory not empty
237
- - `EINVAL` (-22) - Invalid argument
238
- - `EIO` (-5) - I/O error
239
- - `ENOSPC` (-28) - No space left on device
240
- - `EBUSY` (-16) - Device or resource busy
241
- - `EINTR` (-4) - Interrupted system call
242
- - `ENOTSUP` (-95) - Operation not supported
243
- - `ERANGE` (-34) - Result too large
244
- - `EBADF` (-9) - Bad file descriptor
245
-
246
- Each error type extends the base `OPFSError` class and provides specific error codes and context information for better debugging.
143
+ ## Errors
144
+
145
+ Node.js SystemError-compatible. Base class: `OPFSError` (`errno`, `syscall?`, `path?`, `cause?`).
146
+
147
+ | Class | |
148
+ | ---------------------------- | ------------------------------------------------------------------- |
149
+ | `OPFSNotSupportedError` | OPFS missing in the browser |
150
+ | `PathError` | Bad path / traversal |
151
+ | `ExistenceError` | Missing entry — `new ExistenceError(msg, 'ENOENT', path)` |
152
+ | `PermissionError` | Access denied |
153
+ | `StorageError` | Quota / storage full |
154
+ | `TimeoutError` | Timed out |
155
+ | `FileBusyError` | Locked / busy |
156
+ | `FileTypeError` | File vs directory mismatch — `new FileTypeError('directory', path)` |
157
+ | `ValidationError` | Bad args — `new ValidationError(msg, 'EINVAL', path)` |
158
+ | `OperationAbortedError` | Aborted |
159
+ | `IOError` | I/O failure |
160
+ | `OperationNotSupportedError` | Not supported (e.g. FDs on async) |
161
+ | `DirectoryOperationError` | Dir op failed — `new DirectoryOperationError('RM_FAILED', path)` |
162
+ | `InitializationFailedError` | Init failed |
163
+ | `FileSystemOperationError` | Generic FS failure |
164
+ | `PathResolutionFailedError` | Path resolve failed |
165
+ | `AlreadyExistsError` | Already exists |
166
+
167
+ ### errno
168
+
169
+ | Code | errno | |
170
+ | ----------- | ----- | ------------------------- |
171
+ | `ENOENT` | -2 | No such file or directory |
172
+ | `EISDIR` | -21 | Is a directory |
173
+ | `ENOTDIR` | -20 | Not a directory |
174
+ | `EACCES` | -13 | Permission denied |
175
+ | `EEXIST` | -17 | File exists |
176
+ | `ENOTEMPTY` | -39 | Directory not empty |
177
+ | `EINVAL` | -22 | Invalid argument |
178
+ | `EIO` | -5 | I/O error |
179
+ | `ENOSPC` | -28 | No space left |
180
+ | `EBUSY` | -16 | Busy |
181
+ | `EINTR` | -4 | Interrupted |
182
+ | `ENOTSUP` | -95 | Not supported |
183
+ | `ERANGE` | -34 | Result too large |
184
+ | `EBADF` | -9 | Bad file descriptor |