Skip to content

Releases: retiform-labs/beam

BEAM v0.19.0

Choose a tag to compare

@guan-tends guan-tends released this 11 Sep 03:33

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-originated Gets (asks) in a bounded registry
    (local_asks, SEEN_MSGS_MAX_SIZE — matching Gun's ~10K ask ceiling).
    On Hi (initial contact — the one transport-agnostic seam: native
    WsConn, WASM WasmWsConn, and WebRTC all send Hi on pre_start),
    after the dam:"?" ack, the Router re-issues every recorded ask with
    FRESH message IDs (the seen_get_messages dedup would eat reused IDs)
    through the normal pipeline. Live subscriptions now survive reconnects
    with zero user action — Gun mesh.js 'hi' parity.
  • E2E resubscribe test (resubscribe_after_reconnect, wire_live,
    commit b172dab): SAME node, persistent on() subscription survives a
    relay kill + restart on the same port and receives post-restart data
    without a new on() — the full disconnect-detect → reconnect → Hi →
    re-issue chain verified against a real Gun.js relay (unlike the
    existing reconnection_sync test, 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_SENTINEL kept 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 blanket peer_addrs already-sent marking assumed every
    known peer is an OWM/WsServer-managed child. For pure
    Node::connect_peer clients (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 the WsConn actor's lifecycle
    handle via the new start_actor_with_handle; resumption of the
    connect loop IS the disconnect signal. Reconnects within seconds.
  • WASM reconnect loop (commit 25bfab6): browser WebSocket peers
    never reconnected — WasmWsConn.onclose was an empty closure and
    connect_peer_wasm was one-shot. Now: onclose/onerror stop the
    actor (same chain as native), new() split into from_socket (closure
    wiring, written once) + try_new (URL → Result, no panic on
    construction failure), and connect_peer_wasm runs the same reconnect
    loop as native (immediate reconnect on disconnect; exponential backoff
    1s→60s only on construction failures; loop aborted on node.stop()).
    ActorContext::child_task drops its Send bound on WASM (browser
    futures are !Send via spawn_local; existing callers unchanged).
  • once() Lagged panic (commits f1bd37b + 7511542): once() panicked
    on RecvError::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 with broadcast_buffer_size = 2 proves the crash
    path is structurally impossible.
  • Wire msg-id edge fixtures (commit 94c76f7): three edge fixtures
    predated the c8e8268 Gun-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 is opt.wait || 99
    (src/on.js:66). Now exported as DEFAULT_ONCE_WAIT with 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

Choose a tag to compare

@guan-tends guan-tends released this 10 Sep 10:06
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

Choose a tag to compare

@guan-tends guan-tends released this 21 Aug 10:02

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 time crate 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

Choose a tag to compare

@guan-tends guan-tends released this 15 Aug 11:21

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

Choose a tag to compare

@guan-tends guan-tends released this 15 Aug 10:07

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_obj take &str instead of String (zero-alloc common path)
  • Added TryFrom<&SerdeJsonValue> for Value (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-run passes

v0.13.0 — Relay Optimization

Choose a tag to compare

@guan-tends guan-tends released this 15 Aug 01:22

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> on Put — serialize once, reuse for all peers (Arc refcount bump)
  • Mirrors Gun.js meta.raw

Sprint 2: Per-Key HAM Filtering

  • HamFilterResult enum (Stale / New / PartiallyNew)
  • Per-key timestamp filtering — stale keys dropped, only new keys relayed
  • Mirrors Gun.js ham() per-key inside while loop
  • Zero-alloc common case (all-new uses original &Put)

Sprint 3: Batched WebSocket Frames

  • handle_batch packs >1 messages into JSON array in single WS frame
  • Single-message sends unchanged — backwards compatible
  • Mirrors Gun.js peer.batch packing

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

Choose a tag to compare

@guan-tends guan-tends released this 13 Aug 23:53

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::clone instead 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::take replaces 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

Choose a tag to compare

@guan-tends guan-tends released this 09 Aug 10:12

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

Choose a tag to compare

@guan-tends guan-tends released this 08 Aug 11:54

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