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.
@@ -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
  }
@@ -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
- return this.mountFile(request, request.source.url, safeInteger(request.source.size, "external file size"));
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" && !this.module.externalFileBackendAvailable) {
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?.removed(locator);
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?.registered(path, locator);
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?.registered(path, locator);
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?.removed(registered && path !== void 0 ? path : locator);
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", "name", "url", "size"]) && typeof value.source.name === "string" && typeof value.source.url === "string" && typeof value.source.size === "number" && Number.isSafeInteger(value.source.size) && value.source.size >= 0;
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) ?? (locator.startsWith("blob:") ? fetch(locator, { headers: { Range: `bytes=${offset}-${offset + length - 1}` } }).then(async (response) => {
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, files: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set(), temporaryPaths: /* @__PURE__ */ new Set() });
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(file.descriptor.close().then(() => {
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
- const cached = directory.files.get(path);
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
- directory.files.set(path, snapshot);
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
- directory.files.delete(path);
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) => file.descriptor.close()));
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) => isAbsolute2(path) && !nodeRuntimeFS.analyzePath(path).exists;
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();
@@ -10,9 +10,7 @@ export interface HyperDBErrorData {
10
10
  }
11
11
  export type MountSource = {
12
12
  readonly kind: 'browser-file';
13
- readonly name: string;
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';
@@ -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
- return this.mountFile(request, request.source.url, safeInteger(request.source.size, "external file size"));
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" && !this.module.externalFileBackendAvailable) {
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?.removed(locator);
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?.registered(path, locator);
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?.registered(path, locator);
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?.removed(registered && path !== void 0 ? path : locator);
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
  }