opfs-worker 1.3.2 → 2.1.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/README.md +64 -458
- package/dist/BaseOPFS-BPw9AIQ-.cjs +4 -0
- package/dist/BaseOPFS-BPw9AIQ-.cjs.map +1 -0
- package/dist/BaseOPFS-Cylz6ijk.js +2036 -0
- package/dist/BaseOPFS-Cylz6ijk.js.map +1 -0
- package/dist/OPFSAsync-BKSAJJeh.js +124 -0
- package/dist/OPFSAsync-BKSAJJeh.js.map +1 -0
- package/dist/OPFSAsync-s2P7y3sw.cjs +2 -0
- package/dist/OPFSAsync-s2P7y3sw.cjs.map +1 -0
- package/dist/OPFSFacade-DePnNRgt.cjs +2 -0
- package/dist/OPFSFacade-DePnNRgt.cjs.map +1 -0
- package/dist/OPFSFacade-XohWlHuu.js +281 -0
- package/dist/OPFSFacade-XohWlHuu.js.map +1 -0
- package/dist/OPFSSync-C7KBepe_.cjs +2 -0
- package/dist/OPFSSync-C7KBepe_.cjs.map +1 -0
- package/dist/OPFSSync-CsSQkpk5.js +208 -0
- package/dist/OPFSSync-CsSQkpk5.js.map +1 -0
- package/dist/assets/dedicated.worker-nY3tK4zi.js.map +1 -0
- package/dist/{worker.d.ts → core/BaseOPFS.d.ts} +34 -300
- package/dist/core/BaseOPFS.d.ts.map +1 -0
- package/dist/core/OPFSAsync.d.ts +55 -0
- package/dist/core/OPFSAsync.d.ts.map +1 -0
- package/dist/core/OPFSSync.d.ts +54 -0
- package/dist/core/OPFSSync.d.ts.map +1 -0
- package/dist/createOPFSAsync-BwKDmHsy.cjs +2 -0
- package/dist/createOPFSAsync-BwKDmHsy.cjs.map +1 -0
- package/dist/createOPFSAsync-Y4Mg6qte.js +10 -0
- package/dist/createOPFSAsync-Y4Mg6qte.js.map +1 -0
- package/dist/createOPFSDedicated-CsnV6MLP.js +2333 -0
- package/dist/createOPFSDedicated-CsnV6MLP.js.map +1 -0
- package/dist/createOPFSDedicated-pKTRWLdz.cjs +2283 -0
- package/dist/createOPFSDedicated-pKTRWLdz.cjs.map +1 -0
- package/dist/createOPFSShared-CGdd__PH.js +23 -0
- package/dist/createOPFSShared-CGdd__PH.js.map +1 -0
- package/dist/createOPFSShared-CskROt7U.cjs +2 -0
- package/dist/createOPFSShared-CskROt7U.cjs.map +1 -0
- package/dist/dedicated.worker.js +2281 -0
- package/dist/dedicated.worker.js.map +1 -0
- package/dist/{facade.d.ts → facade/OPFSFacade.d.ts} +35 -10
- package/dist/facade/OPFSFacade.d.ts.map +1 -0
- package/dist/facade/createOPFSAsync.d.ts +16 -0
- package/dist/facade/createOPFSAsync.d.ts.map +1 -0
- package/dist/facade/createOPFSDedicated.d.ts +17 -0
- package/dist/facade/createOPFSDedicated.d.ts.map +1 -0
- package/dist/facade/createOPFSShared.d.ts +24 -0
- package/dist/facade/createOPFSShared.d.ts.map +1 -0
- package/dist/index.async.cjs +2 -0
- package/dist/index.async.cjs.map +1 -0
- package/dist/index.async.d.ts +10 -0
- package/dist/index.async.d.ts.map +1 -0
- package/dist/index.async.js +55 -0
- package/dist/index.async.js.map +1 -0
- package/dist/index.cjs +1 -2466
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +15 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -2799
- package/dist/index.js.map +1 -1
- package/dist/index.pure.cjs +2 -0
- package/dist/index.pure.cjs.map +1 -0
- package/dist/index.pure.d.ts +8 -0
- package/dist/index.pure.d.ts.map +1 -0
- package/dist/index.pure.js +53 -0
- package/dist/index.pure.js.map +1 -0
- package/dist/index.sharedworker.cjs +2 -0
- package/dist/index.sharedworker.cjs.map +1 -0
- package/dist/index.sharedworker.d.ts +11 -0
- package/dist/index.sharedworker.d.ts.map +1 -0
- package/dist/index.sharedworker.js +55 -0
- package/dist/index.sharedworker.js.map +1 -0
- package/dist/index.sync.cjs +2 -0
- package/dist/index.sync.cjs.map +1 -0
- package/dist/index.sync.d.ts +11 -0
- package/dist/index.sync.d.ts.map +1 -0
- package/dist/index.sync.js +55 -0
- package/dist/index.sync.js.map +1 -0
- package/dist/shared.worker.js +2166 -0
- package/dist/shared.worker.js.map +1 -0
- package/dist/types.d.ts +9 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/helpers.d.ts +11 -1
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/worker/createDedicatedWorker.d.ts +30 -0
- package/dist/worker/createDedicatedWorker.d.ts.map +1 -0
- package/dist/worker/createSharedWorker.d.ts +37 -0
- package/dist/worker/createSharedWorker.d.ts.map +1 -0
- package/dist/worker/dedicated.worker.d.ts +2 -0
- package/dist/worker/dedicated.worker.d.ts.map +1 -0
- package/dist/worker/shared.worker.d.ts +2 -0
- package/dist/worker/shared.worker.d.ts.map +1 -0
- package/docs/README.md +30 -0
- package/docs/api/backend.md +36 -0
- package/docs/api/create.md +82 -0
- package/docs/api/facade.md +52 -0
- package/docs/choosing-a-mode.md +41 -0
- package/docs/file-descriptors.md +695 -0
- package/docs/guides/async.md +36 -0
- package/docs/guides/dedicated.md +76 -0
- package/docs/guides/hashing.md +34 -0
- package/docs/guides/pure.md +46 -0
- package/docs/guides/sharedworker.md +57 -0
- package/docs/guides/streaming.md +51 -0
- package/docs/guides/watching.md +35 -0
- package/docs/migration.md +41 -0
- package/docs/types.md +246 -0
- package/package.json +55 -10
- package/src/core/BaseOPFS.ts +951 -0
- package/src/core/OPFSAsync.ts +248 -0
- package/src/core/OPFSSync.ts +460 -0
- package/src/facade/OPFSFacade.ts +489 -0
- package/src/facade/createOPFSAsync.ts +22 -0
- package/src/facade/createOPFSDedicated.ts +23 -0
- package/src/facade/createOPFSShared.ts +30 -0
- package/src/index.async.ts +11 -0
- package/src/index.pure.ts +8 -0
- package/src/index.sharedworker.ts +12 -0
- package/src/index.sync.ts +12 -0
- package/src/index.ts +44 -0
- package/src/types.ts +103 -0
- package/src/utils/encoder.ts +205 -0
- package/src/utils/errors.ts +297 -0
- package/src/utils/helpers.ts +465 -0
- package/src/worker/createDedicatedWorker.ts +74 -0
- package/src/worker/createSharedWorker.ts +67 -0
- package/src/worker/dedicated.worker.ts +13 -0
- package/src/worker/shared.worker.ts +17 -0
- package/dist/assets/worker-1Wh1cr7P.js.map +0 -1
- package/dist/assets/worker-BeJaVyBV.js.map +0 -1
- package/dist/assets/worker-DYYLzR1Y.js.map +0 -1
- package/dist/facade.d.ts.map +0 -1
- package/dist/helpers-BuGfPAg0.js +0 -1439
- package/dist/helpers-BuGfPAg0.js.map +0 -1
- package/dist/helpers-CF7A2WQG.cjs +0 -4
- package/dist/helpers-CF7A2WQG.cjs.map +0 -1
- package/dist/helpers-CIiblZ8d.cjs +0 -4
- package/dist/helpers-CIiblZ8d.cjs.map +0 -1
- package/dist/helpers-CKOebsbw.js +0 -1423
- package/dist/helpers-CKOebsbw.js.map +0 -1
- package/dist/helpers-Ca2c767K.js +0 -1427
- package/dist/helpers-Ca2c767K.js.map +0 -1
- package/dist/helpers-Dwc92hv9.cjs +0 -4
- package/dist/helpers-Dwc92hv9.cjs.map +0 -1
- package/dist/raw.cjs +0 -2
- package/dist/raw.cjs.map +0 -1
- package/dist/raw.d.ts +0 -13
- package/dist/raw.d.ts.map +0 -1
- package/dist/raw.js +0 -1001
- package/dist/raw.js.map +0 -1
- package/dist/worker.d.ts.map +0 -1
|
@@ -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` if you care about bundle size — the main entry also carries the dedicated-worker path.
|
|
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, awkward for tons of 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, just talk to `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, if you ever need it:
|
|
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
|
+
## Heads-up
|
|
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.
|
|
75
|
+
|
|
76
|
+
More: [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) | Cheap 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); // etag-ish, 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 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.
|
|
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` won’t hijack 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 skip the boilerplate and 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 single instance, so writes don’t stomp each other across tabs. Watch events go out on `BroadcastChannel` like everywhere else.
|
|
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,51 @@
|
|
|
1
|
+
# Streaming
|
|
2
|
+
|
|
3
|
+
Write big files without holding the whole thing in memory.
|
|
4
|
+
|
|
5
|
+
| | Method |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Facade | `importStream(path, source, options?)` |
|
|
8
|
+
| Backend / raw | `writeStream(path, stream, 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` — you don’t need Comlink `transfer` yourself.
|
|
31
|
+
|
|
32
|
+
## `writeStream` (raw)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createOPFS } from 'opfs-worker';
|
|
36
|
+
|
|
37
|
+
const fs = createOPFS();
|
|
38
|
+
await fs.backend.writeStream('/data.bin', someBlob.stream(), (n) => console.log(n));
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## When to use what
|
|
42
|
+
|
|
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) |
|
|
48
|
+
|
|
49
|
+
Dedicated path chunks through FDs; async path uses `createWritable()` (Safari 26+ for writes).
|
|
50
|
+
|
|
51
|
+
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
|
+
## Gotchas
|
|
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.
|
|
34
|
+
- SharedWorker: one backend + one channel → every listening tab sees the same events.
|
|
35
|
+
- Whether events include `hash` depends on [hashing](./hashing.md) settings.
|
|
@@ -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.createIndex(entries)` |
|
|
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
|
+
- Escape hatch on every facade: `fs.backend` / `fs.worker`
|
|
33
|
+
- Clearer names: `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).
|
package/docs/types.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Types
|
|
2
|
+
|
|
3
|
+
TypeScript types shipped with the package.
|
|
4
|
+
|
|
5
|
+
## Core Types
|
|
6
|
+
|
|
7
|
+
### `FileStat`
|
|
8
|
+
|
|
9
|
+
File or directory statistics.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
interface FileStat {
|
|
13
|
+
kind: 'file' | 'directory';
|
|
14
|
+
size: number;
|
|
15
|
+
mtime: string; // ISO string
|
|
16
|
+
ctime: string; // ISO string
|
|
17
|
+
isFile: boolean;
|
|
18
|
+
isDirectory: boolean;
|
|
19
|
+
hash?: string; // Hash of file content (only for files)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
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)
|
|
32
|
+
|
|
33
|
+
### `DirentData`
|
|
34
|
+
|
|
35
|
+
Directory entry information.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface DirentData {
|
|
39
|
+
name: string;
|
|
40
|
+
kind: 'file' | 'directory';
|
|
41
|
+
isFile: boolean;
|
|
42
|
+
isDirectory: boolean;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Properties:**
|
|
47
|
+
|
|
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
|
|
52
|
+
|
|
53
|
+
### `WatchOptions`
|
|
54
|
+
|
|
55
|
+
Options for file watching operations.
|
|
56
|
+
|
|
57
|
+
```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: [])
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Properties:**
|
|
66
|
+
|
|
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)
|
|
70
|
+
|
|
71
|
+
### `OPFSApi`
|
|
72
|
+
|
|
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)).
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
type OPFSApi = { [K in keyof OPFSSync]: OPFSSync[K] };
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `PathLike`
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
type PathLike = string | URL;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `FileOpenOptions`
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
interface FileOpenOptions {
|
|
89
|
+
create?: boolean;
|
|
90
|
+
exclusive?: boolean;
|
|
91
|
+
truncate?: boolean;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Configuration Types
|
|
96
|
+
|
|
97
|
+
### `OPFSOptions`
|
|
98
|
+
|
|
99
|
+
Configuration for all backends. Human-readable table: [Create helpers → Options](./api/create.md#options).
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface OPFSOptions {
|
|
103
|
+
/** Root path for the file system (default: '/') */
|
|
104
|
+
root?: string;
|
|
105
|
+
/** Namespace for the events (default: 'opfs-worker:${root}') */
|
|
106
|
+
namespace?: string;
|
|
107
|
+
/** Hash algorithm for file hashing, or false/null to disable (default: 'etag') */
|
|
108
|
+
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' */
|
|
110
|
+
maxFileSize?: number;
|
|
111
|
+
/** Custom name for the broadcast channel (default: 'opfs-worker') */
|
|
112
|
+
broadcastChannel?: string | BroadcastChannel | null;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
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.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
enum WatchEventType {
|
|
132
|
+
Added = 'added',
|
|
133
|
+
Changed = 'changed',
|
|
134
|
+
Removed = 'removed'
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
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.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
interface WatchEvent {
|
|
150
|
+
namespace: string;
|
|
151
|
+
path: string;
|
|
152
|
+
type: WatchEventType;
|
|
153
|
+
isDirectory: boolean;
|
|
154
|
+
timestamp: string;
|
|
155
|
+
hash?: string;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
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.
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
type Kind = 'file' | 'directory';
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `Encoding`
|
|
179
|
+
|
|
180
|
+
Supported text encodings.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
type Encoding = 'utf-8' | 'utf-16le' | 'ascii' | 'latin1' | 'base64' | 'hex' | 'binary';
|
|
184
|
+
```
|
|
185
|
+
|
|
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.
|