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
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { proxy, transfer } from 'comlink';
|
|
2
|
+
|
|
3
|
+
import { decodeBuffer, encodeString, isBinaryFileExtension } from '../utils/encoder';
|
|
3
4
|
|
|
4
5
|
import type {
|
|
5
6
|
BinaryEncoding,
|
|
@@ -7,13 +8,16 @@ import type {
|
|
|
7
8
|
Encoding,
|
|
8
9
|
FileOpenOptions,
|
|
9
10
|
FileStat,
|
|
11
|
+
ImportFileData,
|
|
12
|
+
ImportFilesProgress,
|
|
13
|
+
ImportFilesResult,
|
|
14
|
+
OPFSApi,
|
|
10
15
|
OPFSOptions,
|
|
11
16
|
PathLike,
|
|
12
|
-
RemoteOPFSWorker,
|
|
13
17
|
RenameOptions,
|
|
14
18
|
StringEncoding,
|
|
15
19
|
WatchOptions
|
|
16
|
-
} from '
|
|
20
|
+
} from '../types';
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Utility function to convert a PathLike to a string path
|
|
@@ -27,22 +31,48 @@ function normalizePath(path: PathLike): string {
|
|
|
27
31
|
return path;
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
let createIndexWarned = false;
|
|
35
|
+
|
|
36
|
+
function warnCreateIndexDeprecated(): void {
|
|
37
|
+
if (createIndexWarned) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
createIndexWarned = true;
|
|
42
|
+
console.warn('[opfs-worker] createIndex() is deprecated; use importFiles() instead');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Backend the facade talks to: an fs implementation plus its cleanup. */
|
|
46
|
+
export interface OPFSBackend {
|
|
47
|
+
fs: OPFSApi;
|
|
48
|
+
/** Underlying Worker / SharedWorker when the backend lives off-thread */
|
|
49
|
+
worker?: Worker | SharedWorker;
|
|
50
|
+
dispose: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
/**
|
|
31
|
-
*
|
|
32
|
-
* built on top of the raw worker API from {@link createOPFSWorker}.
|
|
54
|
+
* Node-like facade with encoding helpers and string/binary auto-detection.
|
|
33
55
|
*
|
|
34
|
-
*
|
|
56
|
+
* Transport-agnostic: works over any {@link OPFSBackend} — a Comlink proxy to
|
|
57
|
+
* a worker (`createOPFSDedicated`) or an in-process instance (`createOPFSAsync`).
|
|
58
|
+
*
|
|
59
|
+
* Escape hatch: {@link backend} is the raw bytes API; {@link worker} is the
|
|
60
|
+
* browser Worker / SharedWorker when one was created (undefined for async).
|
|
35
61
|
*/
|
|
36
62
|
export class OPFSFacade {
|
|
37
|
-
#fs:
|
|
63
|
+
#fs: OPFSApi;
|
|
38
64
|
#dispose: () => void;
|
|
65
|
+
/** Raw backend (`OPFSApi`) — bytes in / bytes out, no encoding helpers */
|
|
66
|
+
readonly backend: OPFSApi;
|
|
67
|
+
/** Dedicated Worker or SharedWorker, if this facade was created with one */
|
|
68
|
+
readonly worker: Worker | SharedWorker | undefined;
|
|
39
69
|
promises: OPFSFacade = this;
|
|
40
70
|
|
|
41
|
-
constructor(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this
|
|
45
|
-
this.#dispose =
|
|
71
|
+
constructor(backend: OPFSBackend) {
|
|
72
|
+
this.#fs = backend.fs;
|
|
73
|
+
this.backend = backend.fs;
|
|
74
|
+
this.worker = backend.worker;
|
|
75
|
+
this.#dispose = backend.dispose;
|
|
46
76
|
}
|
|
47
77
|
|
|
48
78
|
/**
|
|
@@ -120,12 +150,40 @@ export class OPFSFacade {
|
|
|
120
150
|
return (encoding === 'binary') ? buffer : decodeBuffer(buffer, encoding);
|
|
121
151
|
}
|
|
122
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Normalize writable data into a Uint8Array.
|
|
155
|
+
*
|
|
156
|
+
* Accepts strings (encoded via `encoding`, auto-detected from the file
|
|
157
|
+
* extension when omitted), raw binary (`Uint8Array` / `ArrayBuffer`), and
|
|
158
|
+
* `Blob`/`File` sources (read via `arrayBuffer()`).
|
|
159
|
+
*/
|
|
160
|
+
async #toBuffer(
|
|
161
|
+
path: string,
|
|
162
|
+
data: string | Uint8Array | ArrayBuffer | Blob,
|
|
163
|
+
encoding?: Encoding
|
|
164
|
+
): Promise<Uint8Array> {
|
|
165
|
+
if (data instanceof Blob) {
|
|
166
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// If no encoding specified, auto-detect based on file extension
|
|
170
|
+
if (!encoding) {
|
|
171
|
+
encoding = (typeof data !== 'string' || isBinaryFileExtension(path)) ? 'binary' : 'utf-8';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (typeof data === 'string') {
|
|
175
|
+
return encodeString(data, encoding);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
179
|
+
}
|
|
180
|
+
|
|
123
181
|
/**
|
|
124
182
|
* Write data to a file
|
|
125
183
|
*/
|
|
126
184
|
async writeFile(
|
|
127
185
|
path: PathLike,
|
|
128
|
-
data: string | Uint8Array | ArrayBuffer,
|
|
186
|
+
data: string | Uint8Array | ArrayBuffer | Blob,
|
|
129
187
|
options?: { encoding?: Encoding } | Encoding
|
|
130
188
|
): Promise<void> {
|
|
131
189
|
const normalizedPath = normalizePath(path);
|
|
@@ -139,15 +197,7 @@ export class OPFSFacade {
|
|
|
139
197
|
encoding = options.encoding;
|
|
140
198
|
}
|
|
141
199
|
|
|
142
|
-
|
|
143
|
-
if (!encoding) {
|
|
144
|
-
encoding = (typeof data !== 'string' || isBinaryFileExtension(normalizedPath)) ? 'binary' : 'utf-8';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Convert data to Uint8Array
|
|
148
|
-
const buffer = typeof data === 'string'
|
|
149
|
-
? encodeString(data, encoding)
|
|
150
|
-
: (data instanceof Uint8Array ? data : new Uint8Array(data));
|
|
200
|
+
const buffer = await this.#toBuffer(normalizedPath, data, encoding);
|
|
151
201
|
|
|
152
202
|
return this.#fs.writeFile(normalizedPath, buffer);
|
|
153
203
|
}
|
|
@@ -157,24 +207,37 @@ export class OPFSFacade {
|
|
|
157
207
|
*/
|
|
158
208
|
async appendFile(
|
|
159
209
|
path: PathLike,
|
|
160
|
-
data: string | Uint8Array | ArrayBuffer,
|
|
210
|
+
data: string | Uint8Array | ArrayBuffer | Blob,
|
|
161
211
|
encoding?: Encoding
|
|
162
212
|
): Promise<void> {
|
|
163
213
|
const normalizedPath = normalizePath(path);
|
|
164
214
|
|
|
165
|
-
|
|
166
|
-
if (!encoding) {
|
|
167
|
-
encoding = (typeof data !== 'string' || isBinaryFileExtension(normalizedPath)) ? 'binary' : 'utf-8';
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Convert data to Uint8Array
|
|
171
|
-
const buffer = typeof data === 'string'
|
|
172
|
-
? encodeString(data, encoding)
|
|
173
|
-
: (data instanceof Uint8Array ? data : new Uint8Array(data));
|
|
215
|
+
const buffer = await this.#toBuffer(normalizedPath, data, encoding);
|
|
174
216
|
|
|
175
217
|
return this.#fs.appendFile(normalizedPath, buffer);
|
|
176
218
|
}
|
|
177
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Create or overwrite a file from a byte stream without buffering the
|
|
222
|
+
* complete source in memory.
|
|
223
|
+
*
|
|
224
|
+
* Returns the total number of bytes written.
|
|
225
|
+
*/
|
|
226
|
+
async importStream(
|
|
227
|
+
path: PathLike,
|
|
228
|
+
source: ReadableStream<Uint8Array> | Blob,
|
|
229
|
+
options?: { onProgress?: (bytesWritten: number) => void }
|
|
230
|
+
): Promise<number> {
|
|
231
|
+
const normalizedPath = normalizePath(path);
|
|
232
|
+
const stream = source instanceof Blob ? source.stream() : source;
|
|
233
|
+
const transferredStream = transfer(stream, [stream]) as unknown as ReadableStream<Uint8Array>;
|
|
234
|
+
const onProgress = options?.onProgress
|
|
235
|
+
? proxy((bytesWritten: number) => options.onProgress!(bytesWritten))
|
|
236
|
+
: undefined;
|
|
237
|
+
|
|
238
|
+
return this.#fs.writeStream(normalizedPath, transferredStream, onProgress);
|
|
239
|
+
}
|
|
240
|
+
|
|
178
241
|
/**
|
|
179
242
|
* Create a directory
|
|
180
243
|
*/
|
|
@@ -393,12 +456,45 @@ export class OPFSFacade {
|
|
|
393
456
|
}
|
|
394
457
|
|
|
395
458
|
/**
|
|
396
|
-
*
|
|
459
|
+
* Read a file as a lazy, disk-backed `Blob` without copying the data to memory.
|
|
460
|
+
*
|
|
461
|
+
* Ideal for `URL.createObjectURL()`: the browser streams `<video>` / `<audio>`
|
|
462
|
+
* on demand instead of loading the whole file first.
|
|
397
463
|
*/
|
|
398
|
-
async
|
|
399
|
-
|
|
464
|
+
async readBlob(path: PathLike): Promise<Blob> {
|
|
465
|
+
return this.#fs.readBlob(normalizePath(path));
|
|
466
|
+
}
|
|
400
467
|
|
|
401
|
-
|
|
468
|
+
/**
|
|
469
|
+
* Bulk-import files from `[path, data]` entries (strings, bytes, Blobs, or Files).
|
|
470
|
+
* Each entry is streamed via {@link importStream} / `writeStream` so large
|
|
471
|
+
* Blobs/Files are not fully buffered in memory.
|
|
472
|
+
*
|
|
473
|
+
* Accepts an array of tuples, a `Map`, or any iterable of `[path, data]` pairs.
|
|
474
|
+
* Returns written paths, count, and total bytes.
|
|
475
|
+
*/
|
|
476
|
+
async importFiles(
|
|
477
|
+
entries: Iterable<[PathLike, ImportFileData]> | Map<string, ImportFileData>,
|
|
478
|
+
options?: { onProgress?: (progress: ImportFilesProgress) => void }
|
|
479
|
+
): Promise<ImportFilesResult> {
|
|
480
|
+
const normalizedEntries = [...entries].map(([path, data]) => [
|
|
481
|
+
normalizePath(path),
|
|
482
|
+
data,
|
|
483
|
+
] as [string, ImportFileData]);
|
|
484
|
+
|
|
485
|
+
const onProgress = options?.onProgress
|
|
486
|
+
? proxy((progress: ImportFilesProgress) => options.onProgress!(progress))
|
|
487
|
+
: undefined;
|
|
488
|
+
|
|
489
|
+
return this.#fs.importFiles(normalizedEntries, onProgress);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* @deprecated Use {@link importFiles} instead.
|
|
494
|
+
*/
|
|
495
|
+
async createIndex(entries: Iterable<[PathLike, ImportFileData]> | Map<string, ImportFileData>): Promise<void> {
|
|
496
|
+
warnCreateIndexDeprecated();
|
|
497
|
+
await this.importFiles(entries);
|
|
402
498
|
}
|
|
403
499
|
|
|
404
500
|
/**
|
|
@@ -432,29 +528,9 @@ export class OPFSFacade {
|
|
|
432
528
|
}
|
|
433
529
|
|
|
434
530
|
/**
|
|
435
|
-
* Dispose of resources, detach the
|
|
531
|
+
* Dispose of resources, detach the backend and clean up the file system instance
|
|
436
532
|
*/
|
|
437
533
|
dispose() {
|
|
438
534
|
this.#dispose();
|
|
439
535
|
}
|
|
440
536
|
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* Mode 1: start an inlined worker and get a Node-like `fs` API on top of it.
|
|
444
|
-
*
|
|
445
|
-
* For the raw worker API without the facade, use {@link createOPFSWorker}.
|
|
446
|
-
* To use the worker class inside your own worker, see `opfs-worker/pure`.
|
|
447
|
-
*/
|
|
448
|
-
export function createOPFS(options?: OPFSOptions): OPFSFacade {
|
|
449
|
-
return new OPFSFacade(options);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* @deprecated Use {@link createOPFS}. Kept for 1.x compatibility — still returns the facade.
|
|
454
|
-
*/
|
|
455
|
-
export const createWorker = createOPFS;
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* @deprecated Use {@link OPFSFacade}.
|
|
459
|
-
*/
|
|
460
|
-
export { OPFSFacade as OPFSFileSystem };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { OPFSFacade } from './OPFSFacade';
|
|
2
|
+
import { OPFSAsync } from '../core/OPFSAsync';
|
|
3
|
+
|
|
4
|
+
import type { OPFSOptions } from '../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Async mode: Node-like `fs` API over the promise-based File System API,
|
|
8
|
+
* without any worker.
|
|
9
|
+
*
|
|
10
|
+
* Prefer importing from `opfs-worker/async` when you care about bundle size
|
|
11
|
+
* (avoids the inlined dedicated worker). Importing from `opfs-worker` works too.
|
|
12
|
+
*
|
|
13
|
+
* Writing requires `FileSystemFileHandle.createWritable()` — Chrome, Firefox,
|
|
14
|
+
* Safari 26+. File descriptors (`open`/`read`/`write`/...) are not supported
|
|
15
|
+
* and throw `OperationNotSupportedError` — use `createOPFSDedicated` from `opfs-worker`
|
|
16
|
+
* or `opfs-worker/sync` for positional I/O.
|
|
17
|
+
*/
|
|
18
|
+
export function createOPFSAsync(options?: OPFSOptions): OPFSFacade {
|
|
19
|
+
const fs = new OPFSAsync(options);
|
|
20
|
+
|
|
21
|
+
return new OPFSFacade({ fs, dispose: () => fs.dispose() });
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { OPFSFacade } from './OPFSFacade';
|
|
2
|
+
import { createDedicatedWorker } from '../worker/createDedicatedWorker';
|
|
3
|
+
|
|
4
|
+
import type { DedicatedWorkerOptions } from '../worker/createDedicatedWorker';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dedicated mode: start a dedicated worker and get a Node-like `fs` API
|
|
8
|
+
* backed by `OPFSSync`.
|
|
9
|
+
*
|
|
10
|
+
* Prefer importing from `opfs-worker/sync` when you only need the worker backend
|
|
11
|
+
* (avoids pulling `OPFSAsync` into the graph). Importing from `opfs-worker` works too.
|
|
12
|
+
*
|
|
13
|
+
* By default uses an inlined worker. Pass `url` / `worker` to load
|
|
14
|
+
* `opfs-worker/dedicated.worker.js` instead (see {@link DedicatedWorkerOptions}).
|
|
15
|
+
*
|
|
16
|
+
* Need the raw bytes API or the Worker instance? Use `fs.backend` / `fs.worker`.
|
|
17
|
+
* For the workerless async backend, use {@link createOPFSAsync}.
|
|
18
|
+
*/
|
|
19
|
+
export function createOPFSDedicated(options?: DedicatedWorkerOptions): OPFSFacade {
|
|
20
|
+
const { fs, worker, dispose } = createDedicatedWorker(options);
|
|
21
|
+
|
|
22
|
+
return new OPFSFacade({ fs, worker, dispose });
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { OPFSFacade } from './OPFSFacade';
|
|
2
|
+
import { createSharedWorker } from '../worker/createSharedWorker';
|
|
3
|
+
|
|
4
|
+
import type { SharedWorkerOptions } from '../worker/createSharedWorker';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared mode: Node-like `fs` API over a SharedWorker running one `OPFSAsync`
|
|
8
|
+
* instance for every tab — writes are serialized across tabs by per-path locks,
|
|
9
|
+
* watch events reach all tabs via `BroadcastChannel`.
|
|
10
|
+
*
|
|
11
|
+
* Uses the async backend (sync access handles are unavailable in SharedWorker):
|
|
12
|
+
* writing requires `createWritable()` (Chrome, Firefox, Safari 26+), and file
|
|
13
|
+
* descriptors throw `OperationNotSupportedError`.
|
|
14
|
+
*
|
|
15
|
+
* Need the raw bytes API or the SharedWorker? Use `fs.backend` / `fs.worker`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // Vite: pass the worker url explicitly
|
|
20
|
+
* import workerUrl from 'opfs-worker/shared.worker.js?url';
|
|
21
|
+
* import { createOPFSShared } from 'opfs-worker/sharedworker';
|
|
22
|
+
*
|
|
23
|
+
* const fs = createOPFSShared({ root: '/my-app', url: workerUrl });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function createOPFSShared(options?: SharedWorkerOptions): OPFSFacade {
|
|
27
|
+
const { fs, worker, dispose } = createSharedWorker(options);
|
|
28
|
+
|
|
29
|
+
return new OPFSFacade({ fs, worker, dispose });
|
|
30
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './utils/errors';
|
|
3
|
+
export * from './utils/helpers';
|
|
4
|
+
export * from './utils/encoder';
|
|
5
|
+
|
|
6
|
+
export { BaseOPFS } from './core/BaseOPFS';
|
|
7
|
+
export { OPFSAsync } from './core/OPFSAsync';
|
|
8
|
+
export { OPFSFacade } from './facade/OPFSFacade';
|
|
9
|
+
export type { OPFSBackend } from './facade/OPFSFacade';
|
|
10
|
+
|
|
11
|
+
export { createOPFSAsync } from './facade/createOPFSAsync';
|
package/src/index.pure.ts
CHANGED
|
@@ -3,5 +3,6 @@ export * from './utils/errors';
|
|
|
3
3
|
export * from './utils/helpers';
|
|
4
4
|
export * from './utils/encoder';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
export {
|
|
6
|
+
export { BaseOPFS } from './core/BaseOPFS';
|
|
7
|
+
export { OPFSSync } from './core/OPFSSync';
|
|
8
|
+
export { OPFSAsync } from './core/OPFSAsync';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './utils/errors';
|
|
3
|
+
export * from './utils/helpers';
|
|
4
|
+
export * from './utils/encoder';
|
|
5
|
+
|
|
6
|
+
export { BaseOPFS } from './core/BaseOPFS';
|
|
7
|
+
export { OPFSAsync } from './core/OPFSAsync';
|
|
8
|
+
export { OPFSFacade } from './facade/OPFSFacade';
|
|
9
|
+
export type { OPFSBackend } from './facade/OPFSFacade';
|
|
10
|
+
|
|
11
|
+
export { createOPFSShared } from './facade/createOPFSShared';
|
|
12
|
+
export type { SharedWorkerOptions } from './worker/createSharedWorker';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './utils/errors';
|
|
3
|
+
export * from './utils/helpers';
|
|
4
|
+
export * from './utils/encoder';
|
|
5
|
+
|
|
6
|
+
export { BaseOPFS } from './core/BaseOPFS';
|
|
7
|
+
export { OPFSSync } from './core/OPFSSync';
|
|
8
|
+
export { OPFSFacade } from './facade/OPFSFacade';
|
|
9
|
+
export type { OPFSBackend } from './facade/OPFSFacade';
|
|
10
|
+
|
|
11
|
+
export { createOPFSDedicated } from './facade/createOPFSDedicated';
|
|
12
|
+
export type { DedicatedWorkerOptions } from './worker/createDedicatedWorker';
|
package/src/index.ts
CHANGED
|
@@ -4,12 +4,41 @@ export * from './utils/helpers';
|
|
|
4
4
|
export * from './utils/encoder';
|
|
5
5
|
|
|
6
6
|
export {
|
|
7
|
+
BaseOPFS,
|
|
7
8
|
OPFSFacade,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
OPFSSync,
|
|
10
|
+
createOPFSDedicated,
|
|
11
|
+
} from './index.sync';
|
|
12
|
+
|
|
13
|
+
export type {
|
|
14
|
+
DedicatedWorkerOptions,
|
|
15
|
+
OPFSBackend,
|
|
16
|
+
} from './index.sync';
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
OPFSAsync,
|
|
20
|
+
createOPFSAsync,
|
|
21
|
+
} from './index.async';
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
createOPFSShared,
|
|
25
|
+
} from './index.sharedworker';
|
|
26
|
+
|
|
27
|
+
export type {
|
|
28
|
+
SharedWorkerOptions,
|
|
29
|
+
} from './index.sharedworker';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Kept as a short alias for a default Sync backend
|
|
33
|
+
*/
|
|
34
|
+
export { createOPFSDedicated as createOPFS } from './index.sync';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated Use {@link createOPFSDedicated}. Kept for 1.x → 2.x migration.
|
|
38
|
+
*/
|
|
39
|
+
export { createOPFSDedicated as createWorker } from './index.sync';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Use {@link OPFSFacade}. Kept for 1.x → 2.x migration.
|
|
43
|
+
*/
|
|
44
|
+
export { OPFSFacade as OPFSFileSystem } from './index.sync';
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { Remote } from 'comlink';
|
|
1
|
+
import type { OPFSSync } from './core/OPFSSync';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Type for paths that can be either a string or URI
|
|
@@ -56,8 +55,13 @@ export interface WatchEvent {
|
|
|
56
55
|
hash?: string;
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
export type {
|
|
60
|
-
|
|
58
|
+
export type { OPFSSync };
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Promise-based fs API surface shared by all backends: a Comlink proxy to
|
|
62
|
+
* `OPFSSync` in a worker, or an in-process `OPFSAsync` instance.
|
|
63
|
+
*/
|
|
64
|
+
export type OPFSApi = { [K in keyof OPFSSync]: OPFSSync[K] };
|
|
61
65
|
|
|
62
66
|
export interface OPFSOptions {
|
|
63
67
|
/** Root path for the file system (default: '/') */
|
|
@@ -72,6 +76,43 @@ export interface OPFSOptions {
|
|
|
72
76
|
broadcastChannel?: string | BroadcastChannel | null;
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
/** Payload accepted by {@link importFiles} for a single entry. */
|
|
80
|
+
export type ImportFileData = string | Uint8Array | Blob;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Entries for {@link importFiles}: an array of `[path, data]` tuples, a `Map`,
|
|
84
|
+
* or any iterable that yields the same pairs.
|
|
85
|
+
*/
|
|
86
|
+
export type ImportFilesEntries = Iterable<[string, ImportFileData]> | Map<string, ImportFileData>;
|
|
87
|
+
|
|
88
|
+
/** Progress event fired while {@link importFiles} writes entries. */
|
|
89
|
+
export interface ImportFilesProgress {
|
|
90
|
+
/** Path of the file currently being written */
|
|
91
|
+
path: string;
|
|
92
|
+
/** 0-based index of the current file */
|
|
93
|
+
index: number;
|
|
94
|
+
/** Total number of entries in this import */
|
|
95
|
+
count: number;
|
|
96
|
+
/** Bytes written for the current file so far */
|
|
97
|
+
bytesWritten: number;
|
|
98
|
+
/** Size of the current file in bytes */
|
|
99
|
+
bytesTotal: number;
|
|
100
|
+
/** Bytes written across all files so far */
|
|
101
|
+
totalBytesWritten: number;
|
|
102
|
+
/** Sum of all entry sizes in bytes */
|
|
103
|
+
totalBytes: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Result of a finished {@link importFiles} call. */
|
|
107
|
+
export interface ImportFilesResult {
|
|
108
|
+
/** Paths written, in import order */
|
|
109
|
+
paths: string[];
|
|
110
|
+
/** Number of files imported (`paths.length`) */
|
|
111
|
+
count: number;
|
|
112
|
+
/** Total bytes written across all files */
|
|
113
|
+
bytesWritten: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
75
116
|
export interface RenameOptions {
|
|
76
117
|
/** Whether to overwrite existing files (default: false) */
|
|
77
118
|
overwrite?: boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { wrap } from 'comlink';
|
|
2
|
+
|
|
3
|
+
import WorkerCtor from './dedicated.worker?worker&inline';
|
|
4
|
+
|
|
5
|
+
import type { OPFSOptions, OPFSSync } from '../types';
|
|
6
|
+
import type { Remote } from 'comlink';
|
|
7
|
+
|
|
8
|
+
const InlineWorker = WorkerCtor as new (options?: WorkerOptions) => Worker;
|
|
9
|
+
|
|
10
|
+
type RemoteOPFSSync = Remote<OPFSSync>;
|
|
11
|
+
|
|
12
|
+
export interface DedicatedWorkerOptions extends OPFSOptions {
|
|
13
|
+
/**
|
|
14
|
+
* URL of the worker script, e.g. `import url from 'opfs-worker/dedicated.worker.js?url'` (Vite).
|
|
15
|
+
*
|
|
16
|
+
* Defaults to the inlined worker (no URL needed). Pass an explicit url when you
|
|
17
|
+
* prefer the self-contained file (strict CSP without `blob:`, or DIY hosting).
|
|
18
|
+
*/
|
|
19
|
+
url?: string | URL;
|
|
20
|
+
/** Bring your own Worker instance (overrides `url`) */
|
|
21
|
+
worker?: Worker;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RawWorker {
|
|
25
|
+
/** Comlink proxy to `OPFSSync` (bytes in / bytes out) */
|
|
26
|
+
fs: RemoteOPFSSync;
|
|
27
|
+
/** Underlying browser Worker */
|
|
28
|
+
worker: Worker;
|
|
29
|
+
/** Calls worker `dispose()` then `terminate()` */
|
|
30
|
+
dispose: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A `BroadcastChannel` instance can't cross the wire — send its name instead. */
|
|
34
|
+
function applyWorkerOptions(fs: Pick<RemoteOPFSSync, 'setOptions'>, options?: OPFSOptions): void {
|
|
35
|
+
if (!options) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (options.broadcastChannel instanceof BroadcastChannel) {
|
|
40
|
+
options.broadcastChannel = options.broadcastChannel.name;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
void fs.setOptions(options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Internal helper: spawn a dedicated worker and wrap `OPFSSync` with Comlink.
|
|
48
|
+
* Prefer {@link createOPFSDedicated}; access the proxy / Worker via
|
|
49
|
+
* `facade.backend` / `facade.worker`.
|
|
50
|
+
*/
|
|
51
|
+
export function createDedicatedWorker(options: DedicatedWorkerOptions = {}): RawWorker {
|
|
52
|
+
const { url, worker: providedWorker, ...fsOptions } = options;
|
|
53
|
+
|
|
54
|
+
const worker = providedWorker
|
|
55
|
+
?? (url ? new Worker(url, { type: 'module' }) : new InlineWorker());
|
|
56
|
+
const fs = wrap<RemoteOPFSSync>(worker);
|
|
57
|
+
|
|
58
|
+
applyWorkerOptions(fs, fsOptions);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
fs,
|
|
62
|
+
worker,
|
|
63
|
+
dispose() {
|
|
64
|
+
void (async() => {
|
|
65
|
+
try {
|
|
66
|
+
await fs.dispose();
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
worker.terminate();
|
|
70
|
+
}
|
|
71
|
+
})();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { wrap } from 'comlink';
|
|
2
|
+
|
|
3
|
+
import type { OPFSAsync } from '../core/OPFSAsync';
|
|
4
|
+
import type { OPFSOptions } from '../types';
|
|
5
|
+
import type { Remote } from 'comlink';
|
|
6
|
+
|
|
7
|
+
type RemoteOPFSAsync = Remote<OPFSAsync>;
|
|
8
|
+
|
|
9
|
+
export interface SharedWorkerOptions extends OPFSOptions {
|
|
10
|
+
/**
|
|
11
|
+
* URL of the worker script, e.g. `import url from 'opfs-worker/shared.worker.js?url'` (Vite).
|
|
12
|
+
*
|
|
13
|
+
* Defaults to `new URL('./shared.worker.js', import.meta.url)`, which works when
|
|
14
|
+
* `opfs-worker` is served as-is (CDN, unbundled deps). When your bundler inlines
|
|
15
|
+
* the package into app chunks, pass the url explicitly.
|
|
16
|
+
*/
|
|
17
|
+
url?: string | URL;
|
|
18
|
+
/** Bring your own SharedWorker instance (overrides `url`) */
|
|
19
|
+
worker?: SharedWorker;
|
|
20
|
+
/** Worker name — tabs with the same script URL + name share one instance (default: 'opfs-worker') */
|
|
21
|
+
name?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RawSharedWorker {
|
|
25
|
+
/** Comlink proxy to the `OPFSAsync` instance shared by all connected tabs */
|
|
26
|
+
fs: RemoteOPFSAsync;
|
|
27
|
+
/** Underlying browser SharedWorker */
|
|
28
|
+
worker: SharedWorker;
|
|
29
|
+
/** Closes this tab's port; the worker keeps running for other tabs */
|
|
30
|
+
dispose: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Internal helper: connect to the SharedWorker and wrap `OPFSAsync` with Comlink.
|
|
35
|
+
* Prefer {@link createOPFSShared}; access the proxy / SharedWorker via
|
|
36
|
+
* `facade.backend` / `facade.worker`.
|
|
37
|
+
*
|
|
38
|
+
* Note: `OPFSOptions` are applied via `setOptions()` on the shared instance, so
|
|
39
|
+
* they affect every connected tab — use the same options in all tabs.
|
|
40
|
+
*/
|
|
41
|
+
export function createSharedWorker(options: SharedWorkerOptions = {}): RawSharedWorker {
|
|
42
|
+
const { url, worker: providedWorker, name = 'opfs-worker', ...fsOptions } = options;
|
|
43
|
+
|
|
44
|
+
const worker = providedWorker ?? new SharedWorker(
|
|
45
|
+
url ?? new URL('./shared.worker.js', import.meta.url),
|
|
46
|
+
{ type: 'module', name }
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const fs = wrap<RemoteOPFSAsync>(worker.port);
|
|
50
|
+
|
|
51
|
+
if (Object.keys(fsOptions).length > 0) {
|
|
52
|
+
// A BroadcastChannel instance can't cross the wire — send its name instead
|
|
53
|
+
if (fsOptions.broadcastChannel instanceof BroadcastChannel) {
|
|
54
|
+
fsOptions.broadcastChannel = fsOptions.broadcastChannel.name;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void fs.setOptions(fsOptions);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
fs,
|
|
62
|
+
worker,
|
|
63
|
+
dispose() {
|
|
64
|
+
worker.port.close();
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expose } from 'comlink';
|
|
2
|
+
|
|
3
|
+
import { OPFSSync } from '../core/OPFSSync';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Dedicated worker entry.
|
|
7
|
+
*
|
|
8
|
+
* Built two ways:
|
|
9
|
+
* - Inlined into `createOPFSDedicated` via `?worker&inline` (default path)
|
|
10
|
+
* - Self-contained `dist/dedicated.worker.js` → `opfs-worker/dedicated.worker.js`
|
|
11
|
+
* for DIY `new Worker(url, { type: 'module' })` + Comlink wrap
|
|
12
|
+
*/
|
|
13
|
+
expose(new OPFSSync());
|