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.
- package/README.md +64 -428
- package/dist/BaseOPFS-BusBkyJX.js +2114 -0
- package/dist/BaseOPFS-BusBkyJX.js.map +1 -0
- package/dist/BaseOPFS-CYwrD7-q.cjs +4 -0
- package/dist/BaseOPFS-CYwrD7-q.cjs.map +1 -0
- package/dist/OPFSAsync-B2dR-2VR.cjs +2 -0
- package/dist/OPFSAsync-B2dR-2VR.cjs.map +1 -0
- package/dist/OPFSAsync-CO44W23e.js +124 -0
- package/dist/OPFSAsync-CO44W23e.js.map +1 -0
- package/dist/OPFSFacade-CEaSqTut.js +308 -0
- package/dist/OPFSFacade-CEaSqTut.js.map +1 -0
- package/dist/OPFSFacade-hrNXCn6w.cjs +2 -0
- package/dist/OPFSFacade-hrNXCn6w.cjs.map +1 -0
- package/dist/OPFSSync-BPrNtvFS.cjs +2 -0
- package/dist/OPFSSync-BPrNtvFS.cjs.map +1 -0
- package/dist/OPFSSync-D7obTsl3.js +208 -0
- package/dist/OPFSSync-D7obTsl3.js.map +1 -0
- package/dist/assets/dedicated.worker-DW6UqFo0.js.map +1 -0
- package/dist/{OPFSWorker.d.ts → core/BaseOPFS.d.ts} +53 -292
- 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-DdhJYoC0.js +10 -0
- package/dist/createOPFSAsync-DdhJYoC0.js.map +1 -0
- package/dist/createOPFSAsync-DyWpvmL-.cjs +2 -0
- package/dist/createOPFSAsync-DyWpvmL-.cjs.map +1 -0
- package/dist/createOPFSDedicated-BwTc0NV5.js +2407 -0
- package/dist/createOPFSDedicated-BwTc0NV5.js.map +1 -0
- package/dist/createOPFSDedicated-CEeRexom.cjs +2357 -0
- package/dist/createOPFSDedicated-CEeRexom.cjs.map +1 -0
- package/dist/createOPFSShared-BSFWsoLe.js +23 -0
- package/dist/createOPFSShared-BSFWsoLe.js.map +1 -0
- package/dist/createOPFSShared-BoCft_BS.cjs +2 -0
- package/dist/createOPFSShared-BoCft_BS.cjs.map +1 -0
- package/dist/dedicated.worker.js +2355 -0
- package/dist/dedicated.worker.js.map +1 -0
- package/dist/{OPFSFacade.d.ts → facade/OPFSFacade.d.ts} +51 -25
- 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 -2477
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -2828
- package/dist/index.js.map +1 -1
- package/dist/index.pure.cjs +1 -1
- package/dist/index.pure.cjs.map +1 -1
- package/dist/index.pure.d.ts +3 -1
- package/dist/index.pure.d.ts.map +1 -1
- package/dist/index.pure.js +50 -1053
- package/dist/index.pure.js.map +1 -1
- 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 +2240 -0
- package/dist/shared.worker.js.map +1 -0
- package/dist/types.d.ts +42 -4
- package/dist/types.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/api/backend.md +36 -0
- package/docs/api/create.md +82 -0
- package/docs/api/facade.md +53 -0
- package/docs/choosing-a-mode.md +41 -0
- package/docs/file-descriptors.md +46 -605
- 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 +185 -0
- package/docs/guides/watching.md +35 -0
- package/docs/migration.md +41 -0
- package/docs/types.md +105 -153
- package/package.json +53 -4
- package/src/{OPFSWorker.ts → core/BaseOPFS.ts} +159 -629
- package/src/core/OPFSAsync.ts +248 -0
- package/src/core/OPFSSync.ts +460 -0
- package/src/{OPFSFacade.ts → facade/OPFSFacade.ts} +134 -58
- 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 +3 -2
- package/src/index.sharedworker.ts +12 -0
- package/src/index.sync.ts +12 -0
- package/src/index.ts +37 -8
- package/src/types.ts +45 -4
- 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/OPFSFacade.d.ts.map +0 -1
- package/dist/OPFSWorker.d.ts.map +0 -1
- package/dist/assets/worker.entry-DUlEoroc.js.map +0 -1
- package/dist/createOPFSWorker.d.ts +0 -17
- package/dist/createOPFSWorker.d.ts.map +0 -1
- package/dist/helpers-DNj8ZoMu.cjs +0 -4
- package/dist/helpers-DNj8ZoMu.cjs.map +0 -1
- package/dist/helpers-WY2jfbOT.js +0 -1427
- package/dist/helpers-WY2jfbOT.js.map +0 -1
- package/dist/worker.entry.d.ts +0 -2
- package/dist/worker.entry.d.ts.map +0 -1
- package/docs/api-reference.md +0 -815
- package/src/createOPFSWorker.ts +0 -57
- 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).
|