hyperdb-wasm 0.0.27136-dev.wasm.gcf673716 → 0.0.27136-dev.wasm.ged3c25a2
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/dist/api.d.ts +1 -1
- package/dist/browser.js +5 -21
- package/dist/browser_external_directory.d.ts +7 -3
- package/dist/browser_worker.js +77 -54
- package/dist/hyperdb-wasm.cjs +1 -1
- package/dist/hyperdb-wasm.js +1 -1
- package/dist/hyperdb-wasm.wasm.br +0 -0
- package/dist/node.js +4 -14
- package/dist/node_external_directory.d.ts +4 -3
- package/dist/node_worker.js +64 -44
- package/dist/protocol.d.ts +1 -3
- package/dist/raw.browser.js +17 -14
- package/dist/raw.node.js +17 -14
- package/dist/wasm_bindings.d.ts +7 -4
- package/dist/worker_setup.d.ts +3 -2
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -99,7 +99,7 @@ export declare class HyperDB {
|
|
|
99
99
|
static create(options?: BrowserCreateOptions): Promise<HyperDB>;
|
|
100
100
|
ready(): Promise<void>;
|
|
101
101
|
connect(options?: HyperDBConnectOptions): Promise<HyperDBConnection>;
|
|
102
|
-
protected mountSource(source: MountSource, path: string, mode: MountAccessMode
|
|
102
|
+
protected mountSource(source: MountSource, path: string, mode: MountAccessMode): Promise<MountedResource>;
|
|
103
103
|
terminate(): Promise<void>;
|
|
104
104
|
private ensureRunning;
|
|
105
105
|
protected runDatabaseOperation<Result>(operation: () => Promise<Result>): Promise<Result>;
|
package/dist/browser.js
CHANGED
|
@@ -501,19 +501,16 @@ var EngineQueryOperation = class {
|
|
|
501
501
|
}
|
|
502
502
|
};
|
|
503
503
|
var MountedResourceImpl = class {
|
|
504
|
-
constructor(path, unmount, refreshMount
|
|
504
|
+
constructor(path, unmount, refreshMount) {
|
|
505
505
|
this.path = path;
|
|
506
506
|
this.unmount = unmount;
|
|
507
507
|
this.refreshMount = refreshMount;
|
|
508
|
-
this.cleanup = cleanup;
|
|
509
508
|
}
|
|
510
509
|
path;
|
|
511
510
|
unmount;
|
|
512
511
|
refreshMount;
|
|
513
|
-
cleanup;
|
|
514
512
|
closed = false;
|
|
515
513
|
closing;
|
|
516
|
-
cleaned = false;
|
|
517
514
|
async refresh() {
|
|
518
515
|
if (this.closed) throw new Error("mounted resource is closed");
|
|
519
516
|
if (this.closing) throw new Error("mounted resource is closing");
|
|
@@ -529,19 +526,13 @@ var MountedResourceImpl = class {
|
|
|
529
526
|
this.closing = void 0;
|
|
530
527
|
throw error;
|
|
531
528
|
}
|
|
532
|
-
this.
|
|
529
|
+
this.closed = true;
|
|
533
530
|
})();
|
|
534
531
|
}
|
|
535
532
|
await this.closing;
|
|
536
533
|
}
|
|
537
534
|
releaseAfterShutdown() {
|
|
538
|
-
this.release();
|
|
539
|
-
}
|
|
540
|
-
release() {
|
|
541
535
|
this.closed = true;
|
|
542
|
-
if (this.cleaned) return;
|
|
543
|
-
this.cleaned = true;
|
|
544
|
-
this.cleanup();
|
|
545
536
|
}
|
|
546
537
|
};
|
|
547
538
|
var HyperDB = class _HyperDB {
|
|
@@ -581,7 +572,7 @@ var HyperDB = class _HyperDB {
|
|
|
581
572
|
this.connections.add(result);
|
|
582
573
|
return result;
|
|
583
574
|
}
|
|
584
|
-
mountSource(source, path, mode
|
|
575
|
+
mountSource(source, path, mode) {
|
|
585
576
|
return this.runDatabaseOperation(async () => {
|
|
586
577
|
const result = await this.client.mount(source, path, mode);
|
|
587
578
|
expectOk(result, "mount");
|
|
@@ -592,8 +583,7 @@ var HyperDB = class _HyperDB {
|
|
|
592
583
|
(mounted) => this.unmount(mounted),
|
|
593
584
|
(path2) => this.runDatabaseOperation(async () => {
|
|
594
585
|
expectOk(await this.client.refreshMount(path2), "refresh mount");
|
|
595
|
-
})
|
|
596
|
-
cleanup
|
|
586
|
+
})
|
|
597
587
|
);
|
|
598
588
|
this.mountedResources.add(resource);
|
|
599
589
|
return resource;
|
|
@@ -862,13 +852,7 @@ var HyperDB2 = class _HyperDB extends HyperDB {
|
|
|
862
852
|
throw new Error("Mounted file name must be a basename without path separators");
|
|
863
853
|
}
|
|
864
854
|
if (!Number.isSafeInteger(source.size) || source.size < 0) throw new Error("Mounted file size must be a nonnegative safe integer");
|
|
865
|
-
|
|
866
|
-
try {
|
|
867
|
-
return await this.mountSource({ kind: "browser-file", name: source.name, url, size: source.size }, path, "read", () => URL.revokeObjectURL(url));
|
|
868
|
-
} catch (error) {
|
|
869
|
-
URL.revokeObjectURL(url);
|
|
870
|
-
throw error;
|
|
871
|
-
}
|
|
855
|
+
return this.mountSource({ kind: "browser-file", file: source }, path, "read");
|
|
872
856
|
}
|
|
873
857
|
const handle = source;
|
|
874
858
|
const mode = options.mode ?? "read";
|
|
@@ -8,23 +8,27 @@ export declare class BrowserExternalDirectoryBackend implements ExternalDirector
|
|
|
8
8
|
private readonly paths;
|
|
9
9
|
private readonly files;
|
|
10
10
|
private readonly writers;
|
|
11
|
+
registerFile(file: unknown): {
|
|
12
|
+
readonly locator: string;
|
|
13
|
+
readonly size: number;
|
|
14
|
+
};
|
|
11
15
|
register(handle: unknown, mode: 'read' | 'readwrite'): {
|
|
12
16
|
readonly name: string;
|
|
13
17
|
readonly locator: string;
|
|
14
18
|
};
|
|
15
19
|
registered(path: string, locator: string): void;
|
|
16
|
-
|
|
20
|
+
releaseGeneration(locator: string, generation: string): void;
|
|
17
21
|
removed(pathOrLocator: string): void;
|
|
18
22
|
list(locator: string): Promise<readonly string[]>;
|
|
19
23
|
listEntries(locator: string, path: string): Promise<readonly BrowserDirectoryEntry[]>;
|
|
20
|
-
lookup(locator: string, path: string): Promise<{
|
|
24
|
+
lookup(locator: string, path: string, generation: string): Promise<{
|
|
21
25
|
readonly kind: 'directory';
|
|
22
26
|
} | {
|
|
23
27
|
readonly kind: 'file';
|
|
24
28
|
readonly locator: string;
|
|
25
29
|
readonly size: number;
|
|
26
30
|
} | undefined>;
|
|
27
|
-
open(locator: string, path: string): Promise<{
|
|
31
|
+
open(locator: string, path: string, generation: string): Promise<{
|
|
28
32
|
readonly locator: string;
|
|
29
33
|
readonly size: number;
|
|
30
34
|
}>;
|
package/dist/browser_worker.js
CHANGED
|
@@ -102,24 +102,25 @@
|
|
|
102
102
|
if (registration.kind === "directory") return this.mountDirectory(request, request.source.path);
|
|
103
103
|
return this.mountFile(request, registration.locator, registration.size, this.module.externalFileBackend);
|
|
104
104
|
}
|
|
105
|
-
if (request.type === "mount" && request.source.kind === "browser-file" && !this.module.externalFileBackendAvailable) {
|
|
106
|
-
return { response: { id: request.id, type: "response", result: { state: "error", error: "Blob external files are supported only by the browser client" } }, transfer: [] };
|
|
107
|
-
}
|
|
108
105
|
if (request.type === "mount" && request.source.kind === "browser-directory") {
|
|
109
106
|
return this.mountDirectory(request, request.source.handle);
|
|
110
107
|
}
|
|
111
108
|
if (request.type === "mount" && request.source.kind === "browser-file") {
|
|
112
|
-
|
|
109
|
+
validateCString(request.path, "external registration path");
|
|
110
|
+
const backend = this.module.externalDirectoryBackend;
|
|
111
|
+
if (!backend?.registerFile) {
|
|
112
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error: "Browser external files are supported only by the browser client" } }, transfer: [] };
|
|
113
|
+
}
|
|
114
|
+
const file = backend.registerFile(request.source.file);
|
|
115
|
+
return this.mountFile(request, file.locator, file.size, backend);
|
|
113
116
|
}
|
|
114
117
|
if (request.type === "refreshMount") {
|
|
115
118
|
validateCString(request.path, "external registration path");
|
|
116
|
-
|
|
117
|
-
if (!backend) return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
|
|
119
|
+
if (!this.module.externalDirectoryBackend) return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
|
|
118
120
|
if (!this.mountedDirectoryPaths.has(request.path)) {
|
|
119
121
|
const error = this.mountedPaths.has(request.path) ? "Only directory mounts can be refreshed" : "External directory registration was not found";
|
|
120
122
|
return { response: { id: request.id, type: "response", result: { state: "error", error } }, transfer: [] };
|
|
121
123
|
}
|
|
122
|
-
backend.refresh(request.path);
|
|
123
124
|
}
|
|
124
125
|
if (request.type === "unmount" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
|
|
125
126
|
validateCString(request.path, "external registration path");
|
|
@@ -138,7 +139,7 @@
|
|
|
138
139
|
}
|
|
139
140
|
return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
|
|
140
141
|
}
|
|
141
|
-
if (request.type === "unmount"
|
|
142
|
+
if (request.type === "unmount") {
|
|
142
143
|
return { response: { id: request.id, type: "response", result: { state: "error", error: "External files are not supported by this client" } }, transfer: [] };
|
|
143
144
|
}
|
|
144
145
|
const pointer = safeInteger(this.invoke(request), "result pointer");
|
|
@@ -157,7 +158,6 @@
|
|
|
157
158
|
};
|
|
158
159
|
}
|
|
159
160
|
const response = { id: request.id, type: "response", result };
|
|
160
|
-
if (request.type === "unmount" && result.state === "ok") this.mountedPaths.delete(request.path);
|
|
161
161
|
return { response, transfer: result.state === "chunk" ? [result.payload.buffer] : [] };
|
|
162
162
|
}
|
|
163
163
|
invoke(request) {
|
|
@@ -203,13 +203,13 @@
|
|
|
203
203
|
try {
|
|
204
204
|
const result = this.decodeResult(safeInteger(this.registerExternalFile(request.path, locator, size, request.id), "result pointer"));
|
|
205
205
|
if (result.state !== "ok") {
|
|
206
|
-
backend
|
|
207
|
-
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
206
|
+
const completion = completionFor([backend.removed(locator)]);
|
|
207
|
+
return { response: { id: request.id, type: "response", result }, transfer: [], ...completion && { completion } };
|
|
208
208
|
}
|
|
209
209
|
path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
|
|
210
210
|
const projection = this.decodeResult(safeInteger(this.projectExternalFile(path, locator, size, request.id), "result pointer"));
|
|
211
211
|
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
212
|
-
backend
|
|
212
|
+
backend.registered(path, locator);
|
|
213
213
|
registered = true;
|
|
214
214
|
this.mountedPaths.add(path);
|
|
215
215
|
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
@@ -224,12 +224,15 @@
|
|
|
224
224
|
}
|
|
225
225
|
if (rollbackError !== void 0) {
|
|
226
226
|
if (path !== void 0) {
|
|
227
|
-
backend
|
|
227
|
+
backend.registered(path, locator);
|
|
228
228
|
this.mountedPaths.add(path);
|
|
229
229
|
}
|
|
230
230
|
throw new Error(`mount failed and rollback also failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, { cause: error });
|
|
231
231
|
}
|
|
232
|
-
backend
|
|
232
|
+
const completion = completionFor([backend.removed(registered && path !== void 0 ? path : locator)]);
|
|
233
|
+
if (completion) {
|
|
234
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error: error instanceof Error ? error.message : String(error) } }, transfer: [], completion };
|
|
235
|
+
}
|
|
233
236
|
throw error;
|
|
234
237
|
}
|
|
235
238
|
}
|
|
@@ -354,7 +357,7 @@
|
|
|
354
357
|
if (!hasKeys(value, ["id", "type", "source", "path", "mode"]) || typeof value.path !== "string" || !value.path.startsWith("/mnt/external/") || new TextEncoder().encode(value.path.slice("/mnt/external/".length)).byteLength === 0 || new TextEncoder().encode(value.path.slice("/mnt/external/".length)).byteLength > 200 || value.path.slice("/mnt/external/".length).split("/").some((component) => !component || component === "." || component === ".." || !/^[A-Za-z0-9._-]+$/.test(component)) || value.mode !== "read" && value.mode !== "readwrite" || !isRecord(value.source)) return false;
|
|
355
358
|
switch (value.source.kind) {
|
|
356
359
|
case "browser-file":
|
|
357
|
-
return value.mode === "read" && hasKeys(value.source, ["kind", "
|
|
360
|
+
return value.mode === "read" && hasKeys(value.source, ["kind", "file"]) && typeof File !== "undefined" && value.source.file instanceof File;
|
|
358
361
|
case "browser-directory":
|
|
359
362
|
return hasKeys(value.source, ["kind", "handle"]) && isRecord(value.source.handle) && value.source.handle.kind === "directory";
|
|
360
363
|
case "node-path":
|
|
@@ -462,9 +465,14 @@
|
|
|
462
465
|
};
|
|
463
466
|
module.hyperdbWasmFetchCancel = (id) => fetches.get(id)?.abort();
|
|
464
467
|
}
|
|
465
|
-
function installExternalDirectoryRuntime(module) {
|
|
468
|
+
function installExternalDirectoryRuntime(module, transport) {
|
|
466
469
|
const backend = module.externalDirectoryBackend;
|
|
467
470
|
if (!backend) return;
|
|
471
|
+
module.hyperdbWasmExternalDirectoryReleaseGeneration = (locator, generation) => {
|
|
472
|
+
void Promise.resolve().then(() => backend.releaseGeneration(locator, generation)).catch(() => {
|
|
473
|
+
transport.postMessage({ type: "log", text: "External directory generation cleanup failed; mount removal will retry" });
|
|
474
|
+
});
|
|
475
|
+
};
|
|
468
476
|
const listComplete = module._hyperdb_wasm_external_directory_list_complete;
|
|
469
477
|
const openComplete = module._hyperdb_wasm_external_directory_open_complete;
|
|
470
478
|
if (listComplete) {
|
|
@@ -477,8 +485,8 @@
|
|
|
477
485
|
};
|
|
478
486
|
}
|
|
479
487
|
if (openComplete) {
|
|
480
|
-
module.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path) => {
|
|
481
|
-
void backend.open(locator, path).then((file) => {
|
|
488
|
+
module.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path, generation) => {
|
|
489
|
+
void backend.open(locator, path, generation).then((file) => {
|
|
482
490
|
openComplete(id, safePointer(module.stringToNewUTF8(file.locator), "external file locator pointer"), file.size);
|
|
483
491
|
}).catch((error) => openComplete(id, 0, isNotFoundError(error) ? -2 : -1));
|
|
484
492
|
};
|
|
@@ -567,8 +575,8 @@
|
|
|
567
575
|
complete(id, safePointer(module.stringToNewUTF8(JSON.stringify(encoded)), "filesystem listing pointer"), 0);
|
|
568
576
|
}).catch(() => complete(id, 0, -1));
|
|
569
577
|
};
|
|
570
|
-
module.hyperdbWasmFilesystemLookupStart = (id, locator, path) => {
|
|
571
|
-
const lookup = module.externalDirectoryBackend?.lookup?.(locator, path);
|
|
578
|
+
module.hyperdbWasmFilesystemLookupStart = (id, locator, path, generation) => {
|
|
579
|
+
const lookup = module.externalDirectoryBackend?.lookup?.(locator, path, generation);
|
|
572
580
|
if (!lookup) {
|
|
573
581
|
complete(id, 0, -1);
|
|
574
582
|
return;
|
|
@@ -580,13 +588,7 @@
|
|
|
580
588
|
}).catch(() => complete(id, 0, -1));
|
|
581
589
|
};
|
|
582
590
|
module.hyperdbWasmFilesystemReadStart = (id, locator, offset, length, expectedSize, pointerValue) => {
|
|
583
|
-
const read = module.externalFileBackend?.read(locator, offset, length, expectedSize) ?? module.externalDirectoryBackend?.read(locator, offset, length, expectedSize)
|
|
584
|
-
const contentRange = /^bytes (\d+)-(\d+)\/(\d+)$/.exec(response.headers.get("content-range") ?? "");
|
|
585
|
-
if (response.status !== 206 || !contentRange || contentRange[1] !== String(offset) || contentRange[2] !== String(offset + length - 1) || contentRange[3] !== String(expectedSize)) {
|
|
586
|
-
throw new Error("Blob URL returned an invalid byte range");
|
|
587
|
-
}
|
|
588
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
589
|
-
}) : void 0);
|
|
591
|
+
const read = module.externalFileBackend?.read(locator, offset, length, expectedSize) ?? module.externalDirectoryBackend?.read(locator, offset, length, expectedSize);
|
|
590
592
|
if (!read) {
|
|
591
593
|
complete(id, 0, -1);
|
|
592
594
|
return;
|
|
@@ -633,7 +635,7 @@
|
|
|
633
635
|
configureFilesystem(module);
|
|
634
636
|
installHttpRuntime(module, transport);
|
|
635
637
|
installHostFileRuntime(module);
|
|
636
|
-
installExternalDirectoryRuntime(module);
|
|
638
|
+
installExternalDirectoryRuntime(module, transport);
|
|
637
639
|
installFilesystemProjectionRuntime(module);
|
|
638
640
|
bindings = new HyperDBBindings(module);
|
|
639
641
|
transport.postMessage({ type: "ready" });
|
|
@@ -682,33 +684,51 @@
|
|
|
682
684
|
paths = /* @__PURE__ */ new Map();
|
|
683
685
|
files = /* @__PURE__ */ new Map();
|
|
684
686
|
writers = /* @__PURE__ */ new Map();
|
|
687
|
+
registerFile(file) {
|
|
688
|
+
if (typeof file !== "object" || file === null || !("size" in file) || typeof file.size !== "number" || !("slice" in file) || typeof file.slice !== "function") {
|
|
689
|
+
throw new Error("External file is invalid");
|
|
690
|
+
}
|
|
691
|
+
nonnegativeSafeInteger(file.size, "External file size");
|
|
692
|
+
const locator = `${fileLocatorPrefix}${opaqueId()}`;
|
|
693
|
+
this.files.set(locator, file);
|
|
694
|
+
return { locator, size: file.size };
|
|
695
|
+
}
|
|
685
696
|
register(handle, mode) {
|
|
686
697
|
const directoryHandle = asDirectoryHandle(handle);
|
|
687
698
|
validateSegment(directoryHandle.name);
|
|
688
699
|
const locator = `${directoryLocatorPrefix}${opaqueId()}`;
|
|
689
|
-
this.directories.set(locator, { handle: directoryHandle, mode,
|
|
700
|
+
this.directories.set(locator, { handle: directoryHandle, mode, generations: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set() });
|
|
690
701
|
return { name: directoryHandle.name, locator };
|
|
691
702
|
}
|
|
692
703
|
registered(path, locator) {
|
|
693
|
-
if (!this.directories.has(locator)) throw new Error("External
|
|
704
|
+
if (!this.directories.has(locator) && !this.files.has(locator)) throw new Error("External registration is unavailable");
|
|
694
705
|
this.paths.set(path, locator);
|
|
695
706
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
if (
|
|
700
|
-
|
|
707
|
+
releaseGeneration(locator, generation) {
|
|
708
|
+
const directory = this.directories.get(locator);
|
|
709
|
+
const state = directory?.generations.get(generation);
|
|
710
|
+
if (!directory || !state) return;
|
|
711
|
+
directory.generations.delete(generation);
|
|
712
|
+
for (const fileLocator of state.locators) {
|
|
713
|
+
this.files.delete(fileLocator);
|
|
714
|
+
directory.locators.delete(fileLocator);
|
|
715
|
+
}
|
|
701
716
|
}
|
|
702
717
|
removed(pathOrLocator) {
|
|
703
718
|
const locator = this.paths.get(pathOrLocator) ?? pathOrLocator;
|
|
704
719
|
const directory = this.directories.get(locator);
|
|
705
|
-
if (
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
720
|
+
if (directory) {
|
|
721
|
+
for (const fileLocator of directory.locators) this.files.delete(fileLocator);
|
|
722
|
+
for (const [writer, state] of this.writers) {
|
|
723
|
+
if (state.locator === locator) void this.abort(writer).catch(() => void 0);
|
|
724
|
+
}
|
|
725
|
+
this.directories.delete(locator);
|
|
726
|
+
} else {
|
|
727
|
+
this.files.delete(locator);
|
|
728
|
+
}
|
|
729
|
+
for (const [path, registeredLocator] of this.paths) {
|
|
730
|
+
if (registeredLocator === locator) this.paths.delete(path);
|
|
709
731
|
}
|
|
710
|
-
this.directories.delete(locator);
|
|
711
|
-
this.paths.delete(pathOrLocator);
|
|
712
732
|
}
|
|
713
733
|
async list(locator) {
|
|
714
734
|
const directory = this.getDirectory(locator);
|
|
@@ -737,7 +757,7 @@
|
|
|
737
757
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
738
758
|
return entries;
|
|
739
759
|
}
|
|
740
|
-
async lookup(locator, path) {
|
|
760
|
+
async lookup(locator, path, generation) {
|
|
741
761
|
const directory = this.getDirectory(locator);
|
|
742
762
|
const segments = relativeSegments(path);
|
|
743
763
|
const parent = await this.openDirectory(directory, segments.slice(0, -1));
|
|
@@ -747,14 +767,19 @@
|
|
|
747
767
|
validateSegment(child.name);
|
|
748
768
|
if (child.name !== name) continue;
|
|
749
769
|
if (child.kind === "directory") return { kind: "directory" };
|
|
750
|
-
if (child.kind === "file") return { kind: "file", ...await this.open(locator, path) };
|
|
770
|
+
if (child.kind === "file") return { kind: "file", ...await this.open(locator, path, generation) };
|
|
751
771
|
throw new Error("External directory contains an unsupported entry");
|
|
752
772
|
}
|
|
753
773
|
return void 0;
|
|
754
774
|
}
|
|
755
|
-
async open(locator, path) {
|
|
775
|
+
async open(locator, path, generation) {
|
|
756
776
|
const directory = this.getDirectory(locator);
|
|
757
|
-
|
|
777
|
+
let state = directory.generations.get(generation);
|
|
778
|
+
if (!state) {
|
|
779
|
+
state = { files: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set() };
|
|
780
|
+
directory.generations.set(generation, state);
|
|
781
|
+
}
|
|
782
|
+
const cached = state.files.get(path);
|
|
758
783
|
if (cached) {
|
|
759
784
|
const file = await cached;
|
|
760
785
|
return { locator: file.locator, size: file.file.size };
|
|
@@ -772,25 +797,24 @@
|
|
|
772
797
|
const fileHandle = await parent.getFileHandle(name);
|
|
773
798
|
if (fileHandle.kind !== "file" || fileHandle.name !== name) throw new Error("External directory returned an unexpected file handle");
|
|
774
799
|
const file = await fileHandle.getFile();
|
|
775
|
-
|
|
776
|
-
const fileLocator = `${fileLocatorPrefix}${opaqueId()}`;
|
|
777
|
-
this.files.set(fileLocator, file);
|
|
800
|
+
const { locator: fileLocator } = this.registerFile(file);
|
|
778
801
|
directory.locators.add(fileLocator);
|
|
802
|
+
state.locators.add(fileLocator);
|
|
779
803
|
return { locator: fileLocator, file };
|
|
780
804
|
})();
|
|
781
|
-
|
|
805
|
+
state.files.set(path, snapshot);
|
|
782
806
|
try {
|
|
783
807
|
const file = await snapshot;
|
|
784
808
|
return { locator: file.locator, size: file.file.size };
|
|
785
809
|
} catch (error) {
|
|
786
|
-
if (
|
|
810
|
+
if (state.files.get(path) === snapshot) state.files.delete(path);
|
|
787
811
|
throw error;
|
|
788
812
|
}
|
|
789
813
|
}
|
|
790
814
|
read(locator, offset, length, expectedSize) {
|
|
791
815
|
if (!locator.startsWith(fileLocatorPrefix)) return void 0;
|
|
792
816
|
const file = this.files.get(locator);
|
|
793
|
-
if (!file) return Promise.reject(new Error("External
|
|
817
|
+
if (!file) return Promise.reject(new Error("External file registration does not exist"));
|
|
794
818
|
const end = offset + length;
|
|
795
819
|
if (expectedSize !== file.size || !Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 0 || !Number.isSafeInteger(end) || end > file.size) {
|
|
796
820
|
return Promise.reject(new Error("External file byte range is invalid"));
|
|
@@ -875,7 +899,7 @@
|
|
|
875
899
|
await this.cleanupWriter(state).catch(() => void 0);
|
|
876
900
|
throw failure;
|
|
877
901
|
}
|
|
878
|
-
this.getDirectory(state.locator).files.delete(state.path);
|
|
902
|
+
for (const generation of this.getDirectory(state.locator).generations.values()) generation.files.delete(state.path);
|
|
879
903
|
}, true);
|
|
880
904
|
return operation.finally(() => this.writers.delete(writer));
|
|
881
905
|
}
|
|
@@ -917,7 +941,7 @@
|
|
|
917
941
|
return state;
|
|
918
942
|
}
|
|
919
943
|
async cleanupWriter(state) {
|
|
920
|
-
this.directories.get(state.locator)?.files.delete(state.path);
|
|
944
|
+
for (const generation of this.directories.get(state.locator)?.generations.values() ?? []) generation.files.delete(state.path);
|
|
921
945
|
try {
|
|
922
946
|
await state.stream?.abort();
|
|
923
947
|
} finally {
|
|
@@ -996,7 +1020,6 @@
|
|
|
996
1020
|
};
|
|
997
1021
|
self.Module = module;
|
|
998
1022
|
configureWorker(module, transport, (runtime) => {
|
|
999
|
-
runtime.externalFileBackendAvailable = true;
|
|
1000
1023
|
runtime.externalDirectoryBackend = new BrowserExternalDirectoryBackend();
|
|
1001
1024
|
if (!runtime.OPFS) throw new Error("OPFS backend is unavailable");
|
|
1002
1025
|
runtime.FS.mkdirTree("/mnt/fs");
|