hyperdb-wasm 0.0.27106-dev.wasm.ga8c1b33c → 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;
@@ -611,7 +638,7 @@
611
638
  configureFilesystem(module);
612
639
  installHttpRuntime(module, transport);
613
640
  installHostFileRuntime(module);
614
- installExternalDirectoryRuntime(module);
641
+ installExternalDirectoryRuntime(module, transport);
615
642
  installFilesystemProjectionRuntime(module);
616
643
  bindings = new HyperDBBindings(module);
617
644
  transport.postMessage({ type: "ready" });
@@ -664,13 +691,29 @@
664
691
  const directoryHandle = asDirectoryHandle(handle);
665
692
  validateSegment(directoryHandle.name);
666
693
  const locator = `${directoryLocatorPrefix}${opaqueId()}`;
667
- 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() });
668
695
  return { name: directoryHandle.name, locator };
669
696
  }
670
697
  registered(path, locator) {
671
698
  if (!this.directories.has(locator)) throw new Error("External directory registration is unavailable");
672
699
  this.paths.set(path, locator);
673
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
+ }
674
717
  removed(pathOrLocator) {
675
718
  const locator = this.paths.get(pathOrLocator) ?? pathOrLocator;
676
719
  const directory = this.directories.get(locator);
@@ -709,7 +752,7 @@
709
752
  entries.sort((left, right) => left.name.localeCompare(right.name));
710
753
  return entries;
711
754
  }
712
- async lookup(locator, path) {
755
+ async lookup(locator, path, generation) {
713
756
  const directory = this.getDirectory(locator);
714
757
  const segments = relativeSegments(path);
715
758
  const parent = await this.openDirectory(directory, segments.slice(0, -1));
@@ -719,14 +762,19 @@
719
762
  validateSegment(child.name);
720
763
  if (child.name !== name) continue;
721
764
  if (child.kind === "directory") return { kind: "directory" };
722
- 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) };
723
766
  throw new Error("External directory contains an unsupported entry");
724
767
  }
725
768
  return void 0;
726
769
  }
727
- async open(locator, path) {
770
+ async open(locator, path, generation) {
728
771
  const directory = this.getDirectory(locator);
729
- 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);
730
778
  if (cached) {
731
779
  const file = await cached;
732
780
  return { locator: file.locator, size: file.file.size };
@@ -748,14 +796,15 @@
748
796
  const fileLocator = `${fileLocatorPrefix}${opaqueId()}`;
749
797
  this.files.set(fileLocator, file);
750
798
  directory.locators.add(fileLocator);
799
+ state.locators.add(fileLocator);
751
800
  return { locator: fileLocator, file };
752
801
  })();
753
- directory.files.set(path, snapshot);
802
+ state.files.set(path, snapshot);
754
803
  try {
755
804
  const file = await snapshot;
756
805
  return { locator: file.locator, size: file.file.size };
757
806
  } catch (error) {
758
- directory.files.delete(path);
807
+ if (state.files.get(path) === snapshot) state.files.delete(path);
759
808
  throw error;
760
809
  }
761
810
  }
@@ -847,7 +896,7 @@
847
896
  await this.cleanupWriter(state).catch(() => void 0);
848
897
  throw failure;
849
898
  }
850
- this.getDirectory(state.locator).files.delete(state.path);
899
+ for (const generation of this.getDirectory(state.locator).generations.values()) generation.files.delete(state.path);
851
900
  }, true);
852
901
  return operation.finally(() => this.writers.delete(writer));
853
902
  }
@@ -889,7 +938,7 @@
889
938
  return state;
890
939
  }
891
940
  async cleanupWriter(state) {
892
- 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);
893
942
  try {
894
943
  await state.stream?.abort();
895
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>;