hyperdb-wasm 0.0.26862-dev.wasm.g89537caa → 0.0.27000-dev.wasm.g0bde3882
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 +15 -19
- package/dist/browser.js +91 -60
- package/dist/browser_external_directory.d.ts +13 -0
- package/dist/browser_worker.js +217 -100
- package/dist/client.d.ts +4 -5
- package/dist/hyperdb-wasm.cjs +1 -1
- package/dist/hyperdb-wasm.js +1 -1
- package/dist/hyperdb-wasm.wasm.br +0 -0
- package/dist/index.d.ts +9 -8
- package/dist/index.node.d.ts +4 -4
- package/dist/node.js +74 -60
- package/dist/node_external_directory.d.ts +11 -0
- package/dist/node_worker.js +254 -117
- package/dist/protocol.d.ts +17 -18
- package/dist/raw.browser.js +122 -74
- package/dist/raw.d.ts +2 -1
- package/dist/raw.node.js +122 -74
- package/dist/wasm_bindings.d.ts +21 -1
- package/dist/worker_setup.d.ts +4 -2
- package/package.json +1 -1
package/dist/browser_worker.js
CHANGED
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
if (!value) throw new Error(`${name} must not be empty`);
|
|
17
17
|
if (value.includes("\0")) throw new Error(`${name} must not contain NUL`);
|
|
18
18
|
}
|
|
19
|
+
function resultError(result) {
|
|
20
|
+
if (result.state === "error" || result.state === "busy") {
|
|
21
|
+
return typeof result.error === "string" ? result.error : result.error.message;
|
|
22
|
+
}
|
|
23
|
+
return `unexpected Wasm result state: ${result.state}`;
|
|
24
|
+
}
|
|
19
25
|
function decodeStructuredError(payload) {
|
|
20
26
|
if (!structuredErrorMagic.every((byte, index) => payload[index] === byte)) throw new Error("structured error has invalid magic");
|
|
21
27
|
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
@@ -52,6 +58,8 @@
|
|
|
52
58
|
registerExternalFile;
|
|
53
59
|
registerExternalDirectory;
|
|
54
60
|
removeExternalRegistration;
|
|
61
|
+
projectExternalFile;
|
|
62
|
+
projectExternalDirectory;
|
|
55
63
|
startQuery;
|
|
56
64
|
insertArrowIPCFromPath;
|
|
57
65
|
pollQuery;
|
|
@@ -61,6 +69,7 @@
|
|
|
61
69
|
resultPayloadLength;
|
|
62
70
|
free;
|
|
63
71
|
nextTemporaryFile = 1;
|
|
72
|
+
mountedPaths = /* @__PURE__ */ new Set();
|
|
64
73
|
constructor(module) {
|
|
65
74
|
this.module = module;
|
|
66
75
|
this.initialize = module.cwrap("hyperdb_wasm_initialize", "pointer", ["string", "string"]);
|
|
@@ -70,6 +79,8 @@
|
|
|
70
79
|
this.registerExternalFile = module.cwrap("hyperdb_wasm_register_external_file", "pointer", ["string", "string", "number"]);
|
|
71
80
|
this.registerExternalDirectory = module.cwrap("hyperdb_wasm_register_external_directory", "pointer", ["string", "string", "number"]);
|
|
72
81
|
this.removeExternalRegistration = module.cwrap("hyperdb_wasm_remove_external_registration", "pointer", ["string"]);
|
|
82
|
+
this.projectExternalFile = module.cwrap("hyperdb_wasm_project_external_file", "pointer", ["string", "string", "number"]);
|
|
83
|
+
this.projectExternalDirectory = module.cwrap("hyperdb_wasm_project_external_directory", "pointer", ["string", "string"]);
|
|
73
84
|
this.startQuery = module.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string", "number"]);
|
|
74
85
|
this.insertArrowIPCFromPath = module.cwrap("hyperdb_wasm_arrow_insert_from_path", "pointer", ["number", "string", "string", "string", "number"]);
|
|
75
86
|
this.pollQuery = module.cwrap("hyperdb_wasm_query_poll", "pointer", ["number"]);
|
|
@@ -90,77 +101,40 @@
|
|
|
90
101
|
this.module.FS.unlink(request.path);
|
|
91
102
|
return { response: { id: request.id, type: "response", result: { state: "ok", payload: new Uint8Array() } }, transfer: [] };
|
|
92
103
|
}
|
|
93
|
-
if (request.type === "
|
|
104
|
+
if (request.type === "mount" && request.source.kind === "node-path") {
|
|
94
105
|
if (!this.module.externalFileBackend) {
|
|
95
106
|
return { response: { id: request.id, type: "response", result: { state: "error", error: "Host external files are supported only by the Node client" } }, transfer: [] };
|
|
96
107
|
}
|
|
97
|
-
const registration = this.module.externalFileBackend.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const result2 = this.decodeResult(pointer2);
|
|
101
|
-
if (result2.state === "ok") {
|
|
102
|
-
const path = new TextDecoder("utf-8", { fatal: true }).decode(result2.payload);
|
|
103
|
-
try {
|
|
104
|
-
this.module.externalFileBackend.registered(path, registration.locator);
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const cleanupPointer = safeInteger(this.removeExternalRegistration(path), "result pointer");
|
|
107
|
-
this.decodeResult(cleanupPointer);
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
this.module.externalFileBackend.removed(registration.locator);
|
|
112
|
-
}
|
|
113
|
-
return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
|
|
114
|
-
} catch (error) {
|
|
115
|
-
this.module.externalFileBackend.removed(registration.locator);
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
108
|
+
const registration = this.module.externalFileBackend.mount(request.source.path, request.mode);
|
|
109
|
+
if (registration.kind === "directory") return this.mountDirectory(request, request.source.path);
|
|
110
|
+
return this.mountFile(request, registration.locator, registration.size, this.module.externalFileBackend);
|
|
118
111
|
}
|
|
119
|
-
if (request.type === "
|
|
112
|
+
if (request.type === "mount" && request.source.kind === "browser-file" && !this.module.externalFileBackendAvailable) {
|
|
120
113
|
return { response: { id: request.id, type: "response", result: { state: "error", error: "Blob external files are supported only by the browser client" } }, transfer: [] };
|
|
121
114
|
}
|
|
122
|
-
if (request.type === "
|
|
123
|
-
|
|
124
|
-
if (!backend) {
|
|
125
|
-
return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
|
|
126
|
-
}
|
|
127
|
-
const registration = backend.register("handle" in request ? request.handle : request.path, request.mode);
|
|
128
|
-
try {
|
|
129
|
-
const pointer2 = safeInteger(this.registerExternalDirectory(registration.name, registration.locator, Number(request.mode === "readwrite")), "result pointer");
|
|
130
|
-
const result2 = this.decodeResult(pointer2);
|
|
131
|
-
if (result2.state === "ok") {
|
|
132
|
-
const path = new TextDecoder("utf-8", { fatal: true }).decode(result2.payload);
|
|
133
|
-
try {
|
|
134
|
-
backend.registered(path, registration.locator);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
const cleanupPointer = safeInteger(this.removeExternalRegistration(path), "result pointer");
|
|
137
|
-
this.decodeResult(cleanupPointer);
|
|
138
|
-
throw error;
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
const completion = completionFor([backend.removed(registration.locator)]);
|
|
142
|
-
return { response: { id: request.id, type: "response", result: result2 }, transfer: [], ...completion && { completion } };
|
|
143
|
-
}
|
|
144
|
-
return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
|
|
145
|
-
} catch (error) {
|
|
146
|
-
void Promise.resolve(backend.removed(registration.locator)).catch(() => void 0);
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
115
|
+
if (request.type === "mount" && request.source.kind === "browser-directory") {
|
|
116
|
+
return this.mountDirectory(request, request.source.handle);
|
|
149
117
|
}
|
|
150
|
-
if (request.type === "
|
|
118
|
+
if (request.type === "mount" && request.source.kind === "browser-file") {
|
|
119
|
+
return this.mountFile(request, request.source.url, safeInteger(request.source.size, "external file size"));
|
|
120
|
+
}
|
|
121
|
+
if (request.type === "unmount" && (this.module.externalFileBackend || this.module.externalDirectoryBackend)) {
|
|
151
122
|
validateCString(request.path, "external registration path");
|
|
152
123
|
const pointer2 = safeInteger(this.removeExternalRegistration(request.path), "result pointer");
|
|
153
124
|
const result2 = this.decodeResult(pointer2);
|
|
154
|
-
if (result2.state === "ok") {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
125
|
+
if (result2.state === "ok" || result2.state === "error" && result2.error === "External registration was not found" && this.mountedPaths.has(request.path)) {
|
|
126
|
+
const cleanup = async () => {
|
|
127
|
+
await Promise.all([
|
|
128
|
+
this.module.externalFileBackend?.removed(request.path),
|
|
129
|
+
this.module.externalDirectoryBackend?.removed(request.path)
|
|
130
|
+
]);
|
|
131
|
+
this.mountedPaths.delete(request.path);
|
|
132
|
+
};
|
|
133
|
+
return { response: { id: request.id, type: "response", result: { state: "ok", payload: new Uint8Array() } }, transfer: [], completion: cleanup() };
|
|
160
134
|
}
|
|
161
135
|
return { response: { id: request.id, type: "response", result: result2 }, transfer: [] };
|
|
162
136
|
}
|
|
163
|
-
if (request.type === "
|
|
137
|
+
if (request.type === "unmount" && !this.module.externalFileBackendAvailable) {
|
|
164
138
|
return { response: { id: request.id, type: "response", result: { state: "error", error: "External files are not supported by this client" } }, transfer: [] };
|
|
165
139
|
}
|
|
166
140
|
const pointer = safeInteger(this.invoke(request), "result pointer");
|
|
@@ -190,20 +164,15 @@
|
|
|
190
164
|
return this.disconnect(request.connection);
|
|
191
165
|
case "shutdown":
|
|
192
166
|
return this.shutdown();
|
|
193
|
-
case "
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
validateCString(request.url, "external file URL");
|
|
197
|
-
return this.registerExternalFile(request.name, request.url, safeInteger(request.size, "external file size"));
|
|
198
|
-
case "registerExternalDirectory":
|
|
199
|
-
throw new Error("external directory request has no backend");
|
|
200
|
-
case "removeExternalRegistration":
|
|
167
|
+
case "mount":
|
|
168
|
+
throw new Error("mount request has no backend");
|
|
169
|
+
case "unmount":
|
|
201
170
|
validateCString(request.path, "external registration path");
|
|
202
171
|
return this.removeExternalRegistration(request.path);
|
|
203
172
|
case "startQuery":
|
|
204
173
|
return this.startQuery(request.connection, request.sql, Number(request.sqlTestMode ?? false));
|
|
205
174
|
case "insertArrowIPCFromPath": {
|
|
206
|
-
const path = request.internal ? request.path : this.module.resolveHyperDBPath?.(request.path) ?? request.path;
|
|
175
|
+
const path = request.internal || request.path.startsWith("/mnt/external/") ? request.path : this.module.resolveHyperDBPath?.(request.path) ?? request.path;
|
|
207
176
|
return this.insertArrowIPCFromPath(request.connection, path, request.name, request.schema ?? "", Number(request.create));
|
|
208
177
|
}
|
|
209
178
|
case "pollQuery":
|
|
@@ -214,6 +183,88 @@
|
|
|
214
183
|
return this.finishQuery(request.query);
|
|
215
184
|
}
|
|
216
185
|
}
|
|
186
|
+
mountFile(request, locator, size, backend) {
|
|
187
|
+
validateCString(request.path, "external registration path");
|
|
188
|
+
validateCString(locator, "external file locator");
|
|
189
|
+
let path;
|
|
190
|
+
let registered = false;
|
|
191
|
+
let rollbackError;
|
|
192
|
+
try {
|
|
193
|
+
const result = this.decodeResult(safeInteger(this.registerExternalFile(request.path, locator, size), "result pointer"));
|
|
194
|
+
if (result.state !== "ok") {
|
|
195
|
+
backend?.removed(locator);
|
|
196
|
+
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
197
|
+
}
|
|
198
|
+
path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
|
|
199
|
+
const projection = this.decodeResult(safeInteger(this.projectExternalFile(path, locator, size), "result pointer"));
|
|
200
|
+
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
201
|
+
backend?.registered(path, locator);
|
|
202
|
+
registered = true;
|
|
203
|
+
this.mountedPaths.add(path);
|
|
204
|
+
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (path !== void 0) {
|
|
207
|
+
try {
|
|
208
|
+
const rollback = this.decodeResult(safeInteger(this.removeExternalRegistration(path), "result pointer"));
|
|
209
|
+
if (rollback.state !== "ok") rollbackError = new Error(resultError(rollback));
|
|
210
|
+
} catch (failure) {
|
|
211
|
+
rollbackError = failure;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (rollbackError !== void 0) {
|
|
215
|
+
if (path !== void 0) {
|
|
216
|
+
backend?.registered(path, locator);
|
|
217
|
+
this.mountedPaths.add(path);
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`mount failed and rollback also failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, { cause: error });
|
|
220
|
+
}
|
|
221
|
+
backend?.removed(registered && path !== void 0 ? path : locator);
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
mountDirectory(request, source) {
|
|
226
|
+
const backend = this.module.externalDirectoryBackend;
|
|
227
|
+
if (!backend) {
|
|
228
|
+
return { response: { id: request.id, type: "response", result: { state: "error", error: "External directories are not supported by this client" } }, transfer: [] };
|
|
229
|
+
}
|
|
230
|
+
const registration = backend.register(source, request.mode);
|
|
231
|
+
let preserveBackend = false;
|
|
232
|
+
try {
|
|
233
|
+
const pointer = safeInteger(this.registerExternalDirectory(request.path, registration.locator, Number(request.mode === "readwrite")), "result pointer");
|
|
234
|
+
const result = this.decodeResult(pointer);
|
|
235
|
+
if (result.state === "ok") {
|
|
236
|
+
const path = new TextDecoder("utf-8", { fatal: true }).decode(result.payload);
|
|
237
|
+
try {
|
|
238
|
+
const projectionPointer = this.projectExternalDirectory(path, registration.locator);
|
|
239
|
+
const projection = this.decodeResult(safeInteger(projectionPointer, "result pointer"));
|
|
240
|
+
if (projection.state !== "ok") throw new Error(resultError(projection));
|
|
241
|
+
backend.registered(path, registration.locator);
|
|
242
|
+
this.mountedPaths.add(path);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
const cleanupPointer = safeInteger(this.removeExternalRegistration(path), "result pointer");
|
|
245
|
+
const cleanup = this.decodeResult(cleanupPointer);
|
|
246
|
+
if (cleanup.state !== "ok") {
|
|
247
|
+
backend.registered(path, registration.locator);
|
|
248
|
+
this.mountedPaths.add(path);
|
|
249
|
+
preserveBackend = true;
|
|
250
|
+
throw new Error(`mount failed and rollback also failed: ${resultError(cleanup)}`, { cause: error });
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
const completion = completionFor([backend.removed(registration.locator)]);
|
|
256
|
+
return { response: { id: request.id, type: "response", result }, transfer: [], ...completion && { completion } };
|
|
257
|
+
}
|
|
258
|
+
return { response: { id: request.id, type: "response", result }, transfer: [] };
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (preserveBackend) throw error;
|
|
261
|
+
return {
|
|
262
|
+
response: { id: request.id, type: "response", result: { state: "error", error: error instanceof Error ? error.message : String(error) } },
|
|
263
|
+
transfer: [],
|
|
264
|
+
completion: Promise.resolve(backend.removed(registration.locator))
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
217
268
|
decodeResult(pointer) {
|
|
218
269
|
if (pointer === 0) return { state: "error", error: "out of memory" };
|
|
219
270
|
try {
|
|
@@ -278,12 +329,21 @@
|
|
|
278
329
|
return hasKeys(value, ["id", "type", "bytes"]) && value.bytes instanceof Uint8Array;
|
|
279
330
|
case "removeFile":
|
|
280
331
|
return hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string";
|
|
281
|
-
case "
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
332
|
+
case "mount": {
|
|
333
|
+
if (!hasKeys(value, ["id", "type", "source", "path", "mode"]) || typeof value.path !== "string" || !value.path.startsWith("/mnt/external/") || new TextEncoder().encode(value.path.slice("/mnt/external/".length)).byteLength === 0 || new TextEncoder().encode(value.path.slice("/mnt/external/".length)).byteLength > 200 || value.path.slice("/mnt/external/".length).split("/").some((component) => !component || component === "." || component === ".." || !/^[A-Za-z0-9._-]+$/.test(component)) || value.mode !== "read" && value.mode !== "readwrite" || !isRecord(value.source)) return false;
|
|
334
|
+
switch (value.source.kind) {
|
|
335
|
+
case "browser-file":
|
|
336
|
+
return value.mode === "read" && hasKeys(value.source, ["kind", "name", "url", "size"]) && typeof value.source.name === "string" && typeof value.source.url === "string" && typeof value.source.size === "number" && Number.isSafeInteger(value.source.size) && value.source.size >= 0;
|
|
337
|
+
case "browser-directory":
|
|
338
|
+
return hasKeys(value.source, ["kind", "handle"]) && isRecord(value.source.handle) && value.source.handle.kind === "directory";
|
|
339
|
+
case "node-path":
|
|
340
|
+
return hasKeys(value.source, ["kind", "path"]) && typeof value.source.path === "string" && !!value.source.path && !value.source.path.includes("\0");
|
|
341
|
+
default:
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
case "unmount":
|
|
346
|
+
return hasKeys(value, ["id", "type", "path"]) && typeof value.path === "string" && !!value.path && !value.path.includes("\0");
|
|
287
347
|
case "pollQuery":
|
|
288
348
|
case "cancelQuery":
|
|
289
349
|
case "finishQuery":
|
|
@@ -371,33 +431,6 @@
|
|
|
371
431
|
module._hyperdb_wasm_fetch_complete(id, 0, -1);
|
|
372
432
|
});
|
|
373
433
|
};
|
|
374
|
-
module.hyperdbWasmHttpStart = (id, method, url, headerLines, bodyPointer, bodyLength) => {
|
|
375
|
-
const headers = new Headers();
|
|
376
|
-
for (const line of headerLines.split("\n")) {
|
|
377
|
-
const separator = line.indexOf(":");
|
|
378
|
-
if (separator > 0) headers.set(line.slice(0, separator), line.slice(separator + 1));
|
|
379
|
-
}
|
|
380
|
-
const pointer = safePointer(bodyPointer, "HTTP body pointer");
|
|
381
|
-
const bodyEnd = safePointer(pointer + bodyLength, "HTTP body end");
|
|
382
|
-
if (bodyEnd > module.HEAPU8.byteLength) throw new Error("HTTP body exceeds the wasm heap");
|
|
383
|
-
const body = bodyLength > 0 ? module.HEAPU8.slice(pointer, bodyEnd) : void 0;
|
|
384
|
-
const init = { method, headers };
|
|
385
|
-
if (body) init.body = body;
|
|
386
|
-
void fetch(url, init).then(async (response) => {
|
|
387
|
-
const responseBody = new Uint8Array(await response.arrayBuffer());
|
|
388
|
-
let responseHeaders = "";
|
|
389
|
-
response.headers.forEach((value, name) => {
|
|
390
|
-
responseHeaders += `${name.toLowerCase()}:${value}
|
|
391
|
-
`;
|
|
392
|
-
});
|
|
393
|
-
const headersPointer = responseHeaders ? safePointer(module.stringToNewUTF8(responseHeaders), "headers pointer") : 0;
|
|
394
|
-
const responseBodyPointer = responseBody.byteLength ? allocate(module, responseBody) : 0;
|
|
395
|
-
module._hyperdb_wasm_http_complete(id, response.status, headersPointer, responseBodyPointer, responseBody.byteLength);
|
|
396
|
-
}).catch((error) => {
|
|
397
|
-
const errorBytes = new TextEncoder().encode(errorText(error));
|
|
398
|
-
module._hyperdb_wasm_http_complete(id, -1, 0, allocate(module, errorBytes), errorBytes.byteLength);
|
|
399
|
-
});
|
|
400
|
-
};
|
|
401
434
|
}
|
|
402
435
|
function installExternalDirectoryRuntime(module) {
|
|
403
436
|
const backend = module.externalDirectoryBackend;
|
|
@@ -490,6 +523,54 @@
|
|
|
490
523
|
};
|
|
491
524
|
}
|
|
492
525
|
}
|
|
526
|
+
function installFilesystemProjectionRuntime(module) {
|
|
527
|
+
const complete = module._hyperdb_wasm_filesystem_operation_complete;
|
|
528
|
+
if (!complete) return;
|
|
529
|
+
module.hyperdbWasmFilesystemListStart = (id, locator, path) => {
|
|
530
|
+
const listing = module.externalDirectoryBackend?.listEntries?.(locator, path);
|
|
531
|
+
if (!listing) {
|
|
532
|
+
complete(id, 0, -1);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
void listing.then((entries) => {
|
|
536
|
+
const encoded = entries.map((entry) => `${entry.kind === "file" ? "f" : "d"}:${entry.name}`);
|
|
537
|
+
complete(id, safePointer(module.stringToNewUTF8(JSON.stringify(encoded)), "filesystem listing pointer"), 0);
|
|
538
|
+
}).catch(() => complete(id, 0, -1));
|
|
539
|
+
};
|
|
540
|
+
module.hyperdbWasmFilesystemLookupStart = (id, locator, path) => {
|
|
541
|
+
const lookup = module.externalDirectoryBackend?.lookup?.(locator, path);
|
|
542
|
+
if (!lookup) {
|
|
543
|
+
complete(id, 0, -1);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
void lookup.then((entry) => {
|
|
547
|
+
if (entry === void 0) complete(id, 0, -2);
|
|
548
|
+
else if (entry.kind === "directory") complete(id, 0, -21);
|
|
549
|
+
else complete(id, safePointer(module.stringToNewUTF8(entry.locator), "filesystem file locator pointer"), entry.size);
|
|
550
|
+
}).catch(() => complete(id, 0, -1));
|
|
551
|
+
};
|
|
552
|
+
module.hyperdbWasmFilesystemReadStart = (id, locator, offset, length, expectedSize, pointerValue) => {
|
|
553
|
+
const read = module.externalFileBackend?.read(locator, offset, length, expectedSize) ?? module.externalDirectoryBackend?.read(locator, offset, length, expectedSize) ?? (locator.startsWith("blob:") ? fetch(locator, { headers: { Range: `bytes=${offset}-${offset + length - 1}` } }).then(async (response) => {
|
|
554
|
+
const contentRange = /^bytes (\d+)-(\d+)\/(\d+)$/.exec(response.headers.get("content-range") ?? "");
|
|
555
|
+
if (response.status !== 206 || !contentRange || contentRange[1] !== String(offset) || contentRange[2] !== String(offset + length - 1) || contentRange[3] !== String(expectedSize)) {
|
|
556
|
+
throw new Error("Blob URL returned an invalid byte range");
|
|
557
|
+
}
|
|
558
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
559
|
+
}) : void 0);
|
|
560
|
+
if (!read) {
|
|
561
|
+
complete(id, 0, -1);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
void read.then((bytes) => {
|
|
565
|
+
const pointer = safePointer(pointerValue, "filesystem read pointer");
|
|
566
|
+
const end = safePointer(pointer + bytes.byteLength, "filesystem read end");
|
|
567
|
+
module.getValue(pointer, "i8");
|
|
568
|
+
if (bytes.byteLength !== length || end > module.HEAPU8.byteLength) throw new Error("filesystem read returned an invalid payload");
|
|
569
|
+
module.HEAPU8.set(bytes, pointer);
|
|
570
|
+
complete(id, 0, bytes.byteLength);
|
|
571
|
+
}).catch(() => complete(id, 0, -1));
|
|
572
|
+
};
|
|
573
|
+
}
|
|
493
574
|
function configureWorker(module, transport, configureFilesystem = () => void 0) {
|
|
494
575
|
const queuedRequests = [];
|
|
495
576
|
let bindings;
|
|
@@ -523,6 +604,7 @@
|
|
|
523
604
|
installHttpRuntime(module, transport);
|
|
524
605
|
installHostFileRuntime(module);
|
|
525
606
|
installExternalDirectoryRuntime(module);
|
|
607
|
+
installFilesystemProjectionRuntime(module);
|
|
526
608
|
bindings = new HyperDBBindings(module);
|
|
527
609
|
transport.postMessage({ type: "ready" });
|
|
528
610
|
for (const request of queuedRequests.splice(0)) handle(request);
|
|
@@ -608,6 +690,32 @@
|
|
|
608
690
|
files.sort();
|
|
609
691
|
return files;
|
|
610
692
|
}
|
|
693
|
+
async listEntries(locator, path) {
|
|
694
|
+
const handle = await this.openDirectory(this.getDirectory(locator), directorySegments(path));
|
|
695
|
+
const entries = [];
|
|
696
|
+
for await (const child of handle.values()) {
|
|
697
|
+
validateSegment(child.name);
|
|
698
|
+
if (child.kind !== "file" && child.kind !== "directory") throw new Error("External directory contains an unsupported entry");
|
|
699
|
+
entries.push({ name: child.name, kind: child.kind });
|
|
700
|
+
}
|
|
701
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
702
|
+
return entries;
|
|
703
|
+
}
|
|
704
|
+
async lookup(locator, path) {
|
|
705
|
+
const directory = this.getDirectory(locator);
|
|
706
|
+
const segments = relativeSegments(path);
|
|
707
|
+
const parent = await this.openDirectory(directory, segments.slice(0, -1));
|
|
708
|
+
const name = segments.at(-1);
|
|
709
|
+
if (!name) return { kind: "directory" };
|
|
710
|
+
for await (const child of parent.values()) {
|
|
711
|
+
validateSegment(child.name);
|
|
712
|
+
if (child.name !== name) continue;
|
|
713
|
+
if (child.kind === "directory") return { kind: "directory" };
|
|
714
|
+
if (child.kind === "file") return { kind: "file", ...await this.open(locator, path) };
|
|
715
|
+
throw new Error("External directory contains an unsupported entry");
|
|
716
|
+
}
|
|
717
|
+
return void 0;
|
|
718
|
+
}
|
|
611
719
|
async open(locator, path) {
|
|
612
720
|
const directory = this.getDirectory(locator);
|
|
613
721
|
const cached = directory.files.get(path);
|
|
@@ -757,6 +865,15 @@
|
|
|
757
865
|
if (directory.mode !== "readwrite") throw new Error("External directory registration is not writable");
|
|
758
866
|
return directory;
|
|
759
867
|
}
|
|
868
|
+
async openDirectory(directory, segments) {
|
|
869
|
+
let handle = directory.handle;
|
|
870
|
+
for (const segment of segments) {
|
|
871
|
+
const child = await handle.getDirectoryHandle(segment);
|
|
872
|
+
if (child.kind !== "directory" || child.name !== segment) throw new Error("External directory returned an unexpected child handle");
|
|
873
|
+
handle = child;
|
|
874
|
+
}
|
|
875
|
+
return handle;
|
|
876
|
+
}
|
|
760
877
|
getOpenWriter(writer) {
|
|
761
878
|
nonnegativeSafeInteger(writer, "External directory writer");
|
|
762
879
|
const state = this.writers.get(writer);
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MountAccessMode } from './api.js';
|
|
2
|
+
import type { MountSource, SetupProgress, WasmResult, WorkerTransport } from './protocol.js';
|
|
2
3
|
export declare class HyperDBEngineClient {
|
|
3
4
|
private readonly onSetupProgress?;
|
|
4
5
|
private readonly transport;
|
|
@@ -19,10 +20,8 @@ export declare class HyperDBEngineClient {
|
|
|
19
20
|
insertArrowIPCFromPath(connection: number, path: string, name: string, schema: string | null, create: boolean, internal?: boolean): Promise<WasmResult>;
|
|
20
21
|
createTemporaryFile(bytes: Uint8Array): Promise<WasmResult>;
|
|
21
22
|
removeFile(path: string): Promise<WasmResult>;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
registerExternalDirectory(source: unknown, mode: 'read' | 'readwrite'): Promise<WasmResult>;
|
|
25
|
-
removeExternalRegistration(path: string): Promise<WasmResult>;
|
|
23
|
+
mount(source: MountSource, path: string, mode: MountAccessMode): Promise<WasmResult>;
|
|
24
|
+
unmount(path: string): Promise<WasmResult>;
|
|
26
25
|
pollQuery(query: number): Promise<WasmResult>;
|
|
27
26
|
cancelQuery(query: number): Promise<WasmResult>;
|
|
28
27
|
finishQuery(query: number): Promise<WasmResult>;
|