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,14 +1,21 @@
1
1
  // src/node_worker.ts
2
- import { createReadStream } from "node:fs";
2
+ import { closeSync, createReadStream, fstatSync, openSync, read } from "node:fs";
3
+ import { opendir as opendir2, stat } from "node:fs/promises";
3
4
  import { createRequire } from "node:module";
4
- import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
5
+ import { basename as basename2, dirname, isAbsolute as isAbsolute2, join as join2, posix, resolve as resolve2 } from "node:path";
6
+ import { randomUUID as randomUUID2 } from "node:crypto";
5
7
  import { Readable } from "node:stream";
6
8
  import { parentPort, workerData } from "node:worker_threads";
7
9
  import { fileURLToPath } from "node:url";
8
10
  import { createBrotliDecompress } from "node:zlib";
9
11
 
10
12
  // src/wasm_bindings.ts
11
- var resultStates = ["ok", "pending", "chunk", "done", "error", "busy"];
13
+ function completionFor(cleanups) {
14
+ const pending = cleanups.filter((cleanup) => cleanup !== void 0);
15
+ return pending.length === 0 ? void 0 : Promise.all(pending).then(() => void 0);
16
+ }
17
+ var resultStates = ["ok", "pending", "chunk", "done", "error", "busy", "structuredError"];
18
+ var structuredErrorMagic = new Uint8Array([72, 68, 66, 69]);
12
19
  function safeInteger(value, name) {
13
20
  const number = typeof value === "bigint" && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
14
21
  if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 0) throw new Error(`${name} must be a nonnegative safe integer`);
@@ -18,6 +25,33 @@ function validateCString(value, name) {
18
25
  if (!value) throw new Error(`${name} must not be empty`);
19
26
  if (value.includes("\0")) throw new Error(`${name} must not contain NUL`);
20
27
  }
28
+ function decodeStructuredError(payload) {
29
+ if (!structuredErrorMagic.every((byte, index) => payload[index] === byte)) throw new Error("structured error has invalid magic");
30
+ const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
31
+ if (payload.byteLength < 8 || view.getUint32(4, true) !== 1) throw new Error("unsupported structured error version");
32
+ let offset = 8;
33
+ const decoder = new TextDecoder("utf-8", { fatal: true });
34
+ const readField = () => {
35
+ if (offset + 4 > payload.byteLength) throw new Error("truncated structured error field");
36
+ const length = view.getUint32(offset, true);
37
+ offset += 4;
38
+ if (length > payload.byteLength - offset) throw new Error("truncated structured error field");
39
+ const value = decoder.decode(payload.subarray(offset, offset + length));
40
+ offset += length;
41
+ return value;
42
+ };
43
+ const sqlState = readField();
44
+ const errorSource = readField();
45
+ const message = readField();
46
+ const detail = readField();
47
+ const hint = readField();
48
+ if (offset !== payload.byteLength) throw new Error("structured error has trailing bytes");
49
+ if (!/^[0-9A-Z]{5}$/.test(sqlState)) throw new Error("structured error has invalid SQLSTATE");
50
+ if (errorSource !== "User" && errorSource !== "System" && errorSource !== "OverloadRejection") {
51
+ throw new Error("structured error has invalid source");
52
+ }
53
+ return { sqlState, errorSource, message, detail, hint };
54
+ }
21
55
  var HyperDBBindings = class {
22
56
  module;
23
57
  initialize;
@@ -29,13 +63,18 @@ var HyperDBBindings = class {
29
63
  checkpointDatabase;
30
64
  dropDatabase;
31
65
  shutdown;
66
+ registerExternalFile;
67
+ registerExternalDirectory;
68
+ removeExternalRegistration;
32
69
  attachDatabase;
33
70
  detachDatabase;
34
71
  startQuery;
72
+ startQueryWithInfo;
35
73
  insertArrowIPCFromPath;
36
74
  pollQuery;
37
75
  cancelQuery;
38
76
  releaseQuery;
77
+ releaseQueryWithInfo;
39
78
  resultState;
40
79
  resultPayloadLength;
41
80
  free;
@@ -51,13 +90,18 @@ var HyperDBBindings = class {
51
90
  this.checkpointDatabase = module2.cwrap("hyperdb_wasm_checkpoint_database", "pointer", ["string"]);
52
91
  this.dropDatabase = module2.cwrap("hyperdb_wasm_drop_database", "pointer", ["string"]);
53
92
  this.shutdown = module2.cwrap("hyperdb_wasm_shutdown", "pointer", []);
93
+ this.registerExternalFile = module2.cwrap("hyperdb_wasm_register_external_file", "pointer", ["string", "string", "number"]);
94
+ this.registerExternalDirectory = module2.cwrap("hyperdb_wasm_register_external_directory", "pointer", ["string", "string", "number"]);
95
+ this.removeExternalRegistration = module2.cwrap("hyperdb_wasm_remove_external_registration", "pointer", ["string"]);
54
96
  this.attachDatabase = module2.cwrap("hyperdb_wasm_attach_database", "pointer", ["number", "string", "string"]);
55
97
  this.detachDatabase = module2.cwrap("hyperdb_wasm_detach_database", "pointer", ["number", "string"]);
56
- this.startQuery = module2.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string"]);
98
+ this.startQuery = module2.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string", "number"]);
99
+ this.startQueryWithInfo = module2.cwrap("hyperdb_wasm_query_start_with_info", "pointer", ["number", "string"]);
57
100
  this.insertArrowIPCFromPath = module2.cwrap("hyperdb_wasm_arrow_insert_from_path", "pointer", ["number", "string", "string", "string", "number"]);
58
101
  this.pollQuery = module2.cwrap("hyperdb_wasm_query_poll", "pointer", ["number"]);
59
102
  this.cancelQuery = module2.cwrap("hyperdb_wasm_query_cancel", "pointer", ["number"]);
60
103
  this.releaseQuery = module2.cwrap("hyperdb_wasm_query_release", "pointer", ["number"]);
104
+ this.releaseQueryWithInfo = module2.cwrap("hyperdb_wasm_query_release_with_info", "pointer", ["number"]);
61
105
  this.resultState = module2.cwrap("hyperdb_wasm_result_state", "number", ["number"]);
62
106
  this.resultPayloadLength = module2.cwrap("hyperdb_wasm_result_payload_len", "number", ["number"]);
63
107
  this.free = module2.cwrap("hyperdb_wasm_free", null, ["pointer"]);
@@ -73,8 +117,92 @@ var HyperDBBindings = class {
73
117
  this.module.FS.unlink(request.path);
74
118
  return { response: { id: request.id, type: "response", result: { state: "ok", payload: new Uint8Array() } }, transfer: [] };
75
119
  }
120
+ if (request.type === "registerExternalFile" && "path" in request) {
121
+ if (!this.module.externalFileBackend) {
122
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "Host external files are supported only by the Node client" } }, transfer: [] };
123
+ }
124
+ const registration = this.module.externalFileBackend.register(request.path);
125
+ try {
126
+ const pointer2 = safeInteger(this.registerExternalFile(registration.name, registration.locator, registration.size), "result pointer");
127
+ const result2 = this.decodeResult(pointer2);
128
+ if (result2.state === "ok") {
129
+ const path = new TextDecoder("utf-8", { fatal: true }).decode(result2.payload);
130
+ try {
131
+ this.module.externalFileBackend.registered(path, registration.locator);
132
+ } catch (error) {
133
+ const cleanupPointer = safeInteger(this.removeExternalRegistration(path), "result pointer");
134
+ this.decodeResult(cleanupPointer);
135
+ throw error;
136
+ }
137
+ } else {
138
+ this.module.externalFileBackend.removed(registration.locator);
139
+ }
140
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
141
+ } catch (error) {
142
+ this.module.externalFileBackend.removed(registration.locator);
143
+ throw error;
144
+ }
145
+ }
146
+ if (request.type === "registerExternalFile" && !this.module.externalFileBackendAvailable) {
147
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "Blob external files are supported only by the browser client" } }, transfer: [] };
148
+ }
149
+ if (request.type === "registerExternalDirectory") {
150
+ const backend = this.module.externalDirectoryBackend;
151
+ if (!backend) {
152
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
153
+ }
154
+ const registration = backend.register("handle" in request ? request.handle : request.path, request.mode);
155
+ try {
156
+ const pointer2 = safeInteger(this.registerExternalDirectory(registration.name, registration.locator, Number(request.mode === "readwrite")), "result pointer");
157
+ const result2 = this.decodeResult(pointer2);
158
+ if (result2.state === "ok") {
159
+ const path = new TextDecoder("utf-8", { fatal: true }).decode(result2.payload);
160
+ try {
161
+ backend.registered(path, registration.locator);
162
+ } catch (error) {
163
+ const cleanupPointer = safeInteger(this.removeExternalRegistration(path), "result pointer");
164
+ this.decodeResult(cleanupPointer);
165
+ throw error;
166
+ }
167
+ } else {
168
+ const completion = completionFor([backend.removed(registration.locator)]);
169
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [], ...completion && { completion } };
170
+ }
171
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
172
+ } catch (error) {
173
+ void Promise.resolve(backend.removed(registration.locator)).catch(() => void 0);
174
+ throw error;
175
+ }
176
+ }
177
+ if (request.type === "removeExternalRegistration" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
178
+ validateCString(request.path, "external registration path");
179
+ const pointer2 = safeInteger(this.removeExternalRegistration(request.path), "result pointer");
180
+ const result2 = this.decodeResult(pointer2);
181
+ if (result2.state === "ok") {
182
+ const completion = completionFor([
183
+ this.module.externalFileBackend?.removed(request.path),
184
+ this.module.externalDirectoryBackend?.removed(request.path)
185
+ ]);
186
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [], ...completion && { completion } };
187
+ }
188
+ return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
189
+ }
190
+ if (request.type === "removeExternalRegistration" && !this.module.externalFileBackendAvailable) {
191
+ return { response: { id: request.id, type: "response", result: { state: "error", error: "External files are not supported by this client" } }, transfer: [] };
192
+ }
76
193
  const pointer = safeInteger(this.invoke(request), "result pointer");
77
194
  const result = this.decodeResult(pointer);
195
+ if (request.type === "shutdown" && result.state === "ok") {
196
+ const completion = completionFor([
197
+ this.module.externalFileBackend?.shutdown(),
198
+ this.module.externalDirectoryBackend?.shutdown()
199
+ ]);
200
+ return {
201
+ response: { id: request.id, type: "response", result },
202
+ transfer: [],
203
+ ...completion && { completion }
204
+ };
205
+ }
78
206
  const response = { id: request.id, type: "response", result };
79
207
  return { response, transfer: result.state === "chunk" ? [result.payload.buffer] : [] };
80
208
  }
@@ -110,8 +238,20 @@ var HyperDBBindings = class {
110
238
  return this.detachDatabase(request.connection, request.alias);
111
239
  case "shutdown":
112
240
  return this.shutdown();
241
+ case "registerExternalFile":
242
+ if ("path" in request) throw new Error("host external file request has no backend");
243
+ validateCString(request.name, "external file name");
244
+ validateCString(request.url, "external file URL");
245
+ return this.registerExternalFile(request.name, request.url, safeInteger(request.size, "external file size"));
246
+ case "registerExternalDirectory":
247
+ throw new Error("external directory request has no backend");
248
+ case "removeExternalRegistration":
249
+ validateCString(request.path, "external registration path");
250
+ return this.removeExternalRegistration(request.path);
113
251
  case "startQuery":
114
- return this.startQuery(request.connection, request.sql);
252
+ return this.startQuery(request.connection, request.sql, Number(request.sqlTestMode ?? false));
253
+ case "startQueryWithInfo":
254
+ return this.startQueryWithInfo(request.connection, request.sql);
115
255
  case "insertArrowIPCFromPath": {
116
256
  const path = request.internal ? request.path : this.module.resolveHyperDBPath?.(request.path) ?? request.path;
117
257
  return this.insertArrowIPCFromPath(request.connection, path, request.name, request.schema ?? "", Number(request.create));
@@ -122,6 +262,8 @@ var HyperDBBindings = class {
122
262
  return this.cancelQuery(request.query);
123
263
  case "releaseQuery":
124
264
  return this.releaseQuery(request.query);
265
+ case "releaseQueryWithInfo":
266
+ return this.releaseQueryWithInfo(request.query);
125
267
  }
126
268
  }
127
269
  decodeResult(pointer) {
@@ -151,6 +293,8 @@ var HyperDBBindings = class {
151
293
  return { state, error: new TextDecoder().decode(payload) };
152
294
  case "busy":
153
295
  return { state, error: new TextDecoder().decode(payload) };
296
+ case "structuredError":
297
+ return { state: "error", error: decodeStructuredError(payload) };
154
298
  }
155
299
  } finally {
156
300
  this.free(pointer);
@@ -190,6 +334,8 @@ function isEngineRequest(value) {
190
334
  case "detachDatabase":
191
335
  return hasKeys(value, ["id", "type", "connection", "alias"]) && Number.isSafeInteger(value.connection) && typeof value.alias === "string";
192
336
  case "startQuery":
337
+ return (hasKeys(value, ["id", "type", "connection", "sql"]) || hasKeys(value, ["id", "type", "connection", "sql", "sqlTestMode"])) && Number.isSafeInteger(value.connection) && typeof value.sql === "string" && (value.sqlTestMode === void 0 || typeof value.sqlTestMode === "boolean");
338
+ case "startQueryWithInfo":
193
339
  return hasKeys(value, ["id", "type", "connection", "sql"]) && Number.isSafeInteger(value.connection) && typeof value.sql === "string";
194
340
  case "insertArrowIPCFromPath":
195
341
  return hasKeys(value, ["id", "type", "connection", "path", "name", "schema", "create", "internal"]) && Number.isSafeInteger(value.connection) && typeof value.path === "string" && typeof value.name === "string" && (typeof value.schema === "string" || value.schema === null) && typeof value.create === "boolean" && typeof value.internal === "boolean";
@@ -197,9 +343,16 @@ function isEngineRequest(value) {
197
343
  return hasKeys(value, ["id", "type", "bytes"]) && value.bytes instanceof Uint8Array;
198
344
  case "removeFile":
199
345
  return hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string";
346
+ case "registerExternalFile":
347
+ return hasKeys(value, ["id", "type", "name", "url", "size"]) && typeof value.name === "string" && typeof value.url === "string" && typeof value.size === "number" && Number.isSafeInteger(value.size) && value.size >= 0 || hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string";
348
+ case "registerExternalDirectory":
349
+ return (value.mode === "read" || value.mode === "readwrite") && (hasKeys(value, ["id", "type", "handle", "mode"]) && isRecord(value.handle) && value.handle.kind === "directory" || hasKeys(value, ["id", "type", "path", "mode"]) && typeof value.path === "string" && !!value.path && !value.path.includes("\0"));
350
+ case "removeExternalRegistration":
351
+ return hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string";
200
352
  case "pollQuery":
201
353
  case "cancelQuery":
202
354
  case "releaseQuery":
355
+ case "releaseQueryWithInfo":
203
356
  return hasKeys(value, ["id", "type", "query"]) && Number.isSafeInteger(value.query);
204
357
  default:
205
358
  return false;
@@ -210,6 +363,10 @@ function isEngineRequest(value) {
210
363
  function errorText(error) {
211
364
  return error instanceof Error ? error.message : String(error);
212
365
  }
366
+ function isNotFoundError(error) {
367
+ if (typeof error !== "object" || error === null) return false;
368
+ return "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR") || "name" in error && error.name === "NotFoundError";
369
+ }
213
370
  function safePointer(value, name) {
214
371
  const pointer = typeof value === "bigint" && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
215
372
  if (typeof pointer !== "number" || !Number.isSafeInteger(pointer) || pointer < 0) throw new Error(`${name} must be a nonnegative safe integer`);
@@ -218,22 +375,48 @@ function safePointer(value, name) {
218
375
  function allocate(module2, bytes) {
219
376
  const pointer = safePointer(module2._malloc(bytes.byteLength || 1), "allocation pointer");
220
377
  if (pointer === 0) throw new Error("wasm allocation failed");
221
- if (pointer + bytes.byteLength > module2.HEAPU8.byteLength) throw new Error("allocation exceeds the wasm heap");
222
- module2.HEAPU8.set(bytes, pointer);
378
+ module2.getValue(pointer, "i8");
379
+ const heap = module2.HEAPU8;
380
+ if (pointer + bytes.byteLength > heap.byteLength) throw new Error("allocation exceeds the wasm heap");
381
+ heap.set(bytes, pointer);
223
382
  return pointer;
224
383
  }
384
+ function externalDirectoryError(module2, error) {
385
+ return safePointer(module2.stringToNewUTF8(errorText(error)), "external directory error pointer");
386
+ }
387
+ function completeExternalDirectoryOperation(module2, id, complete, operation) {
388
+ void Promise.resolve().then(operation).then(
389
+ () => complete(id, 0),
390
+ (error) => complete(id, externalDirectoryError(module2, error))
391
+ );
392
+ }
225
393
  function installHttpRuntime(module2, transport2) {
226
- module2.hyperdbWasmFetchStart = (id, url, offset, length) => {
394
+ module2.hyperdbWasmFetchStart = (id, url, offset, length, expectedSize) => {
395
+ if (url.startsWith("hyperdb-node-file:") || url.startsWith("hyperdb-node-host-file:") || url.startsWith("hyperdb-node-directory-file:") || url.startsWith("hyperdb-browser-file:")) {
396
+ const read2 = module2.externalFileBackend?.read(url, offset, length, expectedSize) ?? module2.externalDirectoryBackend?.read(url, offset, length, expectedSize);
397
+ if (!read2) {
398
+ module2._hyperdb_wasm_fetch_complete(id, 0, -1);
399
+ return;
400
+ }
401
+ void read2.then((bytes) => {
402
+ module2._hyperdb_wasm_fetch_complete(id, allocate(module2, bytes), bytes.byteLength);
403
+ }).catch(() => {
404
+ module2._hyperdb_wasm_fetch_complete(id, 0, -1);
405
+ });
406
+ return;
407
+ }
227
408
  const headers = new Headers();
228
409
  if (length > 0) headers.set("Range", `bytes=${offset}-${offset + length - 1}`);
229
410
  void fetch(url, { headers, redirect: "manual" }).then(async (response) => {
230
411
  if (!response.ok || response.type === "opaqueredirect" || (length === 0 ? response.status !== 200 : response.status !== 206)) {
412
+ void response.body?.cancel().catch(() => void 0);
231
413
  module2._hyperdb_wasm_fetch_complete(id, 0, -1);
232
414
  return;
233
415
  }
234
416
  if (length > 0) {
235
417
  const contentRange = /^bytes (\d+)-(\d+)\/(\d+)$/.exec(response.headers.get("content-range") ?? "");
236
- if (!contentRange || contentRange[1] !== String(offset) || contentRange[2] !== String(offset + length - 1)) {
418
+ if (!contentRange || contentRange[1] !== String(offset) || contentRange[2] !== String(offset + length - 1) || expectedSize >= 0 && contentRange[3] !== String(expectedSize)) {
419
+ void response.body?.cancel().catch(() => void 0);
237
420
  module2._hyperdb_wasm_fetch_complete(id, 0, -1);
238
421
  return;
239
422
  }
@@ -282,6 +465,97 @@ function installHttpRuntime(module2, transport2) {
282
465
  });
283
466
  };
284
467
  }
468
+ function installExternalDirectoryRuntime(module2) {
469
+ const backend = module2.externalDirectoryBackend;
470
+ if (!backend) return;
471
+ const listComplete = module2._hyperdb_wasm_external_directory_list_complete;
472
+ const openComplete = module2._hyperdb_wasm_external_directory_open_complete;
473
+ if (listComplete) {
474
+ module2.hyperdbWasmExternalDirectoryListStart = (id, locator) => {
475
+ void backend.list(locator).then((paths) => {
476
+ listComplete(id, safePointer(module2.stringToNewUTF8(JSON.stringify(paths)), "external directory list pointer"), 0);
477
+ }).catch((error) => {
478
+ listComplete(id, 0, externalDirectoryError(module2, error));
479
+ });
480
+ };
481
+ }
482
+ if (openComplete) {
483
+ module2.hyperdbWasmExternalDirectoryOpenStart = (id, locator, path) => {
484
+ void backend.open(locator, path).then((file) => {
485
+ openComplete(id, safePointer(module2.stringToNewUTF8(file.locator), "external file locator pointer"), file.size);
486
+ }).catch((error) => openComplete(id, 0, isNotFoundError(error) ? -2 : -1));
487
+ };
488
+ }
489
+ const createDirectoriesComplete = module2._hyperdb_wasm_external_directory_create_directories_complete;
490
+ if (createDirectoriesComplete) {
491
+ module2.hyperdbWasmExternalDirectoryCreateDirectoriesStart = (id, locator, path) => {
492
+ completeExternalDirectoryOperation(module2, id, createDirectoriesComplete, () => backend.createDirectories(locator, path));
493
+ };
494
+ }
495
+ const writerWriteComplete = module2._hyperdb_wasm_external_directory_writer_write_complete;
496
+ if (writerWriteComplete) {
497
+ module2.hyperdbWasmExternalDirectoryWriterWriteStart = (writer, id, locator, path, offset, pointerValue, length) => {
498
+ try {
499
+ const pointer = safePointer(pointerValue, "external directory write pointer");
500
+ const safeLength = safePointer(length, "external directory write length");
501
+ const end = safePointer(pointer + safeLength, "external directory write end");
502
+ module2.getValue(pointer, "i8");
503
+ if (end > module2.HEAPU8.byteLength) throw new Error("external directory write exceeds the wasm heap");
504
+ const bytes = module2.HEAPU8.slice(pointer, end);
505
+ completeExternalDirectoryOperation(module2, id, writerWriteComplete, async () => {
506
+ await backend.openWriter(writer, locator, path);
507
+ await backend.write(writer, offset, bytes);
508
+ });
509
+ } catch (error) {
510
+ writerWriteComplete(id, externalDirectoryError(module2, error));
511
+ }
512
+ };
513
+ }
514
+ const writerFinishComplete = module2._hyperdb_wasm_external_directory_writer_finish_complete;
515
+ if (writerFinishComplete) {
516
+ module2.hyperdbWasmExternalDirectoryWriterFinishStart = (writer, id, locator, path, size) => {
517
+ completeExternalDirectoryOperation(module2, id, writerFinishComplete, async () => {
518
+ await backend.openWriter(writer, locator, path);
519
+ await backend.finish(writer, size);
520
+ });
521
+ };
522
+ }
523
+ const writerAbortComplete = module2._hyperdb_wasm_external_directory_writer_abort_complete;
524
+ if (writerAbortComplete) {
525
+ module2.hyperdbWasmExternalDirectoryWriterAbortStart = (writer, id) => {
526
+ completeExternalDirectoryOperation(module2, id, writerAbortComplete, () => backend.abort(writer));
527
+ };
528
+ }
529
+ }
530
+ function installHostFileRuntime(module2) {
531
+ const complete = module2._hyperdb_wasm_host_file_open_complete;
532
+ if (!complete) return;
533
+ module2.hyperdbWasmHostFileOpenStart = (id, path) => {
534
+ try {
535
+ const file = module2.externalFileBackend?.openHostFile(path);
536
+ if (!file) throw new Error("Node host-file backend is unavailable");
537
+ complete(id, file.id, file.size);
538
+ } catch (error) {
539
+ complete(id, 0, isNotFoundError(error) ? -2 : -1);
540
+ }
541
+ };
542
+ module2.hyperdbWasmHostFileClose = (id) => module2.externalFileBackend?.closeHostFile(id);
543
+ const listComplete = module2._hyperdb_wasm_external_directory_list_complete;
544
+ if (listComplete) {
545
+ module2.hyperdbWasmHostDirectoryListStart = (id, path) => {
546
+ const list = module2.externalFileBackend?.listHostDirectory(path);
547
+ if (!list) {
548
+ listComplete(id, 0, externalDirectoryError(module2, new Error("Node host-directory backend is unavailable")));
549
+ return;
550
+ }
551
+ void list.then((paths) => {
552
+ listComplete(id, safePointer(module2.stringToNewUTF8(JSON.stringify(paths)), "host directory list pointer"), 0);
553
+ }).catch((error) => {
554
+ listComplete(id, 0, externalDirectoryError(module2, error));
555
+ });
556
+ };
557
+ }
558
+ }
285
559
  function configureWorker(module2, transport2, configureFilesystem = () => void 0) {
286
560
  const queuedRequests = [];
287
561
  let bindings;
@@ -292,8 +566,14 @@ function configureWorker(module2, transport2, configureFilesystem = () => void 0
292
566
  }
293
567
  try {
294
568
  const result = bindings.handle(request);
295
- if (request.type === "shutdown" && result.response.result.state === "ok") module2.PThread.terminateAllThreads();
296
- transport2.postMessage(result.response, result.transfer);
569
+ const complete = () => {
570
+ if (request.type === "shutdown" && result.response.result.state === "ok") module2.PThread.terminateAllThreads();
571
+ transport2.postMessage(result.response, result.transfer);
572
+ };
573
+ if (result.completion) void result.completion.then(complete, (error) => {
574
+ transport2.postMessage({ id: request.id, type: "response", result: { state: "error", error: errorText(error) } });
575
+ });
576
+ else complete();
297
577
  } catch (error) {
298
578
  transport2.postMessage({ id: request.id, type: "response", result: { state: "error", error: errorText(error) } });
299
579
  }
@@ -307,6 +587,8 @@ function configureWorker(module2, transport2, configureFilesystem = () => void 0
307
587
  try {
308
588
  configureFilesystem(module2);
309
589
  installHttpRuntime(module2, transport2);
590
+ installHostFileRuntime(module2);
591
+ installExternalDirectoryRuntime(module2);
310
592
  bindings = new HyperDBBindings(module2);
311
593
  transport2.postMessage({ type: "ready" });
312
594
  for (const request of queuedRequests.splice(0)) handle(request);
@@ -317,17 +599,323 @@ function configureWorker(module2, transport2, configureFilesystem = () => void 0
317
599
  module2.onAbort = (reason) => transport2.postMessage({ type: "fatal", error: `engine aborted: ${String(reason)}` });
318
600
  }
319
601
 
602
+ // src/node_external_directory.ts
603
+ import { constants, lstatSync, realpathSync } from "node:fs";
604
+ import { lstat, mkdir, open, opendir, rename, unlink } from "node:fs/promises";
605
+ import { randomUUID } from "node:crypto";
606
+ import { basename, isAbsolute, join, relative, resolve } from "node:path";
607
+ var directoryLocatorPrefix = "hyperdb-node-directory:";
608
+ var fileLocatorPrefix = "hyperdb-node-directory-file:";
609
+ function validateSegment(segment) {
610
+ if (!segment || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\") || segment.includes("\0")) {
611
+ throw new Error("External directory entries must have safe relative path segments");
612
+ }
613
+ }
614
+ function relativeSegments(path) {
615
+ if (!path || isAbsolute(path) || path.endsWith("/") || path.includes("\\") || path.includes("\0")) {
616
+ throw new Error("External directory file path must be relative");
617
+ }
618
+ const segments = path.split("/");
619
+ for (const segment of segments) validateSegment(segment);
620
+ return segments;
621
+ }
622
+ function directorySegments(path) {
623
+ return path ? relativeSegments(path) : [];
624
+ }
625
+ function nonnegativeSafeInteger(value, name) {
626
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${name} must be a nonnegative safe integer`);
627
+ }
628
+ function isNotFound(error) {
629
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
630
+ }
631
+ function isAlreadyExists(error) {
632
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
633
+ }
634
+ function confinedPath(root, segments) {
635
+ const path = join(root, ...segments);
636
+ const fromRoot = relative(root, path);
637
+ if (isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
638
+ throw new Error("External directory path escapes its registration");
639
+ }
640
+ return path;
641
+ }
642
+ async function ensureDirectory(root, segments, create) {
643
+ let current = root;
644
+ for (const segment of segments) {
645
+ current = join(current, segment);
646
+ const fromRoot = relative(root, current);
647
+ if (isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
648
+ throw new Error("External directory path escapes its registration");
649
+ }
650
+ try {
651
+ const info = await lstat(current);
652
+ if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("External directory path contains a non-directory entry");
653
+ } catch (error) {
654
+ if (!create || !isNotFound(error)) throw error;
655
+ await mkdir(current).catch((mkdirError) => {
656
+ if (!isAlreadyExists(mkdirError)) throw mkdirError;
657
+ });
658
+ const info = await lstat(current);
659
+ if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("External directory path contains a non-directory entry");
660
+ }
661
+ }
662
+ return current;
663
+ }
664
+ var NodeExternalDirectoryBackend = class {
665
+ directories = /* @__PURE__ */ new Map();
666
+ paths = /* @__PURE__ */ new Map();
667
+ files = /* @__PURE__ */ new Map();
668
+ writers = /* @__PURE__ */ new Map();
669
+ register(path, mode) {
670
+ if (typeof path !== "string" || !path || path.includes("\0")) throw new Error("External directory path is invalid");
671
+ const root = realpathSync(resolve(path));
672
+ const info = lstatSync(root);
673
+ if (info.isSymbolicLink() || !info.isDirectory()) throw new Error("External directory path must identify a directory");
674
+ const locator = `${directoryLocatorPrefix}${randomUUID()}`;
675
+ this.directories.set(locator, { root, mode, files: /* @__PURE__ */ new Map(), locators: /* @__PURE__ */ new Set(), temporaryPaths: /* @__PURE__ */ new Set() });
676
+ return { name: basename(root) || "root", locator };
677
+ }
678
+ registered(path, locator) {
679
+ if (!this.directories.has(locator)) throw new Error("External directory registration is unavailable");
680
+ this.paths.set(path, locator);
681
+ }
682
+ async removed(pathOrLocator) {
683
+ const locator = this.paths.get(pathOrLocator) ?? pathOrLocator;
684
+ const directory = this.directories.get(locator);
685
+ if (!directory) return;
686
+ const writerCleanup = [];
687
+ for (const [writer, state] of this.writers) {
688
+ if (state.locator !== locator) continue;
689
+ if (!state.terminal) writerCleanup.push(this.abort(writer));
690
+ else writerCleanup.push(state.chain.then(() => this.cleanupWriter(state)).finally(() => this.writers.delete(writer)));
691
+ }
692
+ await Promise.all(writerCleanup);
693
+ const fileCleanup = [];
694
+ for (const fileLocator of directory.locators) {
695
+ const file = this.files.get(fileLocator);
696
+ if (file) fileCleanup.push(file.descriptor.close());
697
+ this.files.delete(fileLocator);
698
+ }
699
+ await Promise.all(fileCleanup);
700
+ this.directories.delete(locator);
701
+ for (const [path, pathLocator] of this.paths) {
702
+ if (pathLocator === locator) this.paths.delete(path);
703
+ }
704
+ }
705
+ async list(locator) {
706
+ const directory = this.getDirectory(locator);
707
+ const files = [];
708
+ const visit = async (path, prefix) => {
709
+ const entries = await opendir(path);
710
+ for await (const entry of entries) {
711
+ validateSegment(entry.name);
712
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
713
+ const child = confinedPath(directory.root, relativeSegments(relativePath));
714
+ if (directory.temporaryPaths.has(child)) continue;
715
+ const info = await lstat(child);
716
+ if (info.isSymbolicLink()) throw new Error("External directory must not contain symbolic links");
717
+ if (info.isDirectory()) await visit(child, relativePath);
718
+ else if (info.isFile()) files.push(relativePath);
719
+ else throw new Error("External directory contains an unsupported entry");
720
+ }
721
+ };
722
+ await visit(directory.root, "");
723
+ files.sort();
724
+ return files;
725
+ }
726
+ async open(locator, path) {
727
+ const directory = this.getDirectory(locator);
728
+ const cached = directory.files.get(path);
729
+ if (cached) {
730
+ const file = await cached;
731
+ return { locator: file.locator, size: file.size };
732
+ }
733
+ const segments = relativeSegments(path);
734
+ const snapshot = (async () => {
735
+ await ensureDirectory(directory.root, segments.slice(0, -1), false);
736
+ const target = confinedPath(directory.root, segments);
737
+ const info = await lstat(target);
738
+ if (info.isSymbolicLink() || !info.isFile()) throw new Error("External directory entry is not a regular file");
739
+ const descriptor = await open(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
740
+ try {
741
+ const openedInfo = await descriptor.stat({ bigint: true });
742
+ if (!openedInfo.isFile()) throw new Error("External directory entry is not a regular file");
743
+ if (openedInfo.size > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("External file size must be a nonnegative safe integer");
744
+ const file = { locator: `${fileLocatorPrefix}${randomUUID()}`, descriptor, size: Number(openedInfo.size) };
745
+ this.files.set(file.locator, file);
746
+ directory.locators.add(file.locator);
747
+ return file;
748
+ } catch (error) {
749
+ await descriptor.close();
750
+ throw error;
751
+ }
752
+ })();
753
+ directory.files.set(path, snapshot);
754
+ try {
755
+ const file = await snapshot;
756
+ return { locator: file.locator, size: file.size };
757
+ } catch (error) {
758
+ directory.files.delete(path);
759
+ throw error;
760
+ }
761
+ }
762
+ read(locator, offset, length, expectedSize) {
763
+ if (!locator.startsWith(fileLocatorPrefix)) return void 0;
764
+ const file = this.files.get(locator);
765
+ if (!file) return Promise.reject(new Error("External directory file registration does not exist"));
766
+ const end = offset + length;
767
+ if (expectedSize !== file.size || !Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 0 || !Number.isSafeInteger(end) || end > file.size) {
768
+ return Promise.reject(new Error("External file byte range is invalid"));
769
+ }
770
+ return (async () => {
771
+ const result = new Uint8Array(length);
772
+ let bytesRead = 0;
773
+ while (bytesRead < length) {
774
+ const readResult = await file.descriptor.read(result, bytesRead, length - bytesRead, offset + bytesRead);
775
+ if (readResult.bytesRead === 0) throw new Error("External file ended before the registered size");
776
+ bytesRead += readResult.bytesRead;
777
+ }
778
+ return result;
779
+ })();
780
+ }
781
+ async createDirectories(locator, path) {
782
+ const directory = this.getWritableDirectory(locator);
783
+ await ensureDirectory(directory.root, directorySegments(path), true);
784
+ }
785
+ openWriter(writer, locator, path) {
786
+ nonnegativeSafeInteger(writer, "External directory writer");
787
+ const directory = this.getWritableDirectory(locator);
788
+ const segments = relativeSegments(path);
789
+ const target = confinedPath(directory.root, segments);
790
+ const existing = this.writers.get(writer);
791
+ if (existing) {
792
+ if (existing.locator !== locator || existing.path !== path) throw new Error("External directory writer was reused for another path");
793
+ return existing.chain;
794
+ }
795
+ const state = { locator, path, target, temp: void 0, descriptor: void 0, chain: Promise.resolve(), terminal: false };
796
+ this.writers.set(writer, state);
797
+ return this.enqueue(state, async () => {
798
+ const parent = await ensureDirectory(directory.root, segments.slice(0, -1), true);
799
+ try {
800
+ const info = await lstat(target);
801
+ if (info.isSymbolicLink() || !info.isFile()) throw new Error("External directory write target is not a regular file");
802
+ } catch (error) {
803
+ if (!isNotFound(error)) throw error;
804
+ }
805
+ state.temp = join(parent, `.hyperdb-${randomUUID()}.tmp`);
806
+ directory.temporaryPaths.add(state.temp);
807
+ try {
808
+ state.descriptor = await open(state.temp, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
809
+ } catch (error) {
810
+ state.temp = void 0;
811
+ throw error;
812
+ }
813
+ });
814
+ }
815
+ write(writer, offset, bytes) {
816
+ nonnegativeSafeInteger(offset, "External directory write offset");
817
+ nonnegativeSafeInteger(offset + bytes.byteLength, "External directory write end");
818
+ const state = this.getOpenWriter(writer);
819
+ return this.enqueue(state, async () => {
820
+ if (!state.descriptor) throw new Error("External directory writer failed to open");
821
+ let written = 0;
822
+ while (written < bytes.byteLength) {
823
+ const result = await state.descriptor.write(bytes, written, bytes.byteLength - written, offset + written);
824
+ if (result.bytesWritten === 0) throw new Error("External directory write made no progress");
825
+ written += result.bytesWritten;
826
+ }
827
+ });
828
+ }
829
+ finish(writer, size) {
830
+ nonnegativeSafeInteger(size, "External directory file size");
831
+ const state = this.getOpenWriter(writer);
832
+ state.terminal = true;
833
+ const operation = this.enqueue(state, async () => {
834
+ let failure = state.failure;
835
+ if (failure === void 0 && (!state.descriptor || !state.temp)) failure = new Error("External directory writer failed to open");
836
+ if (failure === void 0) {
837
+ try {
838
+ await state.descriptor?.truncate(size);
839
+ await state.descriptor?.close();
840
+ state.descriptor = void 0;
841
+ await rename(state.temp, state.target);
842
+ state.temp = void 0;
843
+ } catch (error) {
844
+ failure = error;
845
+ }
846
+ }
847
+ if (failure !== void 0) {
848
+ await this.cleanupWriter(state);
849
+ throw failure;
850
+ }
851
+ this.getDirectory(state.locator).files.delete(state.path);
852
+ }, true);
853
+ return operation.finally(() => this.writers.delete(writer));
854
+ }
855
+ abort(writer) {
856
+ const state = this.getOpenWriter(writer);
857
+ state.terminal = true;
858
+ const operation = this.enqueue(state, () => this.cleanupWriter(state), true);
859
+ return operation.finally(() => this.writers.delete(writer));
860
+ }
861
+ async shutdown() {
862
+ await Promise.all([...this.directories.keys()].map((locator) => this.removed(locator)));
863
+ await Promise.all([...this.files.values()].map((file) => file.descriptor.close()));
864
+ this.files.clear();
865
+ this.paths.clear();
866
+ this.directories.clear();
867
+ }
868
+ getDirectory(locator) {
869
+ const directory = this.directories.get(locator);
870
+ if (!directory) throw new Error("External directory registration does not exist");
871
+ return directory;
872
+ }
873
+ getWritableDirectory(locator) {
874
+ const directory = this.getDirectory(locator);
875
+ if (directory.mode !== "readwrite") throw new Error("External directory registration is not writable");
876
+ return directory;
877
+ }
878
+ getOpenWriter(writer) {
879
+ nonnegativeSafeInteger(writer, "External directory writer");
880
+ const state = this.writers.get(writer);
881
+ if (!state || state.terminal) throw new Error("External directory writer is not open");
882
+ return state;
883
+ }
884
+ async cleanupWriter(state) {
885
+ this.directories.get(state.locator)?.files.delete(state.path);
886
+ try {
887
+ await state.descriptor?.close();
888
+ } finally {
889
+ state.descriptor = void 0;
890
+ if (state.temp) {
891
+ await unlink(state.temp).catch(() => void 0);
892
+ }
893
+ state.temp = void 0;
894
+ }
895
+ }
896
+ enqueue(state, operation, afterFailure = false) {
897
+ const result = state.chain.then(async () => {
898
+ if (!afterFailure && state.failure !== void 0) throw state.failure;
899
+ await operation();
900
+ });
901
+ state.chain = result.catch((error) => {
902
+ state.failure ??= error;
903
+ });
904
+ return result;
905
+ }
906
+ };
907
+
320
908
  // src/node_worker.ts
321
909
  if (!parentPort) throw new Error("Node engine worker requires a parent port");
322
910
  var enginePort = parentPort;
323
911
  var options = workerData;
324
912
  var engineUrl = options?.engineUrl ?? fileURLToPath(new URL("./hyperdb-wasm.cjs", import.meta.url));
325
- var hostPath = resolve(options?.hostPath ?? process.cwd());
913
+ var storagePath = resolve2(options?.storagePath ?? process.cwd());
326
914
  var transport = {
327
915
  postMessage: (message, transfer = []) => enginePort.postMessage(message, [...transfer]),
328
916
  onMessage: (listener) => enginePort.on("message", listener)
329
917
  };
330
- var compressedWasm = join(dirname(engineUrl), "hyperdb-wasm.wasm.br");
918
+ var compressedWasm = join2(dirname(engineUrl), "hyperdb-wasm.wasm.br");
331
919
  var decompressedWasm = Readable.toWeb(createReadStream(compressedWasm).pipe(createBrotliDecompress()));
332
920
  var compiledWasm = await WebAssembly.compileStreaming(new Response(decompressedWasm, {
333
921
  headers: { "Content-Type": "application/wasm" }
@@ -344,18 +932,126 @@ var module = createRequire(import.meta.url)(engineUrl);
344
932
  delete configuredGlobal.HYPERDB_WASM_MODULE;
345
933
  configureWorker(module, transport, (runtime) => {
346
934
  if (!runtime.NODEFS) throw new Error("Node filesystem backend is unavailable");
935
+ const nodeFS = runtime.NODEFS;
347
936
  runtime.FS.mkdirTree("/host");
348
- runtime.FS.mount(runtime.NODEFS, { root: hostPath }, "/host");
349
937
  runtime.FS.mkdirTree("/hyperdb-persistent");
350
- runtime.FS.mount(runtime.NODEFS, { root: hostPath }, "/hyperdb-persistent");
938
+ runtime.FS.mount(nodeFS, { root: storagePath }, "/hyperdb-persistent");
351
939
  runtime.FS.mkdirTree("/hyperdb-persistent/hyperdb");
352
940
  runtime.FS.mkdirTree("/hyperdb-persistent/logs");
941
+ const nodeRuntimeFS = runtime.FS;
942
+ const shouldAccessHostPath = (path) => isAbsolute2(path) && !nodeRuntimeFS.analyzePath(path).exists;
943
+ runtime.hyperdbWasmShouldOpenHostFile = shouldAccessHostPath;
944
+ runtime.hyperdbWasmShouldListHostDirectory = shouldAccessHostPath;
945
+ runtime.externalDirectoryBackend = new NodeExternalDirectoryBackend();
946
+ const externalFiles = /* @__PURE__ */ new Map();
947
+ const externalPaths = /* @__PURE__ */ new Map();
948
+ let nextHostFileId = 1;
949
+ const openExternalFile = (path, locator) => {
950
+ const descriptor = openSync(path, "r");
951
+ try {
952
+ const stat2 = fstatSync(descriptor, { bigint: true });
953
+ if (!stat2.isFile()) throw new Error(`External file is not a regular file: ${path}`);
954
+ if (stat2.size > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("External file size must be a nonnegative safe integer");
955
+ const file = { descriptor, size: Number(stat2.size) };
956
+ externalFiles.set(locator, file);
957
+ return file;
958
+ } catch (error) {
959
+ closeSync(descriptor);
960
+ throw error;
961
+ }
962
+ };
963
+ runtime.externalFileBackend = {
964
+ register: (path) => {
965
+ const absolute = resolve2(path);
966
+ const locator = `hyperdb-node-file:${randomUUID2()}`;
967
+ const { size } = openExternalFile(absolute, locator);
968
+ return { name: basename2(absolute), locator, size };
969
+ },
970
+ registered: (path, locator) => {
971
+ if (!externalFiles.has(locator)) throw new Error("External file descriptor is unavailable");
972
+ externalPaths.set(path, locator);
973
+ },
974
+ removed: (path) => {
975
+ const locator = externalPaths.get(path) ?? path;
976
+ const file = externalFiles.get(locator);
977
+ if (file === void 0) return;
978
+ closeSync(file.descriptor);
979
+ externalFiles.delete(locator);
980
+ externalPaths.delete(path);
981
+ },
982
+ openHostFile: (path) => {
983
+ if (!isAbsolute2(path)) throw new Error("Host file path must be absolute");
984
+ const id = nextHostFileId++;
985
+ if (!Number.isSafeInteger(id)) throw new Error("Host file identifier limit reached");
986
+ const { size } = openExternalFile(path, `hyperdb-node-host-file:${id}`);
987
+ return { id, size };
988
+ },
989
+ closeHostFile: (id) => {
990
+ const locator = `hyperdb-node-host-file:${id}`;
991
+ const file = externalFiles.get(locator);
992
+ if (!file) return;
993
+ closeSync(file.descriptor);
994
+ externalFiles.delete(locator);
995
+ },
996
+ listHostDirectory: async (path) => {
997
+ if (!isAbsolute2(path)) throw new Error("Host directory path must be absolute");
998
+ const files = [];
999
+ const visit = async (directory, prefix) => {
1000
+ const entries = await opendir2(directory);
1001
+ for await (const entry of entries) {
1002
+ if (!entry.name || entry.name === "." || entry.name === ".." || entry.name.includes("/") || entry.name.includes("\\") || entry.name.includes("\0")) {
1003
+ throw new Error("Host directory contains an invalid entry name");
1004
+ }
1005
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
1006
+ if (entry.isDirectory()) await visit(join2(directory, entry.name), relativePath);
1007
+ else if (entry.isFile()) files.push(relativePath);
1008
+ else if (entry.isSymbolicLink() && (await stat(join2(directory, entry.name))).isFile()) files.push(relativePath);
1009
+ }
1010
+ };
1011
+ await visit(path, "");
1012
+ return files;
1013
+ },
1014
+ read: (locator, offset, length, expectedSize) => {
1015
+ if (!locator.startsWith("hyperdb-node-file:") && !locator.startsWith("hyperdb-node-host-file:")) return void 0;
1016
+ return (async () => {
1017
+ const file = externalFiles.get(locator);
1018
+ if (file === void 0) throw new Error("External file registration does not exist");
1019
+ const end = offset + length;
1020
+ if (expectedSize !== file.size || !Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 0 || !Number.isSafeInteger(end) || end > file.size) {
1021
+ throw new Error("External file byte range is invalid");
1022
+ }
1023
+ const result = new Uint8Array(length);
1024
+ let bytesRead = 0;
1025
+ while (bytesRead < length) {
1026
+ const count = await new Promise((resolveRead, rejectRead) => {
1027
+ read(file.descriptor, result, bytesRead, length - bytesRead, offset + bytesRead, (error, readCount) => {
1028
+ if (error) rejectRead(error);
1029
+ else resolveRead(readCount);
1030
+ });
1031
+ });
1032
+ if (count === 0) throw new Error("External file ended before the registered size");
1033
+ bytesRead += count;
1034
+ }
1035
+ return result;
1036
+ })();
1037
+ },
1038
+ shutdown: () => {
1039
+ for (const { descriptor } of externalFiles.values()) closeSync(descriptor);
1040
+ externalFiles.clear();
1041
+ externalPaths.clear();
1042
+ }
1043
+ };
1044
+ const hostDirectories = /* @__PURE__ */ new Map();
353
1045
  runtime.resolveHyperDBPath = (path) => {
354
- const absolute = resolve(isAbsolute(path) ? path : join(hostPath, path));
355
- const suffix = relative(hostPath, absolute);
356
- if (suffix === ".." || suffix.startsWith(`..${sep}`) || isAbsolute(suffix)) {
357
- throw new Error(`Arrow IPC path is outside the mounted host directory: ${path}`);
1046
+ const absolute = resolve2(path);
1047
+ const directory = dirname(absolute);
1048
+ let mount = hostDirectories.get(directory);
1049
+ if (mount === void 0) {
1050
+ mount = `/host/${hostDirectories.size}`;
1051
+ runtime.FS.mkdirTree(mount);
1052
+ runtime.FS.mount(nodeFS, { root: directory }, mount);
1053
+ hostDirectories.set(directory, mount);
358
1054
  }
359
- return posix.join("/host", ...suffix.split(sep));
1055
+ return posix.join(mount, basename2(absolute));
360
1056
  };
361
1057
  });