crdt-kit 0.5.1

CRDTs optimized for edge computing and local-first applications
Documentation

Crates.io Downloads Docs.rs CI License

WebsiteDocumentationCrateExamplesContributing

Why crdt-kit?

Traditional sync solutions break when devices go offline. CRDTs solve this at the data structure level — every replica can be updated independently, and merges always converge to the same result, guaranteed by math, not by servers.

crdt-kit is built for resource-constrained, latency-sensitive environments where existing solutions (Automerge, Yjs) add too much overhead:

  • Zero heap allocations on node IDs (u64 instead of String)
  • no_std + alloc — runs on bare metal, ESP32, Raspberry Pi
  • 11 CRDT types with delta-state sync, Hybrid Logical Clocks, and versioned serialization
  • Single dependency-free crate (core) — optional serde and wasm features
  • Battle-tested — 137 unit tests, 14 integration tests, property-based testing (proptest), 6 fuzz targets
+-----------+     +-----------+     +-----------+
|  Device A |     |  Device B |     |  Device C |
|  (offline)|     |  (offline)|     |  (offline)|
+-----+-----+     +-----+-----+     +-----+-----+
      |                 |                 |
      |  local edits    |  local edits    |  local edits
      |                 |                 |
      +--------+--------+--------+--------+
               |                 |
               v                 v
         +-----------+     +-----------+
         |   merge   |     |   merge   |
         +-----------+     +-----------+
               |                 |
               v                 v
          Same state!       Same state!    <-- Strong Eventual Consistency

Quick Start

[dependencies]

crdt-kit = "0.5"

use crdt_kit::prelude::*;

// Two devices, working offline — NodeId is u64 (zero heap allocs)
let mut phone = GCounter::new(1);
phone.increment();
phone.increment();

let mut laptop = GCounter::new(2);
laptop.increment();

// When they reconnect — merge. Always converges.
phone.merge(&laptop);
assert_eq!(phone.value(), 3);

Delta Sync (bandwidth-efficient)

use crdt_kit::prelude::*;

let mut edge = GCounter::new(1);
edge.increment_by(1000);

let mut cloud = GCounter::new(100);

// Send only what cloud doesn't have
let delta = edge.delta(&cloud);
cloud.apply_delta(&delta);
assert_eq!(cloud.value(), 1000);

With Serde

[dependencies]

crdt-kit = { version = "0.5", features = ["serde"] }

no_std (embedded / bare metal)

[dependencies]

crdt-kit = { version = "0.5", default-features = false }


Available CRDTs

Counters

Type Description Real-world use
GCounter Grow-only counter Page views, IoT sensor events, download counts
PNCounter Increment & decrement Inventory stock, likes/dislikes, seat reservations

Registers

Type Description Real-world use
LWWRegister Last-writer-wins (HLC) User profile fields, config settings, GPS location
MVRegister Multi-value (shows conflicts) Collaborative fields, version tracking

Sets

Type Description Real-world use
GSet Grow-only set Seen message IDs, tags, audit logs
TwoPSet Add & permanent remove Blocklists, revoked tokens
ORSet Add & remove freely Shopping carts, todo lists, chat members

Maps

Type Description Real-world use
LWWMap Last-writer-wins per key Sensor config, user preferences, feature flags
AWMap Add-wins (concurrent add beats remove) Device registries, permission tables, metadata

Sequences

Type Description Real-world use
Rga Replicated Growable Array Playlists, kanban boards, ordered lists
TextCrdt Collaborative text Google Docs-style editing, shared notes

Traits

Trait Description
Crdt Core merge semantics (commutative, associative, idempotent)
DeltaCrdt Efficient delta sync — send only what changed (all 11 types)
Versioned Schema versioning for serialization envelopes (all 11 types)

v0.5 Highlights

Zero-allocation Node IDs

All CRDTs use NodeId (u64) instead of String for replica identity. This eliminates heap allocations on every operation — critical for embedded and IoT targets.

// Before (v0.4): heap allocation on every construct
let mut c = GCounter::new("sensor-a".to_string());

// After (v0.5): zero-cost u64 identity
let mut c = GCounter::new(1);

Hybrid Logical Clocks (native)

LWWRegister uses HybridTimestamp natively — physical clock + logical counter + node_id for total ordering. No more dual-timestamp correctness bugs.

use crdt_kit::clock::HybridClock;
use crdt_kit::prelude::*;

let mut clock = HybridClock::new(1);
let mut reg = LWWRegister::new("initial", &mut clock);
reg.set("updated", &mut clock);

New Map Types

LWWMap and AWMap complete the CRDT toolbox for key-value workloads:

use crdt_kit::prelude::*;
use crdt_kit::clock::HybridTimestamp;

// LWWMap: per-key last-writer-wins
let mut config = LWWMap::new();
let ts = HybridTimestamp { physical: 100, logical: 0, node_id: 1 };
config.insert("sample_rate", 500, ts);

// AWMap: add-wins (concurrent add beats remove)
let mut registry = AWMap::new(1);
registry.insert("sensor-001", "zone-A");

Examples

cargo run -p crdt-kit --example counter         # Distributed counters

cargo run -p crdt-kit --example todo_list        # Collaborative todo list

cargo run -p crdt-kit --example chat             # Chat with conflict detection

cargo run -p crdt-kit --example ecommerce        # E-commerce multi-store sync

cargo run -p crdt-kit --example iot_dashboard    # Full IoT sensor dashboard (all 11 CRDTs)


Edge Computing & IoT

use crdt_kit::prelude::*;

// Edge sensor nodes count events independently
let mut sensor_a = GCounter::new(1);
let mut sensor_b = GCounter::new(2);

sensor_a.increment_by(142); // 142 events detected
sensor_b.increment_by(89);  // 89 events detected

// Gateway merges — order doesn't matter
sensor_a.merge(&sensor_b);
assert_eq!(sensor_a.value(), 231); // exact total, no double-counting

// Delta sync: only send what changed (saves bandwidth on LoRa/BLE)
let mut gateway = GCounter::new(100);
let delta = sensor_a.delta(&gateway);
gateway.apply_delta(&delta);
assert_eq!(gateway.value(), 231);

Guarantees

All 11 CRDTs satisfy Strong Eventual Consistency (SEC):

Property Meaning Why it matters
Commutativity merge(a, b) == merge(b, a) Order of sync doesn't matter
Associativity merge(a, merge(b, c)) == merge(merge(a, b), c) Group syncs however you want
Idempotency merge(a, a) == a Safe to retry — no duplicates

Verified by 151+ tests (137 unit + 14 integration), property-based testing (proptest: commutativity, associativity, idempotency, and delta equivalence for all 11 types), and 6 fuzz targets for crash resistance under arbitrary input.


Benchmarks

cargo bench --bench crdt_benchmarks   # Core benchmarks (Criterion)

cargo bench --bench comparative       # vs Automerge & Yrs


Feature Flags

Feature Default Description
std Yes Standard library support
serde No Serialize/deserialize all CRDT types
wasm No WebAssembly bindings via wasm-bindgen

For no_std, disable defaults: default-features = false


Roadmap

  • G-Counter, PN-Counter
  • LWW-Register (HLC-native), MV-Register
  • G-Set, 2P-Set, OR-Set
  • LWW-Map, AW-Map (v0.5)
  • RGA List (ordered sequence)
  • Text CRDT (collaborative text, thin Rga<char> wrapper)
  • no_std support (embedded / bare metal)
  • serde serialization support
  • Delta-state optimization (11/11 types with DeltaCrdt trait)
  • HLC (Hybrid Logical Clock) — native HybridTimestamp
  • NodeId (u64) — zero heap allocations
  • Tombstone compaction for ORSet
  • Error handling for RGA/TextCrdt (RgaError, TextError)
  • WASM bindings (GCounter, PNCounter, LWWRegister, GSet, ORSet, TextCrdt)
  • Fuzz testing (6 targets via cargo-fuzz)
  • IoT Sensor Dashboard example (all 11 CRDTs)
  • Versioned trait with CrdtType enum for all 11 types
  • HybridClock derives Debug + Clone, accepts NodeId (u64)
  • RGA merge optimization (two-phase: tombstones then inserts, no index-shift loop)
  • Memory footprint benchmarks for embedded use case
  • Network transport layer (TCP, WebSocket, QUIC)
  • Sync protocol (delta-based replication)
  • AWMap tombstone compaction
  • Rope-backed RGA for large documents (>10K elements)

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.

License

Dual-licensed under your choice of: