beamdb 0.9.2 → 0.12.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.
package/README.md CHANGED
@@ -23,6 +23,7 @@
23
23
  - [Wire Protocol](#wire-protocol)
24
24
  - [Configuration](#configuration)
25
25
  - [Testing](#testing)
26
+ - [Benchmarks](#benchmarks)
26
27
  - [Features](#features)
27
28
  - [Security](#security)
28
29
  - [Contributing](#contributing)
@@ -111,7 +112,7 @@ await init();
111
112
 
112
113
  // Create a BEAM node
113
114
  const beam = new Beam(); // in-memory (lost on reload)
114
- // or: const beam = new Beam.new_persistent(); // IndexedDB (survives reload)
115
+ // or: const beam = Beam.new_persistent(); // IndexedDB (survives reload)
115
116
 
116
117
  // Connect to a relay server
117
118
  beam.connect("wss://relay.example.com/ws");
@@ -125,7 +126,7 @@ beam.put_null("users.alice.deleted"); // explicit null
125
126
  // Read once (returns a Promise)
126
127
  const name = await beam.get("users.alice.name"); // "Alice"
127
128
 
128
- // Subscribe to updates at a path
129
+ // Subscribe to child updates (Gun.js .on() semantics)
129
130
  beam.on("users.alice.name", (value) => {
130
131
  console.log("name changed:", value);
131
132
  });
@@ -180,7 +181,7 @@ beam.stop();
180
181
  import init, { Beam } from "./beam.js";
181
182
  await init();
182
183
 
183
- const beam = new Beam.new_persistent();
184
+ const beam = Beam.new_persistent();
184
185
  beam.connect("wss://relay.example.com/ws");
185
186
 
186
187
  // Writes survive page reload via IndexedDB
@@ -244,6 +245,10 @@ BEAM browser nodes are wire-compatible with Gun.js. A BEAM WASM node can:
244
245
  - Verify Gun.js SEA signatures (P-256 ECDSA, double-hashed)
245
246
  - Interoperate with the Gun.js graph format
246
247
 
248
+ Bidirectional compatibility is verified by Playwright E2E tests
249
+ (`tests/e2e/gun-beam-interop.spec.mjs`) covering Gun.js→BEAM, BEAM→Gun.js,
250
+ and bidirectional convergence scenarios.
251
+
247
252
  ## Quick Start
248
253
 
249
254
  ### Build & Run a Node
@@ -799,6 +804,7 @@ let config = Config {
799
804
  my_pub: Some("x.y".into()),
800
805
  broadcast_buffer_size: 4096,
801
806
  ice_servers: vec!["stun:stun.l.google.com:19302".into()],
807
+ dedup_capacity: 100_000,
802
808
  };
803
809
  # }
804
810
  ```
@@ -872,7 +878,6 @@ cargo clippy -- -D warnings
872
878
  # Doctests only (verifies README code examples compile)
873
879
  cargo test --doc
874
880
 
875
- # Benchmarks
876
881
  cargo bench
877
882
 
878
883
  # Run a specific integration test
@@ -903,6 +908,114 @@ cargo test --test wire_live -- --ignored # Layer 3: live integration (needs Nod
903
908
 
904
909
  ---
905
910
 
911
+ ## Benchmarks
912
+
913
+ BEAM includes a comprehensive benchmarking suite covering relay throughput,
914
+ micro-benchmarks for hot-path components, and storage performance.
915
+
916
+ ### Local Put Throughput
917
+
918
+ Local (non-relay) puts through the actor pipeline — measures the full
919
+ `Node::handle → Router::route → MemoryStorage::apply` path with no network I/O:
920
+
921
+ | Scenario | Messages | Throughput |
922
+ |----------|----------|------------|
923
+ | 1 sender × 10k | 10,000 | ~24,000–53,000 puts/sec |
924
+
925
+ Throughput varies with system load. On a dedicated machine with no
926
+ competing processes, expect 50,000+ puts/sec. The bottleneck is
927
+ `Value` cloning for broadcast channels — further gains require
928
+ `Arc<Value>` to make cloning a refcount bump.
929
+
930
+ ### Relay Throughput
931
+
932
+ Real WebSocket connections through a memory-only relay (no disk I/O):
933
+
934
+ | Scenario | Messages | Throughput |
935
+ |----------|----------|------------|
936
+ | 1 sender × 10k | 10,000 | ~5,300 msgs/sec |
937
+ | 1 sender × 50k | 50,000 | ~10,600 msgs/sec |
938
+ | 10 senders × 5k | 50,000 | ~11,400 msgs/sec |
939
+
940
+ The relay's internal processing (parse + dedup + route + serialize) runs in
941
+ microseconds — the bottleneck is client-side `put().await`, not the relay.
942
+
943
+ ### Micro-Benchmarks (Criterion)
944
+
945
+ | Operation | Time |
946
+ |-----------|------|
947
+ | Parse small Put JSON | 1,067 ns |
948
+ | Parse medium Put JSON | 2.00 µs |
949
+ | Serialize small Put JSON | 69 ns |
950
+ | Parse Get | 677 ns |
951
+ | Dedup check (fresh) | 274 µs |
952
+ | Dedup check (duplicate) | 41.8 µs |
953
+ | Actor mailbox send+recv | 309 µs |
954
+
955
+ ### WASM Benchmarks (Node.js)
956
+
957
+ | Operation | WASM | Native | Ratio |
958
+ |-----------|------|--------|-------|
959
+ | Parse small Put | 7.9 µs | 1,067 ns | ~7.4× |
960
+ | Serialize small Put | 8.6 µs | 69 ns | ~125× |
961
+ | Parse Get | 4.5 µs | 677 ns | ~6.7× |
962
+
963
+ Run with: `wasm-pack test --node --no-default-features -- --nocapture`
964
+
965
+ ### WASM Relay Throughput (Browser-only)
966
+
967
+ `web_sys::WebSocket` callbacks don't fire in Node.js `wasm-bindgen-test-runner`
968
+ ([known limitation](https://github.com/wasm-bindgen/wasm-bindgen/issues/4921)).
969
+ Use the browser benchmark page to measure WASM relay throughput:
970
+
971
+ ```bash
972
+ cargo run -- start --port 4944 --memory-storage true --redb-storage false
973
+ python3 -m http.server 8080 -d examples/
974
+ # Open http://localhost:8080/bench.html in a browser
975
+ ```
976
+
977
+ Previous v0.11.0 browser results: ~115–651 msgs/sec depending on batch size.
978
+
979
+ ### Browser Benchmark
980
+
981
+ An interactive benchmark page is available at `examples/bench.html`:
982
+
983
+ ```bash
984
+ # Start a relay
985
+ cargo run -- start --port 4944 --memory-storage true --redb-storage false
986
+
987
+ # Serve the benchmark page
988
+ python3 -m http.server 8080 -d examples/
989
+
990
+ # Open in browser
991
+ open http://localhost:8080/bench.html
992
+ ```
993
+
994
+ The browser benchmark measures:
995
+ - **Relay TPS**: end-to-end throughput through a real relay
996
+ - **Put throughput**: local WASM API fire-and-forget puts
997
+ - **Get throughput**: local WASM API promise resolution
998
+ - **Put→Get round-trip**: full local cycle
999
+
1000
+ ### Running Benchmarks
1001
+
1002
+ ```bash
1003
+ # Relay throughput (release mode required)
1004
+ cargo test --release --test relay_throughput_bench -- --ignored --nocapture
1005
+
1006
+ # Micro-benchmarks (hot-path components)
1007
+ cargo bench --bench my_benchmark -- "wire_|dup_check|actor_mailbox"
1008
+
1009
+ # Storage benchmarks
1010
+ cargo bench --bench my_benchmark -- "write_storm|read_storm|mixed"
1011
+
1012
+ # Live metrics endpoint (while relay is running)
1013
+ curl http://localhost:8080/metrics
1014
+ ```
1015
+
1016
+ See [`benches/RESULTS.md`](benches/RESULTS.md) for full results with
1017
+ methodology and analysis.
1018
+
906
1019
  ## Features
907
1020
 
908
1021
  | Feature | Default | Enables |
package/beam.d.ts CHANGED
@@ -115,11 +115,11 @@ export interface InitOutput {
115
115
  readonly beam_put_num: (a: number, b: number, c: number, d: number) => void;
116
116
  readonly beam_stop: (a: number) => void;
117
117
  readonly task_worker_entry_point: (a: number, b: number) => void;
118
- readonly __wasm_bindgen_func_elem_1101: (a: number, b: number, c: number, d: number) => void;
119
- readonly __wasm_bindgen_func_elem_1115: (a: number, b: number, c: number, d: number) => void;
120
- readonly __wasm_bindgen_func_elem_234: (a: number, b: number, c: number) => void;
121
- readonly __wasm_bindgen_func_elem_234_2: (a: number, b: number, c: number) => void;
122
- readonly __wasm_bindgen_func_elem_234_3: (a: number, b: number, c: number) => void;
118
+ readonly __wasm_bindgen_func_elem_1109: (a: number, b: number, c: number, d: number) => void;
119
+ readonly __wasm_bindgen_func_elem_1123: (a: number, b: number, c: number, d: number) => void;
120
+ readonly __wasm_bindgen_func_elem_232: (a: number, b: number, c: number) => void;
121
+ readonly __wasm_bindgen_func_elem_232_2: (a: number, b: number, c: number) => void;
122
+ readonly __wasm_bindgen_func_elem_232_3: (a: number, b: number, c: number) => void;
123
123
  readonly __wbindgen_export: (a: number, b: number) => number;
124
124
  readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
125
125
  readonly __wbindgen_export3: (a: number) => void;
package/beam.js CHANGED
@@ -290,7 +290,7 @@ function __wbg_get_imports() {
290
290
  const a = state0.a;
291
291
  state0.a = 0;
292
292
  try {
293
- return __wasm_bindgen_func_elem_1115(a, state0.b, arg0, arg1);
293
+ return __wasm_bindgen_func_elem_1123(a, state0.b, arg0, arg1);
294
294
  } finally {
295
295
  state0.a = a;
296
296
  }
@@ -308,7 +308,7 @@ function __wbg_get_imports() {
308
308
  const a = state0.a;
309
309
  state0.a = 0;
310
310
  try {
311
- return __wasm_bindgen_func_elem_1115(a, state0.b, arg0, arg1);
311
+ return __wasm_bindgen_func_elem_1123(a, state0.b, arg0, arg1);
312
312
  } finally {
313
313
  state0.a = a;
314
314
  }
@@ -432,23 +432,23 @@ function __wbg_get_imports() {
432
432
  return addHeapObject(ret);
433
433
  }, arguments); },
434
434
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
435
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 12, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
436
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_234);
435
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 15, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
436
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_232);
437
437
  return addHeapObject(ret);
438
438
  },
439
439
  __wbindgen_cast_0000000000000002: function(arg0, arg1) {
440
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 269, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
441
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_1101);
440
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 272, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
441
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_1109);
442
442
  return addHeapObject(ret);
443
443
  },
444
444
  __wbindgen_cast_0000000000000003: function(arg0, arg1) {
445
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("IDBVersionChangeEvent")], shim_idx: 12, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
446
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_234_2);
445
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("IDBVersionChangeEvent")], shim_idx: 15, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
446
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_232_2);
447
447
  return addHeapObject(ret);
448
448
  },
449
449
  __wbindgen_cast_0000000000000004: function(arg0, arg1) {
450
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 12, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
451
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_234_3);
450
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 15, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
451
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_232_3);
452
452
  return addHeapObject(ret);
453
453
  },
454
454
  __wbindgen_cast_0000000000000005: function(arg0) {
@@ -475,22 +475,22 @@ function __wbg_get_imports() {
475
475
  };
476
476
  }
477
477
 
478
- function __wasm_bindgen_func_elem_234(arg0, arg1, arg2) {
479
- wasm.__wasm_bindgen_func_elem_234(arg0, arg1, addHeapObject(arg2));
478
+ function __wasm_bindgen_func_elem_232(arg0, arg1, arg2) {
479
+ wasm.__wasm_bindgen_func_elem_232(arg0, arg1, addHeapObject(arg2));
480
480
  }
481
481
 
482
- function __wasm_bindgen_func_elem_234_2(arg0, arg1, arg2) {
483
- wasm.__wasm_bindgen_func_elem_234_2(arg0, arg1, addHeapObject(arg2));
482
+ function __wasm_bindgen_func_elem_232_2(arg0, arg1, arg2) {
483
+ wasm.__wasm_bindgen_func_elem_232_2(arg0, arg1, addHeapObject(arg2));
484
484
  }
485
485
 
486
- function __wasm_bindgen_func_elem_234_3(arg0, arg1, arg2) {
487
- wasm.__wasm_bindgen_func_elem_234_3(arg0, arg1, addHeapObject(arg2));
486
+ function __wasm_bindgen_func_elem_232_3(arg0, arg1, arg2) {
487
+ wasm.__wasm_bindgen_func_elem_232_3(arg0, arg1, addHeapObject(arg2));
488
488
  }
489
489
 
490
- function __wasm_bindgen_func_elem_1101(arg0, arg1, arg2) {
490
+ function __wasm_bindgen_func_elem_1109(arg0, arg1, arg2) {
491
491
  try {
492
492
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
493
- wasm.__wasm_bindgen_func_elem_1101(retptr, arg0, arg1, addHeapObject(arg2));
493
+ wasm.__wasm_bindgen_func_elem_1109(retptr, arg0, arg1, addHeapObject(arg2));
494
494
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
495
495
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
496
496
  if (r1) {
@@ -501,8 +501,8 @@ function __wasm_bindgen_func_elem_1101(arg0, arg1, arg2) {
501
501
  }
502
502
  }
503
503
 
504
- function __wasm_bindgen_func_elem_1115(arg0, arg1, arg2, arg3) {
505
- wasm.__wasm_bindgen_func_elem_1115(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
504
+ function __wasm_bindgen_func_elem_1123(arg0, arg1, arg2, arg3) {
505
+ wasm.__wasm_bindgen_func_elem_1123(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
506
506
  }
507
507
 
508
508
 
package/beam_bg.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "David Newman <david.r.newman@proton.me>"
7
7
  ],
8
8
  "description": "BEAM — distributed graph database syncing over WebSocket, WebRTC, and multicast. Successor to rod.",
9
- "version": "0.9.2",
9
+ "version": "0.12.0",
10
10
  "license": "MIT",
11
11
  "repository": {
12
12
  "type": "git",