Repository navigation
Releases: retiform-labs/beam
Release list
BEAM v0.19.0
The hardening lap: close the one genuine parity gap found in the parity
verification pass — BEAM nodes lost their live subscriptions whenever a
relay connection dropped (Gun.js's mesh.js re-asks node.ask souls on
'hi'; BEAM never did). Verified end-to-end against a real Gun.js relay
with wire-level traces.
Added
- Resubscribe-on-reconnect (Router, commit abfde42): the Router records
locally-originatedGets (asks) in a bounded registry
(local_asks,SEEN_MSGS_MAX_SIZE— matching Gun's ~10K ask ceiling).
OnHi(initial contact — the one transport-agnostic seam: native
WsConn, WASM WasmWsConn, and WebRTC all sendHionpre_start),
after thedam:"?"ack, the Router re-issues every recorded ask with
FRESH message IDs (theseen_get_messagesdedup would eat reused IDs)
through the normal pipeline. Live subscriptions now survive reconnects
with zero user action — Gunmesh.js 'hi'parity. - E2E resubscribe test (
resubscribe_after_reconnect, wire_live,
commit b172dab): SAME node, persistenton()subscription survives a
relay kill + restart on the same port and receives post-restart data
without a newon()— the full disconnect-detect → reconnect → Hi →
re-issue chain verified against a real Gun.js relay (unlike the
existingreconnection_synctest, which reconnects a NEW node). - Protocol sentinel module (
src/sentinel.rs, commit ce91802): the
wire protocol sentinels (__beam_replay_complete__etc.) centralized
as named constants with rename-guard + distinctness tests; 59 scattered
literals refactored across node/router/7 storage adapters;
ack::QUORUM_MET_SENTINELkept as a deprecated alias (API preserved).
Storage table identifiers (rod_nodes_v1,ROD_NODES, …) deliberately
remain literals — they are wire-format/database identifiers, not
consumer sentinels.
Fixed
- Get fan-out for pure-client topologies (Router, commit b172dab):
handle_get's blanketpeer_addrsalready-sent marking assumed every
known peer is an OWM/WsServer-managed child. For pure
Node::connect_peerclients (WsConn actors with no OWM parent) that
suppressed the ONLY path a Get had to the relay — such clients could
push data but never fetch it. Now the marking applies only when a
server peer exists to do the fan-out; otherwise the known-peers sample
delivers the Get directly. (Hybrid topologies keep the marking —
documented pre-existing limitation.) - connect_peer disconnect detection (commit 610adbd):
connect_peer
slept 3600s after connecting ("TODO: detect disconnect") — reconnect
latency up to an hour. It now awaits theWsConnactor's lifecycle
handle via the newstart_actor_with_handle; resumption of the
connect loop IS the disconnect signal. Reconnects within seconds. - WASM reconnect loop (commit 25bfab6): browser
WebSocketpeers
never reconnected —WasmWsConn.onclosewas an empty closure and
connect_peer_wasmwas one-shot. Now:onclose/onerrorstop the
actor (same chain as native),new()split intofrom_socket(closure
wiring, written once) +try_new(URL →Result, no panic on
construction failure), andconnect_peer_wasmruns the same reconnect
loop as native (immediate reconnect on disconnect; exponential backoff
1s→60s only on construction failures; loop aborted onnode.stop()).
ActorContext::child_taskdrops itsSendbound on WASM (browser
futures are!Sendviaspawn_local; existing callers unchanged). - once() Lagged panic (commits f1bd37b + 7511542):
once()panicked
onRecvError::Lagged(.expect("recv error??")) — a routine replay
burst larger than the broadcast buffer crashed the node. Now: bounded
wait with a remaining-budget loop (lag recovery never extends the
caller's timeout; portable across native + WASM),Lagged→ log and
continue (delivery resumes at the oldest retained value),Closed→
None. Burst test withbroadcast_buffer_size = 2proves the crash
path is structurally impossible. - Wire msg-id edge fixtures (commit 94c76f7): three edge fixtures
predated thec8e8268Gun-parity msg-id relaxation and failed on a
clean checkout of main — updated to the standing spec (any string
accepted, missing ID generated). Pre-existing breakage, verified
against a clean worktree before fixing.
Changed
- once() default wait 99ms (commit f1bd37b): was 66ms, documented as
"matching Gun opt.wait" — Gun's actual default isopt.wait || 99
(src/on.js:66). Now exported asDEFAULT_ONCE_WAITwith tuning
guidance; the doc lie is fixed. - CHANGELOG: NACK absence-signal documented as future work with the
Gun.js parity rationale (commit 8ed33a7).
BEAM v0.18.0
chore(release): v0.18.0 — CHANGELOG + version bump CHANGELOG audited line-by-line against git log v0.17.0..HEAD: 20 non-merge commits + 1 merge, all cross-referenced.
v0.17.0 — Fjall Storage Backend + WASM Storage Adapters + Relay Dedup Fixes
What's New
Fjall Storage Backend
LSM-tree storage backend with WAL journalling and LZ4 compression. 3-4× faster writes than redb. Recommended for multi-node deployments. Feature-gated: --features fjall.
Universal Migration Tool
beam migrate subcommand converts between all storage formats (redb ↔ fjall ↔ Persy) with checksum verification.
WASM Storage Adapters
- OPFS — browser-native file persistence (Chrome 102+, Firefox 111+, Safari 15.2+)
- Node.js fs — server-side WASM persistence (
--features node-fs) - IDB — migrated to postcard serialization with JSON backward compat
Relay Dedup Fixes
- Eliminated sender double-send via server_peers + peer_addrs
- Fixed subscriber_fanout counter spin
- Fixed duplicate fan-out in handle_put_relay
Supply Chain
- Patched
timecrate RUSTSEC-2026-0009
Benchmarks (5× isolation)
| Benchmark | Throughput | Dedup |
|---|---|---|
| Local Put 10K | 42,685 puts/sec | — |
| Local Put 100K | 41,795 puts/sec | — |
| Relay 1×10K | 5,163 msgs/sec | 0 |
| Relay 1×50K | 8,725 msgs/sec | 0 |
| Relay 10×5K | 2,934 msgs/sec | ~449K (topological) |
~450 tests, 0 failures, 0 clippy warnings
v0.15.0 — Growable Mailboxes + mimalloc
What's New
mimalloc Global Allocator
Added mimalloc 0.1.52 as the default global allocator on native targets. Profiling showed the system allocator at 31.9% of CPU. mimalloc's per-thread heap segments with deferred freeing reduce allocation contention.
Growable Mailboxes (No Pre-allocation)
Eliminated 512KB per-actor pre-allocation (94.6% of all allocated bytes per DHAT profiling). Mailbox queues now grow on demand via VecDeque::new(). The backpressure ceiling remains as a DoS prevention mechanism.
Configurable Mailbox Ceilings
Added mailbox_capacity (default 65536) and child_mailbox_capacity (default 256) to Config. Operators can tune for memory-constrained or high-throughput environments.
Security
DoS surface reduced 256× — a malicious peer spawning child actors now costs ~2KB per actor instead of 512KB pre-allocated.
Benchmark Results
| Benchmark | v0.14.0 | v0.15.0 (avg) | Change |
|---|---|---|---|
| Local put (100k) | 47,499/sec | 63,568/sec | +33.8% |
| Relay 1×50k | 15,594/sec | 18,337/sec | +17.6% |
| Relay 10×5k | 11,966/sec | 12,277/sec | +2.6% |
Verification
- 384 native tests, 0 failed
- 19 doctests, 0 failed
- 7 WASM tests, 0 failed
- 0 clippy warnings
v0.14.0 — Relay Hot-Path Optimization
Relay Hot-Path Optimization (FxHash + String Allocation Reduction)
Sprint 1: FxHash Replacement (~4% CPU savings)
Replaced SipHasher-1-3 (std default) with FxHash for all non-cryptographic HashMaps/HashSets. FxHash is the hasher used by the Rust compiler itself — ~3-4× faster for non-crypto workloads.
Sprint 2: String Allocation Reduction
from_json_obj/from_put_objtake&strinstead ofString(zero-alloc common path)- Added
TryFrom<&SerdeJsonValue>forValue(borrowing conversion) - Fixed
msg_id.to_string()→msg_id(was cloning owned String)
Benchmark Results (vs v0.13.0)
| Benchmark | v0.13.0 | v0.14.0 | Change |
|---|---|---|---|
| Local put (100k) | 46,380 | 47,499 | +2.4% |
| Relay 1×50k | 15,419 | 15,594 | +1.1% |
| Relay 10×5k | 11,355 | 11,966 | +5.4% |
Verification
- 384 native tests, 19 doctests, 7 WASM tests — all green
- 0 clippy warnings
cargo publish --dry-runpasses
v0.13.0 — Relay Optimization
Relay Optimization: Serialized Caching + Per-Key HAM + Batched Frames
Three Gun.js-inspired relay optimizations that reduce redundant work on the
network fan-out path. All transparent to the wire protocol.
Sprint 1: Serialized Message Caching
OnceLock<Bytes>onPut— serialize once, reuse for all peers (Arc refcount bump)- Mirrors Gun.js
meta.raw
Sprint 2: Per-Key HAM Filtering
HamFilterResultenum (Stale / New / PartiallyNew)- Per-key timestamp filtering — stale keys dropped, only new keys relayed
- Mirrors Gun.js
ham()per-key insidewhileloop - Zero-alloc common case (all-new uses original
&Put)
Sprint 3: Batched WebSocket Frames
handle_batchpacks >1 messages into JSON array in single WS frame- Single-message sends unchanged — backwards compatible
- Mirrors Gun.js
peer.batchpacking
Benchmark Results
| Test | v0.12.0 | v0.13.0 | Change |
|---|---|---|---|
| Relay 1 sender × 10k | 5,287 msgs/sec | 6,984 msgs/sec | +32% |
| Relay 1 sender × 50k | 10,643 msgs/sec | 15,419 msgs/sec | +45% |
| Relay 10 senders × 5k | 11,380 msgs/sec | 11,355 msgs/sec | neutral |
Test Summary
- 358 native tests, 19 doctests, 7 WASM tests — all green
- 0 clippy warnings
v0.12.0 — Performance Overhaul + HAM Pre-Filter
17.5× Throughput Improvement
BEAM v0.12.0 delivers a 17.5× improvement in local put throughput (3,050 → 53,300 puts/sec on a clean machine) with zero functional regressions.
Highlights
- HAM (Hypothetical Amnesia Machine) pre-filter — Router-maintained timestamp index that checks incoming Put timestamps before any storage/relay work. Mirrors Gun.js
ham()function. "Avoid work" not "do work faster." - Arc actor system — Zero-copy message fanout via
Arc::cloneinstead of deep clone per subscriber - Lazy broadcast channels — Channels created on first subscriber, not on node creation. Eliminates 37% allocation hotspot (9.7× alone)
- Arc consolidation — Node clone cost reduced from ~12 atomics + 3 heap allocs to a single atomic increment
- &Put signatures — Eliminates unconditional deep clone of BTreeMap on every message (the 17.5× win)
- Zero-copy WebSocket serialization —
to_writer+std::mem::takereplaces buffer cloning
Performance
| Metric | v0.11.0 | v0.12.0 | Improvement |
|---|---|---|---|
| Local put (clean machine) | 3,050/sec | ~53,300/sec | 17.5× |
| Relay 1×10k | 3,041/sec | 6,952/sec | 2.3× |
| Relay 1×50k | 2,410/sec | 16,005/sec | 6.6× |
| Relay 10×5k | 2,014/sec | 11,604/sec | 5.8× |
Flame graph confirms flat profile — no dominant hotspot above 4.74%.
Test Suite
- 360 native tests pass (0 fail, 24 ignored)
- 7 WASM tests pass (0 fail, 4 ignored)
- 19 doctests pass (0 fail, 12 ignored)
- Zero clippy warnings, cargo fmt clean
Full changelog: CHANGELOG.md
v0.10.0 — Gun.js Wire Protocol Compatibility Verified
Gun.js ↔ BEAM Bidirectional Interop — Proven ✅
All three Playwright E2E tests pass:
- Gun.js → BEAM — Gun.js puts, BEAM WASM receives ✅
- BEAM → Gun.js — BEAM WASM puts, Gun.js receives ✅
- Bidirectional — Both clients exchange data through BEAM relay ✅
Serialization/Relay Fixes
The wire protocol was always sound. All bugs were in the serialization layer leaking BEAM-internal graph structure to Gun.js wire format:
- ws_server.rs: Internal messages (RegisterQuorum, CheckQuorumTimeouts) no longer relayed to wire clients
- message.rs: BEAM-internal souls (empty root pointer, slash-containing value nodes) skipped in wire serialization
- message.rs: json_str cache cleared before re-serialization to ensure soul filtering applies
- ws_conn.rs: Idiomatic while-let + match receive loop with proper WsMessage variant handling
E2E Test Infrastructure
- Playwright headless browser tests with per-test relay isolation
- Dynamic soul subscription prevents cross-test contamination
- Gun.js served locally for VPN-compatible testing
OPSEC
- Removed hardcoded internal network IP from browser-test pages
- Removed stale duplicate README
- Generated test HTML files gitignored
Test Results
256 lib + 21 CLI + 19 doctests + 3 Playwright E2E = all green
Full Changelog: v0.9.2...v0.10.0
BEAM v0.9.0 — Enterprise-Grade Release
First public release of BEAM — a distributed graph database syncing over WebSocket, WebRTC, and multicast.
Highlights
- 15 dependency modernization sprints across 3 tiers, eliminating 5 direct + 3 transitive dependencies
- Full graceful shutdown via tokio::sync::watch channel with SIGINT/SIGTERM handling
- Supply chain security: cargo-audit (0 findings) + cargo-deny (advisories, licenses, bans, sources — all passing)
- 5 RUSTSEC advisories resolved (ring, bincode, nix, atomic-polyfill, time)
- CI modernized: full 257-test suite, clippy -D warnings, fmt check, security jobs
- Release profile hardened: opt-level=3, strip, LTO, codegen-units=1
- Gun.js wire-format compatible (3-layer proof: golden fixtures, Node.js mirror, live integration)
Lineage
Gun.js (Mark Nadal, 2014) → rod (Martti Malmi, 2021) → BEAM (2026)
Verified
- 257 tests pass (238 lib + 19 doctests)
- 0 clippy warnings, 0 compiler warnings
- cargo fmt clean
- cargo audit: 0 vulnerabilities
- cargo deny: all checks pass
- cargo publish --dry-run: passes