@loradb/lora-wasm 0.6.0 → 0.8.1

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.
@@ -5,10 +5,13 @@ export const __wbg_wasmdatabase_free: (a: number, b: number) => void;
5
5
  export const __wbg_wasmquerystream_free: (a: number, b: number) => void;
6
6
  export const wasmdatabase_clear: (a: number) => void;
7
7
  export const wasmdatabase_execute: (a: number, b: number, c: number, d: any) => [number, number, number];
8
+ export const wasmdatabase_executeBuffer: (a: number, b: number, c: number, d: any) => [number, number, number, number];
9
+ export const wasmdatabase_explain: (a: number, b: number, c: number, d: any) => [number, number, number];
8
10
  export const wasmdatabase_loadSnapshot: (a: number, b: number, c: number, d: any) => [number, number, number];
9
11
  export const wasmdatabase_new: () => number;
10
12
  export const wasmdatabase_nodeCount: (a: number) => number;
11
13
  export const wasmdatabase_openStream: (a: number, b: number, c: number, d: any) => [number, number, number];
14
+ export const wasmdatabase_profile: (a: number, b: number, c: number, d: any) => [number, number, number];
12
15
  export const wasmdatabase_relationshipCount: (a: number) => number;
13
16
  export const wasmdatabase_saveSnapshot: (a: number, b: any) => [number, number, number, number];
14
17
  export const wasmdatabase_transaction: (a: number, b: any, c: number, d: number) => [number, number, number];
@@ -5,7 +5,7 @@
5
5
  "LoraDB, Inc."
6
6
  ],
7
7
  "description": "WebAssembly bindings for the Lora in-memory graph database",
8
- "version": "0.6.0",
8
+ "version": "0.8.1",
9
9
  "license": "BUSL-1.1",
10
10
  "files": [
11
11
  "lora_wasm_bg.wasm",
@@ -1,17 +1,17 @@
1
1
  # lora-wasm
2
2
 
3
- WebAssembly bindings for the [Lora](../../README.md) in-memory graph
3
+ WebAssembly bindings for the [Lora](../../../README.md) in-memory graph
4
4
  database. The package is designed for browsers and Node.js and exposes a
5
5
  **strongly typed, async-facing API** that keeps the main thread responsive:
6
6
  heavy query work can run inside a Web Worker while your UI code simply awaits
7
7
  the result.
8
8
 
9
- > **Status:** prototype / feasibility check. Not published to npm.
9
+ > **Package:** `@loradb/lora-wasm`.
10
10
 
11
11
  ## Build
12
12
 
13
13
  ```bash
14
- cd crates/lora-wasm
14
+ cd crates/bindings/lora-wasm
15
15
  npm install
16
16
  npm run build # wasm-pack (node + bundler) + tsc
17
17
  npm test # vitest
@@ -52,7 +52,7 @@ fails during startup, the package emits one `console.warn` and falls back to
52
52
  the in-process WASM engine so the app can still run.
53
53
 
54
54
  ```ts
55
- import { createDatabase, isNode } from "lora-wasm";
55
+ import { createDatabase, isNode } from "@loradb/lora-wasm";
56
56
 
57
57
  const db = await createDatabase(); // Worker-backed in browsers when possible
58
58
  await db.execute("CREATE (:Person {name: $n})", { n: "Alice" });
@@ -63,6 +63,37 @@ for (const row of r.rows) {
63
63
  }
64
64
  ```
65
65
 
66
+ ### Explain & Profile
67
+
68
+ `db.explain()` and `db.profile()` are first-class methods alongside
69
+ `db.execute()`. They are intentionally *separate calls*, not a flag
70
+ on `execute()`, so plan inspection and runtime metrics must be
71
+ requested explicitly.
72
+
73
+ ```ts
74
+ const plan = await db.explain(
75
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
76
+ { name: "Alice" },
77
+ );
78
+ console.log(plan.shape); // "readOnly"
79
+ console.log(plan.tree.operator);
80
+
81
+ const profile = await db.profile(
82
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
83
+ { name: "Alice" },
84
+ );
85
+ console.log(profile.metrics.totalElapsedNs);
86
+ console.log(profile.metrics.perOperator); // per-step inclusive timing
87
+ ```
88
+
89
+ `explain()` never invokes the executor — calling it on a mutating
90
+ query (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`) leaves the graph
91
+ untouched.
92
+
93
+ > **`profile()` executes the query for real.** Mutating queries
94
+ > produce the same side effects as `execute()`. Use `explain()` to
95
+ > inspect a mutating plan without running it.
96
+
66
97
  To force the in-process engine:
67
98
 
68
99
  ```ts
@@ -84,7 +115,7 @@ const db = await createDatabase({ warnOnFallback: false });
84
115
  ### 2. Explicit main-thread WASM
85
116
 
86
117
  ```ts
87
- import { createMainThreadDatabase } from "lora-wasm";
118
+ import { createMainThreadDatabase } from "@loradb/lora-wasm";
88
119
 
89
120
  const db = await createMainThreadDatabase();
90
121
  ```
@@ -122,14 +153,14 @@ WASM has no filesystem access, so snapshots never accept string paths.
122
153
  accepts `URL`, `Uint8Array`, `ArrayBuffer`, `Blob`, `Response`, and web
123
154
  `ReadableStream<Uint8Array | ArrayBuffer>`. The bytes use the database
124
155
  snapshot codec (`LORACOL1`) and can be loaded by native LoraDB. Older store
125
- snapshots (`LORASNAP`) are still accepted by `loadSnapshot` for compatibility.
156
+ snapshots use the current `LORACOL1` columnar format.
126
157
  Snapshot bytes are uncompressed by default; pass `{ compression: "gzip" }` or
127
158
  `{ compression: { format: "gzip", level: 1 } }` to save smaller
128
159
  WASM-portable snapshots. Gzip levels `0..9` are supported; level `1` is the
129
160
  fast default.
130
161
 
131
162
  ```ts
132
- import { createDatabase } from "lora-wasm";
163
+ import { createDatabase } from "@loradb/lora-wasm";
133
164
 
134
165
  const db = await createDatabase();
135
166
  await db.execute("CREATE (:Person {name: 'Alice'})");
@@ -172,31 +203,42 @@ Helper constructors: `date`, `time`, `datetime`, `localtime`, `localdatetime`,
172
203
  `isNode`, `isRelationship`, `isPath`, `isPoint`, `isTemporal`.
173
204
 
174
205
  > `distance()` on WGS-84-3D points ignores `height`. See
175
- > [functions reference](../../apps/loradb.com/docs/functions/overview.md) for the spatial
206
+ > [functions reference](../../../apps/loradb.com/docs/functions/overview.md) for the spatial
176
207
  > reference and out-of-scope operations.
177
208
 
178
209
  ## Errors
179
210
 
180
- `db.execute(...)` and the worker client throw `LoraError` with a narrowed
181
- `code`:
211
+ `db.execute(...)` and the worker client throw `LoraError` with a
212
+ narrowed `code` from the `LoraErrorCode` union — these mirror
213
+ `lora_database::LoraErrorCode` 1:1, plus `WORKER_ERROR` and `UNKNOWN`.
182
214
 
183
- - `LORA_ERROR` — parse / analyze / execute failure
184
- - `INVALID_PARAMS` — a param value could not be mapped to a Lora value
215
+ Common ones:
216
+
217
+ - `LORA_PARSE` — Cypher syntax could not be parsed
218
+ - `LORA_SEMANTIC` — analysis failure (unknown variable, label, type mismatch, …)
219
+ - `LORA_INVALID_PARAMS` — a parameter value could not be coerced to a `LoraValue`
220
+ - `LORA_INVALID_VECTOR` — vector value failed dimension / coordinate-type validation
221
+ - `LORA_TIMEOUT` — query exceeded its cooperative deadline
222
+ - `LORA_IO`, `LORA_SNAPSHOT_CODEC`, `LORA_SNAPSHOT_CRYPTO` — storage / snapshot failures
223
+ - `LORA_INTERNAL` — last-resort fallback when the engine cannot classify the failure
185
224
  - `WORKER_ERROR` — worker transport / lifecycle failure (worker client only)
186
225
 
226
+ See `ts/types.ts` (`LoraErrorCode`) for the full list.
227
+
187
228
  ## Shared type contract
188
229
 
189
230
  The public TypeScript value model (`LoraValue`, `LoraNode`, …,
190
231
  `QueryResult`, `LoraError`) lives in a single canonical file at
191
- `crates/shared-ts/types.ts` and is copied into each consumer package by
232
+ `crates/bindings/shared-ts/types.ts` and is copied into each consumer package by
192
233
  its `sync:types` npm script. CI runs `verify:types` to fail on drift.
193
234
  That keeps `lora-node` and `lora-wasm` locked to one identical
194
235
  public surface — consumers can swap backends without rewriting types.
195
236
 
196
237
  ## Known limitations
197
238
 
198
- - The wasm module is single-threaded. Parallel queries inside one worker
199
- serialise; spawn more workers for true parallelism.
239
+ - The wasm module runs inside one JavaScript runtime. Auto-commit reads can
240
+ overlap on engine snapshots; write commits serialize. Use more Workers for
241
+ separate graphs or stronger UI isolation.
200
242
  - I64 values are delivered as JS `number` and lose precision above 2^53.
201
243
  Applications that need bigint precision should use the native
202
244
  `lora-node` binding instead.
@@ -228,7 +270,7 @@ public surface — consumers can swap backends without rewriting types.
228
270
  forms, parameter validation errors, and concurrent queries across the
229
271
  worker message protocol.
230
272
  - The TypeScript public contract is shared verbatim with `lora-node`
231
- via `crates/shared-ts/types.ts`, enforced by `verify:types` in CI.
273
+ via `crates/bindings/shared-ts/types.ts`, enforced by `verify:types` in CI.
232
274
  - `npm pack --dry-run` produces a 45-file, 6.6 MB tarball containing
233
275
  all three wasm bundles plus the compiled TS wrapper. No `file:` deps,
234
276
  no postinstall scripts.
@@ -245,9 +287,8 @@ public surface — consumers can swap backends without rewriting types.
245
287
  - I64 precision is capped at `Number.MAX_SAFE_INTEGER`. For larger
246
288
  integer properties we need a `bigint`-aware serializer on the wasm
247
289
  boundary.
248
- - The engine is synchronous inside wasm — a single Worker serialises
249
- queries. Multi-tenant workloads need either multiple Workers (already
250
- possible), or an engine-level cooperative scheduler.
290
+ - The engine is synchronous inside wasm once a call enters the module. A slow
291
+ query blocks that Worker until completion.
251
292
  - No query cancellation or progress streaming crosses the Worker
252
293
  boundary; a slow query blocks that Worker until completion.
253
294
 
@@ -39,6 +39,16 @@ export class WasmDatabase {
39
39
  * Returns the serialized bytes as a `Uint8Array`.
40
40
  */
41
41
  saveSnapshot(options: any): Uint8Array;
42
+ /**
43
+ * Execute a Lora query and return the result as a binary buffer.
44
+ *
45
+ * Wire format is the shared `lora_binding_buffer` layout. The TS
46
+ * wrapper decodes it once into the same `{ columns, rows }` shape
47
+ * `execute()` returns, but in a tight V8 loop instead of going
48
+ * through `serde_wasm_bindgen` value-by-value. Materially faster
49
+ * on bulk reads.
50
+ */
51
+ executeBuffer(query: string, params: any): Uint8Array;
42
52
  relationshipCount(): number;
43
53
  constructor();
44
54
  clear(): void;
@@ -50,6 +60,21 @@ export class WasmDatabase {
50
60
  * as a plain JS object (structured-clonable).
51
61
  */
52
62
  execute(query: string, params: any): any;
63
+ /**
64
+ * Compile a query and return its plan without executing it.
65
+ *
66
+ * Mutating queries (CREATE / MERGE / SET / DELETE / REMOVE) leave
67
+ * the graph untouched — this method never runs the executor.
68
+ */
69
+ explain(query: string, params: any): any;
70
+ /**
71
+ * Execute a query and return the plan plus runtime metrics.
72
+ *
73
+ * **PROFILE executes the query for real.** Mutating queries are
74
+ * persisted exactly as in `execute()`. Use `explain()` to inspect
75
+ * a mutating plan without running it.
76
+ */
77
+ profile(query: string, params: any): any;
53
78
  }
54
79
  export class WasmQueryStream {
55
80
  private constructor();
@@ -293,6 +293,29 @@ class WasmDatabase {
293
293
  wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
294
294
  return v1;
295
295
  }
296
+ /**
297
+ * Execute a Lora query and return the result as a binary buffer.
298
+ *
299
+ * Wire format is the shared `lora_binding_buffer` layout. The TS
300
+ * wrapper decodes it once into the same `{ columns, rows }` shape
301
+ * `execute()` returns, but in a tight V8 loop instead of going
302
+ * through `serde_wasm_bindgen` value-by-value. Materially faster
303
+ * on bulk reads.
304
+ * @param {string} query
305
+ * @param {any} params
306
+ * @returns {Uint8Array}
307
+ */
308
+ executeBuffer(query, params) {
309
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
310
+ const len0 = WASM_VECTOR_LEN;
311
+ const ret = wasm.wasmdatabase_executeBuffer(this.__wbg_ptr, ptr0, len0, params);
312
+ if (ret[3]) {
313
+ throw takeFromExternrefTable0(ret[2]);
314
+ }
315
+ var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
316
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
317
+ return v2;
318
+ }
296
319
  /**
297
320
  * @returns {number}
298
321
  */
@@ -328,6 +351,43 @@ class WasmDatabase {
328
351
  }
329
352
  return takeFromExternrefTable0(ret[0]);
330
353
  }
354
+ /**
355
+ * Compile a query and return its plan without executing it.
356
+ *
357
+ * Mutating queries (CREATE / MERGE / SET / DELETE / REMOVE) leave
358
+ * the graph untouched — this method never runs the executor.
359
+ * @param {string} query
360
+ * @param {any} params
361
+ * @returns {any}
362
+ */
363
+ explain(query, params) {
364
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
365
+ const len0 = WASM_VECTOR_LEN;
366
+ const ret = wasm.wasmdatabase_explain(this.__wbg_ptr, ptr0, len0, params);
367
+ if (ret[2]) {
368
+ throw takeFromExternrefTable0(ret[1]);
369
+ }
370
+ return takeFromExternrefTable0(ret[0]);
371
+ }
372
+ /**
373
+ * Execute a query and return the plan plus runtime metrics.
374
+ *
375
+ * **PROFILE executes the query for real.** Mutating queries are
376
+ * persisted exactly as in `execute()`. Use `explain()` to inspect
377
+ * a mutating plan without running it.
378
+ * @param {string} query
379
+ * @param {any} params
380
+ * @returns {any}
381
+ */
382
+ profile(query, params) {
383
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
384
+ const len0 = WASM_VECTOR_LEN;
385
+ const ret = wasm.wasmdatabase_profile(this.__wbg_ptr, ptr0, len0, params);
386
+ if (ret[2]) {
387
+ throw takeFromExternrefTable0(ret[1]);
388
+ }
389
+ return takeFromExternrefTable0(ret[0]);
390
+ }
331
391
  }
332
392
  module.exports.WasmDatabase = WasmDatabase;
333
393
 
Binary file
@@ -5,10 +5,13 @@ export const __wbg_wasmdatabase_free: (a: number, b: number) => void;
5
5
  export const __wbg_wasmquerystream_free: (a: number, b: number) => void;
6
6
  export const wasmdatabase_clear: (a: number) => void;
7
7
  export const wasmdatabase_execute: (a: number, b: number, c: number, d: any) => [number, number, number];
8
+ export const wasmdatabase_executeBuffer: (a: number, b: number, c: number, d: any) => [number, number, number, number];
9
+ export const wasmdatabase_explain: (a: number, b: number, c: number, d: any) => [number, number, number];
8
10
  export const wasmdatabase_loadSnapshot: (a: number, b: number, c: number, d: any) => [number, number, number];
9
11
  export const wasmdatabase_new: () => number;
10
12
  export const wasmdatabase_nodeCount: (a: number) => number;
11
13
  export const wasmdatabase_openStream: (a: number, b: number, c: number, d: any) => [number, number, number];
14
+ export const wasmdatabase_profile: (a: number, b: number, c: number, d: any) => [number, number, number];
12
15
  export const wasmdatabase_relationshipCount: (a: number) => number;
13
16
  export const wasmdatabase_saveSnapshot: (a: number, b: any) => [number, number, number, number];
14
17
  export const wasmdatabase_transaction: (a: number, b: any, c: number, d: number) => [number, number, number];
@@ -4,7 +4,7 @@
4
4
  "LoraDB, Inc."
5
5
  ],
6
6
  "description": "WebAssembly bindings for the Lora in-memory graph database",
7
- "version": "0.6.0",
7
+ "version": "0.8.1",
8
8
  "license": "BUSL-1.1",
9
9
  "files": [
10
10
  "lora_wasm_bg.wasm",
package/pkg-web/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # lora-wasm
2
2
 
3
- WebAssembly bindings for the [Lora](../../README.md) in-memory graph
3
+ WebAssembly bindings for the [Lora](../../../README.md) in-memory graph
4
4
  database. The package is designed for browsers and Node.js and exposes a
5
5
  **strongly typed, async-facing API** that keeps the main thread responsive:
6
6
  heavy query work can run inside a Web Worker while your UI code simply awaits
7
7
  the result.
8
8
 
9
- > **Status:** prototype / feasibility check. Not published to npm.
9
+ > **Package:** `@loradb/lora-wasm`.
10
10
 
11
11
  ## Build
12
12
 
13
13
  ```bash
14
- cd crates/lora-wasm
14
+ cd crates/bindings/lora-wasm
15
15
  npm install
16
16
  npm run build # wasm-pack (node + bundler) + tsc
17
17
  npm test # vitest
@@ -52,7 +52,7 @@ fails during startup, the package emits one `console.warn` and falls back to
52
52
  the in-process WASM engine so the app can still run.
53
53
 
54
54
  ```ts
55
- import { createDatabase, isNode } from "lora-wasm";
55
+ import { createDatabase, isNode } from "@loradb/lora-wasm";
56
56
 
57
57
  const db = await createDatabase(); // Worker-backed in browsers when possible
58
58
  await db.execute("CREATE (:Person {name: $n})", { n: "Alice" });
@@ -63,6 +63,37 @@ for (const row of r.rows) {
63
63
  }
64
64
  ```
65
65
 
66
+ ### Explain & Profile
67
+
68
+ `db.explain()` and `db.profile()` are first-class methods alongside
69
+ `db.execute()`. They are intentionally *separate calls*, not a flag
70
+ on `execute()`, so plan inspection and runtime metrics must be
71
+ requested explicitly.
72
+
73
+ ```ts
74
+ const plan = await db.explain(
75
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
76
+ { name: "Alice" },
77
+ );
78
+ console.log(plan.shape); // "readOnly"
79
+ console.log(plan.tree.operator);
80
+
81
+ const profile = await db.profile(
82
+ "MATCH (p:Person) WHERE p.name = $name RETURN p",
83
+ { name: "Alice" },
84
+ );
85
+ console.log(profile.metrics.totalElapsedNs);
86
+ console.log(profile.metrics.perOperator); // per-step inclusive timing
87
+ ```
88
+
89
+ `explain()` never invokes the executor — calling it on a mutating
90
+ query (`CREATE`, `MERGE`, `SET`, `DELETE`, `REMOVE`) leaves the graph
91
+ untouched.
92
+
93
+ > **`profile()` executes the query for real.** Mutating queries
94
+ > produce the same side effects as `execute()`. Use `explain()` to
95
+ > inspect a mutating plan without running it.
96
+
66
97
  To force the in-process engine:
67
98
 
68
99
  ```ts
@@ -84,7 +115,7 @@ const db = await createDatabase({ warnOnFallback: false });
84
115
  ### 2. Explicit main-thread WASM
85
116
 
86
117
  ```ts
87
- import { createMainThreadDatabase } from "lora-wasm";
118
+ import { createMainThreadDatabase } from "@loradb/lora-wasm";
88
119
 
89
120
  const db = await createMainThreadDatabase();
90
121
  ```
@@ -122,14 +153,14 @@ WASM has no filesystem access, so snapshots never accept string paths.
122
153
  accepts `URL`, `Uint8Array`, `ArrayBuffer`, `Blob`, `Response`, and web
123
154
  `ReadableStream<Uint8Array | ArrayBuffer>`. The bytes use the database
124
155
  snapshot codec (`LORACOL1`) and can be loaded by native LoraDB. Older store
125
- snapshots (`LORASNAP`) are still accepted by `loadSnapshot` for compatibility.
156
+ snapshots use the current `LORACOL1` columnar format.
126
157
  Snapshot bytes are uncompressed by default; pass `{ compression: "gzip" }` or
127
158
  `{ compression: { format: "gzip", level: 1 } }` to save smaller
128
159
  WASM-portable snapshots. Gzip levels `0..9` are supported; level `1` is the
129
160
  fast default.
130
161
 
131
162
  ```ts
132
- import { createDatabase } from "lora-wasm";
163
+ import { createDatabase } from "@loradb/lora-wasm";
133
164
 
134
165
  const db = await createDatabase();
135
166
  await db.execute("CREATE (:Person {name: 'Alice'})");
@@ -172,31 +203,42 @@ Helper constructors: `date`, `time`, `datetime`, `localtime`, `localdatetime`,
172
203
  `isNode`, `isRelationship`, `isPath`, `isPoint`, `isTemporal`.
173
204
 
174
205
  > `distance()` on WGS-84-3D points ignores `height`. See
175
- > [functions reference](../../apps/loradb.com/docs/functions/overview.md) for the spatial
206
+ > [functions reference](../../../apps/loradb.com/docs/functions/overview.md) for the spatial
176
207
  > reference and out-of-scope operations.
177
208
 
178
209
  ## Errors
179
210
 
180
- `db.execute(...)` and the worker client throw `LoraError` with a narrowed
181
- `code`:
211
+ `db.execute(...)` and the worker client throw `LoraError` with a
212
+ narrowed `code` from the `LoraErrorCode` union — these mirror
213
+ `lora_database::LoraErrorCode` 1:1, plus `WORKER_ERROR` and `UNKNOWN`.
182
214
 
183
- - `LORA_ERROR` — parse / analyze / execute failure
184
- - `INVALID_PARAMS` — a param value could not be mapped to a Lora value
215
+ Common ones:
216
+
217
+ - `LORA_PARSE` — Cypher syntax could not be parsed
218
+ - `LORA_SEMANTIC` — analysis failure (unknown variable, label, type mismatch, …)
219
+ - `LORA_INVALID_PARAMS` — a parameter value could not be coerced to a `LoraValue`
220
+ - `LORA_INVALID_VECTOR` — vector value failed dimension / coordinate-type validation
221
+ - `LORA_TIMEOUT` — query exceeded its cooperative deadline
222
+ - `LORA_IO`, `LORA_SNAPSHOT_CODEC`, `LORA_SNAPSHOT_CRYPTO` — storage / snapshot failures
223
+ - `LORA_INTERNAL` — last-resort fallback when the engine cannot classify the failure
185
224
  - `WORKER_ERROR` — worker transport / lifecycle failure (worker client only)
186
225
 
226
+ See `ts/types.ts` (`LoraErrorCode`) for the full list.
227
+
187
228
  ## Shared type contract
188
229
 
189
230
  The public TypeScript value model (`LoraValue`, `LoraNode`, …,
190
231
  `QueryResult`, `LoraError`) lives in a single canonical file at
191
- `crates/shared-ts/types.ts` and is copied into each consumer package by
232
+ `crates/bindings/shared-ts/types.ts` and is copied into each consumer package by
192
233
  its `sync:types` npm script. CI runs `verify:types` to fail on drift.
193
234
  That keeps `lora-node` and `lora-wasm` locked to one identical
194
235
  public surface — consumers can swap backends without rewriting types.
195
236
 
196
237
  ## Known limitations
197
238
 
198
- - The wasm module is single-threaded. Parallel queries inside one worker
199
- serialise; spawn more workers for true parallelism.
239
+ - The wasm module runs inside one JavaScript runtime. Auto-commit reads can
240
+ overlap on engine snapshots; write commits serialize. Use more Workers for
241
+ separate graphs or stronger UI isolation.
200
242
  - I64 values are delivered as JS `number` and lose precision above 2^53.
201
243
  Applications that need bigint precision should use the native
202
244
  `lora-node` binding instead.
@@ -228,7 +270,7 @@ public surface — consumers can swap backends without rewriting types.
228
270
  forms, parameter validation errors, and concurrent queries across the
229
271
  worker message protocol.
230
272
  - The TypeScript public contract is shared verbatim with `lora-node`
231
- via `crates/shared-ts/types.ts`, enforced by `verify:types` in CI.
273
+ via `crates/bindings/shared-ts/types.ts`, enforced by `verify:types` in CI.
232
274
  - `npm pack --dry-run` produces a 45-file, 6.6 MB tarball containing
233
275
  all three wasm bundles plus the compiled TS wrapper. No `file:` deps,
234
276
  no postinstall scripts.
@@ -245,9 +287,8 @@ public surface — consumers can swap backends without rewriting types.
245
287
  - I64 precision is capped at `Number.MAX_SAFE_INTEGER`. For larger
246
288
  integer properties we need a `bigint`-aware serializer on the wasm
247
289
  boundary.
248
- - The engine is synchronous inside wasm — a single Worker serialises
249
- queries. Multi-tenant workloads need either multiple Workers (already
250
- possible), or an engine-level cooperative scheduler.
290
+ - The engine is synchronous inside wasm once a call enters the module. A slow
291
+ query blocks that Worker until completion.
251
292
  - No query cancellation or progress streaming crosses the Worker
252
293
  boundary; a slow query blocks that Worker until completion.
253
294
 
@@ -39,6 +39,16 @@ export class WasmDatabase {
39
39
  * Returns the serialized bytes as a `Uint8Array`.
40
40
  */
41
41
  saveSnapshot(options: any): Uint8Array;
42
+ /**
43
+ * Execute a Lora query and return the result as a binary buffer.
44
+ *
45
+ * Wire format is the shared `lora_binding_buffer` layout. The TS
46
+ * wrapper decodes it once into the same `{ columns, rows }` shape
47
+ * `execute()` returns, but in a tight V8 loop instead of going
48
+ * through `serde_wasm_bindgen` value-by-value. Materially faster
49
+ * on bulk reads.
50
+ */
51
+ executeBuffer(query: string, params: any): Uint8Array;
42
52
  relationshipCount(): number;
43
53
  constructor();
44
54
  clear(): void;
@@ -50,6 +60,21 @@ export class WasmDatabase {
50
60
  * as a plain JS object (structured-clonable).
51
61
  */
52
62
  execute(query: string, params: any): any;
63
+ /**
64
+ * Compile a query and return its plan without executing it.
65
+ *
66
+ * Mutating queries (CREATE / MERGE / SET / DELETE / REMOVE) leave
67
+ * the graph untouched — this method never runs the executor.
68
+ */
69
+ explain(query: string, params: any): any;
70
+ /**
71
+ * Execute a query and return the plan plus runtime metrics.
72
+ *
73
+ * **PROFILE executes the query for real.** Mutating queries are
74
+ * persisted exactly as in `execute()`. Use `explain()` to inspect
75
+ * a mutating plan without running it.
76
+ */
77
+ profile(query: string, params: any): any;
53
78
  }
54
79
  export class WasmQueryStream {
55
80
  private constructor();
@@ -67,10 +92,13 @@ export interface InitOutput {
67
92
  readonly __wbg_wasmquerystream_free: (a: number, b: number) => void;
68
93
  readonly wasmdatabase_clear: (a: number) => void;
69
94
  readonly wasmdatabase_execute: (a: number, b: number, c: number, d: any) => [number, number, number];
95
+ readonly wasmdatabase_executeBuffer: (a: number, b: number, c: number, d: any) => [number, number, number, number];
96
+ readonly wasmdatabase_explain: (a: number, b: number, c: number, d: any) => [number, number, number];
70
97
  readonly wasmdatabase_loadSnapshot: (a: number, b: number, c: number, d: any) => [number, number, number];
71
98
  readonly wasmdatabase_new: () => number;
72
99
  readonly wasmdatabase_nodeCount: (a: number) => number;
73
100
  readonly wasmdatabase_openStream: (a: number, b: number, c: number, d: any) => [number, number, number];
101
+ readonly wasmdatabase_profile: (a: number, b: number, c: number, d: any) => [number, number, number];
74
102
  readonly wasmdatabase_relationshipCount: (a: number) => number;
75
103
  readonly wasmdatabase_saveSnapshot: (a: number, b: any) => [number, number, number, number];
76
104
  readonly wasmdatabase_transaction: (a: number, b: any, c: number, d: number) => [number, number, number];
@@ -289,6 +289,29 @@ export class WasmDatabase {
289
289
  wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
290
290
  return v1;
291
291
  }
292
+ /**
293
+ * Execute a Lora query and return the result as a binary buffer.
294
+ *
295
+ * Wire format is the shared `lora_binding_buffer` layout. The TS
296
+ * wrapper decodes it once into the same `{ columns, rows }` shape
297
+ * `execute()` returns, but in a tight V8 loop instead of going
298
+ * through `serde_wasm_bindgen` value-by-value. Materially faster
299
+ * on bulk reads.
300
+ * @param {string} query
301
+ * @param {any} params
302
+ * @returns {Uint8Array}
303
+ */
304
+ executeBuffer(query, params) {
305
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
306
+ const len0 = WASM_VECTOR_LEN;
307
+ const ret = wasm.wasmdatabase_executeBuffer(this.__wbg_ptr, ptr0, len0, params);
308
+ if (ret[3]) {
309
+ throw takeFromExternrefTable0(ret[2]);
310
+ }
311
+ var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
312
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
313
+ return v2;
314
+ }
292
315
  /**
293
316
  * @returns {number}
294
317
  */
@@ -324,6 +347,43 @@ export class WasmDatabase {
324
347
  }
325
348
  return takeFromExternrefTable0(ret[0]);
326
349
  }
350
+ /**
351
+ * Compile a query and return its plan without executing it.
352
+ *
353
+ * Mutating queries (CREATE / MERGE / SET / DELETE / REMOVE) leave
354
+ * the graph untouched — this method never runs the executor.
355
+ * @param {string} query
356
+ * @param {any} params
357
+ * @returns {any}
358
+ */
359
+ explain(query, params) {
360
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
361
+ const len0 = WASM_VECTOR_LEN;
362
+ const ret = wasm.wasmdatabase_explain(this.__wbg_ptr, ptr0, len0, params);
363
+ if (ret[2]) {
364
+ throw takeFromExternrefTable0(ret[1]);
365
+ }
366
+ return takeFromExternrefTable0(ret[0]);
367
+ }
368
+ /**
369
+ * Execute a query and return the plan plus runtime metrics.
370
+ *
371
+ * **PROFILE executes the query for real.** Mutating queries are
372
+ * persisted exactly as in `execute()`. Use `explain()` to inspect
373
+ * a mutating plan without running it.
374
+ * @param {string} query
375
+ * @param {any} params
376
+ * @returns {any}
377
+ */
378
+ profile(query, params) {
379
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
380
+ const len0 = WASM_VECTOR_LEN;
381
+ const ret = wasm.wasmdatabase_profile(this.__wbg_ptr, ptr0, len0, params);
382
+ if (ret[2]) {
383
+ throw takeFromExternrefTable0(ret[1]);
384
+ }
385
+ return takeFromExternrefTable0(ret[0]);
386
+ }
327
387
  }
328
388
 
329
389
  const WasmQueryStreamFinalization = (typeof FinalizationRegistry === 'undefined')
Binary file
@@ -5,10 +5,13 @@ export const __wbg_wasmdatabase_free: (a: number, b: number) => void;
5
5
  export const __wbg_wasmquerystream_free: (a: number, b: number) => void;
6
6
  export const wasmdatabase_clear: (a: number) => void;
7
7
  export const wasmdatabase_execute: (a: number, b: number, c: number, d: any) => [number, number, number];
8
+ export const wasmdatabase_executeBuffer: (a: number, b: number, c: number, d: any) => [number, number, number, number];
9
+ export const wasmdatabase_explain: (a: number, b: number, c: number, d: any) => [number, number, number];
8
10
  export const wasmdatabase_loadSnapshot: (a: number, b: number, c: number, d: any) => [number, number, number];
9
11
  export const wasmdatabase_new: () => number;
10
12
  export const wasmdatabase_nodeCount: (a: number) => number;
11
13
  export const wasmdatabase_openStream: (a: number, b: number, c: number, d: any) => [number, number, number];
14
+ export const wasmdatabase_profile: (a: number, b: number, c: number, d: any) => [number, number, number];
12
15
  export const wasmdatabase_relationshipCount: (a: number) => number;
13
16
  export const wasmdatabase_saveSnapshot: (a: number, b: any) => [number, number, number, number];
14
17
  export const wasmdatabase_transaction: (a: number, b: any, c: number, d: number) => [number, number, number];
@@ -5,7 +5,7 @@
5
5
  "LoraDB, Inc."
6
6
  ],
7
7
  "description": "WebAssembly bindings for the Lora in-memory graph database",
8
- "version": "0.6.0",
8
+ "version": "0.8.1",
9
9
  "license": "BUSL-1.1",
10
10
  "files": [
11
11
  "lora_wasm_bg.wasm",