hyperdb-wasm 0.0.27136-dev.wasm.gd997ff1c → 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 +3 -1
- package/dist/browser.js +21 -20
- package/dist/browser_external_directory.d.ts +7 -2
- package/dist/browser_worker.js +96 -45
- package/dist/client.d.ts +1 -0
- 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 +20 -13
- package/dist/node_external_directory.d.ts +4 -2
- package/dist/node_worker.js +93 -37
- package/dist/protocol.d.ts +5 -3
- package/dist/raw.browser.js +37 -10
- package/dist/raw.node.js +37 -10
- package/dist/wasm_bindings.d.ts +9 -3
- package/dist/worker_setup.d.ts +3 -2
- package/package.json +1 -1
|
@@ -9,20 +9,21 @@ export declare class NodeExternalDirectoryBackend implements ExternalDirectoryBa
|
|
|
9
9
|
readonly locator: string;
|
|
10
10
|
};
|
|
11
11
|
registered(path: string, locator: string): void;
|
|
12
|
+
releaseGeneration(locator: string, generation: string): Promise<void>;
|
|
12
13
|
removed(pathOrLocator: string): Promise<void>;
|
|
13
14
|
list(locator: string): Promise<readonly string[]>;
|
|
14
15
|
listEntries(locator: string, path: string): Promise<readonly {
|
|
15
16
|
readonly name: string;
|
|
16
17
|
readonly kind: 'file' | 'directory';
|
|
17
18
|
}[]>;
|
|
18
|
-
lookup(locator: string, path: string): Promise<{
|
|
19
|
+
lookup(locator: string, path: string, generation: string): Promise<{
|
|
19
20
|
readonly kind: 'directory';
|
|
20
21
|
} | {
|
|
21
22
|
readonly kind: 'file';
|
|
22
23
|
readonly locator: string;
|
|
23
24
|
readonly size: number;
|
|
24
25
|
} | undefined>;
|
|
25
|
-
open(locator: string, path: string): Promise<{
|
|
26
|
+
open(locator: string, path: string, generation: string): Promise<{
|
|
26
27
|
readonly locator: string;
|
|
27
28
|
readonly size: number;
|
|
28
29
|
}>;
|
|
@@ -36,6 +37,7 @@ export declare class NodeExternalDirectoryBackend implements ExternalDirectoryBa
|
|
|
36
37
|
private getDirectory;
|
|
37
38
|
private getWritableDirectory;
|
|
38
39
|
private getOpenWriter;
|
|
40
|
+
private closeFile;
|
|
39
41
|
private cleanupWriter;
|
|
40
42
|
private enqueue;
|
|
41
43
|
}
|
package/dist/node_worker.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { closeSync, createReadStream, fstatSync, openSync, read, statSync } from "node:fs";
|
|
3
3
|
import { opendir as opendir2, stat } from "node:fs/promises";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
-
import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
|
|
5
|
+
import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join2, posix, resolve as resolve2 } from "node:path";
|
|
6
6
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
import { parentPort, workerData } from "node:worker_threads";
|
|
@@ -70,6 +70,7 @@ var HyperDBBindings = class {
|
|
|
70
70
|
registerExternalFile;
|
|
71
71
|
registerExternalDirectory;
|
|
72
72
|
removeExternalRegistration;
|
|
73
|
+
refreshExternalDirectory;
|
|
73
74
|
projectExternalFile;
|
|
74
75
|
projectExternalDirectory;
|
|
75
76
|
startQuery;
|
|
@@ -80,6 +81,7 @@ var HyperDBBindings = class {
|
|
|
80
81
|
resultPayloadLength;
|
|
81
82
|
free;
|
|
82
83
|
mountedPaths = /* @__PURE__ */ new Set();
|
|
84
|
+
mountedDirectoryPaths = /* @__PURE__ */ new Set();
|
|
83
85
|
constructor(module2) {
|
|
84
86
|
this.module = module2;
|
|
85
87
|
this.initialize = module2.cwrap("hyperdb_wasm_initialize", "pointer", ["string", "string", "number"]);
|
|
@@ -89,6 +91,7 @@ var HyperDBBindings = class {
|
|
|
89
91
|
this.registerExternalFile = module2.cwrap("hyperdb_wasm_register_external_file", "pointer", ["string", "string", "number", "number"]);
|
|
90
92
|
this.registerExternalDirectory = module2.cwrap("hyperdb_wasm_register_external_directory", "pointer", ["string", "string", "number", "number"]);
|
|
91
93
|
this.removeExternalRegistration = module2.cwrap("hyperdb_wasm_remove_external_registration", "pointer", ["string", "number"]);
|
|
94
|
+
this.refreshExternalDirectory = module2.cwrap("hyperdb_wasm_refresh_external_directory", "pointer", ["string", "number"]);
|
|
92
95
|
this.projectExternalFile = module2.cwrap("hyperdb_wasm_project_external_file", "pointer", ["string", "string", "number", "number"]);
|
|
93
96
|
this.projectExternalDirectory = module2.cwrap("hyperdb_wasm_project_external_directory", "pointer", ["string", "string", "number"]);
|
|
94
97
|
this.startQuery = module2.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string", "number", "number"]);
|
|
@@ -108,20 +111,32 @@ var HyperDBBindings = class {
|
|
|
108
111
|
if (registration.kind === "directory") return this.mountDirectory(request, request.source.path);
|
|
109
112
|
return this.mountFile(request, registration.locator, registration.size, this.module.externalFileBackend);
|
|
110
113
|
}
|
|
111
|
-
if (request.type === "mount" && request.source.kind === "browser-file" && !this.module.externalFileBackendAvailable) {
|
|
112
|
-
return { response: { id: request.id, type: "response", result: { state: "error", error: "Blob external files are supported only by the browser client" } }, transfer: [] };
|
|
113
|
-
}
|
|
114
114
|
if (request.type === "mount" && request.source.kind === "browser-directory") {
|
|
115
115
|
return this.mountDirectory(request, request.source.handle);
|
|
116
116
|
}
|
|
117
117
|
if (request.type === "mount" && request.source.kind === "browser-file") {
|
|
118
|
-
|
|
118
|
+
validateCString(request.path, "external registration path");
|
|
119
|
+
const backend = this.module.externalDirectoryBackend;
|
|
120
|
+
if (!backend?.registerFile) {
|
|
121
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error: "Browser external files are supported only by the browser client" } }, transfer: [] };
|
|
122
|
+
}
|
|
123
|
+
const file = backend.registerFile(request.source.file);
|
|
124
|
+
return this.mountFile(request, file.locator, file.size, backend);
|
|
125
|
+
}
|
|
126
|
+
if (request.type === "refreshMount") {
|
|
127
|
+
validateCString(request.path, "external registration path");
|
|
128
|
+
if (!this.module.externalDirectoryBackend) return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
|
|
129
|
+
if (!this.mountedDirectoryPaths.has(request.path)) {
|
|
130
|
+
const error = this.mountedPaths.has(request.path) ? "Only directory mounts can be refreshed" : "External directory registration was not found";
|
|
131
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error } }, transfer: [] };
|
|
132
|
+
}
|
|
119
133
|
}
|
|
120
134
|
if (request.type === "unmount" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
|
|
121
135
|
validateCString(request.path, "external registration path");
|
|
122
136
|
const pointer2 = safeInteger(this.removeExternalRegistration(request.path, request.id), "result pointer");
|
|
123
137
|
const result2 = this.decodeResult(pointer2);
|
|
124
138
|
if (result2.state === "ok" || result2.state === "error" && result2.error === "External registration was not found" && this.mountedPaths.has(request.path)) {
|
|
139
|
+
this.mountedDirectoryPaths.delete(request.path);
|
|
125
140
|
const cleanup = async () => {
|
|
126
141
|
await Promise.all([
|
|
127
142
|
this.module.externalFileBackend?.removed(request.path),
|
|
@@ -133,12 +148,14 @@ var HyperDBBindings = class {
|
|
|
133
148
|
}
|
|
134
149
|
return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
|
|
135
150
|
}
|
|
136
|
-
if (request.type === "unmount"
|
|
151
|
+
if (request.type === "unmount") {
|
|
137
152
|
return { response: { id: request.id, type: "response", result: { state: "error", error: "External files are not supported by this client" } }, transfer: [] };
|
|
138
153
|
}
|
|
139
154
|
const pointer = safeInteger(this.invoke(request), "result pointer");
|
|
140
155
|
const result = this.decodeResult(pointer);
|
|
141
156
|
if (request.type === "shutdown" && result.state === "ok") {
|
|
157
|
+
this.mountedPaths.clear();
|
|
158
|
+
this.mountedDirectoryPaths.clear();
|
|
142
159
|
const completion = completionFor([
|
|
143
160
|
this.module.externalFileBackend?.shutdown(),
|
|
144
161
|
this.module.externalDirectoryBackend?.shutdown()
|
|
@@ -173,6 +190,8 @@ var HyperDBBindings = class {
|
|
|
173
190
|
case "unmount":
|
|
174
191
|
validateCString(request.path, "external registration path");
|
|
175
192
|
return this.removeExternalRegistration(request.path, request.id);
|
|
193
|
+
case "refreshMount":
|
|
194
|
+
return this.refreshExternalDirectory(request.path, request.id);
|
|
176
195
|
case "startQuery":
|
|
177
196
|
validateCStringContents(request.sql, "SQL");
|
|
178
197
|
return this.startQuery(request.connection, request.sql, Number(request.sqlTestMode ?? false), request.id);
|
|
@@ -193,13 +212,13 @@ var HyperDBBindings = class {
|
|
|
193
212
|
try {
|
|
194
213
|
const result = this.decodeResult(safeInteger(this.registerExternalFile(request.path, locator, size, request.id), "result pointer"));
|
|
195
214
|
if (result.state !== "ok") {
|
|
196
|
-
backend
|
|
197
|
-
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
215
|
+
const completion = completionFor([backend.removed(locator)]);
|
|
216
|
+
return { response: { id: request.id, type: "response", result }, transfer: [], ...completion && { completion } };
|
|
198
217
|
}
|
|
199
218
|
path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
|
|
200
219
|
const projection = this.decodeResult(safeInteger(this.projectExternalFile(path, locator, size, request.id), "result pointer"));
|
|
201
220
|
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
202
|
-
backend
|
|
221
|
+
backend.registered(path, locator);
|
|
203
222
|
registered = true;
|
|
204
223
|
this.mountedPaths.add(path);
|
|
205
224
|
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
@@ -214,12 +233,15 @@ var HyperDBBindings = class {
|
|
|
214
233
|
}
|
|
215
234
|
if (rollbackError !== void 0) {
|
|
216
235
|
if (path !== void 0) {
|
|
217
|
-
backend
|
|
236
|
+
backend.registered(path, locator);
|
|
218
237
|
this.mountedPaths.add(path);
|
|
219
238
|
}
|
|
220
239
|
throw new Error(`mount failed and rollback also failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, { cause: error });
|
|
221
240
|
}
|
|
222
|
-
backend
|
|
241
|
+
const completion = completionFor([backend.removed(registered && path !== void 0 ? path : locator)]);
|
|
242
|
+
if (completion) {
|
|
243
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error: error instanceof Error ? error.message : String(error) } }, transfer: [], completion };
|
|
244
|
+
}
|
|
223
245
|
throw error;
|
|
224
246
|
}
|
|
225
247
|
}
|
|
@@ -243,12 +265,14 @@ var HyperDBBindings = class {
|
|
|
243
265
|
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
244
266
|
backend.registered(path, registration.locator);
|
|
245
267
|
this.mountedPaths.add(path);
|
|
268
|
+
this.mountedDirectoryPaths.add(path);
|
|
246
269
|
} catch (error) {
|
|
247
270
|
const cleanupPointer = safeInteger(this.removeExternalRegistration(path, request.id), "result pointer");
|
|
248
271
|
const cleanup = this.decodeResult(cleanupPointer);
|
|
249
272
|
if (cleanup.state !== "ok") {
|
|
250
273
|
backend.registered(path, registration.locator);
|
|
251
274
|
this.mountedPaths.add(path);
|
|
275
|
+
this.mountedDirectoryPaths.add(path);
|
|
252
276
|
preserveBackend = true;
|
|
253
277
|
throw new Error(`mount failed and rollback also failed: ${resultError(cleanup)}`, { cause: error });
|
|
254
278
|
}
|
|
@@ -342,7 +366,7 @@ function isEngineRequest(value) {
|
|
|
342
366
|
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;
|
|
343
367
|
switch (value.source.kind) {
|
|
344
368
|
case "browser-file":
|
|
345
|
-
return value.mode === "read" && hasKeys(value.source, ["kind", "
|
|
369
|
+
return value.mode === "read" && hasKeys(value.source, ["kind", "file"]) && typeof File !== "undefined" && value.source.file instanceof File;
|
|
346
370
|
case "browser-directory":
|
|
347
371
|
return hasKeys(value.source, ["kind", "handle"]) && isRecord(value.source.handle) && value.source.handle.kind === "directory";
|
|
348
372
|
case "node-path":
|
|
@@ -352,6 +376,7 @@ function isEngineRequest(value) {
|
|
|
352
376
|
}
|
|
353
377
|
}
|
|
354
378
|
case "unmount":
|
|
379
|
+
case "refreshMount":
|
|
355
380
|
return hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string" && !!value.path && !value.path.includes("\0");
|
|
356
381
|
case "pollQuery":
|
|
357
382
|
case "cancelQuery":
|
|
@@ -449,9 +474,14 @@ function installHttpRuntime(module2, transport2) {
|
|
|
449
474
|
};
|
|
450
475
|
module2.hyperdbWasmFetchCancel = (id) => fetches.get(id)?.abort();
|
|
451
476
|
}
|
|
452
|
-
function installExternalDirectoryRuntime(module2) {
|
|
477
|
+
function installExternalDirectoryRuntime(module2, transport2) {
|
|
453
478
|
const backend = module2.externalDirectoryBackend;
|
|
454
479
|
if (!backend) return;
|
|
480
|
+
module2.hyperdbWasmExternalDirectoryReleaseGeneration = (locator, generation) => {
|
|
481
|
+
void Promise.resolve().then(() => backend.releaseGeneration(locator, generation)).catch(() => {
|
|
482
|
+
transport2.postMessage({ type: "log", text: "External directory generation cleanup failed; mount removal will retry" });
|
|
483
|
+
});
|
|
484
|
+
};
|
|
455
485
|
const listComplete = module2._hyperdb_wasm_external_directory_list_complete;
|
|
456
486
|
const openComplete = module2._hyperdb_wasm_external_directory_open_complete;
|
|
457
487
|
if (listComplete) {
|
|
@@ -464,8 +494,8 @@ function installExternalDirectoryRuntime(module2) {
|
|
|
464
494
|
};
|
|
465
495
|
}
|
|
466
496
|
if (openComplete) {
|
|
467
|
-
module2.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path) => {
|
|
468
|
-
void backend.open(locator, path).then((file) => {
|
|
497
|
+
module2.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path, generation) => {
|
|
498
|
+
void backend.open(locator, path, generation).then((file) => {
|
|
469
499
|
openComplete(id, safePointer(module2.stringToNewUTF8(file.locator), "external file locator pointer"), file.size);
|
|
470
500
|
}).catch((error) => openComplete(id, 0, isNotFoundError(error) ? -2 : -1));
|
|
471
501
|
};
|
|
@@ -554,8 +584,8 @@ function installFilesystemProjectionRuntime(module2) {
|
|
|
554
584
|
complete(id, safePointer(module2.stringToNewUTF8(JSON.stringify(encoded)), "filesystem listing pointer"), 0);
|
|
555
585
|
}).catch(() => complete(id, 0, -1));
|
|
556
586
|
};
|
|
557
|
-
module2.hyperdbWasmFilesystemLookupStart = (id, locator, path) => {
|
|
558
|
-
const lookup = module2.externalDirectoryBackend?.lookup?.(locator, path);
|
|
587
|
+
module2.hyperdbWasmFilesystemLookupStart = (id, locator, path, generation) => {
|
|
588
|
+
const lookup = module2.externalDirectoryBackend?.lookup?.(locator, path, generation);
|
|
559
589
|
if (!lookup) {
|
|
560
590
|
complete(id, 0, -1);
|
|
561
591
|
return;
|
|
@@ -567,13 +597,7 @@ function installFilesystemProjectionRuntime(module2) {
|
|
|
567
597
|
}).catch(() => complete(id, 0, -1));
|
|
568
598
|
};
|
|
569
599
|
module2.hyperdbWasmFilesystemReadStart = (id, locator, offset, length, expectedSize, pointerValue) => {
|
|
570
|
-
const read2 = module2.externalFileBackend?.read(locator, offset, length, expectedSize) ?? module2.externalDirectoryBackend?.read(locator, offset, length, expectedSize)
|
|
571
|
-
const contentRange = /^bytes (\d+)-(\d+)\/(\d+)$/.exec(response.headers.get("content-range") ?? "");
|
|
572
|
-
if (response.status !== 206 || !contentRange || contentRange[1] !== String(offset) || contentRange[2] !== String(offset + length - 1) || contentRange[3] !== String(expectedSize)) {
|
|
573
|
-
throw new Error("Blob URL returned an invalid byte range");
|
|
574
|
-
}
|
|
575
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
576
|
-
}) : void 0);
|
|
600
|
+
const read2 = module2.externalFileBackend?.read(locator, offset, length, expectedSize) ?? module2.externalDirectoryBackend?.read(locator, offset, length, expectedSize);
|
|
577
601
|
if (!read2) {
|
|
578
602
|
complete(id, 0, -1);
|
|
579
603
|
return;
|
|
@@ -620,7 +644,7 @@ function configureWorker(module2, transport2, configureFilesystem = () => void 0
|
|
|
620
644
|
configureFilesystem(module2);
|
|
621
645
|
installHttpRuntime(module2, transport2);
|
|
622
646
|
installHostFileRuntime(module2);
|
|
623
|
-
installExternalDirectoryRuntime(module2);
|
|
647
|
+
installExternalDirectoryRuntime(module2, transport2);
|
|
624
648
|
installFilesystemProjectionRuntime(module2);
|
|
625
649
|
bindings = new HyperDBBindings(module2);
|
|
626
650
|
transport2.postMessage({ type: "ready" });
|
|
@@ -705,13 +729,25 @@ var NodeExternalDirectoryBackend = class {
|
|
|
705
729
|
const info = lstatSync(root);
|
|
706
730
|
if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("External directory path must identify a directory");
|
|
707
731
|
const locator = `${directoryLocatorPrefix}${randomUUID()}`;
|
|
708
|
-
this.directories.set(locator, { root, mode,
|
|
732
|
+
this.directories.set(locator, { root, mode, generations: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set(), temporaryPaths: /* @__PURE__ */ new Set() });
|
|
709
733
|
return { name: basename(root) || "root", locator };
|
|
710
734
|
}
|
|
711
735
|
registered(path, locator) {
|
|
712
736
|
if (!this.directories.has(locator)) throw new Error("External directory registration is unavailable");
|
|
713
737
|
this.paths.set(path, locator);
|
|
714
738
|
}
|
|
739
|
+
async releaseGeneration(locator, generation) {
|
|
740
|
+
const directory = this.directories.get(locator);
|
|
741
|
+
const state = directory?.generations.get(generation);
|
|
742
|
+
if (!directory || !state) return;
|
|
743
|
+
directory.generations.delete(generation);
|
|
744
|
+
await Promise.all([...state.locators].map(async (fileLocator) => {
|
|
745
|
+
const file = this.files.get(fileLocator);
|
|
746
|
+
if (file) await this.closeFile(file);
|
|
747
|
+
this.files.delete(fileLocator);
|
|
748
|
+
directory.locators.delete(fileLocator);
|
|
749
|
+
}));
|
|
750
|
+
}
|
|
715
751
|
async removed(pathOrLocator) {
|
|
716
752
|
const locator = this.paths.get(pathOrLocator) ?? pathOrLocator;
|
|
717
753
|
const directory = this.directories.get(locator);
|
|
@@ -732,7 +768,7 @@ var NodeExternalDirectoryBackend = class {
|
|
|
732
768
|
directory.locators.delete(fileLocator);
|
|
733
769
|
continue;
|
|
734
770
|
}
|
|
735
|
-
fileCleanup.push(
|
|
771
|
+
fileCleanup.push(this.closeFile(file).then(() => {
|
|
736
772
|
this.files.delete(fileLocator);
|
|
737
773
|
directory.locators.delete(fileLocator);
|
|
738
774
|
}));
|
|
@@ -782,7 +818,7 @@ var NodeExternalDirectoryBackend = class {
|
|
|
782
818
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
783
819
|
return entries;
|
|
784
820
|
}
|
|
785
|
-
async lookup(locator, path) {
|
|
821
|
+
async lookup(locator, path, generation) {
|
|
786
822
|
const directory = this.getDirectory(locator);
|
|
787
823
|
const segments = relativeSegments(path);
|
|
788
824
|
await ensureDirectory(directory.root, segments.slice(0, -1), false);
|
|
@@ -792,15 +828,20 @@ var NodeExternalDirectoryBackend = class {
|
|
|
792
828
|
if (info.isSymbolicLink()) throw new Error("External directory must not contain symbolic links");
|
|
793
829
|
if (info.isDirectory()) return { kind: "directory" };
|
|
794
830
|
if (!info.isFile()) throw new Error("External directory contains an unsupported entry");
|
|
795
|
-
return { kind: "file", ...await this.open(locator, path) };
|
|
831
|
+
return { kind: "file", ...await this.open(locator, path, generation) };
|
|
796
832
|
} catch (error) {
|
|
797
833
|
if (isNotFound(error)) return void 0;
|
|
798
834
|
throw error;
|
|
799
835
|
}
|
|
800
836
|
}
|
|
801
|
-
async open(locator, path) {
|
|
837
|
+
async open(locator, path, generation) {
|
|
802
838
|
const directory = this.getDirectory(locator);
|
|
803
|
-
|
|
839
|
+
let state = directory.generations.get(generation);
|
|
840
|
+
if (!state) {
|
|
841
|
+
state = { files: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set() };
|
|
842
|
+
directory.generations.set(generation, state);
|
|
843
|
+
}
|
|
844
|
+
const cached = state.files.get(path);
|
|
804
845
|
if (cached) {
|
|
805
846
|
const file = await cached;
|
|
806
847
|
return { locator: file.locator, size: file.size };
|
|
@@ -819,18 +860,19 @@ var NodeExternalDirectoryBackend = class {
|
|
|
819
860
|
const file = { locator: `${fileLocatorPrefix}${randomUUID()}`, descriptor, size: Number(openedInfo.size) };
|
|
820
861
|
this.files.set(file.locator, file);
|
|
821
862
|
directory.locators.add(file.locator);
|
|
863
|
+
state.locators.add(file.locator);
|
|
822
864
|
return file;
|
|
823
865
|
} catch (error) {
|
|
824
866
|
await descriptor.close();
|
|
825
867
|
throw error;
|
|
826
868
|
}
|
|
827
869
|
})();
|
|
828
|
-
|
|
870
|
+
state.files.set(path, snapshot);
|
|
829
871
|
try {
|
|
830
872
|
const file = await snapshot;
|
|
831
873
|
return { locator: file.locator, size: file.size };
|
|
832
874
|
} catch (error) {
|
|
833
|
-
|
|
875
|
+
if (state.files.get(path) === snapshot) state.files.delete(path);
|
|
834
876
|
throw error;
|
|
835
877
|
}
|
|
836
878
|
}
|
|
@@ -927,7 +969,7 @@ var NodeExternalDirectoryBackend = class {
|
|
|
927
969
|
}
|
|
928
970
|
throw failure;
|
|
929
971
|
}
|
|
930
|
-
this.getDirectory(state.locator).files.delete(state.path);
|
|
972
|
+
for (const generation of this.getDirectory(state.locator).generations.values()) generation.files.delete(state.path);
|
|
931
973
|
}, true);
|
|
932
974
|
return operation.then(() => {
|
|
933
975
|
this.writers.delete(writer);
|
|
@@ -945,7 +987,7 @@ var NodeExternalDirectoryBackend = class {
|
|
|
945
987
|
}
|
|
946
988
|
async shutdown() {
|
|
947
989
|
await Promise.all([...this.directories.keys()].map((locator) => this.removed(locator)));
|
|
948
|
-
await Promise.all([...this.files.values()].map((file) =>
|
|
990
|
+
await Promise.all([...this.files.values()].map((file) => this.closeFile(file)));
|
|
949
991
|
this.files.clear();
|
|
950
992
|
this.paths.clear();
|
|
951
993
|
this.directories.clear();
|
|
@@ -966,8 +1008,14 @@ var NodeExternalDirectoryBackend = class {
|
|
|
966
1008
|
if (!state || state.terminal) throw new Error("External directory writer is not open");
|
|
967
1009
|
return state;
|
|
968
1010
|
}
|
|
1011
|
+
closeFile(file) {
|
|
1012
|
+
return file.closing ??= file.descriptor.close().catch((error) => {
|
|
1013
|
+
delete file.closing;
|
|
1014
|
+
throw error;
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
969
1017
|
async cleanupWriter(state) {
|
|
970
|
-
this.directories.get(state.locator)?.files.delete(state.path);
|
|
1018
|
+
for (const generation of this.directories.get(state.locator)?.generations.values() ?? []) generation.files.delete(state.path);
|
|
971
1019
|
if (state.descriptor) {
|
|
972
1020
|
await state.descriptor.close();
|
|
973
1021
|
state.descriptor = void 0;
|
|
@@ -1022,7 +1070,15 @@ configureWorker(module, transport, (runtime) => {
|
|
|
1022
1070
|
runtime.FS.mount(nodeFS, { root: storagePath }, "/mnt/fs");
|
|
1023
1071
|
runtime.FS.mkdirTree("/mnt/fs/logs");
|
|
1024
1072
|
const nodeRuntimeFS = runtime.FS;
|
|
1025
|
-
const shouldAccessHostPath = (path) =>
|
|
1073
|
+
const shouldAccessHostPath = (path) => {
|
|
1074
|
+
if (!isAbsolute2(path)) return false;
|
|
1075
|
+
let prefix = "/";
|
|
1076
|
+
for (const component of path.split("/")) {
|
|
1077
|
+
prefix = posix.join(prefix, component);
|
|
1078
|
+
if (prefix === "/mnt/external") return false;
|
|
1079
|
+
}
|
|
1080
|
+
return !nodeRuntimeFS.analyzePath(path).exists;
|
|
1081
|
+
};
|
|
1026
1082
|
runtime.hyperdbWasmShouldOpenHostFile = shouldAccessHostPath;
|
|
1027
1083
|
runtime.hyperdbWasmShouldListHostDirectory = shouldAccessHostPath;
|
|
1028
1084
|
runtime.externalDirectoryBackend = new NodeExternalDirectoryBackend();
|
package/dist/protocol.d.ts
CHANGED
|
@@ -10,9 +10,7 @@ export interface HyperDBErrorData {
|
|
|
10
10
|
}
|
|
11
11
|
export type MountSource = {
|
|
12
12
|
readonly kind: 'browser-file';
|
|
13
|
-
readonly
|
|
14
|
-
readonly url: string;
|
|
15
|
-
readonly size: number;
|
|
13
|
+
readonly file: File;
|
|
16
14
|
} | {
|
|
17
15
|
readonly kind: 'browser-directory';
|
|
18
16
|
readonly handle: unknown;
|
|
@@ -51,6 +49,10 @@ export type EngineRequest = {
|
|
|
51
49
|
id: RequestId;
|
|
52
50
|
type: 'unmount';
|
|
53
51
|
path: string;
|
|
52
|
+
} | {
|
|
53
|
+
id: RequestId;
|
|
54
|
+
type: 'refreshMount';
|
|
55
|
+
path: string;
|
|
54
56
|
} | {
|
|
55
57
|
id: RequestId;
|
|
56
58
|
type: 'pollQuery';
|
package/dist/raw.browser.js
CHANGED
|
@@ -93,6 +93,9 @@ var HyperDBEngineClient = class {
|
|
|
93
93
|
unmount(path) {
|
|
94
94
|
return this.request({ id: this.nextId++, type: "unmount", path });
|
|
95
95
|
}
|
|
96
|
+
refreshMount(path) {
|
|
97
|
+
return this.request({ id: this.nextId++, type: "refreshMount", path });
|
|
98
|
+
}
|
|
96
99
|
pollQuery(query) {
|
|
97
100
|
return this.request({ id: this.nextId++, type: "pollQuery", query });
|
|
98
101
|
}
|
|
@@ -209,6 +212,7 @@ var HyperDBBindings = class {
|
|
|
209
212
|
registerExternalFile;
|
|
210
213
|
registerExternalDirectory;
|
|
211
214
|
removeExternalRegistration;
|
|
215
|
+
refreshExternalDirectory;
|
|
212
216
|
projectExternalFile;
|
|
213
217
|
projectExternalDirectory;
|
|
214
218
|
startQuery;
|
|
@@ -219,6 +223,7 @@ var HyperDBBindings = class {
|
|
|
219
223
|
resultPayloadLength;
|
|
220
224
|
free;
|
|
221
225
|
mountedPaths = /* @__PURE__ */ new Set();
|
|
226
|
+
mountedDirectoryPaths = /* @__PURE__ */ new Set();
|
|
222
227
|
constructor(module) {
|
|
223
228
|
this.module = module;
|
|
224
229
|
this.initialize = module.cwrap("hyperdb_wasm_initialize", "pointer", ["string", "string", "number"]);
|
|
@@ -228,6 +233,7 @@ var HyperDBBindings = class {
|
|
|
228
233
|
this.registerExternalFile = module.cwrap("hyperdb_wasm_register_external_file", "pointer", ["string", "string", "number", "number"]);
|
|
229
234
|
this.registerExternalDirectory = module.cwrap("hyperdb_wasm_register_external_directory", "pointer", ["string", "string", "number", "number"]);
|
|
230
235
|
this.removeExternalRegistration = module.cwrap("hyperdb_wasm_remove_external_registration", "pointer", ["string", "number"]);
|
|
236
|
+
this.refreshExternalDirectory = module.cwrap("hyperdb_wasm_refresh_external_directory", "pointer", ["string", "number"]);
|
|
231
237
|
this.projectExternalFile = module.cwrap("hyperdb_wasm_project_external_file", "pointer", ["string", "string", "number", "number"]);
|
|
232
238
|
this.projectExternalDirectory = module.cwrap("hyperdb_wasm_project_external_directory", "pointer", ["string", "string", "number"]);
|
|
233
239
|
this.startQuery = module.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string", "number", "number"]);
|
|
@@ -247,20 +253,32 @@ var HyperDBBindings = class {
|
|
|
247
253
|
if (registration.kind === "directory") return this.mountDirectory(request, request.source.path);
|
|
248
254
|
return this.mountFile(request, registration.locator, registration.size, this.module.externalFileBackend);
|
|
249
255
|
}
|
|
250
|
-
if (request.type === "mount" && request.source.kind === "browser-file" && !this.module.externalFileBackendAvailable) {
|
|
251
|
-
return { response: { id: request.id, type: "response", result: { state: "error", error: "Blob external files are supported only by the browser client" } }, transfer: [] };
|
|
252
|
-
}
|
|
253
256
|
if (request.type === "mount" && request.source.kind === "browser-directory") {
|
|
254
257
|
return this.mountDirectory(request, request.source.handle);
|
|
255
258
|
}
|
|
256
259
|
if (request.type === "mount" && request.source.kind === "browser-file") {
|
|
257
|
-
|
|
260
|
+
validateCString(request.path, "external registration path");
|
|
261
|
+
const backend = this.module.externalDirectoryBackend;
|
|
262
|
+
if (!backend?.registerFile) {
|
|
263
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error: "Browser external files are supported only by the browser client" } }, transfer: [] };
|
|
264
|
+
}
|
|
265
|
+
const file = backend.registerFile(request.source.file);
|
|
266
|
+
return this.mountFile(request, file.locator, file.size, backend);
|
|
267
|
+
}
|
|
268
|
+
if (request.type === "refreshMount") {
|
|
269
|
+
validateCString(request.path, "external registration path");
|
|
270
|
+
if (!this.module.externalDirectoryBackend) return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
|
|
271
|
+
if (!this.mountedDirectoryPaths.has(request.path)) {
|
|
272
|
+
const error = this.mountedPaths.has(request.path) ? "Only directory mounts can be refreshed" : "External directory registration was not found";
|
|
273
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error } }, transfer: [] };
|
|
274
|
+
}
|
|
258
275
|
}
|
|
259
276
|
if (request.type === "unmount" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
|
|
260
277
|
validateCString(request.path, "external registration path");
|
|
261
278
|
const pointer2 = safeInteger(this.removeExternalRegistration(request.path, request.id), "result pointer");
|
|
262
279
|
const result2 = this.decodeResult(pointer2);
|
|
263
280
|
if (result2.state === "ok" || result2.state === "error" && result2.error === "External registration was not found" && this.mountedPaths.has(request.path)) {
|
|
281
|
+
this.mountedDirectoryPaths.delete(request.path);
|
|
264
282
|
const cleanup = async () => {
|
|
265
283
|
await Promise.all([
|
|
266
284
|
this.module.externalFileBackend?.removed(request.path),
|
|
@@ -272,12 +290,14 @@ var HyperDBBindings = class {
|
|
|
272
290
|
}
|
|
273
291
|
return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
|
|
274
292
|
}
|
|
275
|
-
if (request.type === "unmount"
|
|
293
|
+
if (request.type === "unmount") {
|
|
276
294
|
return { response: { id: request.id, type: "response", result: { state: "error", error: "External files are not supported by this client" } }, transfer: [] };
|
|
277
295
|
}
|
|
278
296
|
const pointer = safeInteger(this.invoke(request), "result pointer");
|
|
279
297
|
const result = this.decodeResult(pointer);
|
|
280
298
|
if (request.type === "shutdown" && result.state === "ok") {
|
|
299
|
+
this.mountedPaths.clear();
|
|
300
|
+
this.mountedDirectoryPaths.clear();
|
|
281
301
|
const completion = completionFor([
|
|
282
302
|
this.module.externalFileBackend?.shutdown(),
|
|
283
303
|
this.module.externalDirectoryBackend?.shutdown()
|
|
@@ -312,6 +332,8 @@ var HyperDBBindings = class {
|
|
|
312
332
|
case "unmount":
|
|
313
333
|
validateCString(request.path, "external registration path");
|
|
314
334
|
return this.removeExternalRegistration(request.path, request.id);
|
|
335
|
+
case "refreshMount":
|
|
336
|
+
return this.refreshExternalDirectory(request.path, request.id);
|
|
315
337
|
case "startQuery":
|
|
316
338
|
validateCStringContents(request.sql, "SQL");
|
|
317
339
|
return this.startQuery(request.connection, request.sql, Number(request.sqlTestMode ?? false), request.id);
|
|
@@ -332,13 +354,13 @@ var HyperDBBindings = class {
|
|
|
332
354
|
try {
|
|
333
355
|
const result = this.decodeResult(safeInteger(this.registerExternalFile(request.path, locator, size, request.id), "result pointer"));
|
|
334
356
|
if (result.state !== "ok") {
|
|
335
|
-
backend
|
|
336
|
-
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
357
|
+
const completion = completionFor([backend.removed(locator)]);
|
|
358
|
+
return { response: { id: request.id, type: "response", result }, transfer: [], ...completion && { completion } };
|
|
337
359
|
}
|
|
338
360
|
path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
|
|
339
361
|
const projection = this.decodeResult(safeInteger(this.projectExternalFile(path, locator, size, request.id), "result pointer"));
|
|
340
362
|
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
341
|
-
backend
|
|
363
|
+
backend.registered(path, locator);
|
|
342
364
|
registered = true;
|
|
343
365
|
this.mountedPaths.add(path);
|
|
344
366
|
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
@@ -353,12 +375,15 @@ var HyperDBBindings = class {
|
|
|
353
375
|
}
|
|
354
376
|
if (rollbackError !== void 0) {
|
|
355
377
|
if (path !== void 0) {
|
|
356
|
-
backend
|
|
378
|
+
backend.registered(path, locator);
|
|
357
379
|
this.mountedPaths.add(path);
|
|
358
380
|
}
|
|
359
381
|
throw new Error(`mount failed and rollback also failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, { cause: error });
|
|
360
382
|
}
|
|
361
|
-
backend
|
|
383
|
+
const completion = completionFor([backend.removed(registered && path !== void 0 ? path : locator)]);
|
|
384
|
+
if (completion) {
|
|
385
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error: error instanceof Error ? error.message : String(error) } }, transfer: [], completion };
|
|
386
|
+
}
|
|
362
387
|
throw error;
|
|
363
388
|
}
|
|
364
389
|
}
|
|
@@ -382,12 +407,14 @@ var HyperDBBindings = class {
|
|
|
382
407
|
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
383
408
|
backend.registered(path, registration.locator);
|
|
384
409
|
this.mountedPaths.add(path);
|
|
410
|
+
this.mountedDirectoryPaths.add(path);
|
|
385
411
|
} catch (error) {
|
|
386
412
|
const cleanupPointer = safeInteger(this.removeExternalRegistration(path, request.id), "result pointer");
|
|
387
413
|
const cleanup = this.decodeResult(cleanupPointer);
|
|
388
414
|
if (cleanup.state !== "ok") {
|
|
389
415
|
backend.registered(path, registration.locator);
|
|
390
416
|
this.mountedPaths.add(path);
|
|
417
|
+
this.mountedDirectoryPaths.add(path);
|
|
391
418
|
preserveBackend = true;
|
|
392
419
|
throw new Error(`mount failed and rollback also failed: ${resultError(cleanup)}`, { cause: error });
|
|
393
420
|
}
|