hyperdb-wasm 0.0.27361-dev.wasm.g7817abf3 → 0.0.27368-dev.wasm.ge6142229

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
@@ -14,6 +14,8 @@ export interface HyperDBClient {
14
14
  mount(source: MountSource, path: string, mode: MountAccessMode): Promise<WasmResult>;
15
15
  unmount(path: string): Promise<WasmResult>;
16
16
  refreshMount(path: string): Promise<WasmResult>;
17
+ listMount(path: string): Promise<WasmResult>;
18
+ readMountFile(path: string, file: string): Promise<WasmResult>;
17
19
  pollQuery(query: number): Promise<WasmResult>;
18
20
  cancelQuery(query: number): Promise<WasmResult>;
19
21
  finishQuery(query: number): Promise<WasmResult>;
@@ -31,8 +33,14 @@ export interface MountOptions {
31
33
  export interface MountedResource {
32
34
  readonly path: string;
33
35
  refresh(): Promise<void>;
36
+ list(): Promise<readonly MountFileEntry[]>;
37
+ readFile(path: string): Promise<Blob>;
34
38
  close(): Promise<void>;
35
39
  }
40
+ export interface MountFileEntry {
41
+ readonly path: string;
42
+ readonly size: number;
43
+ }
36
44
  export declare function validateMountPath(path: unknown): asserts path is string;
37
45
  export type HyperDBSettingValue = string | number | boolean | null | readonly HyperDBSettingValue[] | {
38
46
  readonly [key: string]: HyperDBSettingValue;
package/dist/browser.d.ts CHANGED
@@ -8,12 +8,18 @@ export interface BrowserWorkerLike {
8
8
  terminate(): void;
9
9
  }
10
10
  export interface BrowserClientOptions {
11
+ /** Blob workers inline both scripts so pthreads can start without an origin */
12
+ readonly worker?: 'url' | 'blob';
13
+ /** Memory storage is private to this engine and discarded on termination */
14
+ readonly storage?: 'persistent' | 'memory';
11
15
  readonly workerUrl?: string | URL;
12
16
  readonly engineUrl?: string | URL;
13
17
  readonly wasmUrl?: string | URL;
14
18
  /** OPFS-relative directory mounted as the durable filesystem root. */
15
19
  readonly storagePath?: string;
16
20
  readonly onSetupProgress?: (progress: SetupProgress) => void;
21
+ /** Receives engine output; omitted callbacks discard output, and callback exceptions are ignored */
22
+ readonly onLog?: (stream: 'stdout' | 'stderr', text: string) => void;
17
23
  }
18
24
  type BrowserEnvironment = {
19
25
  readonly WebAssembly?: typeof WebAssembly;
@@ -28,10 +34,11 @@ type BrowserEnvironment = {
28
34
  };
29
35
  };
30
36
  };
31
- export declare function assertBrowserSupport(environment?: BrowserEnvironment): void;
37
+ export declare function assertBrowserSupport(environment?: BrowserEnvironment, storage?: BrowserClientOptions['storage']): void;
32
38
  export declare class BrowserWorker implements WorkerTransport {
33
39
  private readonly worker;
34
- constructor(worker: BrowserWorkerLike);
40
+ private blobUrl?;
41
+ constructor(worker: BrowserWorkerLike, blobUrl?: string | undefined);
35
42
  postMessage(message: EngineRequest, transfer?: readonly ArrayBuffer[]): void;
36
43
  onMessage(listener: (message: unknown) => void): () => void;
37
44
  onError(listener: (error: Error) => void): () => void;
@@ -39,5 +46,11 @@ export declare class BrowserWorker implements WorkerTransport {
39
46
  terminate(): void;
40
47
  }
41
48
  export declare function resolveBrowserWorkerUrl(workerUrl: string | URL, engineUrl: string | URL, wasmUrl?: string | URL, storagePath?: string, baseUrl?: string | URL): URL;
42
- export declare function createBrowserClient(options?: BrowserClientOptions): HyperDBEngineClient;
49
+ export declare function createBrowserClient(options: BrowserClientOptions & {
50
+ worker: 'blob';
51
+ }): Promise<HyperDBEngineClient>;
52
+ export declare function createBrowserClient(options?: BrowserClientOptions & {
53
+ worker?: 'url';
54
+ }): HyperDBEngineClient;
55
+ export declare function createBrowserClient(options: BrowserClientOptions): HyperDBEngineClient | Promise<HyperDBEngineClient>;
43
56
  export {};
package/dist/browser.js CHANGED
@@ -28,6 +28,10 @@ function isWasmResult(value) {
28
28
  case "ok":
29
29
  case "chunk":
30
30
  return hasKeys(value, ["state", "payload"]) && value.payload instanceof Uint8Array;
31
+ case "mountList":
32
+ return hasKeys(value, ["state", "entries"]) && Array.isArray(value.entries) && value.entries.every((entry) => isRecord(entry) && hasKeys(entry, ["path", "size"]) && typeof entry.path === "string" && !!entry.path && Number.isSafeInteger(entry.size) && entry.size >= 0);
33
+ case "mountFile":
34
+ return hasKeys(value, ["state", "blob"]) && value.blob instanceof Blob;
31
35
  case "pending":
32
36
  case "done":
33
37
  return hasKeys(value, ["state"]);
@@ -52,7 +56,7 @@ function isWorkerEvent(message) {
52
56
  case "fatal":
53
57
  return hasKeys(message, ["type", "error"]) && typeof message.error === "string";
54
58
  case "log":
55
- return hasKeys(message, ["type", "text"]) && typeof message.text === "string";
59
+ return hasKeys(message, ["type", "stream", "text"]) && (message.stream === "stdout" || message.stream === "stderr") && typeof message.text === "string";
56
60
  case "response":
57
61
  return hasKeys(message, ["id", "type", "result"]) && Number.isSafeInteger(message.id) && isWasmResult(message.result);
58
62
  default:
@@ -72,8 +76,9 @@ var init_client = __esm({
72
76
  "use strict";
73
77
  init_protocol();
74
78
  HyperDBEngineClient = class {
75
- constructor(transport, onSetupProgress) {
79
+ constructor(transport, onSetupProgress, onLog) {
76
80
  this.onSetupProgress = onSetupProgress;
81
+ this.onLog = onLog;
77
82
  this.transport = transport;
78
83
  this.readyPromise = new Promise((resolve, reject) => {
79
84
  this.resolveReady = resolve;
@@ -86,6 +91,7 @@ var init_client = __esm({
86
91
  ];
87
92
  }
88
93
  onSetupProgress;
94
+ onLog;
89
95
  transport;
90
96
  pending = /* @__PURE__ */ new Map();
91
97
  readyPromise;
@@ -125,6 +131,12 @@ var init_client = __esm({
125
131
  refreshMount(path) {
126
132
  return this.request({ id: this.nextId++, type: "refreshMount", path });
127
133
  }
134
+ listMount(path) {
135
+ return this.request({ id: this.nextId++, type: "listMount", path });
136
+ }
137
+ readMountFile(path, file) {
138
+ return this.request({ id: this.nextId++, type: "readMountFile", path, file });
139
+ }
128
140
  pollQuery(query) {
129
141
  return this.request({ id: this.nextId++, type: "pollQuery", query });
130
142
  }
@@ -167,6 +179,13 @@ var init_client = __esm({
167
179
  this.onSetupProgress?.({ bytesLoaded: message.bytesLoaded, bytesTotal: message.bytesTotal });
168
180
  return;
169
181
  }
182
+ if (message.type === "log") {
183
+ try {
184
+ this.onLog?.(message.stream, message.text);
185
+ } catch {
186
+ }
187
+ return;
188
+ }
170
189
  if (message.type === "fatal") {
171
190
  this.failAndTerminate(new Error(message.error || "engine aborted"));
172
191
  return;
@@ -201,7 +220,7 @@ __export(browser_exports, {
201
220
  createBrowserClient: () => createBrowserClient,
202
221
  resolveBrowserWorkerUrl: () => resolveBrowserWorkerUrl
203
222
  });
204
- function assertBrowserSupport(environment = globalThis) {
223
+ function assertBrowserSupport(environment = globalThis, storage = "persistent") {
205
224
  if (typeof environment.WebAssembly?.validate !== "function") {
206
225
  throw new Error("HyperDB cannot initialize because this browser does not support WebAssembly");
207
226
  }
@@ -232,7 +251,7 @@ function assertBrowserSupport(environment = globalThis) {
232
251
  if (environment.crossOriginIsolated !== true) {
233
252
  throw new Error("HyperDB cannot initialize because this page is not cross-origin isolated");
234
253
  }
235
- if (typeof environment.navigator?.storage?.getDirectory !== "function") {
254
+ if (storage !== "memory" && typeof environment.navigator?.storage?.getDirectory !== "function") {
236
255
  throw new Error("HyperDB cannot initialize because this browser does not support the origin-private file system");
237
256
  }
238
257
  }
@@ -244,14 +263,51 @@ function resolveBrowserWorkerUrl(workerUrl, engineUrl, wasmUrl, storagePath, bas
244
263
  return resolved;
245
264
  }
246
265
  function createBrowserClient(options = {}) {
247
- assertBrowserSupport();
266
+ if (options.storage === "memory" && options.storagePath !== void 0) throw new Error("storagePath cannot be used with memory storage");
267
+ if (options.storage !== void 0 && options.storage !== "persistent" && options.storage !== "memory") throw new Error("Unsupported storage mode");
268
+ if (options.worker !== void 0 && options.worker !== "url" && options.worker !== "blob") throw new Error("Unsupported worker mode");
269
+ assertBrowserSupport(globalThis, options.storage);
248
270
  const workerUrl = options.workerUrl ?? new URL("./browser_worker.js", import.meta.url);
249
271
  const engineUrl = options.engineUrl ?? new URL("./hyperdb-wasm.js", import.meta.url);
272
+ if (options.worker === "blob") return createBlobClient(options, workerUrl, engineUrl);
273
+ const resolved = resolveBrowserWorkerUrl(workerUrl, engineUrl, options.wasmUrl, options.storagePath);
274
+ if (options.storage === "memory") resolved.searchParams.set("storage", "memory");
250
275
  return new HyperDBEngineClient(
251
- new BrowserWorker(new Worker(resolveBrowserWorkerUrl(workerUrl, engineUrl, options.wasmUrl, options.storagePath), { type: "classic" })),
252
- options.onSetupProgress
276
+ new BrowserWorker(new Worker(resolved, { type: "classic" })),
277
+ options.onSetupProgress,
278
+ options.onLog
253
279
  );
254
280
  }
281
+ async function createBlobClient(options, workerUrl, engineUrl) {
282
+ const baseUrl = globalThis.document?.baseURI ?? globalThis.location?.href ?? import.meta.url;
283
+ const resolvedEngineUrl = new URL(engineUrl, baseUrl);
284
+ const configuration = {
285
+ engineUrl: resolvedEngineUrl.href,
286
+ wasmUrl: options.wasmUrl === void 0 ? new URL("hyperdb-wasm.wasm.br", resolvedEngineUrl).href : new URL(options.wasmUrl, baseUrl).href,
287
+ storage: options.storage ?? "persistent",
288
+ storagePath: options.storagePath ?? "",
289
+ inlineEngine: true
290
+ };
291
+ const fetchScript = async (url) => {
292
+ const response = await fetch(url, { mode: "cors", credentials: "omit" });
293
+ if (!response.ok) throw new Error(`Failed to fetch HyperDB script: ${response.status} ${response.statusText}`);
294
+ return (await response.text()).replace(/^#![^\r\n]*/, "");
295
+ };
296
+ const [workerScript, engineScript] = await Promise.all([fetchScript(new URL(workerUrl, baseUrl)), fetchScript(resolvedEngineUrl)]);
297
+ const blobUrl = URL.createObjectURL(new Blob([
298
+ `self.HYPERDB_WASM_CONFIGURATION = ${JSON.stringify(configuration)};
299
+ `,
300
+ workerScript,
301
+ "\n",
302
+ engineScript
303
+ ], { type: "text/javascript" }));
304
+ try {
305
+ return new HyperDBEngineClient(new BrowserWorker(new Worker(blobUrl, { type: "classic" }), blobUrl), options.onSetupProgress, options.onLog);
306
+ } catch (error) {
307
+ URL.revokeObjectURL(blobUrl);
308
+ throw error;
309
+ }
310
+ }
255
311
  var memory64Probe, threadsProbe, exceptionsProbe, tailCallsProbe, BrowserWorker;
256
312
  var init_browser = __esm({
257
313
  "src/browser.ts"() {
@@ -262,10 +318,12 @@ var init_browser = __esm({
262
318
  exceptionsProbe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 13, 3, 1, 0, 0]);
263
319
  tailCallsProbe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 3, 2, 0, 0, 10, 9, 2, 4, 0, 18, 1, 11, 2, 0, 11]);
264
320
  BrowserWorker = class {
265
- constructor(worker) {
321
+ constructor(worker, blobUrl) {
266
322
  this.worker = worker;
323
+ this.blobUrl = blobUrl;
267
324
  }
268
325
  worker;
326
+ blobUrl;
269
327
  postMessage(message, transfer = []) {
270
328
  this.worker.postMessage(message, [...transfer]);
271
329
  }
@@ -283,7 +341,12 @@ var init_browser = __esm({
283
341
  return () => void 0;
284
342
  }
285
343
  terminate() {
286
- this.worker.terminate();
344
+ try {
345
+ this.worker.terminate();
346
+ } finally {
347
+ if (this.blobUrl !== void 0) URL.revokeObjectURL(this.blobUrl);
348
+ this.blobUrl = void 0;
349
+ }
287
350
  }
288
351
  };
289
352
  }
@@ -291,6 +354,43 @@ var init_browser = __esm({
291
354
 
292
355
  // src/api.ts
293
356
  import { RecordBatchReader, Table } from "apache-arrow";
357
+
358
+ // src/memory_directory.ts
359
+ function validateMemoryFilePath(path) {
360
+ if (!path || path.startsWith("/") || path.endsWith("/") || path.includes("\\") || path.includes("\0") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
361
+ throw new Error("Memory directory file path must contain safe relative path segments");
362
+ }
363
+ }
364
+ var MemoryDirectory = class {
365
+ kind = "memory-directory";
366
+ files;
367
+ maxBytes;
368
+ constructor(options = {}) {
369
+ if (options.maxBytes !== void 0 && (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0)) {
370
+ throw new Error("Memory directory maxBytes must be a nonnegative safe integer");
371
+ }
372
+ const files = { ...options.files };
373
+ let size = 0;
374
+ for (const [path, blob] of Object.entries(files)) {
375
+ validateMemoryFilePath(path);
376
+ if (!(blob instanceof Blob)) throw new Error("Memory directory files must be Blobs");
377
+ size += blob.size;
378
+ if (!Number.isSafeInteger(size)) throw new Error("Memory directory size must be a safe integer");
379
+ const segments = path.split("/");
380
+ for (let index = 1; index < segments.length; ++index) {
381
+ if (Object.hasOwn(files, segments.slice(0, index).join("/"))) throw new Error("Memory directory file paths overlap");
382
+ }
383
+ }
384
+ if (options.maxBytes !== void 0 && size > options.maxBytes) throw new Error("Memory directory quota exceeded (maxBytes)");
385
+ this.files = Object.freeze(files);
386
+ if (options.maxBytes !== void 0) this.maxBytes = options.maxBytes;
387
+ }
388
+ toMountSource() {
389
+ return { kind: "memory-directory", files: this.files, ...this.maxBytes !== void 0 && { maxBytes: this.maxBytes } };
390
+ }
391
+ };
392
+
393
+ // src/api.ts
294
394
  function validateMountPath(path) {
295
395
  if (typeof path !== "string" || !path.startsWith("/mnt/external/")) {
296
396
  throw new Error("Mount path must be an absolute path below /mnt/external");
@@ -516,21 +616,33 @@ var EngineQueryOperation = class {
516
616
  }
517
617
  };
518
618
  var MountedResourceImpl = class {
519
- constructor(path, unmount, refreshMount) {
619
+ constructor(path, unmount, refreshMount, listMount, readMountFile) {
520
620
  this.path = path;
521
621
  this.unmount = unmount;
522
622
  this.refreshMount = refreshMount;
623
+ this.listMount = listMount;
624
+ this.readMountFile = readMountFile;
523
625
  }
524
626
  path;
525
627
  unmount;
526
628
  refreshMount;
629
+ listMount;
630
+ readMountFile;
527
631
  closed = false;
528
632
  closing;
529
633
  async refresh() {
530
- if (this.closed) throw new Error("mounted resource is closed");
531
- if (this.closing) throw new Error("mounted resource is closing");
634
+ this.ensureOpen();
532
635
  await this.refreshMount(this.path);
533
636
  }
637
+ async list() {
638
+ this.ensureOpen();
639
+ return this.listMount(this.path);
640
+ }
641
+ async readFile(path) {
642
+ this.ensureOpen();
643
+ validateMemoryFilePath(path);
644
+ return this.readMountFile(this.path, path);
645
+ }
534
646
  async close() {
535
647
  if (this.closed) return;
536
648
  if (!this.closing) {
@@ -549,6 +661,10 @@ var MountedResourceImpl = class {
549
661
  releaseAfterShutdown() {
550
662
  this.closed = true;
551
663
  }
664
+ ensureOpen() {
665
+ if (this.closed) throw new Error("mounted resource is closed");
666
+ if (this.closing) throw new Error("mounted resource is closing");
667
+ }
552
668
  };
553
669
  var HyperDB = class _HyperDB {
554
670
  constructor(client) {
@@ -562,7 +678,7 @@ var HyperDB = class _HyperDB {
562
678
  mountedResources = /* @__PURE__ */ new Set();
563
679
  static async create(options = {}) {
564
680
  const { createBrowserClient: createBrowserClient2 } = await Promise.resolve().then(() => (init_browser(), browser_exports));
565
- const client = createBrowserClient2(options);
681
+ const client = await createBrowserClient2(options);
566
682
  const database = new _HyperDB(client);
567
683
  try {
568
684
  await database.ready();
@@ -598,6 +714,16 @@ var HyperDB = class _HyperDB {
598
714
  (mounted) => this.unmount(mounted),
599
715
  (path2) => this.runDatabaseOperation(async () => {
600
716
  expectOk(await this.client.refreshMount(path2), "refresh mount");
717
+ }),
718
+ (path2) => this.runDatabaseOperation(async () => {
719
+ const result2 = await this.client.listMount(path2);
720
+ if (result2.state !== "mountList") throw readErrorFromResult(result2, "list mount");
721
+ return result2.entries;
722
+ }),
723
+ (path2, file) => this.runDatabaseOperation(async () => {
724
+ const result2 = await this.client.readMountFile(path2, file);
725
+ if (result2.state !== "mountFile") throw readErrorFromResult(result2, "read mount file");
726
+ return result2.blob;
601
727
  })
602
728
  );
603
729
  this.mountedResources.add(resource);
@@ -755,12 +881,17 @@ var HyperDBConnection = class {
755
881
  const result = {
756
882
  [Symbol.asyncIterator]() {
757
883
  return {
758
- next() {
759
- if (operation.aborted && !abortDelivered) {
760
- abortDelivered = true;
761
- return Promise.reject(operation.abortReason);
884
+ async next() {
885
+ try {
886
+ if (operation.aborted && !abortDelivered) {
887
+ abortDelivered = true;
888
+ throw operation.abortReason;
889
+ }
890
+ return await operation.interrupt(generator.next());
891
+ } catch (error) {
892
+ await generator.return(void 0);
893
+ throw error;
762
894
  }
763
- return operation.interrupt(generator.next());
764
895
  },
765
896
  return(value) {
766
897
  return generator.return(value);
@@ -804,12 +935,14 @@ var HyperDBConnection = class {
804
935
  return queryPromise(result, stream.info);
805
936
  }
806
937
  query(sql, options = {}) {
807
- const stream = this.queryStream(sql, options);
938
+ const stream = this.queryStreamIPC(sql, options);
808
939
  const result = (async () => {
809
- const reader = await stream;
940
+ const reader = await RecordBatchReader.from(stream);
941
+ await reader.open();
942
+ const schema = reader.schema;
810
943
  const batches = [];
811
944
  for await (const batch of reader) batches.push(batch);
812
- return new Table(reader.schema, batches);
945
+ return schema ? new Table(schema, batches) : new Table();
813
946
  })();
814
947
  return queryPromise(result, stream.info);
815
948
  }
@@ -844,7 +977,7 @@ var HyperDBConnection = class {
844
977
  init_browser();
845
978
  var HyperDB2 = class _HyperDB extends HyperDB {
846
979
  static async create(options = {}) {
847
- const client = createBrowserClient(options);
980
+ const client = await createBrowserClient(options);
848
981
  const database = new _HyperDB(client);
849
982
  try {
850
983
  await database.ready();
@@ -861,6 +994,11 @@ var HyperDB2 = class _HyperDB extends HyperDB {
861
994
  async mount(source, options) {
862
995
  const path = options?.path;
863
996
  validateMountPath(path);
997
+ if (source && "kind" in source && source.kind === "memory-directory") {
998
+ const mode2 = options.mode ?? "read";
999
+ if (mode2 !== "read" && mode2 !== "readwrite") throw new Error(`Unsupported mount mode: ${String(mode2)}`);
1000
+ return this.mountSource(new MemoryDirectory(source).toMountSource(), path, mode2);
1001
+ }
864
1002
  if (typeof File !== "undefined" && source instanceof File) {
865
1003
  if (options.mode !== void 0 && options.mode !== "read") throw new Error("File mounts are always read-only");
866
1004
  if (!source.name) throw new Error("Mounted file name must not be empty");
@@ -890,5 +1028,6 @@ var HyperDB2 = class _HyperDB extends HyperDB {
890
1028
  export {
891
1029
  HyperDB2 as HyperDB,
892
1030
  HyperDBConnection,
893
- HyperDBError
1031
+ HyperDBError,
1032
+ MemoryDirectory
894
1033
  };