@loradb/lora-wasm 0.5.6 → 0.8.0

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.
Files changed (45) hide show
  1. package/README.md +76 -34
  2. package/dist/decode.d.ts +29 -0
  3. package/dist/decode.d.ts.map +1 -0
  4. package/dist/decode.js +338 -0
  5. package/dist/decode.js.map +1 -0
  6. package/dist/index.d.ts +21 -31
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +35 -37
  9. package/dist/index.js.map +1 -1
  10. package/dist/loader-node.d.ts +1 -1
  11. package/dist/snapshot.d.ts +49 -16
  12. package/dist/snapshot.d.ts.map +1 -1
  13. package/dist/snapshot.js +36 -54
  14. package/dist/snapshot.js.map +1 -1
  15. package/dist/types.d.ts +81 -7
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/types.js +10 -2
  18. package/dist/types.js.map +1 -1
  19. package/dist/worker-client.d.ts +20 -20
  20. package/dist/worker-client.d.ts.map +1 -1
  21. package/dist/worker-client.js +32 -22
  22. package/dist/worker-client.js.map +1 -1
  23. package/dist/worker-protocol.d.ts +5 -2
  24. package/dist/worker-protocol.d.ts.map +1 -1
  25. package/dist/worker.js +18 -10
  26. package/dist/worker.js.map +1 -1
  27. package/package.json +5 -5
  28. package/pkg-bundler/README.md +76 -34
  29. package/pkg-bundler/lora_wasm.d.ts +37 -10
  30. package/pkg-bundler/lora_wasm_bg.js +173 -26
  31. package/pkg-bundler/lora_wasm_bg.wasm +0 -0
  32. package/pkg-bundler/lora_wasm_bg.wasm.d.ts +5 -2
  33. package/pkg-bundler/package.json +1 -1
  34. package/pkg-node/README.md +76 -34
  35. package/pkg-node/lora_wasm.d.ts +37 -10
  36. package/pkg-node/lora_wasm.js +173 -26
  37. package/pkg-node/lora_wasm_bg.wasm +0 -0
  38. package/pkg-node/lora_wasm_bg.wasm.d.ts +5 -2
  39. package/pkg-node/package.json +1 -1
  40. package/pkg-web/README.md +76 -34
  41. package/pkg-web/lora_wasm.d.ts +42 -12
  42. package/pkg-web/lora_wasm.js +156 -26
  43. package/pkg-web/lora_wasm_bg.wasm +0 -0
  44. package/pkg-web/lora_wasm_bg.wasm.d.ts +5 -2
  45. package/pkg-web/package.json +1 -1
@@ -177,17 +177,17 @@ function takeFromExternrefTable0(idx) {
177
177
  return value;
178
178
  }
179
179
 
180
- function getArrayU8FromWasm0(ptr, len) {
181
- ptr = ptr >>> 0;
182
- return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
183
- }
184
-
185
180
  function passArray8ToWasm0(arg, malloc) {
186
181
  const ptr = malloc(arg.length * 1, 1) >>> 0;
187
182
  getUint8ArrayMemory0().set(arg, ptr / 1);
188
183
  WASM_VECTOR_LEN = arg.length;
189
184
  return ptr;
190
185
  }
186
+
187
+ function getArrayU8FromWasm0(ptr, len) {
188
+ ptr = ptr >>> 0;
189
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
190
+ }
191
191
  /**
192
192
  * Call once at module start to install a panic hook that routes Rust
193
193
  * panics to `console.error`. No-op if compiled without the default feature.
@@ -255,23 +255,37 @@ class WasmDatabase {
255
255
  return takeFromExternrefTable0(ret[0]);
256
256
  }
257
257
  /**
258
- * @returns {number}
258
+ * Replace the graph state with a database snapshot decoded from `bytes`.
259
+ * Legacy store snapshot bytes are accepted for compatibility.
260
+ *
261
+ * Returns a plain object matching the shape of `SnapshotMeta`:
262
+ * `{ formatVersion, nodeCount, relationshipCount, walLsn }`.
263
+ * @param {Uint8Array} bytes
264
+ * @param {any} options
265
+ * @returns {any}
259
266
  */
260
- relationshipCount() {
261
- const ret = wasm.wasmdatabase_relationshipCount(this.__wbg_ptr);
262
- return ret >>> 0;
267
+ loadSnapshot(bytes, options) {
268
+ const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
269
+ const len0 = WASM_VECTOR_LEN;
270
+ const ret = wasm.wasmdatabase_loadSnapshot(this.__wbg_ptr, ptr0, len0, options);
271
+ if (ret[2]) {
272
+ throw takeFromExternrefTable0(ret[1]);
273
+ }
274
+ return takeFromExternrefTable0(ret[0]);
263
275
  }
264
276
  /**
265
- * Serialize the graph into a byte buffer. The caller is responsible for
266
- * writing it to disk, IndexedDB, localStorage, or wherever — WASM does
267
- * not have direct filesystem access. The returned bytes can later be
268
- * passed to `loadSnapshotFromBytes` on any `WasmDatabase` instance.
277
+ * Serialize the graph into database snapshot bytes. The caller is
278
+ * responsible for writing them to IndexedDB, localStorage, a server, or
279
+ * another host-provided store — WASM has no direct filesystem access.
280
+ * The returned bytes can later be passed to `loadSnapshot` on any
281
+ * `WasmDatabase` instance.
269
282
  *
270
283
  * Returns the serialized bytes as a `Uint8Array`.
284
+ * @param {any} options
271
285
  * @returns {Uint8Array}
272
286
  */
273
- saveSnapshotToBytes() {
274
- const ret = wasm.wasmdatabase_saveSnapshotToBytes(this.__wbg_ptr);
287
+ saveSnapshot(options) {
288
+ const ret = wasm.wasmdatabase_saveSnapshot(this.__wbg_ptr, options);
275
289
  if (ret[3]) {
276
290
  throw takeFromExternrefTable0(ret[2]);
277
291
  }
@@ -280,21 +294,34 @@ class WasmDatabase {
280
294
  return v1;
281
295
  }
282
296
  /**
283
- * Replace the graph state with a snapshot decoded from `bytes`.
297
+ * Execute a Lora query and return the result as a binary buffer.
284
298
  *
285
- * Returns a plain object matching the shape of `SnapshotMeta`:
286
- * `{ formatVersion, nodeCount, relationshipCount, walLsn }`.
287
- * @param {Uint8Array} bytes
288
- * @returns {any}
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}
289
307
  */
290
- loadSnapshotFromBytes(bytes) {
291
- const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
308
+ executeBuffer(query, params) {
309
+ const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
292
310
  const len0 = WASM_VECTOR_LEN;
293
- const ret = wasm.wasmdatabase_loadSnapshotFromBytes(this.__wbg_ptr, ptr0, len0);
294
- if (ret[2]) {
295
- throw takeFromExternrefTable0(ret[1]);
311
+ const ret = wasm.wasmdatabase_executeBuffer(this.__wbg_ptr, ptr0, len0, params);
312
+ if (ret[3]) {
313
+ throw takeFromExternrefTable0(ret[2]);
296
314
  }
297
- return takeFromExternrefTable0(ret[0]);
315
+ var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
316
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
317
+ return v2;
318
+ }
319
+ /**
320
+ * @returns {number}
321
+ */
322
+ relationshipCount() {
323
+ const ret = wasm.wasmdatabase_relationshipCount(this.__wbg_ptr);
324
+ return ret >>> 0;
298
325
  }
299
326
  constructor() {
300
327
  const ret = wasm.wasmdatabase_new();
@@ -324,6 +351,43 @@ class WasmDatabase {
324
351
  }
325
352
  return takeFromExternrefTable0(ret[0]);
326
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
+ }
327
391
  }
328
392
  module.exports.WasmDatabase = WasmDatabase;
329
393
 
@@ -396,6 +460,16 @@ module.exports.__wbg_call_672a4d21634d4a24 = function() { return handleError(fun
396
460
  return ret;
397
461
  }, arguments) };
398
462
 
463
+ module.exports.__wbg_call_7cccdd69e0791ae2 = function() { return handleError(function (arg0, arg1, arg2) {
464
+ const ret = arg0.call(arg1, arg2);
465
+ return ret;
466
+ }, arguments) };
467
+
468
+ module.exports.__wbg_crypto_86f2631e91b51511 = function(arg0) {
469
+ const ret = arg0.crypto;
470
+ return ret;
471
+ };
472
+
399
473
  module.exports.__wbg_done_769e5ede4b31c67b = function(arg0) {
400
474
  const ret = arg0.done;
401
475
  return ret;
@@ -418,6 +492,10 @@ module.exports.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) {
418
492
  }
419
493
  };
420
494
 
495
+ module.exports.__wbg_getRandomValues_b3f15fcbfabb0f8b = function() { return handleError(function (arg0, arg1) {
496
+ arg0.getRandomValues(arg1);
497
+ }, arguments) };
498
+
421
499
  module.exports.__wbg_get_67b2ba62fc30de12 = function() { return handleError(function (arg0, arg1) {
422
500
  const ret = Reflect.get(arg0, arg1);
423
501
  return ret;
@@ -486,6 +564,11 @@ module.exports.__wbg_length_e2d2a49132c1b256 = function(arg0) {
486
564
  return ret;
487
565
  };
488
566
 
567
+ module.exports.__wbg_msCrypto_d562bbe83e0d4b91 = function(arg0) {
568
+ const ret = arg0.msCrypto;
569
+ return ret;
570
+ };
571
+
489
572
  module.exports.__wbg_new_405e22f390576ce2 = function() {
490
573
  const ret = new Object();
491
574
  return ret;
@@ -511,6 +594,21 @@ module.exports.__wbg_new_a12002a7f91c75be = function(arg0) {
511
594
  return ret;
512
595
  };
513
596
 
597
+ module.exports.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) {
598
+ const ret = new Function(getStringFromWasm0(arg0, arg1));
599
+ return ret;
600
+ };
601
+
602
+ module.exports.__wbg_newwithbyteoffsetandlength_d97e637ebe145a9a = function(arg0, arg1, arg2) {
603
+ const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0);
604
+ return ret;
605
+ };
606
+
607
+ module.exports.__wbg_newwithlength_a381634e90c276d4 = function(arg0) {
608
+ const ret = new Uint8Array(arg0 >>> 0);
609
+ return ret;
610
+ };
611
+
514
612
  module.exports.__wbg_next_25feadfc0913fea9 = function(arg0) {
515
613
  const ret = arg0.next;
516
614
  return ret;
@@ -521,11 +619,30 @@ module.exports.__wbg_next_6574e1a8a62d1055 = function() { return handleError(fun
521
619
  return ret;
522
620
  }, arguments) };
523
621
 
622
+ module.exports.__wbg_node_e1f24f89a7336c2e = function(arg0) {
623
+ const ret = arg0.node;
624
+ return ret;
625
+ };
626
+
524
627
  module.exports.__wbg_now_807e54c39636c349 = function() {
525
628
  const ret = Date.now();
526
629
  return ret;
527
630
  };
528
631
 
632
+ module.exports.__wbg_process_3975fd6c72f520aa = function(arg0) {
633
+ const ret = arg0.process;
634
+ return ret;
635
+ };
636
+
637
+ module.exports.__wbg_randomFillSync_f8c153b79f285817 = function() { return handleError(function (arg0, arg1) {
638
+ arg0.randomFillSync(arg1);
639
+ }, arguments) };
640
+
641
+ module.exports.__wbg_require_b74f47fc2d022fd6 = function() { return handleError(function () {
642
+ const ret = module.require;
643
+ return ret;
644
+ }, arguments) };
645
+
529
646
  module.exports.__wbg_set_37837023f3d740e8 = function(arg0, arg1, arg2) {
530
647
  arg0[arg1 >>> 0] = arg2;
531
648
  };
@@ -551,11 +668,41 @@ module.exports.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) {
551
668
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
552
669
  };
553
670
 
671
+ module.exports.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() {
672
+ const ret = typeof global === 'undefined' ? null : global;
673
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
674
+ };
675
+
676
+ module.exports.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() {
677
+ const ret = typeof globalThis === 'undefined' ? null : globalThis;
678
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
679
+ };
680
+
681
+ module.exports.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() {
682
+ const ret = typeof self === 'undefined' ? null : self;
683
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
684
+ };
685
+
686
+ module.exports.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() {
687
+ const ret = typeof window === 'undefined' ? null : window;
688
+ return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
689
+ };
690
+
691
+ module.exports.__wbg_subarray_aa9065fa9dc5df96 = function(arg0, arg1, arg2) {
692
+ const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);
693
+ return ret;
694
+ };
695
+
554
696
  module.exports.__wbg_value_cd1ffa7b1ab794f1 = function(arg0) {
555
697
  const ret = arg0.value;
556
698
  return ret;
557
699
  };
558
700
 
701
+ module.exports.__wbg_versions_4e31226f5e8dc909 = function(arg0) {
702
+ const ret = arg0.versions;
703
+ return ret;
704
+ };
705
+
559
706
  module.exports.__wbindgen_bigint_from_i64 = function(arg0) {
560
707
  const ret = arg0;
561
708
  return ret;
Binary file
@@ -5,12 +5,15 @@ 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_loadSnapshotFromBytes: (a: number, b: number, c: number) => [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];
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
- export const wasmdatabase_saveSnapshotToBytes: (a: number) => [number, number, number, number];
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];
15
18
  export const wasmquerystream_close: (a: number) => void;
16
19
  export const wasmquerystream_columns: (a: 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.5.6",
7
+ "version": "0.8.0",
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
  ```
@@ -116,35 +147,36 @@ useful for tests and for swapping execution backends behind the same API.
116
147
 
117
148
  ## Snapshots
118
149
 
119
- WASM has no filesystem access, so `saveSnapshot()` defaults to a `Uint8Array`.
120
- It also accepts typed formats: `"base64"` returns base64 text, `"blob"` returns
121
- a `Blob`, and `"download"` triggers a browser download from the main thread.
122
-
123
- `loadSnapshot` accepts a `WasmSnapshotSource`: `Uint8Array`, `ArrayBuffer`,
124
- `Blob`/`File`, `Response`, web `ReadableStream`, string URL, or `URL`. It does
125
- not include Node `Buffer`; use `Uint8Array` bytes when running the WASM package
126
- from Node.
150
+ WASM has no filesystem access, so snapshots never accept string paths.
151
+ `saveSnapshot()` defaults to `Uint8Array`, and can also return `ArrayBuffer`,
152
+ `Blob`, `Response`, `ReadableStream`, or an object `URL`. `loadSnapshot`
153
+ accepts `URL`, `Uint8Array`, `ArrayBuffer`, `Blob`, `Response`, and web
154
+ `ReadableStream<Uint8Array | ArrayBuffer>`. The bytes use the database
155
+ snapshot codec (`LORACOL1`) and can be loaded by native LoraDB. Older store
156
+ snapshots use the current `LORACOL1` columnar format.
157
+ Snapshot bytes are uncompressed by default; pass `{ compression: "gzip" }` or
158
+ `{ compression: { format: "gzip", level: 1 } }` to save smaller
159
+ WASM-portable snapshots. Gzip levels `0..9` are supported; level `1` is the
160
+ fast default.
127
161
 
128
162
  ```ts
129
- import { createDatabase } from "lora-wasm";
163
+ import { createDatabase } from "@loradb/lora-wasm";
130
164
 
131
165
  const db = await createDatabase();
132
166
  await db.execute("CREATE (:Person {name: 'Alice'})");
133
167
 
134
- const bytes = await db.saveSnapshotToBytes();
135
- const sameBytes = await db.saveSnapshot("binary");
136
- const blob = await db.saveSnapshot({ format: "blob" });
137
- await db.saveSnapshot({ format: "download", filename: "graph.lorasnap" });
168
+ const bytes = await db.saveSnapshot();
169
+ const compressed = await db.saveSnapshot({ compression: "gzip" });
170
+ const blob = await db.saveSnapshot({ format: "blob", compression: "gzip" });
171
+ const response = await db.saveSnapshot({ format: "response" });
138
172
 
139
173
  await db.loadSnapshot(bytes);
174
+ await db.loadSnapshot(compressed);
140
175
  await db.loadSnapshot(blob);
141
- await db.loadSnapshot(new Response(bytes).body!);
176
+ await db.loadSnapshot(response);
142
177
  await db.loadSnapshot(new URL("/graph.lorasnap", location.href));
143
178
  ```
144
179
 
145
- `loadSnapshotFromBytes(bytes)` remains available for the narrow
146
- `Uint8Array`-only path.
147
-
148
180
  ## Typed value model
149
181
 
150
182
  | TS type | Runtime shape |
@@ -171,31 +203,42 @@ Helper constructors: `date`, `time`, `datetime`, `localtime`, `localdatetime`,
171
203
  `isNode`, `isRelationship`, `isPath`, `isPoint`, `isTemporal`.
172
204
 
173
205
  > `distance()` on WGS-84-3D points ignores `height`. See
174
- > [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
175
207
  > reference and out-of-scope operations.
176
208
 
177
209
  ## Errors
178
210
 
179
- `db.execute(...)` and the worker client throw `LoraError` with a narrowed
180
- `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`.
181
214
 
182
- - `LORA_ERROR` — parse / analyze / execute failure
183
- - `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
184
224
  - `WORKER_ERROR` — worker transport / lifecycle failure (worker client only)
185
225
 
226
+ See `ts/types.ts` (`LoraErrorCode`) for the full list.
227
+
186
228
  ## Shared type contract
187
229
 
188
230
  The public TypeScript value model (`LoraValue`, `LoraNode`, …,
189
231
  `QueryResult`, `LoraError`) lives in a single canonical file at
190
- `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
191
233
  its `sync:types` npm script. CI runs `verify:types` to fail on drift.
192
234
  That keeps `lora-node` and `lora-wasm` locked to one identical
193
235
  public surface — consumers can swap backends without rewriting types.
194
236
 
195
237
  ## Known limitations
196
238
 
197
- - The wasm module is single-threaded. Parallel queries inside one worker
198
- 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.
199
242
  - I64 values are delivered as JS `number` and lose precision above 2^53.
200
243
  Applications that need bigint precision should use the native
201
244
  `lora-node` binding instead.
@@ -227,7 +270,7 @@ public surface — consumers can swap backends without rewriting types.
227
270
  forms, parameter validation errors, and concurrent queries across the
228
271
  worker message protocol.
229
272
  - The TypeScript public contract is shared verbatim with `lora-node`
230
- 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.
231
274
  - `npm pack --dry-run` produces a 45-file, 6.6 MB tarball containing
232
275
  all three wasm bundles plus the compiled TS wrapper. No `file:` deps,
233
276
  no postinstall scripts.
@@ -244,9 +287,8 @@ public surface — consumers can swap backends without rewriting types.
244
287
  - I64 precision is capped at `Number.MAX_SAFE_INTEGER`. For larger
245
288
  integer properties we need a `bigint`-aware serializer on the wasm
246
289
  boundary.
247
- - The engine is synchronous inside wasm — a single Worker serialises
248
- queries. Multi-tenant workloads need either multiple Workers (already
249
- 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.
250
292
  - No query cancellation or progress streaming crosses the Worker
251
293
  boundary; a slow query blocks that Worker until completion.
252
294
 
@@ -21,23 +21,35 @@ export class WasmDatabase {
21
21
  * transaction. Returns an array of query results in statement order.
22
22
  */
23
23
  transaction(statements: any, mode?: string | null): any;
24
- relationshipCount(): number;
25
24
  /**
26
- * Serialize the graph into a byte buffer. The caller is responsible for
27
- * writing it to disk, IndexedDB, localStorage, or wherever — WASM does
28
- * not have direct filesystem access. The returned bytes can later be
29
- * passed to `loadSnapshotFromBytes` on any `WasmDatabase` instance.
25
+ * Replace the graph state with a database snapshot decoded from `bytes`.
26
+ * Legacy store snapshot bytes are accepted for compatibility.
27
+ *
28
+ * Returns a plain object matching the shape of `SnapshotMeta`:
29
+ * `{ formatVersion, nodeCount, relationshipCount, walLsn }`.
30
+ */
31
+ loadSnapshot(bytes: Uint8Array, options: any): any;
32
+ /**
33
+ * Serialize the graph into database snapshot bytes. The caller is
34
+ * responsible for writing them to IndexedDB, localStorage, a server, or
35
+ * another host-provided store — WASM has no direct filesystem access.
36
+ * The returned bytes can later be passed to `loadSnapshot` on any
37
+ * `WasmDatabase` instance.
30
38
  *
31
39
  * Returns the serialized bytes as a `Uint8Array`.
32
40
  */
33
- saveSnapshotToBytes(): Uint8Array;
41
+ saveSnapshot(options: any): Uint8Array;
34
42
  /**
35
- * Replace the graph state with a snapshot decoded from `bytes`.
43
+ * Execute a Lora query and return the result as a binary buffer.
36
44
  *
37
- * Returns a plain object matching the shape of `SnapshotMeta`:
38
- * `{ formatVersion, nodeCount, relationshipCount, walLsn }`.
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.
39
50
  */
40
- loadSnapshotFromBytes(bytes: Uint8Array): any;
51
+ executeBuffer(query: string, params: any): Uint8Array;
52
+ relationshipCount(): number;
41
53
  constructor();
42
54
  clear(): void;
43
55
  /**
@@ -48,6 +60,21 @@ export class WasmDatabase {
48
60
  * as a plain JS object (structured-clonable).
49
61
  */
50
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;
51
78
  }
52
79
  export class WasmQueryStream {
53
80
  private constructor();
@@ -65,12 +92,15 @@ export interface InitOutput {
65
92
  readonly __wbg_wasmquerystream_free: (a: number, b: number) => void;
66
93
  readonly wasmdatabase_clear: (a: number) => void;
67
94
  readonly wasmdatabase_execute: (a: number, b: number, c: number, d: any) => [number, number, number];
68
- readonly wasmdatabase_loadSnapshotFromBytes: (a: number, b: number, c: number) => [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];
97
+ readonly wasmdatabase_loadSnapshot: (a: number, b: number, c: number, d: any) => [number, number, number];
69
98
  readonly wasmdatabase_new: () => number;
70
99
  readonly wasmdatabase_nodeCount: (a: number) => number;
71
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];
72
102
  readonly wasmdatabase_relationshipCount: (a: number) => number;
73
- readonly wasmdatabase_saveSnapshotToBytes: (a: number) => [number, number, number, number];
103
+ readonly wasmdatabase_saveSnapshot: (a: number, b: any) => [number, number, number, number];
74
104
  readonly wasmdatabase_transaction: (a: number, b: any, c: number, d: number) => [number, number, number];
75
105
  readonly wasmquerystream_close: (a: number) => void;
76
106
  readonly wasmquerystream_columns: (a: number) => [number, number, number];