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
package/dist/api.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface HyperDBClient {
|
|
|
12
12
|
startQuery(connection: number, sql: string): Promise<WasmResult>;
|
|
13
13
|
mount(source: MountSource, path: string, mode: MountAccessMode): Promise<WasmResult>;
|
|
14
14
|
unmount(path: string): Promise<WasmResult>;
|
|
15
|
+
refreshMount(path: string): Promise<WasmResult>;
|
|
15
16
|
pollQuery(query: number): Promise<WasmResult>;
|
|
16
17
|
cancelQuery(query: number): Promise<WasmResult>;
|
|
17
18
|
finishQuery(query: number): Promise<WasmResult>;
|
|
@@ -28,6 +29,7 @@ export interface MountOptions {
|
|
|
28
29
|
}
|
|
29
30
|
export interface MountedResource {
|
|
30
31
|
readonly path: string;
|
|
32
|
+
refresh(): Promise<void>;
|
|
31
33
|
close(): Promise<void>;
|
|
32
34
|
}
|
|
33
35
|
export declare function validateMountPath(path: unknown): asserts path is string;
|
|
@@ -97,7 +99,7 @@ export declare class HyperDB {
|
|
|
97
99
|
static create(options?: BrowserCreateOptions): Promise<HyperDB>;
|
|
98
100
|
ready(): Promise<void>;
|
|
99
101
|
connect(options?: HyperDBConnectOptions): Promise<HyperDBConnection>;
|
|
100
|
-
protected mountSource(source: MountSource, path: string, mode: MountAccessMode
|
|
102
|
+
protected mountSource(source: MountSource, path: string, mode: MountAccessMode): Promise<MountedResource>;
|
|
101
103
|
terminate(): Promise<void>;
|
|
102
104
|
private ensureRunning;
|
|
103
105
|
protected runDatabaseOperation<Result>(operation: () => Promise<Result>): Promise<Result>;
|
package/dist/browser.js
CHANGED
|
@@ -118,6 +118,9 @@ var init_client = __esm({
|
|
|
118
118
|
unmount(path) {
|
|
119
119
|
return this.request({ id: this.nextId++, type: "unmount", path });
|
|
120
120
|
}
|
|
121
|
+
refreshMount(path) {
|
|
122
|
+
return this.request({ id: this.nextId++, type: "refreshMount", path });
|
|
123
|
+
}
|
|
121
124
|
pollQuery(query) {
|
|
122
125
|
return this.request({ id: this.nextId++, type: "pollQuery", query });
|
|
123
126
|
}
|
|
@@ -498,17 +501,21 @@ var EngineQueryOperation = class {
|
|
|
498
501
|
}
|
|
499
502
|
};
|
|
500
503
|
var MountedResourceImpl = class {
|
|
501
|
-
constructor(path, unmount,
|
|
504
|
+
constructor(path, unmount, refreshMount) {
|
|
502
505
|
this.path = path;
|
|
503
506
|
this.unmount = unmount;
|
|
504
|
-
this.
|
|
507
|
+
this.refreshMount = refreshMount;
|
|
505
508
|
}
|
|
506
509
|
path;
|
|
507
510
|
unmount;
|
|
508
|
-
|
|
511
|
+
refreshMount;
|
|
509
512
|
closed = false;
|
|
510
513
|
closing;
|
|
511
|
-
|
|
514
|
+
async refresh() {
|
|
515
|
+
if (this.closed) throw new Error("mounted resource is closed");
|
|
516
|
+
if (this.closing) throw new Error("mounted resource is closing");
|
|
517
|
+
await this.refreshMount(this.path);
|
|
518
|
+
}
|
|
512
519
|
async close() {
|
|
513
520
|
if (this.closed) return;
|
|
514
521
|
if (!this.closing) {
|
|
@@ -519,19 +526,13 @@ var MountedResourceImpl = class {
|
|
|
519
526
|
this.closing = void 0;
|
|
520
527
|
throw error;
|
|
521
528
|
}
|
|
522
|
-
this.
|
|
529
|
+
this.closed = true;
|
|
523
530
|
})();
|
|
524
531
|
}
|
|
525
532
|
await this.closing;
|
|
526
533
|
}
|
|
527
534
|
releaseAfterShutdown() {
|
|
528
|
-
this.release();
|
|
529
|
-
}
|
|
530
|
-
release() {
|
|
531
535
|
this.closed = true;
|
|
532
|
-
if (this.cleaned) return;
|
|
533
|
-
this.cleaned = true;
|
|
534
|
-
this.cleanup();
|
|
535
536
|
}
|
|
536
537
|
};
|
|
537
538
|
var HyperDB = class _HyperDB {
|
|
@@ -571,13 +572,19 @@ var HyperDB = class _HyperDB {
|
|
|
571
572
|
this.connections.add(result);
|
|
572
573
|
return result;
|
|
573
574
|
}
|
|
574
|
-
mountSource(source, path, mode
|
|
575
|
+
mountSource(source, path, mode) {
|
|
575
576
|
return this.runDatabaseOperation(async () => {
|
|
576
577
|
const result = await this.client.mount(source, path, mode);
|
|
577
578
|
expectOk(result, "mount");
|
|
578
579
|
const mountedPath = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
|
|
579
580
|
if (mountedPath !== path) throw new Error("mount returned an invalid path");
|
|
580
|
-
const resource = new MountedResourceImpl(
|
|
581
|
+
const resource = new MountedResourceImpl(
|
|
582
|
+
mountedPath,
|
|
583
|
+
(mounted) => this.unmount(mounted),
|
|
584
|
+
(path2) => this.runDatabaseOperation(async () => {
|
|
585
|
+
expectOk(await this.client.refreshMount(path2), "refresh mount");
|
|
586
|
+
})
|
|
587
|
+
);
|
|
581
588
|
this.mountedResources.add(resource);
|
|
582
589
|
return resource;
|
|
583
590
|
});
|
|
@@ -845,13 +852,7 @@ var HyperDB2 = class _HyperDB extends HyperDB {
|
|
|
845
852
|
throw new Error("Mounted file name must be a basename without path separators");
|
|
846
853
|
}
|
|
847
854
|
if (!Number.isSafeInteger(source.size) || source.size < 0) throw new Error("Mounted file size must be a nonnegative safe integer");
|
|
848
|
-
|
|
849
|
-
try {
|
|
850
|
-
return await this.mountSource({ kind: "browser-file", name: source.name, url, size: source.size }, path, "read", () => URL.revokeObjectURL(url));
|
|
851
|
-
} catch (error) {
|
|
852
|
-
URL.revokeObjectURL(url);
|
|
853
|
-
throw error;
|
|
854
|
-
}
|
|
855
|
+
return this.mountSource({ kind: "browser-file", file: source }, path, "read");
|
|
855
856
|
}
|
|
856
857
|
const handle = source;
|
|
857
858
|
const mode = options.mode ?? "read";
|
|
@@ -8,22 +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;
|
|
20
|
+
releaseGeneration(locator: string, generation: string): void;
|
|
16
21
|
removed(pathOrLocator: string): void;
|
|
17
22
|
list(locator: string): Promise<readonly string[]>;
|
|
18
23
|
listEntries(locator: string, path: string): Promise<readonly BrowserDirectoryEntry[]>;
|
|
19
|
-
lookup(locator: string, path: string): Promise<{
|
|
24
|
+
lookup(locator: string, path: string, generation: string): Promise<{
|
|
20
25
|
readonly kind: 'directory';
|
|
21
26
|
} | {
|
|
22
27
|
readonly kind: 'file';
|
|
23
28
|
readonly locator: string;
|
|
24
29
|
readonly size: number;
|
|
25
30
|
} | undefined>;
|
|
26
|
-
open(locator: string, path: string): Promise<{
|
|
31
|
+
open(locator: string, path: string, generation: string): Promise<{
|
|
27
32
|
readonly locator: string;
|
|
28
33
|
readonly size: number;
|
|
29
34
|
}>;
|
package/dist/browser_worker.js
CHANGED
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
registerExternalFile;
|
|
62
62
|
registerExternalDirectory;
|
|
63
63
|
removeExternalRegistration;
|
|
64
|
+
refreshExternalDirectory;
|
|
64
65
|
projectExternalFile;
|
|
65
66
|
projectExternalDirectory;
|
|
66
67
|
startQuery;
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
resultPayloadLength;
|
|
72
73
|
free;
|
|
73
74
|
mountedPaths = /* @__PURE__ */ new Set();
|
|
75
|
+
mountedDirectoryPaths = /* @__PURE__ */ new Set();
|
|
74
76
|
constructor(module) {
|
|
75
77
|
this.module = module;
|
|
76
78
|
this.initialize = module.cwrap("hyperdb_wasm_initialize", "pointer", ["string", "string", "number"]);
|
|
@@ -80,6 +82,7 @@
|
|
|
80
82
|
this.registerExternalFile = module.cwrap("hyperdb_wasm_register_external_file", "pointer", ["string", "string", "number", "number"]);
|
|
81
83
|
this.registerExternalDirectory = module.cwrap("hyperdb_wasm_register_external_directory", "pointer", ["string", "string", "number", "number"]);
|
|
82
84
|
this.removeExternalRegistration = module.cwrap("hyperdb_wasm_remove_external_registration", "pointer", ["string", "number"]);
|
|
85
|
+
this.refreshExternalDirectory = module.cwrap("hyperdb_wasm_refresh_external_directory", "pointer", ["string", "number"]);
|
|
83
86
|
this.projectExternalFile = module.cwrap("hyperdb_wasm_project_external_file", "pointer", ["string", "string", "number", "number"]);
|
|
84
87
|
this.projectExternalDirectory = module.cwrap("hyperdb_wasm_project_external_directory", "pointer", ["string", "string", "number"]);
|
|
85
88
|
this.startQuery = module.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string", "number", "number"]);
|
|
@@ -99,20 +102,32 @@
|
|
|
99
102
|
if (registration.kind === "directory") return this.mountDirectory(request, request.source.path);
|
|
100
103
|
return this.mountFile(request, registration.locator, registration.size, this.module.externalFileBackend);
|
|
101
104
|
}
|
|
102
|
-
if (request.type === "mount" && request.source.kind === "browser-file" && !this.module.externalFileBackendAvailable) {
|
|
103
|
-
return { response: { id: request.id, type: "response", result: { state: "error", error: "Blob external files are supported only by the browser client" } }, transfer: [] };
|
|
104
|
-
}
|
|
105
105
|
if (request.type === "mount" && request.source.kind === "browser-directory") {
|
|
106
106
|
return this.mountDirectory(request, request.source.handle);
|
|
107
107
|
}
|
|
108
108
|
if (request.type === "mount" && request.source.kind === "browser-file") {
|
|
109
|
-
|
|
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);
|
|
116
|
+
}
|
|
117
|
+
if (request.type === "refreshMount") {
|
|
118
|
+
validateCString(request.path, "external registration path");
|
|
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: [] };
|
|
120
|
+
if (!this.mountedDirectoryPaths.has(request.path)) {
|
|
121
|
+
const error = this.mountedPaths.has(request.path) ? "Only directory mounts can be refreshed" : "External directory registration was not found";
|
|
122
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error } }, transfer: [] };
|
|
123
|
+
}
|
|
110
124
|
}
|
|
111
125
|
if (request.type === "unmount" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
|
|
112
126
|
validateCString(request.path, "external registration path");
|
|
113
127
|
const pointer2 = safeInteger(this.removeExternalRegistration(request.path, request.id), "result pointer");
|
|
114
128
|
const result2 = this.decodeResult(pointer2);
|
|
115
129
|
if (result2.state === "ok" || result2.state === "error" && result2.error === "External registration was not found" && this.mountedPaths.has(request.path)) {
|
|
130
|
+
this.mountedDirectoryPaths.delete(request.path);
|
|
116
131
|
const cleanup = async () => {
|
|
117
132
|
await Promise.all([
|
|
118
133
|
this.module.externalFileBackend?.removed(request.path),
|
|
@@ -124,12 +139,14 @@
|
|
|
124
139
|
}
|
|
125
140
|
return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
|
|
126
141
|
}
|
|
127
|
-
if (request.type === "unmount"
|
|
142
|
+
if (request.type === "unmount") {
|
|
128
143
|
return { response: { id: request.id, type: "response", result: { state: "error", error: "External files are not supported by this client" } }, transfer: [] };
|
|
129
144
|
}
|
|
130
145
|
const pointer = safeInteger(this.invoke(request), "result pointer");
|
|
131
146
|
const result = this.decodeResult(pointer);
|
|
132
147
|
if (request.type === "shutdown" && result.state === "ok") {
|
|
148
|
+
this.mountedPaths.clear();
|
|
149
|
+
this.mountedDirectoryPaths.clear();
|
|
133
150
|
const completion = completionFor([
|
|
134
151
|
this.module.externalFileBackend?.shutdown(),
|
|
135
152
|
this.module.externalDirectoryBackend?.shutdown()
|
|
@@ -164,6 +181,8 @@
|
|
|
164
181
|
case "unmount":
|
|
165
182
|
validateCString(request.path, "external registration path");
|
|
166
183
|
return this.removeExternalRegistration(request.path, request.id);
|
|
184
|
+
case "refreshMount":
|
|
185
|
+
return this.refreshExternalDirectory(request.path, request.id);
|
|
167
186
|
case "startQuery":
|
|
168
187
|
validateCStringContents(request.sql, "SQL");
|
|
169
188
|
return this.startQuery(request.connection, request.sql, Number(request.sqlTestMode ?? false), request.id);
|
|
@@ -184,13 +203,13 @@
|
|
|
184
203
|
try {
|
|
185
204
|
const result = this.decodeResult(safeInteger(this.registerExternalFile(request.path, locator, size, request.id), "result pointer"));
|
|
186
205
|
if (result.state !== "ok") {
|
|
187
|
-
backend
|
|
188
|
-
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 } };
|
|
189
208
|
}
|
|
190
209
|
path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
|
|
191
210
|
const projection = this.decodeResult(safeInteger(this.projectExternalFile(path, locator, size, request.id), "result pointer"));
|
|
192
211
|
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
193
|
-
backend
|
|
212
|
+
backend.registered(path, locator);
|
|
194
213
|
registered = true;
|
|
195
214
|
this.mountedPaths.add(path);
|
|
196
215
|
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
@@ -205,12 +224,15 @@
|
|
|
205
224
|
}
|
|
206
225
|
if (rollbackError !== void 0) {
|
|
207
226
|
if (path !== void 0) {
|
|
208
|
-
backend
|
|
227
|
+
backend.registered(path, locator);
|
|
209
228
|
this.mountedPaths.add(path);
|
|
210
229
|
}
|
|
211
230
|
throw new Error(`mount failed and rollback also failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, { cause: error });
|
|
212
231
|
}
|
|
213
|
-
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
|
+
}
|
|
214
236
|
throw error;
|
|
215
237
|
}
|
|
216
238
|
}
|
|
@@ -234,12 +256,14 @@
|
|
|
234
256
|
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
235
257
|
backend.registered(path, registration.locator);
|
|
236
258
|
this.mountedPaths.add(path);
|
|
259
|
+
this.mountedDirectoryPaths.add(path);
|
|
237
260
|
} catch (error) {
|
|
238
261
|
const cleanupPointer = safeInteger(this.removeExternalRegistration(path, request.id), "result pointer");
|
|
239
262
|
const cleanup = this.decodeResult(cleanupPointer);
|
|
240
263
|
if (cleanup.state !== "ok") {
|
|
241
264
|
backend.registered(path, registration.locator);
|
|
242
265
|
this.mountedPaths.add(path);
|
|
266
|
+
this.mountedDirectoryPaths.add(path);
|
|
243
267
|
preserveBackend = true;
|
|
244
268
|
throw new Error(`mount failed and rollback also failed: ${resultError(cleanup)}`, { cause: error });
|
|
245
269
|
}
|
|
@@ -333,7 +357,7 @@
|
|
|
333
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;
|
|
334
358
|
switch (value.source.kind) {
|
|
335
359
|
case "browser-file":
|
|
336
|
-
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;
|
|
337
361
|
case "browser-directory":
|
|
338
362
|
return hasKeys(value.source, ["kind", "handle"]) && isRecord(value.source.handle) && value.source.handle.kind === "directory";
|
|
339
363
|
case "node-path":
|
|
@@ -343,6 +367,7 @@
|
|
|
343
367
|
}
|
|
344
368
|
}
|
|
345
369
|
case "unmount":
|
|
370
|
+
case "refreshMount":
|
|
346
371
|
return hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string" && !!value.path && !value.path.includes("\0");
|
|
347
372
|
case "pollQuery":
|
|
348
373
|
case "cancelQuery":
|
|
@@ -440,9 +465,14 @@
|
|
|
440
465
|
};
|
|
441
466
|
module.hyperdbWasmFetchCancel = (id) => fetches.get(id)?.abort();
|
|
442
467
|
}
|
|
443
|
-
function installExternalDirectoryRuntime(module) {
|
|
468
|
+
function installExternalDirectoryRuntime(module, transport) {
|
|
444
469
|
const backend = module.externalDirectoryBackend;
|
|
445
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
|
+
};
|
|
446
476
|
const listComplete = module._hyperdb_wasm_external_directory_list_complete;
|
|
447
477
|
const openComplete = module._hyperdb_wasm_external_directory_open_complete;
|
|
448
478
|
if (listComplete) {
|
|
@@ -455,8 +485,8 @@
|
|
|
455
485
|
};
|
|
456
486
|
}
|
|
457
487
|
if (openComplete) {
|
|
458
|
-
module.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path) => {
|
|
459
|
-
void backend.open(locator, path).then((file) => {
|
|
488
|
+
module.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path, generation) => {
|
|
489
|
+
void backend.open(locator, path, generation).then((file) => {
|
|
460
490
|
openComplete(id, safePointer(module.stringToNewUTF8(file.locator), "external file locator pointer"), file.size);
|
|
461
491
|
}).catch((error) => openComplete(id, 0, isNotFoundError(error) ? -2 : -1));
|
|
462
492
|
};
|
|
@@ -545,8 +575,8 @@
|
|
|
545
575
|
complete(id, safePointer(module.stringToNewUTF8(JSON.stringify(encoded)), "filesystem listing pointer"), 0);
|
|
546
576
|
}).catch(() => complete(id, 0, -1));
|
|
547
577
|
};
|
|
548
|
-
module.hyperdbWasmFilesystemLookupStart = (id, locator, path) => {
|
|
549
|
-
const lookup = module.externalDirectoryBackend?.lookup?.(locator, path);
|
|
578
|
+
module.hyperdbWasmFilesystemLookupStart = (id, locator, path, generation) => {
|
|
579
|
+
const lookup = module.externalDirectoryBackend?.lookup?.(locator, path, generation);
|
|
550
580
|
if (!lookup) {
|
|
551
581
|
complete(id, 0, -1);
|
|
552
582
|
return;
|
|
@@ -558,13 +588,7 @@
|
|
|
558
588
|
}).catch(() => complete(id, 0, -1));
|
|
559
589
|
};
|
|
560
590
|
module.hyperdbWasmFilesystemReadStart = (id, locator, offset, length, expectedSize, pointerValue) => {
|
|
561
|
-
const read = module.externalFileBackend?.read(locator, offset, length, expectedSize) ?? module.externalDirectoryBackend?.read(locator, offset, length, expectedSize)
|
|
562
|
-
const contentRange = /^bytes (\d+)-(\d+)\/(\d+)$/.exec(response.headers.get("content-range") ?? "");
|
|
563
|
-
if (response.status !== 206 || !contentRange || contentRange[1] !== String(offset) || contentRange[2] !== String(offset + length - 1) || contentRange[3] !== String(expectedSize)) {
|
|
564
|
-
throw new Error("Blob URL returned an invalid byte range");
|
|
565
|
-
}
|
|
566
|
-
return new Uint8Array(await response.arrayBuffer());
|
|
567
|
-
}) : void 0);
|
|
591
|
+
const read = module.externalFileBackend?.read(locator, offset, length, expectedSize) ?? module.externalDirectoryBackend?.read(locator, offset, length, expectedSize);
|
|
568
592
|
if (!read) {
|
|
569
593
|
complete(id, 0, -1);
|
|
570
594
|
return;
|
|
@@ -611,7 +635,7 @@
|
|
|
611
635
|
configureFilesystem(module);
|
|
612
636
|
installHttpRuntime(module, transport);
|
|
613
637
|
installHostFileRuntime(module);
|
|
614
|
-
installExternalDirectoryRuntime(module);
|
|
638
|
+
installExternalDirectoryRuntime(module, transport);
|
|
615
639
|
installFilesystemProjectionRuntime(module);
|
|
616
640
|
bindings = new HyperDBBindings(module);
|
|
617
641
|
transport.postMessage({ type: "ready" });
|
|
@@ -660,27 +684,51 @@
|
|
|
660
684
|
paths = /* @__PURE__ */ new Map();
|
|
661
685
|
files = /* @__PURE__ */ new Map();
|
|
662
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
|
+
}
|
|
663
696
|
register(handle, mode) {
|
|
664
697
|
const directoryHandle = asDirectoryHandle(handle);
|
|
665
698
|
validateSegment(directoryHandle.name);
|
|
666
699
|
const locator = `${directoryLocatorPrefix}${opaqueId()}`;
|
|
667
|
-
this.directories.set(locator, { handle: directoryHandle, mode,
|
|
700
|
+
this.directories.set(locator, { handle: directoryHandle, mode, generations: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set() });
|
|
668
701
|
return { name: directoryHandle.name, locator };
|
|
669
702
|
}
|
|
670
703
|
registered(path, locator) {
|
|
671
|
-
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");
|
|
672
705
|
this.paths.set(path, locator);
|
|
673
706
|
}
|
|
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
|
+
}
|
|
716
|
+
}
|
|
674
717
|
removed(pathOrLocator) {
|
|
675
718
|
const locator = this.paths.get(pathOrLocator) ?? pathOrLocator;
|
|
676
719
|
const directory = this.directories.get(locator);
|
|
677
|
-
if (
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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);
|
|
681
731
|
}
|
|
682
|
-
this.directories.delete(locator);
|
|
683
|
-
this.paths.delete(pathOrLocator);
|
|
684
732
|
}
|
|
685
733
|
async list(locator) {
|
|
686
734
|
const directory = this.getDirectory(locator);
|
|
@@ -709,7 +757,7 @@
|
|
|
709
757
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
710
758
|
return entries;
|
|
711
759
|
}
|
|
712
|
-
async lookup(locator, path) {
|
|
760
|
+
async lookup(locator, path, generation) {
|
|
713
761
|
const directory = this.getDirectory(locator);
|
|
714
762
|
const segments = relativeSegments(path);
|
|
715
763
|
const parent = await this.openDirectory(directory, segments.slice(0, -1));
|
|
@@ -719,14 +767,19 @@
|
|
|
719
767
|
validateSegment(child.name);
|
|
720
768
|
if (child.name !== name) continue;
|
|
721
769
|
if (child.kind === "directory") return { kind: "directory" };
|
|
722
|
-
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) };
|
|
723
771
|
throw new Error("External directory contains an unsupported entry");
|
|
724
772
|
}
|
|
725
773
|
return void 0;
|
|
726
774
|
}
|
|
727
|
-
async open(locator, path) {
|
|
775
|
+
async open(locator, path, generation) {
|
|
728
776
|
const directory = this.getDirectory(locator);
|
|
729
|
-
|
|
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);
|
|
730
783
|
if (cached) {
|
|
731
784
|
const file = await cached;
|
|
732
785
|
return { locator: file.locator, size: file.file.size };
|
|
@@ -744,25 +797,24 @@
|
|
|
744
797
|
const fileHandle = await parent.getFileHandle(name);
|
|
745
798
|
if (fileHandle.kind !== "file" || fileHandle.name !== name) throw new Error("External directory returned an unexpected file handle");
|
|
746
799
|
const file = await fileHandle.getFile();
|
|
747
|
-
|
|
748
|
-
const fileLocator = `${fileLocatorPrefix}${opaqueId()}`;
|
|
749
|
-
this.files.set(fileLocator, file);
|
|
800
|
+
const { locator: fileLocator } = this.registerFile(file);
|
|
750
801
|
directory.locators.add(fileLocator);
|
|
802
|
+
state.locators.add(fileLocator);
|
|
751
803
|
return { locator: fileLocator, file };
|
|
752
804
|
})();
|
|
753
|
-
|
|
805
|
+
state.files.set(path, snapshot);
|
|
754
806
|
try {
|
|
755
807
|
const file = await snapshot;
|
|
756
808
|
return { locator: file.locator, size: file.file.size };
|
|
757
809
|
} catch (error) {
|
|
758
|
-
|
|
810
|
+
if (state.files.get(path) === snapshot) state.files.delete(path);
|
|
759
811
|
throw error;
|
|
760
812
|
}
|
|
761
813
|
}
|
|
762
814
|
read(locator, offset, length, expectedSize) {
|
|
763
815
|
if (!locator.startsWith(fileLocatorPrefix)) return void 0;
|
|
764
816
|
const file = this.files.get(locator);
|
|
765
|
-
if (!file) return Promise.reject(new Error("External
|
|
817
|
+
if (!file) return Promise.reject(new Error("External file registration does not exist"));
|
|
766
818
|
const end = offset + length;
|
|
767
819
|
if (expectedSize !== file.size || !Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 0 || !Number.isSafeInteger(end) || end > file.size) {
|
|
768
820
|
return Promise.reject(new Error("External file byte range is invalid"));
|
|
@@ -847,7 +899,7 @@
|
|
|
847
899
|
await this.cleanupWriter(state).catch(() => void 0);
|
|
848
900
|
throw failure;
|
|
849
901
|
}
|
|
850
|
-
this.getDirectory(state.locator).files.delete(state.path);
|
|
902
|
+
for (const generation of this.getDirectory(state.locator).generations.values()) generation.files.delete(state.path);
|
|
851
903
|
}, true);
|
|
852
904
|
return operation.finally(() => this.writers.delete(writer));
|
|
853
905
|
}
|
|
@@ -889,7 +941,7 @@
|
|
|
889
941
|
return state;
|
|
890
942
|
}
|
|
891
943
|
async cleanupWriter(state) {
|
|
892
|
-
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);
|
|
893
945
|
try {
|
|
894
946
|
await state.stream?.abort();
|
|
895
947
|
} finally {
|
|
@@ -968,7 +1020,6 @@
|
|
|
968
1020
|
};
|
|
969
1021
|
self.Module = module;
|
|
970
1022
|
configureWorker(module, transport, (runtime) => {
|
|
971
|
-
runtime.externalFileBackendAvailable = true;
|
|
972
1023
|
runtime.externalDirectoryBackend = new BrowserExternalDirectoryBackend();
|
|
973
1024
|
if (!runtime.OPFS) throw new Error("OPFS backend is unavailable");
|
|
974
1025
|
runtime.FS.mkdirTree("/mnt/fs");
|
package/dist/client.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class HyperDBEngineClient {
|
|
|
19
19
|
startQuery(connection: number, sql: string, sqlTestMode?: boolean): Promise<WasmResult>;
|
|
20
20
|
mount(source: MountSource, path: string, mode: MountAccessMode): Promise<WasmResult>;
|
|
21
21
|
unmount(path: string): Promise<WasmResult>;
|
|
22
|
+
refreshMount(path: string): Promise<WasmResult>;
|
|
22
23
|
pollQuery(query: number): Promise<WasmResult>;
|
|
23
24
|
cancelQuery(query: number): Promise<WasmResult>;
|
|
24
25
|
finishQuery(query: number): Promise<WasmResult>;
|