hyperdb-wasm 0.0.26268-dev.remotes-origin-wasm.gcf8d815a
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 +61 -0
- package/dist/browser.d.ts +25 -0
- package/dist/browser.js +12323 -0
- package/dist/browser_worker.d.ts +1 -0
- package/dist/browser_worker.js +271 -0
- package/dist/client.d.ts +26 -0
- package/dist/hyperdb-wasm.cjs +3 -0
- package/dist/hyperdb-wasm.js +3 -0
- package/dist/hyperdb-wasm.wasm.br +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.node.d.ts +7 -0
- package/dist/node.d.ts +26 -0
- package/dist/node.js +532 -0
- package/dist/node_worker.d.ts +1 -0
- package/dist/node_worker.js +277 -0
- package/dist/protocol.d.ts +84 -0
- package/dist/raw.browser.d.ts +3 -0
- package/dist/raw.browser.js +279 -0
- package/dist/raw.d.ts +3 -0
- package/dist/raw.node.d.ts +3 -0
- package/dist/raw.node.js +281 -0
- package/dist/wasm_bindings.d.ts +37 -0
- package/dist/worker_setup.d.ts +25 -0
- package/examples/browser.html +84 -0
- package/examples/node.mjs +68 -0
- package/examples/serve.mjs +35 -0
- package/package.json +41 -0
package/dist/raw.node.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// protocol.ts
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null;
|
|
4
|
+
}
|
|
5
|
+
function hasKeys(value, keys) {
|
|
6
|
+
const actual = Object.keys(value).sort();
|
|
7
|
+
const expected = [...keys].sort();
|
|
8
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
9
|
+
}
|
|
10
|
+
function isWasmResult(value) {
|
|
11
|
+
if (!isRecord(value) || typeof value.state !== "string") return false;
|
|
12
|
+
switch (value.state) {
|
|
13
|
+
case "ok":
|
|
14
|
+
case "chunk":
|
|
15
|
+
return hasKeys(value, ["state", "payload"]) && value.payload instanceof Uint8Array;
|
|
16
|
+
case "pending":
|
|
17
|
+
case "done":
|
|
18
|
+
return hasKeys(value, ["state"]);
|
|
19
|
+
case "error":
|
|
20
|
+
case "busy":
|
|
21
|
+
return hasKeys(value, ["state", "error"]) && typeof value.error === "string";
|
|
22
|
+
default:
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isWorkerEvent(message) {
|
|
27
|
+
if (!isRecord(message) || typeof message.type !== "string") return false;
|
|
28
|
+
switch (message.type) {
|
|
29
|
+
case "ready":
|
|
30
|
+
return hasKeys(message, ["type"]);
|
|
31
|
+
case "fatal":
|
|
32
|
+
return hasKeys(message, ["type", "error"]) && typeof message.error === "string";
|
|
33
|
+
case "log":
|
|
34
|
+
return hasKeys(message, ["type", "text"]) && typeof message.text === "string";
|
|
35
|
+
case "response":
|
|
36
|
+
return hasKeys(message, ["id", "type", "result"]) && Number.isSafeInteger(message.id) && isWasmResult(message.result);
|
|
37
|
+
default:
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// client.ts
|
|
43
|
+
var HyperDBEngineClient = class {
|
|
44
|
+
transport;
|
|
45
|
+
pending = /* @__PURE__ */ new Map();
|
|
46
|
+
readyPromise;
|
|
47
|
+
resolveReady;
|
|
48
|
+
rejectReady;
|
|
49
|
+
nextId = 1;
|
|
50
|
+
failed;
|
|
51
|
+
unsubscribe;
|
|
52
|
+
constructor(transport) {
|
|
53
|
+
this.transport = transport;
|
|
54
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
55
|
+
this.resolveReady = resolve;
|
|
56
|
+
this.rejectReady = reject;
|
|
57
|
+
});
|
|
58
|
+
this.unsubscribe = [
|
|
59
|
+
transport.onMessage((message) => this.onMessage(message)),
|
|
60
|
+
transport.onError((error) => this.fail(error)),
|
|
61
|
+
transport.onClose((error) => this.fail(error))
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
ready() {
|
|
65
|
+
return this.readyPromise;
|
|
66
|
+
}
|
|
67
|
+
connect() {
|
|
68
|
+
return this.request({ id: this.nextId++, type: "connect" });
|
|
69
|
+
}
|
|
70
|
+
disconnect(connection) {
|
|
71
|
+
return this.request({ id: this.nextId++, type: "disconnect", connection });
|
|
72
|
+
}
|
|
73
|
+
startQuery(connection, sql) {
|
|
74
|
+
return this.request({ id: this.nextId++, type: "startQuery", connection, sql });
|
|
75
|
+
}
|
|
76
|
+
insertArrowIPCFromPath(connection, path, name, schema, create, internal = false) {
|
|
77
|
+
return this.request({ id: this.nextId++, type: "insertArrowIPCFromPath", connection, path, name, schema, create, internal });
|
|
78
|
+
}
|
|
79
|
+
createTemporaryFile(bytes) {
|
|
80
|
+
const payload = bytes.slice();
|
|
81
|
+
return this.request({ id: this.nextId++, type: "createTemporaryFile", bytes: payload }, [payload.buffer]);
|
|
82
|
+
}
|
|
83
|
+
removeFile(path) {
|
|
84
|
+
return this.request({ id: this.nextId++, type: "removeFile", path });
|
|
85
|
+
}
|
|
86
|
+
pollQuery(query) {
|
|
87
|
+
return this.request({ id: this.nextId++, type: "pollQuery", query });
|
|
88
|
+
}
|
|
89
|
+
cancelQuery(query) {
|
|
90
|
+
return this.request({ id: this.nextId++, type: "cancelQuery", query });
|
|
91
|
+
}
|
|
92
|
+
releaseQuery(query) {
|
|
93
|
+
return this.request({ id: this.nextId++, type: "releaseQuery", query });
|
|
94
|
+
}
|
|
95
|
+
async terminate() {
|
|
96
|
+
this.fail(new Error("engine worker terminated"));
|
|
97
|
+
await this.transport.terminate();
|
|
98
|
+
}
|
|
99
|
+
request(request, transfer = []) {
|
|
100
|
+
if (this.failed) return Promise.reject(this.failed);
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
this.pending.set(request.id, { resolve, reject });
|
|
103
|
+
try {
|
|
104
|
+
this.transport.postMessage(request, transfer);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.pending.delete(request.id);
|
|
107
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
onMessage(message) {
|
|
112
|
+
if (!isWorkerEvent(message)) return;
|
|
113
|
+
if (message.type === "ready") {
|
|
114
|
+
this.resolveReady();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (message.type === "fatal") {
|
|
118
|
+
this.fail(new Error(message.error || "engine aborted"));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (message.type !== "response") return;
|
|
122
|
+
const pending = this.pending.get(message.id);
|
|
123
|
+
if (!pending) return;
|
|
124
|
+
this.pending.delete(message.id);
|
|
125
|
+
pending.resolve(message.result);
|
|
126
|
+
}
|
|
127
|
+
fail(error) {
|
|
128
|
+
if (this.failed) return;
|
|
129
|
+
this.failed = error;
|
|
130
|
+
for (const unsubscribe of this.unsubscribe.splice(0)) unsubscribe();
|
|
131
|
+
this.rejectReady(error);
|
|
132
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
133
|
+
this.pending.clear();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// wasm_bindings.ts
|
|
138
|
+
var resultStates = ["ok", "pending", "chunk", "done", "error", "busy"];
|
|
139
|
+
function safeInteger(value, name) {
|
|
140
|
+
const number = typeof value === "bigint" && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
|
|
141
|
+
if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 0) throw new Error(`${name} must be a nonnegative safe integer`);
|
|
142
|
+
return number;
|
|
143
|
+
}
|
|
144
|
+
var HyperDBBindings = class {
|
|
145
|
+
module;
|
|
146
|
+
connect;
|
|
147
|
+
disconnect;
|
|
148
|
+
startQuery;
|
|
149
|
+
insertArrowIPCFromPath;
|
|
150
|
+
pollQuery;
|
|
151
|
+
cancelQuery;
|
|
152
|
+
releaseQuery;
|
|
153
|
+
resultState;
|
|
154
|
+
resultPayloadLength;
|
|
155
|
+
free;
|
|
156
|
+
nextTemporaryFile = 1;
|
|
157
|
+
constructor(module) {
|
|
158
|
+
this.module = module;
|
|
159
|
+
this.connect = module.cwrap("hyperdb_wasm_connect", "pointer", []);
|
|
160
|
+
this.disconnect = module.cwrap("hyperdb_wasm_disconnect", "pointer", ["number"]);
|
|
161
|
+
this.startQuery = module.cwrap("hyperdb_wasm_query_start", "pointer", ["number", "string"]);
|
|
162
|
+
this.insertArrowIPCFromPath = module.cwrap("hyperdb_wasm_arrow_insert_from_path", "pointer", ["number", "string", "string", "string", "number"]);
|
|
163
|
+
this.pollQuery = module.cwrap("hyperdb_wasm_query_poll", "pointer", ["number"]);
|
|
164
|
+
this.cancelQuery = module.cwrap("hyperdb_wasm_query_cancel", "pointer", ["number"]);
|
|
165
|
+
this.releaseQuery = module.cwrap("hyperdb_wasm_query_release", "pointer", ["number"]);
|
|
166
|
+
this.resultState = module.cwrap("hyperdb_wasm_result_state", "number", ["number"]);
|
|
167
|
+
this.resultPayloadLength = module.cwrap("hyperdb_wasm_result_payload_len", "number", ["number"]);
|
|
168
|
+
this.free = module.cwrap("hyperdb_wasm_free", null, ["pointer"]);
|
|
169
|
+
}
|
|
170
|
+
handle(request) {
|
|
171
|
+
if (request.type === "createTemporaryFile") {
|
|
172
|
+
const path = `/tmp/hyperdb/arrow-${this.nextTemporaryFile++}.arrows`;
|
|
173
|
+
this.module.FS.mkdirTree("/tmp/hyperdb");
|
|
174
|
+
this.module.FS.writeFile(path, request.bytes);
|
|
175
|
+
return { response: { id: request.id, type: "response", result: { state: "ok", payload: new TextEncoder().encode(path) } }, transfer: [] };
|
|
176
|
+
}
|
|
177
|
+
if (request.type === "removeFile") {
|
|
178
|
+
this.module.FS.unlink(request.path);
|
|
179
|
+
return { response: { id: request.id, type: "response", result: { state: "ok", payload: new Uint8Array() } }, transfer: [] };
|
|
180
|
+
}
|
|
181
|
+
const pointer = safeInteger(this.invoke(request), "result pointer");
|
|
182
|
+
const result = this.decodeResult(pointer);
|
|
183
|
+
const response = { id: request.id, type: "response", result };
|
|
184
|
+
return { response, transfer: result.state === "chunk" ? [result.payload.buffer] : [] };
|
|
185
|
+
}
|
|
186
|
+
invoke(request) {
|
|
187
|
+
switch (request.type) {
|
|
188
|
+
case "connect":
|
|
189
|
+
return this.connect();
|
|
190
|
+
case "disconnect":
|
|
191
|
+
return this.disconnect(request.connection);
|
|
192
|
+
case "startQuery":
|
|
193
|
+
return this.startQuery(request.connection, request.sql);
|
|
194
|
+
case "insertArrowIPCFromPath": {
|
|
195
|
+
const path = request.internal ? request.path : this.module.resolveHyperDBPath?.(request.path) ?? request.path;
|
|
196
|
+
return this.insertArrowIPCFromPath(request.connection, path, request.name, request.schema ?? "", Number(request.create));
|
|
197
|
+
}
|
|
198
|
+
case "pollQuery":
|
|
199
|
+
return this.pollQuery(request.query);
|
|
200
|
+
case "cancelQuery":
|
|
201
|
+
return this.cancelQuery(request.query);
|
|
202
|
+
case "releaseQuery":
|
|
203
|
+
return this.releaseQuery(request.query);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
decodeResult(pointer) {
|
|
207
|
+
if (pointer === 0) return { state: "error", error: "out of memory" };
|
|
208
|
+
try {
|
|
209
|
+
const rawState = safeInteger(this.resultState(pointer), "result state");
|
|
210
|
+
const state = resultStates[rawState];
|
|
211
|
+
if (!state) return { state: "error", error: `invalid result state ${rawState}` };
|
|
212
|
+
const payloadLength = safeInteger(this.resultPayloadLength(pointer), "result payload length");
|
|
213
|
+
const payloadStart = safeInteger(pointer + 8, "result payload start");
|
|
214
|
+
const payloadEnd = safeInteger(payloadStart + payloadLength, "result payload end");
|
|
215
|
+
if (payloadEnd > this.module.HEAPU8.byteLength) throw new Error("result payload exceeds the wasm heap");
|
|
216
|
+
const payload = new Uint8Array(payloadLength);
|
|
217
|
+
payload.set(this.module.HEAPU8.subarray(payloadStart, payloadEnd));
|
|
218
|
+
switch (state) {
|
|
219
|
+
case "ok":
|
|
220
|
+
return { state, payload };
|
|
221
|
+
case "pending":
|
|
222
|
+
return { state };
|
|
223
|
+
case "chunk":
|
|
224
|
+
return { state, payload };
|
|
225
|
+
case "done":
|
|
226
|
+
return { state };
|
|
227
|
+
case "error":
|
|
228
|
+
return { state, error: new TextDecoder().decode(payload) };
|
|
229
|
+
case "busy":
|
|
230
|
+
return { state, error: new TextDecoder().decode(payload) };
|
|
231
|
+
}
|
|
232
|
+
} finally {
|
|
233
|
+
this.free(pointer);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// node.ts
|
|
239
|
+
import { Worker as NativeWorker } from "node:worker_threads";
|
|
240
|
+
import { fileURLToPath } from "node:url";
|
|
241
|
+
var NodeWorker = class {
|
|
242
|
+
constructor(worker) {
|
|
243
|
+
this.worker = worker;
|
|
244
|
+
}
|
|
245
|
+
worker;
|
|
246
|
+
postMessage(message, transfer = []) {
|
|
247
|
+
this.worker.postMessage(message, transfer);
|
|
248
|
+
}
|
|
249
|
+
onMessage(listener) {
|
|
250
|
+
const wrapped = (message) => listener(message);
|
|
251
|
+
this.worker.on("message", wrapped);
|
|
252
|
+
return () => this.worker.off("message", wrapped);
|
|
253
|
+
}
|
|
254
|
+
onError(listener) {
|
|
255
|
+
const wrapped = (error) => listener(error instanceof Error ? error : new Error(String(error)));
|
|
256
|
+
this.worker.on("error", wrapped);
|
|
257
|
+
return () => this.worker.off("error", wrapped);
|
|
258
|
+
}
|
|
259
|
+
onClose(listener) {
|
|
260
|
+
const wrapped = (code) => listener(new Error(`engine worker exited with code ${String(code)}`));
|
|
261
|
+
this.worker.on("exit", wrapped);
|
|
262
|
+
return () => this.worker.off("exit", wrapped);
|
|
263
|
+
}
|
|
264
|
+
terminate() {
|
|
265
|
+
return this.worker.terminate();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
function resolveNodeEnginePath(engineUrl) {
|
|
269
|
+
return fileURLToPath(engineUrl);
|
|
270
|
+
}
|
|
271
|
+
function createNodeClient(options = {}) {
|
|
272
|
+
const workerUrl = options.workerUrl ?? new URL("./node_worker.js", import.meta.url);
|
|
273
|
+
const engineUrl = options.engineUrl ?? resolveNodeEnginePath(new URL("./hyperdb-wasm.cjs", import.meta.url));
|
|
274
|
+
return new HyperDBEngineClient(new NodeWorker(new NativeWorker(workerUrl, { workerData: { engineUrl, hostPath: options.hostPath } })));
|
|
275
|
+
}
|
|
276
|
+
export {
|
|
277
|
+
HyperDBBindings,
|
|
278
|
+
HyperDBEngineClient,
|
|
279
|
+
NodeWorker,
|
|
280
|
+
createNodeClient
|
|
281
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EngineRequest, WasmResponse } from './protocol.js';
|
|
2
|
+
type CFunction = (...args: readonly unknown[]) => unknown;
|
|
3
|
+
export interface HyperDBWasmModule {
|
|
4
|
+
readonly HEAPU8: Uint8Array;
|
|
5
|
+
readonly FS: {
|
|
6
|
+
mkdirTree(path: string): void;
|
|
7
|
+
mount(type: unknown, options: Record<string, unknown>, path: string): void;
|
|
8
|
+
writeFile(path: string, bytes: Uint8Array): void;
|
|
9
|
+
unlink(path: string): void;
|
|
10
|
+
};
|
|
11
|
+
resolveHyperDBPath?(path: string): string;
|
|
12
|
+
_malloc(size: number): number | bigint;
|
|
13
|
+
cwrap(name: string, returnType?: string | null, argumentTypes?: readonly string[]): CFunction;
|
|
14
|
+
}
|
|
15
|
+
export type BindingResponse = {
|
|
16
|
+
response: WasmResponse;
|
|
17
|
+
transfer: readonly ArrayBuffer[];
|
|
18
|
+
};
|
|
19
|
+
export declare class HyperDBBindings {
|
|
20
|
+
private readonly module;
|
|
21
|
+
private readonly connect;
|
|
22
|
+
private readonly disconnect;
|
|
23
|
+
private readonly startQuery;
|
|
24
|
+
private readonly insertArrowIPCFromPath;
|
|
25
|
+
private readonly pollQuery;
|
|
26
|
+
private readonly cancelQuery;
|
|
27
|
+
private readonly releaseQuery;
|
|
28
|
+
private readonly resultState;
|
|
29
|
+
private readonly resultPayloadLength;
|
|
30
|
+
private readonly free;
|
|
31
|
+
private nextTemporaryFile;
|
|
32
|
+
constructor(module: HyperDBWasmModule);
|
|
33
|
+
handle(request: EngineRequest): BindingResponse;
|
|
34
|
+
private invoke;
|
|
35
|
+
private decodeResult;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HyperDBWasmModule } from './wasm_bindings.js';
|
|
2
|
+
import type { WorkerEvent } from './protocol.js';
|
|
3
|
+
export interface WorkerHostTransport {
|
|
4
|
+
postMessage(message: WorkerEvent, transfer?: readonly ArrayBuffer[]): void;
|
|
5
|
+
onMessage(listener: (message: unknown) => void): void;
|
|
6
|
+
}
|
|
7
|
+
export interface HyperDBWasmRuntimeModule extends HyperDBWasmModule {
|
|
8
|
+
HEAPU8: Uint8Array;
|
|
9
|
+
_malloc(size: number): number | bigint;
|
|
10
|
+
_hyperdb_wasm_fetch_complete(id: number, pointer: number, length: number): void;
|
|
11
|
+
_hyperdb_wasm_http_complete(id: number, status: number, headersPointer: number, bodyPointer: number, bodyLength: number): void;
|
|
12
|
+
stringToNewUTF8(value: string): number | bigint;
|
|
13
|
+
NODEFS?: unknown;
|
|
14
|
+
OPFS?: unknown;
|
|
15
|
+
resolveHyperDBPath?: (path: string) => string;
|
|
16
|
+
arguments?: string[];
|
|
17
|
+
print?: (text: string) => void;
|
|
18
|
+
printErr?: (text: string) => void;
|
|
19
|
+
onRuntimeInitialized?: () => void;
|
|
20
|
+
onAbort?: (reason: unknown) => void;
|
|
21
|
+
locateFile?: (path: string, prefix: string) => string;
|
|
22
|
+
hyperdbWasmFetchStart?: (id: number, url: string, offset: number, length: number) => void;
|
|
23
|
+
hyperdbWasmHttpStart?: (id: number, method: string, url: string, headers: string, bodyPointer: number | bigint, bodyLength: number) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare function configureWorker(module: HyperDBWasmRuntimeModule, transport: WorkerHostTransport, configureFilesystem?: (module: HyperDBWasmRuntimeModule) => void): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>HyperDB WebAssembly Example</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: light dark; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
|
|
9
|
+
body { margin: 0 auto; max-width: 960px; padding: 2rem; }
|
|
10
|
+
textarea { box-sizing: border-box; min-height: 10rem; padding: 1rem; resize: vertical; width: 100%; }
|
|
11
|
+
button { cursor: pointer; margin: 1rem 0; padding: 0.6rem 1rem; }
|
|
12
|
+
table { border-collapse: collapse; width: 100%; }
|
|
13
|
+
th, td { border: 1px solid currentColor; padding: 0.5rem; text-align: left; }
|
|
14
|
+
#status { min-height: 1.5rem; }
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<h1>HyperDB in the browser</h1>
|
|
19
|
+
<p>Edit the SQL and run it locally in the WebAssembly worker.</p>
|
|
20
|
+
<textarea id="sql">select sensor_id, count(*) as samples, avg(reading) as average_reading
|
|
21
|
+
from (values
|
|
22
|
+
(101, 18.5),
|
|
23
|
+
(101, 19.5),
|
|
24
|
+
(202, 22.0)
|
|
25
|
+
) as telemetry(sensor_id, reading)
|
|
26
|
+
group by sensor_id
|
|
27
|
+
order by sensor_id</textarea>
|
|
28
|
+
<button id="run" type="button">Run query</button>
|
|
29
|
+
<p id="status">Loading HyperDB...</p>
|
|
30
|
+
<div id="result"></div>
|
|
31
|
+
|
|
32
|
+
<script type="module">
|
|
33
|
+
import { HyperDB } from '../dist/browser.js';
|
|
34
|
+
|
|
35
|
+
const sql = document.querySelector('#sql');
|
|
36
|
+
const run = document.querySelector('#run');
|
|
37
|
+
const status = document.querySelector('#status');
|
|
38
|
+
const result = document.querySelector('#result');
|
|
39
|
+
|
|
40
|
+
const database = await HyperDB.create();
|
|
41
|
+
const connection = await database.connect();
|
|
42
|
+
status.textContent = 'Ready';
|
|
43
|
+
|
|
44
|
+
function render(table) {
|
|
45
|
+
const names = table.schema.fields.map(({ name }) => name);
|
|
46
|
+
const rows = Array.from({ length: table.numRows }, (_, row) =>
|
|
47
|
+
names.map((name) => String(table.getChild(name)?.get(row) ?? 'NULL')),
|
|
48
|
+
);
|
|
49
|
+
const tableElement = document.createElement('table');
|
|
50
|
+
const header = tableElement.createTHead().insertRow();
|
|
51
|
+
for (const name of names) {
|
|
52
|
+
const cell = document.createElement('th');
|
|
53
|
+
cell.textContent = name;
|
|
54
|
+
header.append(cell);
|
|
55
|
+
}
|
|
56
|
+
const body = tableElement.createTBody();
|
|
57
|
+
for (const values of rows) {
|
|
58
|
+
const row = body.insertRow();
|
|
59
|
+
for (const value of values) row.insertCell().textContent = value;
|
|
60
|
+
}
|
|
61
|
+
result.replaceChildren(tableElement);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
run.addEventListener('click', async () => {
|
|
65
|
+
run.disabled = true;
|
|
66
|
+
status.textContent = 'Running...';
|
|
67
|
+
try {
|
|
68
|
+
const table = await connection.query(sql.value);
|
|
69
|
+
render(table);
|
|
70
|
+
status.textContent = `${table.numRows} row(s)`;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
result.textContent = error instanceof Error ? error.message : String(error);
|
|
73
|
+
status.textContent = 'Query failed';
|
|
74
|
+
} finally {
|
|
75
|
+
run.disabled = false;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
window.addEventListener('pagehide', () => {
|
|
80
|
+
void connection.close().then(() => database.terminate());
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { HyperDB } from 'hyperdb-wasm';
|
|
2
|
+
import { makeTable, tableToIPC } from 'apache-arrow';
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
function rows(table) {
|
|
8
|
+
return Array.from({ length: table.numRows }, (_, row) => Object.fromEntries(
|
|
9
|
+
table.schema.fields.map(({ name }) => [name, table.getChild(name)?.get(row)]),
|
|
10
|
+
));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'hyperdb-wasm-'));
|
|
14
|
+
const database = await HyperDB.create({ hostPath: temporaryDirectory });
|
|
15
|
+
const connection = await database.connect();
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const expressions = await connection.query(`
|
|
19
|
+
select
|
|
20
|
+
6 * 7 as answer,
|
|
21
|
+
upper('query Hyper from Node.js') as message
|
|
22
|
+
`);
|
|
23
|
+
console.table(rows(expressions));
|
|
24
|
+
|
|
25
|
+
const grouped = await connection.query(`
|
|
26
|
+
select sensor_id, count(*) as samples, min(reading) as minimum, max(reading) as maximum
|
|
27
|
+
from (values
|
|
28
|
+
(101, 18),
|
|
29
|
+
(101, 19),
|
|
30
|
+
(202, 22)
|
|
31
|
+
) as readings(sensor_id, reading)
|
|
32
|
+
group by sensor_id
|
|
33
|
+
order by sensor_id
|
|
34
|
+
`);
|
|
35
|
+
console.table(rows(grouped));
|
|
36
|
+
|
|
37
|
+
const reader = await connection.send(`
|
|
38
|
+
select value, value * value as square
|
|
39
|
+
from generate_series(1, 5) as numbers(value)
|
|
40
|
+
order by value
|
|
41
|
+
`);
|
|
42
|
+
for await (const batch of reader) {
|
|
43
|
+
console.log(`streamed Arrow batch with ${batch.numRows} rows`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const memoryReadings = makeTable({
|
|
47
|
+
sensor_id: new Int32Array([303, 404]),
|
|
48
|
+
reading: new Int32Array([24, 27]),
|
|
49
|
+
});
|
|
50
|
+
await connection.insertArrowTable(memoryReadings, { name: 'memory_readings' });
|
|
51
|
+
|
|
52
|
+
const temporaryReadings = makeTable({
|
|
53
|
+
sensor_id: new Int32Array([505, 606]),
|
|
54
|
+
reading: new Int32Array([29, 31]),
|
|
55
|
+
});
|
|
56
|
+
const readingsPath = join(temporaryDirectory, 'arrow-readings.arrows');
|
|
57
|
+
await writeFile(readingsPath, tableToIPC(temporaryReadings, 'stream'));
|
|
58
|
+
await connection.insertArrowIPCFromPath(readingsPath, { name: 'temporary_readings' });
|
|
59
|
+
|
|
60
|
+
console.log('Arrow IPC staged in the in-memory WasmFS:');
|
|
61
|
+
console.table(rows(await connection.query('select * from memory_readings order by sensor_id')));
|
|
62
|
+
console.log(`Arrow IPC read from the Node temporary file ${readingsPath}:`);
|
|
63
|
+
console.table(rows(await connection.query('select * from temporary_readings order by sensor_id')));
|
|
64
|
+
} finally {
|
|
65
|
+
await connection.close();
|
|
66
|
+
await database.terminate();
|
|
67
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
68
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createReadStream, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { extname, resolve, sep } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const packageRoot = resolve(import.meta.dirname, '..');
|
|
6
|
+
const port = Number(process.env.PORT ?? 8000);
|
|
7
|
+
const contentTypes = {
|
|
8
|
+
'.html': 'text/html; charset=utf-8',
|
|
9
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
10
|
+
'.map': 'application/json',
|
|
11
|
+
'.wasm': 'application/wasm',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
createServer((request, response) => {
|
|
15
|
+
response.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
16
|
+
response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
17
|
+
|
|
18
|
+
const pathname = new URL(request.url ?? '/', 'http://localhost').pathname;
|
|
19
|
+
const relativePath = pathname === '/' ? 'examples/browser.html' : pathname.slice(1);
|
|
20
|
+
const file = resolve(packageRoot, relativePath);
|
|
21
|
+
if (!file.startsWith(`${packageRoot}${sep}`) || !existsSync(file) || !statSync(file).isFile()) {
|
|
22
|
+
response.writeHead(404).end('Not found');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const compressedWasm = file.endsWith('.wasm.br');
|
|
27
|
+
response.writeHead(200, {
|
|
28
|
+
...(compressedWasm ? { 'Content-Encoding': 'br' } : {}),
|
|
29
|
+
'Content-Length': statSync(file).size,
|
|
30
|
+
'Content-Type': compressedWasm ? 'application/wasm' : contentTypes[extname(file)] ?? 'application/octet-stream',
|
|
31
|
+
});
|
|
32
|
+
createReadStream(file).pipe(response);
|
|
33
|
+
}).listen(port, '127.0.0.1', () => {
|
|
34
|
+
console.log(`HyperDB browser example: http://127.0.0.1:${port}/`);
|
|
35
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hyperdb-wasm",
|
|
3
|
+
"version": "0.0.26268-dev.remotes-origin-wasm.gcf8d815a",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=24"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"examples"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"browser": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/browser.js"
|
|
17
|
+
},
|
|
18
|
+
"node": {
|
|
19
|
+
"types": "./dist/index.node.d.ts",
|
|
20
|
+
"default": "./dist/node.js"
|
|
21
|
+
},
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/browser.js"
|
|
24
|
+
},
|
|
25
|
+
"./raw": {
|
|
26
|
+
"browser": {
|
|
27
|
+
"types": "./dist/raw.browser.d.ts",
|
|
28
|
+
"default": "./dist/raw.browser.js"
|
|
29
|
+
},
|
|
30
|
+
"node": {
|
|
31
|
+
"types": "./dist/raw.node.d.ts",
|
|
32
|
+
"default": "./dist/raw.node.js"
|
|
33
|
+
},
|
|
34
|
+
"types": "./dist/raw.browser.d.ts",
|
|
35
|
+
"default": "./dist/raw.browser.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"apache-arrow": "^19.0.1"
|
|
40
|
+
}
|
|
41
|
+
}
|