hyperdb-wasm 0.0.27136-dev.wasm.g077c0cd8 → 0.0.27136-dev.wasm.g5f4b267e

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 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;
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,24 @@ var EngineQueryOperation = class {
498
501
  }
499
502
  };
500
503
  var MountedResourceImpl = class {
501
- constructor(path, unmount, cleanup) {
504
+ constructor(path, unmount, refreshMount, cleanup) {
502
505
  this.path = path;
503
506
  this.unmount = unmount;
507
+ this.refreshMount = refreshMount;
504
508
  this.cleanup = cleanup;
505
509
  }
506
510
  path;
507
511
  unmount;
512
+ refreshMount;
508
513
  cleanup;
509
514
  closed = false;
510
515
  closing;
511
516
  cleaned = false;
517
+ async refresh() {
518
+ if (this.closed) throw new Error("mounted resource is closed");
519
+ if (this.closing) throw new Error("mounted resource is closing");
520
+ await this.refreshMount(this.path);
521
+ }
512
522
  async close() {
513
523
  if (this.closed) return;
514
524
  if (!this.closing) {
@@ -577,7 +587,14 @@ var HyperDB = class _HyperDB {
577
587
  expectOk(result, "mount");
578
588
  const mountedPath = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
579
589
  if (mountedPath !== path) throw new Error("mount returned an invalid path");
580
- const resource = new MountedResourceImpl(mountedPath, (mounted) => this.unmount(mounted), cleanup);
590
+ const resource = new MountedResourceImpl(
591
+ mountedPath,
592
+ (mounted) => this.unmount(mounted),
593
+ (path2) => this.runDatabaseOperation(async () => {
594
+ expectOk(await this.client.refreshMount(path2), "refresh mount");
595
+ }),
596
+ cleanup
597
+ );
581
598
  this.mountedResources.add(resource);
582
599
  return resource;
583
600
  });
@@ -13,17 +13,19 @@ export declare class BrowserExternalDirectoryBackend implements ExternalDirector
13
13
  readonly locator: string;
14
14
  };
15
15
  registered(path: string, locator: string): void;
16
+ refresh(path: string): void;
17
+ releaseGeneration(locator: string, generation: string): void;
16
18
  removed(pathOrLocator: string): void;
17
19
  list(locator: string): Promise<readonly string[]>;
18
20
  listEntries(locator: string, path: string): Promise<readonly BrowserDirectoryEntry[]>;
19
- lookup(locator: string, path: string): Promise<{
21
+ lookup(locator: string, path: string, generation: string): Promise<{
20
22
  readonly kind: 'directory';
21
23
  } | {
22
24
  readonly kind: 'file';
23
25
  readonly locator: string;
24
26
  readonly size: number;
25
27
  } | undefined>;
26
- open(locator: string, path: string): Promise<{
28
+ open(locator: string, path: string, generation: string): Promise<{
27
29
  readonly locator: string;
28
30
  readonly size: number;
29
31
  }>;
@@ -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"]);
@@ -108,11 +111,22 @@
108
111
  if (request.type === "mount" && request.source.kind === "browser-file") {
109
112
  return this.mountFile(request, request.source.url, safeInteger(request.source.size, "external file size"));
110
113
  }
114
+ if (request.type === "refreshMount") {
115
+ validateCString(request.path, "external registration path");
116
+ const backend = this.module.externalDirectoryBackend;
117
+ if (!backend) return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
118
+ if (!this.mountedDirectoryPaths.has(request.path)) {
119
+ const error = this.mountedPaths.has(request.path) ? "Only directory mounts can be refreshed" : "External directory registration was not found";
120
+ return { response: { id: request.id, type: "response", result: { state: "error", error } }, transfer: [] };
121
+ }
122
+ backend.refresh(request.path);
123
+ }
111
124
  if (request.type === "unmount" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
112
125
  validateCString(request.path, "external registration path");
113
126
  const pointer2 = safeInteger(this.removeExternalRegistration(request.path, request.id), "result pointer");
114
127
  const result2 = this.decodeResult(pointer2);
115
128
  if (result2.state === "ok" || result2.state === "error" && result2.error === "External registration was not found" && this.mountedPaths.has(request.path)) {
129
+ this.mountedDirectoryPaths.delete(request.path);
116
130
  const cleanup = async () => {
117
131
  await Promise.all([
118
132
  this.module.externalFileBackend?.removed(request.path),
@@ -130,6 +144,8 @@
130
144
  const pointer = safeInteger(this.invoke(request), "result pointer");
131
145
  const result = this.decodeResult(pointer);
132
146
  if (request.type === "shutdown" && result.state === "ok") {
147
+ this.mountedPaths.clear();
148
+ this.mountedDirectoryPaths.clear();
133
149
  const completion = completionFor([
134
150
  this.module.externalFileBackend?.shutdown(),
135
151
  this.module.externalDirectoryBackend?.shutdown()
@@ -141,6 +157,7 @@
141
157
  };
142
158
  }
143
159
  const response = { id: request.id, type: "response", result };
160
+ if (request.type === "unmount" && result.state === "ok") this.mountedPaths.delete(request.path);
144
161
  return { response, transfer: result.state === "chunk" ? [result.payload.buffer] : [] };
145
162
  }
146
163
  invoke(request) {
@@ -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);
@@ -234,12 +253,14 @@
234
253
  if (projection.state !== "ok") throw new Error(resultError(projection));
235
254
  backend.registered(path, registration.locator);
236
255
  this.mountedPaths.add(path);
256
+ this.mountedDirectoryPaths.add(path);
237
257
  } catch (error) {
238
258
  const cleanupPointer = safeInteger(this.removeExternalRegistration(path, request.id), "result pointer");
239
259
  const cleanup = this.decodeResult(cleanupPointer);
240
260
  if (cleanup.state !== "ok") {
241
261
  backend.registered(path, registration.locator);
242
262
  this.mountedPaths.add(path);
263
+ this.mountedDirectoryPaths.add(path);
243
264
  preserveBackend = true;
244
265
  throw new Error(`mount failed and rollback also failed: ${resultError(cleanup)}`, { cause: error });
245
266
  }
@@ -343,6 +364,7 @@
343
364
  }
344
365
  }
345
366
  case "unmount":
367
+ case "refreshMount":
346
368
  return hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string" && !!value.path && !value.path.includes("\0");
347
369
  case "pollQuery":
348
370
  case "cancelQuery":
@@ -440,9 +462,14 @@
440
462
  };
441
463
  module.hyperdbWasmFetchCancel = (id) => fetches.get(id)?.abort();
442
464
  }
443
- function installExternalDirectoryRuntime(module) {
465
+ function installExternalDirectoryRuntime(module, transport) {
444
466
  const backend = module.externalDirectoryBackend;
445
467
  if (!backend) return;
468
+ module.hyperdbWasmExternalDirectoryReleaseGeneration = (locator, generation) => {
469
+ void Promise.resolve().then(() => backend.releaseGeneration(locator, generation)).catch(() => {
470
+ transport.postMessage({ type: "log", text: "External directory generation cleanup failed; mount removal will retry" });
471
+ });
472
+ };
446
473
  const listComplete = module._hyperdb_wasm_external_directory_list_complete;
447
474
  const openComplete = module._hyperdb_wasm_external_directory_open_complete;
448
475
  if (listComplete) {
@@ -455,8 +482,8 @@
455
482
  };
456
483
  }
457
484
  if (openComplete) {
458
- module.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path) => {
459
- void backend.open(locator, path).then((file) => {
485
+ module.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path, generation) => {
486
+ void backend.open(locator, path, generation).then((file) => {
460
487
  openComplete(id, safePointer(module.stringToNewUTF8(file.locator), "external file locator pointer"), file.size);
461
488
  }).catch((error) => openComplete(id, 0, isNotFoundError(error) ? -2 : -1));
462
489
  };
@@ -545,8 +572,8 @@
545
572
  complete(id, safePointer(module.stringToNewUTF8(JSON.stringify(encoded)), "filesystem listing pointer"), 0);
546
573
  }).catch(() => complete(id, 0, -1));
547
574
  };
548
- module.hyperdbWasmFilesystemLookupStart = (id, locator, path) => {
549
- const lookup = module.externalDirectoryBackend?.lookup?.(locator, path);
575
+ module.hyperdbWasmFilesystemLookupStart = (id, locator, path, generation) => {
576
+ const lookup = module.externalDirectoryBackend?.lookup?.(locator, path, generation);
550
577
  if (!lookup) {
551
578
  complete(id, 0, -1);
552
579
  return;
@@ -582,22 +609,7 @@
582
609
  function configureWorker(module, transport, configureFilesystem = () => void 0) {
583
610
  const queuedRequests = [];
584
611
  let bindings;
585
- let failed = false;
586
- const fail = (error) => {
587
- if (failed) return;
588
- failed = true;
589
- bindings = void 0;
590
- queuedRequests.length = 0;
591
- transport.postMessage({ type: "fatal", error });
592
- module.PThread?.terminateAllThreads();
593
- };
594
- const handleError = (request, error) => {
595
- if (failed) return;
596
- if (error instanceof WebAssembly.RuntimeError) fail(`engine trapped: ${errorText(error)}`);
597
- else transport.postMessage({ id: request.id, type: "response", result: { state: "error", error: errorText(error) } });
598
- };
599
612
  const handle = (request) => {
600
- if (failed) return;
601
613
  if (!bindings) {
602
614
  queuedRequests.push(request);
603
615
  return;
@@ -605,14 +617,15 @@
605
617
  try {
606
618
  const result = bindings.handle(request);
607
619
  const complete = () => {
608
- if (failed) return;
609
620
  if (request.type === "shutdown" && result.response.result.state === "ok") module.PThread.terminateAllThreads();
610
621
  transport.postMessage(result.response, result.transfer);
611
622
  };
612
- if (result.completion) void result.completion.then(complete).catch((error) => handleError(request, error));
623
+ if (result.completion) void result.completion.then(complete, (error) => {
624
+ transport.postMessage({ id: request.id, type: "response", result: { state: "error", error: errorText(error) } });
625
+ });
613
626
  else complete();
614
627
  } catch (error) {
615
- handleError(request, error);
628
+ transport.postMessage({ id: request.id, type: "response", result: { state: "error", error: errorText(error) } });
616
629
  }
617
630
  };
618
631
  transport.onMessage((message) => {
@@ -621,21 +634,20 @@
621
634
  module.print = (text) => transport.postMessage({ type: "log", text });
622
635
  module.printErr = (text) => transport.postMessage({ type: "log", text });
623
636
  module.onRuntimeInitialized = () => {
624
- if (failed) return;
625
637
  try {
626
638
  configureFilesystem(module);
627
639
  installHttpRuntime(module, transport);
628
640
  installHostFileRuntime(module);
629
- installExternalDirectoryRuntime(module);
641
+ installExternalDirectoryRuntime(module, transport);
630
642
  installFilesystemProjectionRuntime(module);
631
643
  bindings = new HyperDBBindings(module);
632
644
  transport.postMessage({ type: "ready" });
633
645
  for (const request of queuedRequests.splice(0)) handle(request);
634
646
  } catch (error) {
635
- fail(`engine initialization failed: ${errorText(error)}`);
647
+ transport.postMessage({ type: "fatal", error: `engine initialization failed: ${errorText(error)}` });
636
648
  }
637
649
  };
638
- module.onAbort = (reason) => fail(`engine aborted: ${String(reason)}`);
650
+ module.onAbort = (reason) => transport.postMessage({ type: "fatal", error: `engine aborted: ${String(reason)}` });
639
651
  }
640
652
 
641
653
  // src/browser_external_directory.ts
@@ -679,13 +691,29 @@
679
691
  const directoryHandle = asDirectoryHandle(handle);
680
692
  validateSegment(directoryHandle.name);
681
693
  const locator = `${directoryLocatorPrefix}${opaqueId()}`;
682
- this.directories.set(locator, { handle: directoryHandle, mode, files: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set() });
694
+ this.directories.set(locator, { handle: directoryHandle, mode, generations: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set() });
683
695
  return { name: directoryHandle.name, locator };
684
696
  }
685
697
  registered(path, locator) {
686
698
  if (!this.directories.has(locator)) throw new Error("External directory registration is unavailable");
687
699
  this.paths.set(path, locator);
688
700
  }
701
+ /// Clears future path lookups, retaining File locators until generation release
702
+ refresh(path) {
703
+ const locator = this.paths.get(path);
704
+ if (locator === void 0) throw new Error("External directory registration does not exist");
705
+ for (const generation of this.getDirectory(locator).generations.values()) generation.files.clear();
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
+ }
689
717
  removed(pathOrLocator) {
690
718
  const locator = this.paths.get(pathOrLocator) ?? pathOrLocator;
691
719
  const directory = this.directories.get(locator);
@@ -724,7 +752,7 @@
724
752
  entries.sort((left, right) => left.name.localeCompare(right.name));
725
753
  return entries;
726
754
  }
727
- async lookup(locator, path) {
755
+ async lookup(locator, path, generation) {
728
756
  const directory = this.getDirectory(locator);
729
757
  const segments = relativeSegments(path);
730
758
  const parent = await this.openDirectory(directory, segments.slice(0, -1));
@@ -734,14 +762,19 @@
734
762
  validateSegment(child.name);
735
763
  if (child.name !== name) continue;
736
764
  if (child.kind === "directory") return { kind: "directory" };
737
- if (child.kind === "file") return { kind: "file", ...await this.open(locator, path) };
765
+ if (child.kind === "file") return { kind: "file", ...await this.open(locator, path, generation) };
738
766
  throw new Error("External directory contains an unsupported entry");
739
767
  }
740
768
  return void 0;
741
769
  }
742
- async open(locator, path) {
770
+ async open(locator, path, generation) {
743
771
  const directory = this.getDirectory(locator);
744
- const cached = directory.files.get(path);
772
+ let state = directory.generations.get(generation);
773
+ if (!state) {
774
+ state = { files: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set() };
775
+ directory.generations.set(generation, state);
776
+ }
777
+ const cached = state.files.get(path);
745
778
  if (cached) {
746
779
  const file = await cached;
747
780
  return { locator: file.locator, size: file.file.size };
@@ -763,14 +796,15 @@
763
796
  const fileLocator = `${fileLocatorPrefix}${opaqueId()}`;
764
797
  this.files.set(fileLocator, file);
765
798
  directory.locators.add(fileLocator);
799
+ state.locators.add(fileLocator);
766
800
  return { locator: fileLocator, file };
767
801
  })();
768
- directory.files.set(path, snapshot);
802
+ state.files.set(path, snapshot);
769
803
  try {
770
804
  const file = await snapshot;
771
805
  return { locator: file.locator, size: file.file.size };
772
806
  } catch (error) {
773
- directory.files.delete(path);
807
+ if (state.files.get(path) === snapshot) state.files.delete(path);
774
808
  throw error;
775
809
  }
776
810
  }
@@ -862,7 +896,7 @@
862
896
  await this.cleanupWriter(state).catch(() => void 0);
863
897
  throw failure;
864
898
  }
865
- this.getDirectory(state.locator).files.delete(state.path);
899
+ for (const generation of this.getDirectory(state.locator).generations.values()) generation.files.delete(state.path);
866
900
  }, true);
867
901
  return operation.finally(() => this.writers.delete(writer));
868
902
  }
@@ -904,7 +938,7 @@
904
938
  return state;
905
939
  }
906
940
  async cleanupWriter(state) {
907
- this.directories.get(state.locator)?.files.delete(state.path);
941
+ for (const generation of this.directories.get(state.locator)?.generations.values() ?? []) generation.files.delete(state.path);
908
942
  try {
909
943
  await state.stream?.abort();
910
944
  } finally {
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>;