hyperdb-wasm 0.0.26792-dev.wasm.g72cc44c1 → 0.0.26862-dev.wasm.g42f1012b

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.
@@ -1,4 +1,12 @@
1
1
  export type RequestId = number;
2
+ export type HyperDBErrorSource = 'User' | 'System' | 'OverloadRejection';
3
+ export interface HyperDBErrorData {
4
+ readonly sqlState: string;
5
+ readonly errorSource: HyperDBErrorSource;
6
+ readonly message: string;
7
+ readonly detail: string;
8
+ readonly hint: string;
9
+ }
2
10
  export type EngineRequest = {
3
11
  id: RequestId;
4
12
  type: 'initialize';
@@ -49,6 +57,12 @@ export type EngineRequest = {
49
57
  type: 'startQuery';
50
58
  connection: number;
51
59
  sql: string;
60
+ sqlTestMode?: boolean;
61
+ } | {
62
+ id: RequestId;
63
+ type: 'startQueryWithInfo';
64
+ connection: number;
65
+ sql: string;
52
66
  } | {
53
67
  id: RequestId;
54
68
  type: 'insertArrowIPCFromPath';
@@ -66,6 +80,30 @@ export type EngineRequest = {
66
80
  id: RequestId;
67
81
  type: 'removeFile';
68
82
  path: string;
83
+ } | {
84
+ id: RequestId;
85
+ type: 'registerExternalFile';
86
+ name: string;
87
+ url: string;
88
+ size: number;
89
+ } | {
90
+ id: RequestId;
91
+ type: 'registerExternalFile';
92
+ path: string;
93
+ } | {
94
+ id: RequestId;
95
+ type: 'registerExternalDirectory';
96
+ handle: unknown;
97
+ mode: 'read' | 'readwrite';
98
+ } | {
99
+ id: RequestId;
100
+ type: 'registerExternalDirectory';
101
+ path: string;
102
+ mode: 'read' | 'readwrite';
103
+ } | {
104
+ id: RequestId;
105
+ type: 'removeExternalRegistration';
106
+ path: string;
69
107
  } | {
70
108
  id: RequestId;
71
109
  type: 'pollQuery';
@@ -78,6 +116,10 @@ export type EngineRequest = {
78
116
  id: RequestId;
79
117
  type: 'releaseQuery';
80
118
  query: number;
119
+ } | {
120
+ id: RequestId;
121
+ type: 'releaseQueryWithInfo';
122
+ query: number;
81
123
  };
82
124
  export type WasmResult = {
83
125
  state: 'ok';
@@ -91,7 +133,7 @@ export type WasmResult = {
91
133
  state: 'done';
92
134
  } | {
93
135
  state: 'error';
94
- error: string;
136
+ error: string | HyperDBErrorData;
95
137
  } | {
96
138
  state: 'busy';
97
139
  error: string;
@@ -124,5 +166,6 @@ export interface WorkerTransport {
124
166
  terminate(): void | Promise<unknown>;
125
167
  }
126
168
  export declare function isWasmResult(value: unknown): value is WasmResult;
169
+ export declare function isHyperDBErrorData(value: unknown): value is HyperDBErrorData;
127
170
  export declare function isEngineRequest(value: unknown): value is EngineRequest;
128
171
  export declare function isWorkerEvent(message: unknown): message is WorkerEvent;
@@ -17,12 +17,16 @@ function isWasmResult(value) {
17
17
  case "done":
18
18
  return hasKeys(value, ["state"]);
19
19
  case "error":
20
+ return hasKeys(value, ["state", "error"]) && (typeof value.error === "string" || isHyperDBErrorData(value.error));
20
21
  case "busy":
21
22
  return hasKeys(value, ["state", "error"]) && typeof value.error === "string";
22
23
  default:
23
24
  return false;
24
25
  }
25
26
  }
27
+ function isHyperDBErrorData(value) {
28
+ return isRecord(value) && hasKeys(value, ["sqlState", "errorSource", "message", "detail", "hint"]) && typeof value.sqlState === "string" && /^[0-9A-Z]{5}$/.test(value.sqlState) && (value.errorSource === "User" || value.errorSource === "System" || value.errorSource === "OverloadRejection") && typeof value.message === "string" && typeof value.detail === "string" && typeof value.hint === "string";
29
+ }
26
30
  function isWorkerEvent(message) {
27
31
  if (!isRecord(message) || typeof message.type !== "string") return false;
28
32
  switch (message.type) {
@@ -101,8 +105,11 @@ var HyperDBEngineClient = class {
101
105
  shutdown() {
102
106
  return this.request({ id: this.nextId++, type: "shutdown" });
103
107
  }
104
- startQuery(connection, sql) {
105
- return this.request({ id: this.nextId++, type: "startQuery", connection, sql });
108
+ startQuery(connection, sql, sqlTestMode = false) {
109
+ return this.request({ id: this.nextId++, type: "startQuery", connection, sql, ...sqlTestMode && { sqlTestMode } });
110
+ }
111
+ startQueryWithInfo(connection, sql) {
112
+ return this.request({ id: this.nextId++, type: "startQueryWithInfo", connection, sql });
106
113
  }
107
114
  insertArrowIPCFromPath(connection, path, name, schema, create, internal = false) {
108
115
  return this.request({ id: this.nextId++, type: "insertArrowIPCFromPath", connection, path, name, schema, create, internal });
@@ -114,6 +121,15 @@ var HyperDBEngineClient = class {
114
121
  removeFile(path) {
115
122
  return this.request({ id: this.nextId++, type: "removeFile", path });
116
123
  }
124
+ registerExternalFile(pathOrName, url, size) {
125
+ return url === void 0 || size === void 0 ? this.request({ id: this.nextId++, type: "registerExternalFile", path: pathOrName }) : this.request({ id: this.nextId++, type: "registerExternalFile", name: pathOrName, url, size });
126
+ }
127
+ registerExternalDirectory(source, mode) {
128
+ return typeof source === "string" ? this.request({ id: this.nextId++, type: "registerExternalDirectory", path: source, mode }) : this.request({ id: this.nextId++, type: "registerExternalDirectory", handle: source, mode });
129
+ }
130
+ removeExternalRegistration(path) {
131
+ return this.request({ id: this.nextId++, type: "removeExternalRegistration", path });
132
+ }
117
133
  pollQuery(query) {
118
134
  return this.request({ id: this.nextId++, type: "pollQuery", query });
119
135
  }
@@ -123,6 +139,9 @@ var HyperDBEngineClient = class {
123
139
  releaseQuery(query) {
124
140
  return this.request({ id: this.nextId++, type: "releaseQuery", query });
125
141
  }
142
+ releaseQueryWithInfo(query) {
143
+ return this.request({ id: this.nextId++, type: "releaseQueryWithInfo", query });
144
+ }
126
145
  async terminate() {
127
146
  await this.transport.terminate();
128
147
  this.fail(new Error("engine worker terminated"));
@@ -170,7 +189,12 @@ var HyperDBEngineClient = class {
170
189
  };
171
190
 
172
191
  // src/wasm_bindings.ts
173
- var resultStates = ["ok", "pending", "chunk", "done", "error", "busy"];
192
+ function completionFor(cleanups) {
193
+ const pending = cleanups.filter((cleanup) => cleanup !== void 0);
194
+ return pending.length === 0 ? void 0 : Promise.all(pending).then(() => void 0);
195
+ }
196
+ var resultStates = ["ok", "pending", "chunk", "done", "error", "busy", "structuredError"];
197
+ var structuredErrorMagic = new Uint8Array([72, 68, 66, 69]);
174
198
  function safeInteger(value, name) {
175
199
  const number = typeof value === "bigint" && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
176
200
  if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 0) throw new Error(`${name} must be a nonnegative safe integer`);
@@ -180,6 +204,33 @@ function validateCString(value, name) {
180
204
  if (!value) throw new Error(`${name} must not be empty`);
181
205
  if (value.includes("\0")) throw new Error(`${name} must not contain NUL`);
182
206
  }
207
+ function decodeStructuredError(payload) {
208
+ if (!structuredErrorMagic.every((byte, index) => payload[index] === byte)) throw new Error("structured error has invalid magic");
209
+ const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
210
+ if (payload.byteLength < 8 || view.getUint32(4, true) !== 1) throw new Error("unsupported structured error version");
211
+ let offset = 8;
212
+ const decoder = new TextDecoder("utf-8", { fatal: true });
213
+ const readField = () => {
214
+ if (offset + 4 > payload.byteLength) throw new Error("truncated structured error field");
215
+ const length = view.getUint32(offset, true);
216
+ offset += 4;
217
+ if (length > payload.byteLength - offset) throw new Error("truncated structured error field");
218
+ const value = decoder.decode(payload.subarray(offset, offset + length));
219
+ offset += length;
220
+ return value;
221
+ };
222
+ const sqlState = readField();
223
+ const errorSource = readField();
224
+ const message = readField();
225
+ const detail = readField();
226
+ const hint = readField();
227
+ if (offset !== payload.byteLength) throw new Error("structured error has trailing bytes");
228
+ if (!/^[0-9A-Z]{5}$/.test(sqlState)) throw new Error("structured error has invalid SQLSTATE");
229
+ if (errorSource !== "User" && errorSource !== "System" && errorSource !== "OverloadRejection") {
230
+ throw new Error("structured error has invalid source");
231
+ }
232
+ return { sqlState, errorSource, message, detail, hint };
233
+ }
183
234
  var HyperDBBindings = class {
184
235
  module;
185
236
  initialize;
@@ -191,13 +242,18 @@ var HyperDBBindings = class {
191
242
  checkpointDatabase;
192
243
  dropDatabase;
193
244
  shutdown;
245
+ registerExternalFile;
246
+ registerExternalDirectory;
247
+ removeExternalRegistration;
194
248
  attachDatabase;
195
249
  detachDatabase;
196
250
  startQuery;
251
+ startQueryWithInfo;
197
252
  insertArrowIPCFromPath;
198
253
  pollQuery;
199
254
  cancelQuery;
200
255
  releaseQuery;
256
+ releaseQueryWithInfo;
201
257
  resultState;
202
258
  resultPayloadLength;
203
259
  free;
@@ -213,13 +269,18 @@ var HyperDBBindings = class {
213
269
  this.checkpointDatabase = module.cwrap("hyperdb_wasm_checkpoint_database", "pointer", ["string"]);
214
270
  this.dropDatabase = module.cwrap("hyperdb_wasm_drop_database", "pointer", ["string"]);
215
271
  this.shutdown = module.cwrap("hyperdb_wasm_shutdown", "pointer", []);
272
+ this.registerExternalFile = module.cwrap("hyperdb_wasm_register_external_file", "pointer", ["string", "string", "number"]);
273
+ this.registerExternalDirectory = module.cwrap("hyperdb_wasm_register_external_directory", "pointer", ["string", "string", "number"]);
274
+ this.removeExternalRegistration = module.cwrap("hyperdb_wasm_remove_external_registration", "pointer", ["string"]);
216
275
  this.attachDatabase = module.cwrap("hyperdb_wasm_attach_database", "pointer", ["number", "string", "string"]);
217
276
  this.detachDatabase = module.cwrap("hyperdb_wasm_detach_database", "pointer", ["number", "string"]);
218
- this.startQuery = module.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string"]);
277
+ this.startQuery = module.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string", "number"]);
278
+ this.startQueryWithInfo = module.cwrap("hyperdb_wasm_query_start_with_info", "pointer", ["number", "string"]);
219
279
  this.insertArrowIPCFromPath = module.cwrap("hyperdb_wasm_arrow_insert_from_path", "pointer", ["number", "string", "string", "string", "number"]);
220
280
  this.pollQuery = module.cwrap("hyperdb_wasm_query_poll", "pointer", ["number"]);
221
281
  this.cancelQuery = module.cwrap("hyperdb_wasm_query_cancel", "pointer", ["number"]);
222
282
  this.releaseQuery = module.cwrap("hyperdb_wasm_query_release", "pointer", ["number"]);
283
+ this.releaseQueryWithInfo = module.cwrap("hyperdb_wasm_query_release_with_info", "pointer", ["number"]);
223
284
  this.resultState = module.cwrap("hyperdb_wasm_result_state", "number", ["number"]);
224
285
  this.resultPayloadLength = module.cwrap("hyperdb_wasm_result_payload_len", "number", ["number"]);
225
286
  this.free = module.cwrap("hyperdb_wasm_free", null, ["pointer"]);
@@ -235,8 +296,92 @@ var HyperDBBindings = class {
235
296
  this.module.FS.unlink(request.path);
236
297
  return { response: { id: request.id, type: "response", result: { state: "ok", payload: new Uint8Array() } }, transfer: [] };
237
298
  }
299
+ if (request.type === "registerExternalFile" && "path" in request) {
300
+ if (!this.module.externalFileBackend) {
301
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "Host external files are supported only by the Node client" } }, transfer: [] };
302
+ }
303
+ const registration = this.module.externalFileBackend.register(request.path);
304
+ try {
305
+ const pointer2 = safeInteger(this.registerExternalFile(registration.name, registration.locator, registration.size), "result pointer");
306
+ const result2 = this.decodeResult(pointer2);
307
+ if (result2.state === "ok") {
308
+ const path = new TextDecoder("utf-8", { fatal: true }).decode(result2.payload);
309
+ try {
310
+ this.module.externalFileBackend.registered(path, registration.locator);
311
+ } catch (error) {
312
+ const cleanupPointer = safeInteger(this.removeExternalRegistration(path), "result pointer");
313
+ this.decodeResult(cleanupPointer);
314
+ throw error;
315
+ }
316
+ } else {
317
+ this.module.externalFileBackend.removed(registration.locator);
318
+ }
319
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
320
+ } catch (error) {
321
+ this.module.externalFileBackend.removed(registration.locator);
322
+ throw error;
323
+ }
324
+ }
325
+ if (request.type === "registerExternalFile" && !this.module.externalFileBackendAvailable) {
326
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "Blob external files are supported only by the browser client" } }, transfer: [] };
327
+ }
328
+ if (request.type === "registerExternalDirectory") {
329
+ const backend = this.module.externalDirectoryBackend;
330
+ if (!backend) {
331
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
332
+ }
333
+ const registration = backend.register("handle" in request ? request.handle : request.path, request.mode);
334
+ try {
335
+ const pointer2 = safeInteger(this.registerExternalDirectory(registration.name, registration.locator, Number(request.mode === "readwrite")), "result pointer");
336
+ const result2 = this.decodeResult(pointer2);
337
+ if (result2.state === "ok") {
338
+ const path = new TextDecoder("utf-8", { fatal: true }).decode(result2.payload);
339
+ try {
340
+ backend.registered(path, registration.locator);
341
+ } catch (error) {
342
+ const cleanupPointer = safeInteger(this.removeExternalRegistration(path), "result pointer");
343
+ this.decodeResult(cleanupPointer);
344
+ throw error;
345
+ }
346
+ } else {
347
+ const completion = completionFor([backend.removed(registration.locator)]);
348
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [], ...completion && { completion } };
349
+ }
350
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
351
+ } catch (error) {
352
+ void Promise.resolve(backend.removed(registration.locator)).catch(() => void 0);
353
+ throw error;
354
+ }
355
+ }
356
+ if (request.type === "removeExternalRegistration" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
357
+ validateCString(request.path, "external registration path");
358
+ const pointer2 = safeInteger(this.removeExternalRegistration(request.path), "result pointer");
359
+ const result2 = this.decodeResult(pointer2);
360
+ if (result2.state === "ok") {
361
+ const completion = completionFor([
362
+ this.module.externalFileBackend?.removed(request.path),
363
+ this.module.externalDirectoryBackend?.removed(request.path)
364
+ ]);
365
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [], ...completion && { completion } };
366
+ }
367
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
368
+ }
369
+ if (request.type === "removeExternalRegistration" && !this.module.externalFileBackendAvailable) {
370
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "External files are not supported by this client" } }, transfer: [] };
371
+ }
238
372
  const pointer = safeInteger(this.invoke(request), "result pointer");
239
373
  const result = this.decodeResult(pointer);
374
+ if (request.type === "shutdown" && result.state === "ok") {
375
+ const completion = completionFor([
376
+ this.module.externalFileBackend?.shutdown(),
377
+ this.module.externalDirectoryBackend?.shutdown()
378
+ ]);
379
+ return {
380
+ response: { id: request.id, type: "response", result },
381
+ transfer: [],
382
+ ...completion && { completion }
383
+ };
384
+ }
240
385
  const response = { id: request.id, type: "response", result };
241
386
  return { response, transfer: result.state === "chunk" ? [result.payload.buffer] : [] };
242
387
  }
@@ -272,8 +417,20 @@ var HyperDBBindings = class {
272
417
  return this.detachDatabase(request.connection, request.alias);
273
418
  case "shutdown":
274
419
  return this.shutdown();
420
+ case "registerExternalFile":
421
+ if ("path" in request) throw new Error("host external file request has no backend");
422
+ validateCString(request.name, "external file name");
423
+ validateCString(request.url, "external file URL");
424
+ return this.registerExternalFile(request.name, request.url, safeInteger(request.size, "external file size"));
425
+ case "registerExternalDirectory":
426
+ throw new Error("external directory request has no backend");
427
+ case "removeExternalRegistration":
428
+ validateCString(request.path, "external registration path");
429
+ return this.removeExternalRegistration(request.path);
275
430
  case "startQuery":
276
- return this.startQuery(request.connection, request.sql);
431
+ return this.startQuery(request.connection, request.sql, Number(request.sqlTestMode ?? false));
432
+ case "startQueryWithInfo":
433
+ return this.startQueryWithInfo(request.connection, request.sql);
277
434
  case "insertArrowIPCFromPath": {
278
435
  const path = request.internal ? request.path : this.module.resolveHyperDBPath?.(request.path) ?? request.path;
279
436
  return this.insertArrowIPCFromPath(request.connection, path, request.name, request.schema ?? "", Number(request.create));
@@ -284,6 +441,8 @@ var HyperDBBindings = class {
284
441
  return this.cancelQuery(request.query);
285
442
  case "releaseQuery":
286
443
  return this.releaseQuery(request.query);
444
+ case "releaseQueryWithInfo":
445
+ return this.releaseQueryWithInfo(request.query);
287
446
  }
288
447
  }
289
448
  decodeResult(pointer) {
@@ -313,6 +472,8 @@ var HyperDBBindings = class {
313
472
  return { state, error: new TextDecoder().decode(payload) };
314
473
  case "busy":
315
474
  return { state, error: new TextDecoder().decode(payload) };
475
+ case "structuredError":
476
+ return { state: "error", error: decodeStructuredError(payload) };
316
477
  }
317
478
  } finally {
318
479
  this.free(pointer);
@@ -324,6 +485,7 @@ var HyperDBBindings = class {
324
485
  var memory64Probe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 5, 3, 1, 4, 1]);
325
486
  var threadsProbe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 5, 4, 1, 3, 1, 1]);
326
487
  var exceptionsProbe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 13, 3, 1, 0, 0]);
488
+ var 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]);
327
489
  function assertBrowserSupport(environment = globalThis) {
328
490
  if (typeof environment.WebAssembly?.validate !== "function") {
329
491
  throw new Error("HyperDB cannot initialize because this browser does not support WebAssembly");
@@ -331,6 +493,12 @@ function assertBrowserSupport(environment = globalThis) {
331
493
  if (!environment.WebAssembly.validate(memory64Probe)) {
332
494
  throw new Error("HyperDB cannot initialize because this browser does not yet support WebAssembly memory64. Support for platforms such as iOS is coming soon.");
333
495
  }
496
+ try {
497
+ const Memory64 = environment.WebAssembly.Memory;
498
+ new Memory64({ initial: 1n, maximum: 262144n, shared: true, address: "i64" });
499
+ } catch {
500
+ throw new Error("HyperDB cannot initialize because this browser does not support a shared 16 GiB WebAssembly memory64 declaration");
501
+ }
334
502
  if (typeof environment.BigInt !== "function") {
335
503
  throw new Error("HyperDB cannot initialize because this browser does not yet support WebAssembly BigInt integration. Support for platforms such as iOS is coming soon.");
336
504
  }
@@ -340,6 +508,9 @@ function assertBrowserSupport(environment = globalThis) {
340
508
  if (!environment.WebAssembly.validate(exceptionsProbe)) {
341
509
  throw new Error("HyperDB cannot initialize because this browser does not support WebAssembly exception handling");
342
510
  }
511
+ if (!environment.WebAssembly.validate(tailCallsProbe)) {
512
+ throw new Error("HyperDB cannot initialize because this browser does not support WebAssembly tail calls");
513
+ }
343
514
  if (typeof environment.Worker !== "function") {
344
515
  throw new Error("HyperDB cannot initialize because this browser does not support Web Workers");
345
516
  }
package/dist/raw.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { HyperDBEngineClient } from './client.js';
2
2
  export { HyperDBBindings } from './wasm_bindings.js';
3
- export type { EngineRequest, SetupProgress, WasmResponse, WasmResult, WorkerEvent, WorkerTransport } from './protocol.js';
3
+ export type { EngineRequest, HyperDBErrorData, HyperDBErrorSource, SetupProgress, WasmResponse, WasmResult, WorkerEvent, WorkerTransport } from './protocol.js';