hyperdb-wasm 0.0.26845-dev.wasm.g5b622080 → 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.
package/dist/api.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { AsyncRecordBatchStreamReader, TypeMap } from 'apache-arrow';
3
3
  import type { HyperDBEngineClient } from './client.js';
4
4
  import type { BrowserClientOptions } from './browser.js';
5
5
  import type { WasmResult } from './protocol.js';
6
+ import type { HyperDBErrorSource } from './protocol.js';
6
7
  export interface HyperDBClient {
7
8
  ready(): Promise<void>;
8
9
  initialize(settings: string): Promise<WasmResult>;
@@ -16,14 +17,18 @@ export interface HyperDBClient {
16
17
  attachDatabase(connection: number, databaseName: string, alias: string): Promise<WasmResult>;
17
18
  detachDatabase(connection: number, alias: string): Promise<WasmResult>;
18
19
  startQuery(connection: number, sql: string): Promise<WasmResult>;
20
+ startQueryWithInfo(connection: number, sql: string): Promise<WasmResult>;
19
21
  insertArrowIPCFromPath(connection: number, path: string, name: string, schema: string | null, create: boolean, internal?: boolean): Promise<WasmResult>;
20
22
  createTemporaryFile(bytes: Uint8Array): Promise<WasmResult>;
21
23
  removeFile(path: string): Promise<WasmResult>;
24
+ registerExternalFile(path: string): Promise<WasmResult>;
22
25
  registerExternalFile(name: string, url: string, size: number): Promise<WasmResult>;
23
- removeExternalFile(path: string): Promise<WasmResult>;
26
+ registerExternalDirectory(handle: unknown, mode: 'read' | 'readwrite'): Promise<WasmResult>;
27
+ removeExternalRegistration(path: string): Promise<WasmResult>;
24
28
  pollQuery(query: number): Promise<WasmResult>;
25
29
  cancelQuery(query: number): Promise<WasmResult>;
26
30
  releaseQuery(query: number): Promise<WasmResult>;
31
+ releaseQueryWithInfo(query: number): Promise<WasmResult>;
27
32
  shutdown(): Promise<WasmResult>;
28
33
  terminate(): Promise<void>;
29
34
  }
@@ -33,13 +38,20 @@ export interface ArrowInsertOptions {
33
38
  readonly create?: boolean;
34
39
  }
35
40
  export interface DatabaseCreateOptions {
36
- readonly storage?: 'memory' | 'persistent';
41
+ readonly storage?: 'transient' | 'persistent';
37
42
  }
38
43
  export interface ExternalFileRegistration {
39
44
  readonly name: string;
40
45
  readonly url: string;
41
46
  readonly size: number;
42
47
  }
48
+ export type ExternalDirectoryMode = 'read' | 'readwrite';
49
+ export interface ExternalDirectoryRegistrationOptions {
50
+ readonly mode?: ExternalDirectoryMode;
51
+ }
52
+ type BrowserExternalFileArguments = readonly [name: string, url: string, size: number];
53
+ type NodeExternalFileArguments = readonly [path: string];
54
+ export type ExternalFileArguments = BrowserExternalFileArguments | NodeExternalFileArguments;
43
55
  export type HyperDBSettingValue = string | number | boolean | null | readonly HyperDBSettingValue[] | {
44
56
  readonly [key: string]: HyperDBSettingValue;
45
57
  };
@@ -50,11 +62,50 @@ export interface HyperDBCreateOptions {
50
62
  export type BrowserCreateOptions = BrowserClientOptions & HyperDBCreateOptions;
51
63
  export interface DatabaseMetadata {
52
64
  readonly name: string;
53
- readonly storage: 'memory' | 'persistent';
65
+ readonly storage: 'transient' | 'persistent';
66
+ }
67
+ export type QueryMetricValue = null | boolean | number | string | readonly QueryMetricValue[] | {
68
+ readonly [key: string]: QueryMetricValue;
69
+ };
70
+ export type QueryMetrics = Readonly<Record<string, QueryMetricValue>>;
71
+ export interface QueryTimings {
72
+ readonly elapsedSeconds: number | null;
73
+ readonly parsingSeconds: number | null;
74
+ readonly compilationSeconds: number | null;
75
+ readonly executionSeconds: number | null;
76
+ readonly commitSeconds: number | null;
77
+ readonly timeToScheduleSeconds: number | null;
78
+ readonly timeToFirstResultSeconds: number | null;
79
+ readonly cancellationSeconds: number | null;
80
+ }
81
+ export interface QueryInfo {
82
+ readonly status: 'succeeded' | 'failed' | 'canceled';
83
+ readonly started: boolean;
84
+ readonly statementKind: string | null;
85
+ readonly producesRows: boolean;
86
+ readonly rowCount: bigint | null;
87
+ readonly columnCount: number | null;
88
+ readonly cancellationRequestedByUser: boolean;
89
+ readonly queryPlan: Readonly<Record<string, unknown>> | null;
90
+ readonly dependencies: readonly string[];
91
+ readonly timings: QueryTimings;
92
+ readonly preExecutionMetrics: QueryMetrics | null;
93
+ readonly executionMetrics: QueryMetrics | null;
94
+ }
95
+ export interface QueryResult<Result> {
96
+ readonly result: Result;
97
+ readonly info: Promise<QueryInfo>;
98
+ }
99
+ export declare class HyperDBError extends Error {
100
+ readonly sqlState: string;
101
+ readonly errorSource: HyperDBErrorSource;
102
+ readonly detail: string;
103
+ readonly hint: string;
104
+ constructor(message: string, sqlState: string, errorSource: HyperDBErrorSource, detail?: string, hint?: string);
54
105
  }
55
106
  declare function initializeHyperDB(client: HyperDBClient, settings: HyperDBSettings | undefined): Promise<void>;
56
- export declare class HyperDB {
57
- private readonly client;
107
+ export declare class HyperDB<ExternalFileArgs extends ExternalFileArguments = BrowserExternalFileArguments> {
108
+ protected readonly client: HyperDBClient;
58
109
  private terminated;
59
110
  private termination;
60
111
  private activeDatabaseOperations;
@@ -66,13 +117,14 @@ export declare class HyperDB {
66
117
  createDatabase(databaseName: string, options?: DatabaseCreateOptions): Promise<void>;
67
118
  openDatabase(databaseName: string): Promise<void>;
68
119
  listDatabases(): Promise<DatabaseMetadata[]>;
69
- registerExternalFile(name: string, url: string, size: number): Promise<string>;
70
- removeExternalFile(path: string): Promise<void>;
120
+ registerExternalFile(...args: ExternalFileArgs): Promise<string>;
121
+ removeExternalRegistration(path: string): Promise<void>;
122
+ protected registerExternalDirectorySource(source: unknown, mode: ExternalDirectoryMode): Promise<string>;
71
123
  checkpointDatabase(databaseName: string): Promise<void>;
72
124
  dropDatabase(databaseName: string): Promise<void>;
73
125
  terminate(): Promise<void>;
74
126
  private ensureRunning;
75
- private runDatabaseOperation;
127
+ protected runDatabaseOperation<Result>(operation: () => Promise<Result>): Promise<Result>;
76
128
  }
77
129
  export declare class HyperDBConnection {
78
130
  private readonly client;
@@ -85,19 +137,20 @@ export declare class HyperDBConnection {
85
137
  close(): Promise<void>;
86
138
  attachDatabase(databaseName: string, alias?: string): Promise<void>;
87
139
  detachDatabase(alias: string): Promise<void>;
88
- sendIPC(sql: string): AsyncIterable<Uint8Array>;
89
- queryIPC(sql: string): Promise<Uint8Array>;
140
+ sendIPC(sql: string): QueryResult<AsyncIterable<Uint8Array>>;
141
+ queryIPC(sql: string): QueryResult<Promise<Uint8Array>>;
90
142
  send<T extends TypeMap = {
91
143
  [key: string]: DataType;
92
- }>(sql: string): Promise<AsyncRecordBatchStreamReader<T>>;
144
+ }>(sql: string): QueryResult<Promise<AsyncRecordBatchStreamReader<T>>>;
93
145
  query<T extends TypeMap = {
94
146
  [key: string]: DataType;
95
- }>(sql: string): Promise<Table<T>>;
147
+ }>(sql: string): QueryResult<Promise<Table<T>>>;
96
148
  insertArrowTable(table: Table, options: ArrowInsertOptions): Promise<void>;
97
149
  insertArrowIPCFromPath(path: string, options: ArrowInsertOptions): Promise<void>;
98
150
  private insertArrowIPCFromPathInternal;
99
151
  private ensureAvailable;
100
152
  private release;
153
+ private releaseWithInfo;
101
154
  closeForTermination(): Promise<void>;
102
155
  }
103
156
  export type { HyperDBEngineClient };
package/dist/browser.js CHANGED
@@ -32,12 +32,16 @@ function isWasmResult(value) {
32
32
  case "done":
33
33
  return hasKeys(value, ["state"]);
34
34
  case "error":
35
+ return hasKeys(value, ["state", "error"]) && (typeof value.error === "string" || isHyperDBErrorData(value.error));
35
36
  case "busy":
36
37
  return hasKeys(value, ["state", "error"]) && typeof value.error === "string";
37
38
  default:
38
39
  return false;
39
40
  }
40
41
  }
42
+ function isHyperDBErrorData(value) {
43
+ 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";
44
+ }
41
45
  function isWorkerEvent(message) {
42
46
  if (!isRecord(message) || typeof message.type !== "string") return false;
43
47
  switch (message.type) {
@@ -126,8 +130,11 @@ var init_client = __esm({
126
130
  shutdown() {
127
131
  return this.request({ id: this.nextId++, type: "shutdown" });
128
132
  }
129
- startQuery(connection, sql) {
130
- return this.request({ id: this.nextId++, type: "startQuery", connection, sql });
133
+ startQuery(connection, sql, sqlTestMode = false) {
134
+ return this.request({ id: this.nextId++, type: "startQuery", connection, sql, ...sqlTestMode && { sqlTestMode } });
135
+ }
136
+ startQueryWithInfo(connection, sql) {
137
+ return this.request({ id: this.nextId++, type: "startQueryWithInfo", connection, sql });
131
138
  }
132
139
  insertArrowIPCFromPath(connection, path, name, schema, create, internal = false) {
133
140
  return this.request({ id: this.nextId++, type: "insertArrowIPCFromPath", connection, path, name, schema, create, internal });
@@ -139,11 +146,14 @@ var init_client = __esm({
139
146
  removeFile(path) {
140
147
  return this.request({ id: this.nextId++, type: "removeFile", path });
141
148
  }
142
- registerExternalFile(name, url, size) {
143
- return this.request({ id: this.nextId++, type: "registerExternalFile", name, url, size });
149
+ registerExternalFile(pathOrName, url, size) {
150
+ 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 });
151
+ }
152
+ registerExternalDirectory(source, mode) {
153
+ return typeof source === "string" ? this.request({ id: this.nextId++, type: "registerExternalDirectory", path: source, mode }) : this.request({ id: this.nextId++, type: "registerExternalDirectory", handle: source, mode });
144
154
  }
145
- removeExternalFile(path) {
146
- return this.request({ id: this.nextId++, type: "removeExternalFile", path });
155
+ removeExternalRegistration(path) {
156
+ return this.request({ id: this.nextId++, type: "removeExternalRegistration", path });
147
157
  }
148
158
  pollQuery(query) {
149
159
  return this.request({ id: this.nextId++, type: "pollQuery", query });
@@ -154,6 +164,9 @@ var init_client = __esm({
154
164
  releaseQuery(query) {
155
165
  return this.request({ id: this.nextId++, type: "releaseQuery", query });
156
166
  }
167
+ releaseQueryWithInfo(query) {
168
+ return this.request({ id: this.nextId++, type: "releaseQueryWithInfo", query });
169
+ }
157
170
  async terminate() {
158
171
  await this.transport.terminate();
159
172
  this.fail(new Error("engine worker terminated"));
@@ -232,6 +245,9 @@ function assertBrowserSupport(environment = globalThis) {
232
245
  if (!environment.WebAssembly.validate(exceptionsProbe)) {
233
246
  throw new Error("HyperDB cannot initialize because this browser does not support WebAssembly exception handling");
234
247
  }
248
+ if (!environment.WebAssembly.validate(tailCallsProbe)) {
249
+ throw new Error("HyperDB cannot initialize because this browser does not support WebAssembly tail calls");
250
+ }
235
251
  if (typeof environment.Worker !== "function") {
236
252
  throw new Error("HyperDB cannot initialize because this browser does not support Web Workers");
237
253
  }
@@ -257,7 +273,7 @@ function createBrowserClient(options = {}) {
257
273
  options.onSetupProgress
258
274
  );
259
275
  }
260
- var memory64Probe, threadsProbe, exceptionsProbe, BrowserWorker;
276
+ var memory64Probe, threadsProbe, exceptionsProbe, tailCallsProbe, BrowserWorker;
261
277
  var init_browser = __esm({
262
278
  "src/browser.ts"() {
263
279
  "use strict";
@@ -265,6 +281,7 @@ var init_browser = __esm({
265
281
  memory64Probe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 5, 3, 1, 4, 1]);
266
282
  threadsProbe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 5, 4, 1, 3, 1, 1]);
267
283
  exceptionsProbe = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 13, 3, 1, 0, 0]);
284
+ 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]);
268
285
  BrowserWorker = class {
269
286
  constructor(worker) {
270
287
  this.worker = worker;
@@ -12135,7 +12152,72 @@ RecordBatchFileWriter["throughDOM"] = recordBatchWriterThroughDOMStream;
12135
12152
  RecordBatchStreamWriter["throughDOM"] = recordBatchWriterThroughDOMStream;
12136
12153
 
12137
12154
  // src/api.ts
12155
+ var HyperDBError = class extends Error {
12156
+ sqlState;
12157
+ errorSource;
12158
+ detail;
12159
+ hint;
12160
+ constructor(message, sqlState, errorSource, detail = "", hint = "") {
12161
+ super(message);
12162
+ this.name = "HyperDBError";
12163
+ this.sqlState = sqlState;
12164
+ this.errorSource = errorSource;
12165
+ this.detail = detail;
12166
+ this.hint = hint;
12167
+ }
12168
+ };
12138
12169
  var maxPersistentDatabaseNameLength = 200;
12170
+ function deferred() {
12171
+ let resolve;
12172
+ let reject;
12173
+ const promise = new Promise((resolvePromise, rejectPromise) => {
12174
+ resolve = resolvePromise;
12175
+ reject = rejectPromise;
12176
+ });
12177
+ void promise.catch(() => void 0);
12178
+ return { promise, resolve, reject };
12179
+ }
12180
+ function queryResult(result, info) {
12181
+ return { result, info };
12182
+ }
12183
+ function isRecord2(value) {
12184
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12185
+ }
12186
+ function decodeQueryInfo(payload) {
12187
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload));
12188
+ if (!isRecord2(value) || value.version !== 1) throw new Error("query information has an unsupported version");
12189
+ if (value.status !== "succeeded" && value.status !== "failed" && value.status !== "canceled") throw new Error("query information has an invalid status");
12190
+ if (typeof value.started !== "boolean" || typeof value.producesRows !== "boolean" || typeof value.cancellationRequestedByUser !== "boolean") {
12191
+ throw new Error("query information has invalid flags");
12192
+ }
12193
+ if (value.statementKind !== null && typeof value.statementKind !== "string") throw new Error("query information has an invalid statement kind");
12194
+ if (value.rowCount !== null && (typeof value.rowCount !== "string" || !/^\d+$/.test(value.rowCount))) throw new Error("query information has an invalid row count");
12195
+ if (value.columnCount !== null && (!Number.isSafeInteger(value.columnCount) || value.columnCount < 0)) throw new Error("query information has an invalid column count");
12196
+ if (value.queryPlan !== null && !isRecord2(value.queryPlan)) throw new Error("query information has an invalid query plan");
12197
+ if (!Array.isArray(value.dependencies) || value.dependencies.some((dependency) => typeof dependency !== "string")) throw new Error("query information has invalid dependencies");
12198
+ if (!isRecord2(value.timings)) throw new Error("query information has invalid timings");
12199
+ const timingNames = ["elapsedSeconds", "parsingSeconds", "compilationSeconds", "executionSeconds", "commitSeconds", "timeToScheduleSeconds", "timeToFirstResultSeconds", "cancellationSeconds"];
12200
+ for (const name of timingNames) {
12201
+ const timing = value.timings[name];
12202
+ if (timing !== null && (typeof timing !== "number" || !Number.isFinite(timing) || timing < 0)) throw new Error(`query information has an invalid ${name}`);
12203
+ }
12204
+ if (value.preExecutionMetrics !== null && !isRecord2(value.preExecutionMetrics)) throw new Error("query information has invalid pre-execution metrics");
12205
+ if (value.executionMetrics !== null && !isRecord2(value.executionMetrics)) throw new Error("query information has invalid execution metrics");
12206
+ return {
12207
+ status: value.status,
12208
+ started: value.started,
12209
+ statementKind: value.statementKind,
12210
+ producesRows: value.producesRows,
12211
+ rowCount: value.rowCount === null ? null : BigInt(value.rowCount),
12212
+ columnCount: value.columnCount,
12213
+ cancellationRequestedByUser: value.cancellationRequestedByUser,
12214
+ queryPlan: value.queryPlan,
12215
+ dependencies: value.dependencies,
12216
+ timings: value.timings,
12217
+ preExecutionMetrics: value.preExecutionMetrics,
12218
+ executionMetrics: value.executionMetrics
12219
+ };
12220
+ }
12139
12221
  function normalizeArrowInsertOptions(options) {
12140
12222
  if (!options.name) throw new Error("Arrow insert table name must not be empty");
12141
12223
  return { name: options.name, schema: options.schema ?? null, create: options.create ?? true };
@@ -12184,7 +12266,11 @@ function readHandleFromResult(result, operation) {
12184
12266
  return new DataView(result.payload.buffer, result.payload.byteOffset, result.payload.byteLength).getUint32(0, true);
12185
12267
  }
12186
12268
  function readErrorFromResult(result, operation) {
12187
- if (result.state === "error" || result.state === "busy") return new Error(result.error);
12269
+ if (result.state === "error") {
12270
+ if (typeof result.error === "string") return new Error(result.error);
12271
+ return new HyperDBError(result.error.message, result.error.sqlState, result.error.errorSource, result.error.detail, result.error.hint);
12272
+ }
12273
+ if (result.state === "busy") return new Error(result.error);
12188
12274
  return new Error(`${operation} returned unexpected state ${result.state}`);
12189
12275
  }
12190
12276
  function expectOk(result, operation) {
@@ -12218,7 +12304,7 @@ function readDatabasesFromResult(result) {
12218
12304
  throw invalidResult();
12219
12305
  }
12220
12306
  if (!name || name.includes("\0") || rawStorage !== 0 && rawStorage !== 1) throw invalidResult();
12221
- databases.push({ name, storage: rawStorage === 0 ? "memory" : "persistent" });
12307
+ databases.push({ name, storage: rawStorage === 0 ? "transient" : "persistent" });
12222
12308
  offset += nameLength;
12223
12309
  }
12224
12310
  if (offset !== result.payload.byteLength) throw invalidResult();
@@ -12265,7 +12351,7 @@ var HyperDB = class _HyperDB {
12265
12351
  }
12266
12352
  async createDatabase(databaseName, options = {}) {
12267
12353
  validateDatabaseName(databaseName);
12268
- if (options.storage !== void 0 && options.storage !== "memory" && options.storage !== "persistent") {
12354
+ if (options.storage !== void 0 && options.storage !== "transient" && options.storage !== "persistent") {
12269
12355
  throw new Error(`Unsupported database storage: ${String(options.storage)}`);
12270
12356
  }
12271
12357
  if (options.storage === "persistent") validatePersistentDatabaseName(databaseName);
@@ -12282,7 +12368,20 @@ var HyperDB = class _HyperDB {
12282
12368
  listDatabases() {
12283
12369
  return this.runDatabaseOperation(async () => readDatabasesFromResult(await this.client.listDatabases()));
12284
12370
  }
12285
- async registerExternalFile(name, url, size) {
12371
+ async registerExternalFile(...args) {
12372
+ if (args.length === 1) {
12373
+ const [sourcePath] = args;
12374
+ if (!sourcePath) throw new Error("External file path must not be empty");
12375
+ if (sourcePath.includes("\0")) throw new Error("External file path must not contain NUL");
12376
+ return this.runDatabaseOperation(async () => {
12377
+ const result = await this.client.registerExternalFile(sourcePath);
12378
+ expectOk(result, "register external file");
12379
+ const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12380
+ if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external file returned an invalid path");
12381
+ return path;
12382
+ });
12383
+ }
12384
+ const [name, url, size] = args;
12286
12385
  if (!name) throw new Error("External file name must not be empty");
12287
12386
  if (name.includes("\0")) throw new Error("External file name must not contain NUL");
12288
12387
  if (name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
@@ -12304,10 +12403,20 @@ var HyperDB = class _HyperDB {
12304
12403
  return path;
12305
12404
  });
12306
12405
  }
12307
- async removeExternalFile(path) {
12308
- if (!path) throw new Error("External file path must not be empty");
12406
+ async removeExternalRegistration(path) {
12407
+ if (!path) throw new Error("External registration path must not be empty");
12408
+ if (path.includes("\0")) throw new Error("External registration path must not contain null bytes");
12309
12409
  await this.runDatabaseOperation(async () => {
12310
- expectOk(await this.client.removeExternalFile(path), "remove external file");
12410
+ expectOk(await this.client.removeExternalRegistration(path), "remove external registration");
12411
+ });
12412
+ }
12413
+ registerExternalDirectorySource(source, mode) {
12414
+ return this.runDatabaseOperation(async () => {
12415
+ const result = await this.client.registerExternalDirectory(source, mode);
12416
+ expectOk(result, "register external directory");
12417
+ const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
12418
+ if (!/^\/mnt\/external\/[^/]+\/[^/]+$/.test(path)) throw new Error("register external directory returned an invalid path");
12419
+ return path;
12311
12420
  });
12312
12421
  }
12313
12422
  async checkpointDatabase(databaseName) {
@@ -12414,89 +12523,111 @@ var HyperDBConnection = class {
12414
12523
  }
12415
12524
  sendIPC(sql) {
12416
12525
  const connection = this;
12417
- return {
12418
- async *[Symbol.asyncIterator]() {
12526
+ const queryInfo = deferred();
12527
+ const result = (async function* () {
12528
+ let query;
12529
+ let terminal = false;
12530
+ let prefetched;
12531
+ let acquired = false;
12532
+ try {
12419
12533
  connection.ensureAvailable();
12420
- let query;
12421
- let terminal = false;
12422
- let prefetched;
12423
- try {
12424
- query = readHandleFromResult(await connection.client.startQuery(connection.connection, sql), "start query");
12425
- for (; ; ) {
12426
- let result = prefetched ? await prefetched : await connection.client.pollQuery(query);
12427
- prefetched = void 0;
12428
- while (result.state === "pending") {
12429
- await yieldToEventLoop();
12430
- result = await connection.client.pollQuery(query);
12431
- }
12432
- if (result.state === "chunk") {
12433
- prefetched = connection.client.pollQuery(query);
12434
- void prefetched.catch(() => void 0);
12435
- yield result.payload;
12436
- continue;
12437
- }
12438
- terminal = true;
12439
- if (result.state === "done") return;
12440
- throw readErrorFromResult(result, "poll query");
12534
+ acquired = true;
12535
+ query = readHandleFromResult(await connection.client.startQueryWithInfo(connection.connection, sql), "start query");
12536
+ for (; ; ) {
12537
+ let result2 = prefetched ? await prefetched : await connection.client.pollQuery(query);
12538
+ prefetched = void 0;
12539
+ while (result2.state === "pending") {
12540
+ await yieldToEventLoop();
12541
+ result2 = await connection.client.pollQuery(query);
12441
12542
  }
12442
- } finally {
12443
- try {
12444
- if (query !== void 0) {
12445
- let cleanupError;
12446
- if (prefetched) {
12447
- try {
12448
- await prefetched;
12449
- } catch (error) {
12450
- cleanupError = error;
12451
- }
12452
- prefetched = void 0;
12453
- }
12454
- if (!terminal) {
12455
- try {
12456
- expectOk(await connection.client.cancelQuery(query), "cancel query");
12457
- } catch (error) {
12458
- cleanupError ??= error;
12459
- }
12543
+ if (result2.state === "chunk") {
12544
+ prefetched = connection.client.pollQuery(query);
12545
+ void prefetched.catch(() => void 0);
12546
+ yield result2.payload;
12547
+ continue;
12548
+ }
12549
+ terminal = true;
12550
+ if (result2.state === "done") return;
12551
+ throw readErrorFromResult(result2, "poll query");
12552
+ }
12553
+ } catch (error) {
12554
+ if (query === void 0 || !terminal) queryInfo.reject(error);
12555
+ throw error;
12556
+ } finally {
12557
+ try {
12558
+ if (query !== void 0) {
12559
+ let cleanupError;
12560
+ let info;
12561
+ if (prefetched) {
12562
+ try {
12563
+ await prefetched;
12564
+ } catch (error) {
12565
+ cleanupError = error;
12460
12566
  }
12567
+ prefetched = void 0;
12568
+ }
12569
+ if (!terminal) {
12461
12570
  try {
12462
- await connection.release(query);
12571
+ expectOk(await connection.client.cancelQuery(query), "cancel query");
12463
12572
  } catch (error) {
12464
12573
  cleanupError ??= error;
12465
12574
  }
12466
- if (cleanupError) throw cleanupError;
12467
12575
  }
12468
- } finally {
12469
- connection.active = false;
12576
+ try {
12577
+ info = await connection.releaseWithInfo(query);
12578
+ } catch (error) {
12579
+ cleanupError ??= error;
12580
+ }
12581
+ if (cleanupError) {
12582
+ queryInfo.reject(cleanupError);
12583
+ throw cleanupError;
12584
+ }
12585
+ queryInfo.resolve(info);
12470
12586
  }
12587
+ } finally {
12588
+ if (acquired) connection.active = false;
12471
12589
  }
12472
12590
  }
12473
- };
12474
- }
12475
- async queryIPC(sql) {
12476
- const chunks = [];
12477
- let length = 0;
12478
- for await (const chunk of this.sendIPC(sql)) {
12479
- chunks.push(chunk);
12480
- length += chunk.byteLength;
12481
- }
12482
- const result = new Uint8Array(length);
12483
- let offset = 0;
12484
- for (const chunk of chunks) {
12485
- result.set(chunk, offset);
12486
- offset += chunk.byteLength;
12487
- }
12488
- return result;
12489
- }
12490
- async send(sql) {
12491
- const reader = await RecordBatchReader.from(this.sendIPC(sql));
12492
- if (!reader.isAsync() || !reader.isStream()) throw new Error("query did not produce an Arrow async stream");
12493
- return reader;
12494
- }
12495
- async query(sql) {
12496
- const reader = await this.send(sql);
12497
- const batches = [];
12498
- for await (const batch of reader) batches.push(batch);
12499
- return new Table(reader.schema, batches);
12591
+ })();
12592
+ return queryResult(result, queryInfo.promise);
12593
+ }
12594
+ queryIPC(sql) {
12595
+ const stream = this.sendIPC(sql);
12596
+ const result = (async () => {
12597
+ const chunks = [];
12598
+ let length = 0;
12599
+ for await (const chunk of stream.result) {
12600
+ chunks.push(chunk);
12601
+ length += chunk.byteLength;
12602
+ }
12603
+ const bytes = new Uint8Array(length);
12604
+ let offset = 0;
12605
+ for (const chunk of chunks) {
12606
+ bytes.set(chunk, offset);
12607
+ offset += chunk.byteLength;
12608
+ }
12609
+ return bytes;
12610
+ })();
12611
+ return queryResult(result, stream.info);
12612
+ }
12613
+ send(sql) {
12614
+ const stream = this.sendIPC(sql);
12615
+ const result = (async () => {
12616
+ const reader = await RecordBatchReader.from(stream.result);
12617
+ if (!reader.isAsync() || !reader.isStream()) throw new Error("query did not produce an Arrow async stream");
12618
+ return reader;
12619
+ })();
12620
+ return queryResult(result, stream.info);
12621
+ }
12622
+ query(sql) {
12623
+ const stream = this.send(sql);
12624
+ const result = (async () => {
12625
+ const reader = await stream.result;
12626
+ const batches = [];
12627
+ for await (const batch of reader) batches.push(batch);
12628
+ return new Table(reader.schema, batches);
12629
+ })();
12630
+ return queryResult(result, stream.info);
12500
12631
  }
12501
12632
  async insertArrowTable(table, options) {
12502
12633
  this.ensureAvailable();
@@ -12560,12 +12691,54 @@ var HyperDBConnection = class {
12560
12691
  delay = Math.min(delay * 2, 32);
12561
12692
  }
12562
12693
  }
12694
+ async releaseWithInfo(query) {
12695
+ let delay = 1;
12696
+ for (; ; ) {
12697
+ const result = await this.client.releaseQueryWithInfo(query);
12698
+ if (result.state === "ok") return decodeQueryInfo(result.payload);
12699
+ if (result.state !== "pending") throw readErrorFromResult(result, "release query");
12700
+ await new Promise((resolve) => setTimeout(resolve, delay));
12701
+ delay = Math.min(delay * 2, 32);
12702
+ }
12703
+ }
12563
12704
  async closeForTermination() {
12564
12705
  if (this.active) throw new Error("cannot terminate while a connection has an active operation");
12565
12706
  await this.close();
12566
12707
  }
12567
12708
  };
12709
+
12710
+ // src/index.ts
12711
+ init_browser();
12712
+ var HyperDB2 = class _HyperDB extends HyperDB {
12713
+ static async create(options = {}) {
12714
+ const client = createBrowserClient(options);
12715
+ const database = new _HyperDB(client);
12716
+ try {
12717
+ await database.ready();
12718
+ await initializeHyperDB(client, options.settings);
12719
+ return database;
12720
+ } catch (error) {
12721
+ try {
12722
+ await client.terminate();
12723
+ } catch {
12724
+ }
12725
+ throw error;
12726
+ }
12727
+ }
12728
+ async registerExternalDirectory(handle, options = {}) {
12729
+ const mode = options.mode ?? "read";
12730
+ if (mode !== "read" && mode !== "readwrite") throw new Error(`Unsupported external directory mode: ${String(mode)}`);
12731
+ if (!handle || handle.kind !== "directory" || typeof handle.queryPermission !== "function" || typeof handle.requestPermission !== "function") {
12732
+ throw new Error("External directory handle must be a FileSystemDirectoryHandle");
12733
+ }
12734
+ let permission = await handle.queryPermission({ mode });
12735
+ if (permission !== "granted") permission = await handle.requestPermission({ mode });
12736
+ if (permission !== "granted") throw new Error(`External directory ${mode} permission was not granted`);
12737
+ return this.registerExternalDirectorySource(handle, mode);
12738
+ }
12739
+ };
12568
12740
  export {
12569
- HyperDB,
12570
- HyperDBConnection
12741
+ HyperDB2 as HyperDB,
12742
+ HyperDBConnection,
12743
+ HyperDBError
12571
12744
  };
@@ -0,0 +1,30 @@
1
+ import type { ExternalDirectoryBackend } from './wasm_bindings.js';
2
+ export declare class BrowserExternalDirectoryBackend implements ExternalDirectoryBackend {
3
+ private readonly directories;
4
+ private readonly paths;
5
+ private readonly files;
6
+ private readonly writers;
7
+ register(handle: unknown, mode: 'read' | 'readwrite'): {
8
+ readonly name: string;
9
+ readonly locator: string;
10
+ };
11
+ registered(path: string, locator: string): void;
12
+ removed(pathOrLocator: string): void;
13
+ list(locator: string): Promise<readonly string[]>;
14
+ open(locator: string, path: string): Promise<{
15
+ readonly locator: string;
16
+ readonly size: number;
17
+ }>;
18
+ read(locator: string, offset: number, length: number, expectedSize: number): Promise<Uint8Array> | undefined;
19
+ createDirectories(locator: string, path: string): Promise<void>;
20
+ openWriter(writer: number, locator: string, path: string): Promise<void>;
21
+ write(writer: number, offset: number, bytes: Uint8Array): Promise<void>;
22
+ finish(writer: number, size: number): Promise<void>;
23
+ abort(writer: number): Promise<void>;
24
+ shutdown(): void;
25
+ private getDirectory;
26
+ private getWritableDirectory;
27
+ private getOpenWriter;
28
+ private cleanupWriter;
29
+ private enqueue;
30
+ }