crdt-kit

Conflict-Free Replicated Data Types for Rust

Lightweight (~50KB), no_std compatible, optimized for IoT, edge computing, WASM, and local-first architectures.

no_std~50KB9 CRDTsDelta SyncWASM0 dependencies
crates.io downloads docs ci
terminal
$ cargo add crdt-kit --features serde

What are CRDTs?

Conflict-Free Replicated Data Types are data structures that can be replicated across multiple devices, updated independently and concurrently, and merged automatically without conflicts.

Unlike traditional databases that require a central server to coordinate writes, CRDTs guarantee that all replicas will converge to the same state regardless of the order in which updates are received.

This makes them ideal for offline-first apps, peer-to-peer systems, IoT networks, and any scenario where devices need to work independently and sync later.

Three devices create data independently while disconnected from the network.

PhoneAlice
Offline
Notes
Meeting at 3pm
{A:1, B:0, C:0}
LaptopBob
Offline
Notes
Buy groceries
{A:0, B:1, C:0}
SensorCarol
Offline
Notes
Deploy v2.1
{A:0, B:0, C:1}

Why crdt-kit?

Built for resource-constrained, latency-sensitive environments where existing solutions add too much overhead.

~50KB Binary size
0 Dependencies
9 CRDT types
268 Tests
19M ops/sec peak

Features

no_std

Runs on bare metal, Raspberry Pi, ESP32. All types work with #![no_std] + alloc.

Delta Sync

Only send what changed. DeltaCrdt trait for GCounter, PNCounter, ORSet. Minimizes bandwidth on LoRa, BLE.

WASM

First-class wasm-bindgen bindings. Same CRDTs in browser, Deno, Node.js, and Rust backend.

Migrations

Transparent, lazy migrations on read. #[crdt_schema] + #[migration] proc macros. Deterministic.

Storage

Three backends: SQLite (bundled), redb (pure Rust), memory. Event sourcing, snapshots, compaction.

Codegen

Define entities in TOML, run crdt generate. Get models, migrations, repositories, events, sync.

Serde

Serialize/Deserialize for all 9 CRDT types. JSON, MessagePack, Postcard, CBOR — any serde format.

Events

Full event log with append, replay, snapshots, compaction. EventStore trait on all backends.

Dev Tools

CLI: status, inspect, compact, export, generate, dev-ui. Web panel for visual inspection.

How It Compares

crdt-kit Automerge Yjs / Yrs
Language Rust Rust JS / Rust
Zero deps (core) 30+ N/A
no_std
WASM Partial Native
Storage SQLite, redb, mem Custom N/A
Migrations Automatic Manual N/A
Delta sync
Event sourcing
Code generation
CLI
Size ~50KB ~500KB+ ~150KB

Quick Start

Cargo.toml
[dependencies]
crdt-kit = "0.3"
main.rs
use crdt_kit::prelude::*;

// Two devices, working offline
let mut phone = GCounter::new("phone");
phone.increment();
phone.increment();

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

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

Delta Sync

delta_sync.rs
let mut sensor = GCounter::new("sensor-a");
sensor.increment_by(142);

let mut gateway = GCounter::new("gateway");

// Delta: only send what the gateway doesn't have
let delta = sensor.delta(&gateway);
gateway.apply_delta(&delta);
assert_eq!(gateway.value(), 142);

Schema Migrations

migration.rs
use crdt_migrate::{crdt_schema, migration};

#[crdt_schema(version = 1, table = "sensors")]
struct SensorV1 { device_id: String, temperature: f32 }

#[crdt_schema(version = 2, table = "sensors")]
struct SensorV2 { device_id: String, temperature: f32, humidity: Option<f32> }

#[migration(from = 1, to = 2)]
fn add_humidity(old: SensorV1) -> SensorV2 {
  SensorV2 { device_id: old.device_id, temperature: old.temperature, humidity: None }
}
// v1 records auto-migrate to v2 on load

Architecture

Multi-crate workspace. Each crate is independently versioned. Use only what you need.

1

Define

Write a crdt-schema.toml with entities, versions, CRDT fields, and relations.

2

Generate

Run crdt generate. Get models, migrations, repository traits, events, sync.

3

Use

Import Persistence<S> in your app. Access repos, store data, sync between nodes.

Use Cases

IoT & Sensors

no_std core on ESP32, Raspberry Pi. Delta sync over LoRa/BLE. Schema migrations handle OTA updates.

Mobile Apps

Offline-first. Edit without network. Changes merge automatically on reconnect. No conflict dialogs.

Real-time Collaboration

TextCrdt for docs-style editing. ORSet for shared collections. No central coordinator needed.

Edge Computing

CRDTs at CDN edges. Local writes, delta sync between nodes. Pure-Rust redb — no C deps.

P2P Networks

No server. Every peer is equal. Any transport: WebSocket, WebRTC, Bluetooth. Order doesn't matter.

WASM & Browser

Same logic in Rust backend and browser. wasm-bindgen bindings. Ship one codebase everywhere.

Interactive Demo

Two devices, one chat. Send messages offline, then sync — CRDTs guarantee convergence.

Two devices chat offline. Messages are unique by ID. On merge, the ORSet unions all messages — none are lost.
Alice's Phone
offline
No messages yet — click send
Bob's Laptop
offline
No messages yet — click send
Alice
Bob

Performance

Measured with Criterion on optimized builds. 37–700x faster than Automerge and Yrs.

vs Automerge & Yrs

Comparative benchmarks with the two most popular CRDT libraries in the Rust ecosystem.

700× faster

Counter ×1000

crdt-kit 33 μs

Automerge 23 ms

37× faster

Text insert 1000

crdt-kit 83 μs · Yrs 3.1 ms

Automerge 16.5 ms

62× faster

List insert 1000

crdt-kit 265 μs · Yrs 16.5 ms

Automerge 34.4 ms

136× faster

Set insert 1000

crdt-kit 203 μs

Automerge 27.6 ms

8M ops/sec

GCounter increment

Incrementing a distributed counter. The most basic and frequent operation.

125 μs / 1000 ops

How does this benchmark work?

Creates a GCounter with one node and calls .increment() 1000 times consecutively. Measures total time. A GCounter is a map {node → count}. Each increment only adds 1 to its own slot — O(1), no locks.

4.5M merges/sec

GCounter merge

Merging 10 replicas. Simulates 10 devices reconnecting at once.

2.2 μs / 10 replicas

How does this benchmark work?

Creates 10 GCounters with different nodes, each incremented multiple times. Merges them all into one. Merge takes max() of each slot. Linear time in number of nodes.

3.7M ops/sec

Rga insert ×1000

Inserting 1000 elements into a replicated list. For playlists, kanban boards.

267 μs / 1000 ops

How does this benchmark work?

Rga uses a flat Vec with direct positioning. No sequence rebuild — each insert finds its position in amortized O(n). 62× faster than Yrs Y.Array, 130× faster than Automerge List.

4M ops/sec

TextCrdt insert 1000

Inserting 1000 characters in collaborative text. For real-time editors.

246 μs / 1000 chars

How does this benchmark work?

TextCrdt uses flat Vec with O(1) cached len(). Each insert computes the visible position and places the element directly. 37× faster than Yrs Y.Text, 199× faster than Automerge Text.

2.8M ops/sec

ORSet insert ×1000

Inserting into a replicated set. For shopping carts, lists.

350 μs / 1000 ops

How does this benchmark work?

Each insert generates a unique tag (node + logical clock). On merge, "add wins" over concurrent remove. 136× faster than Automerge Map.

7.9M merges/sec

LWWRegister merge

Last-Writer-Wins: most recent value wins. For profiles, config, GPS.

12.6 μs / 100 replicas

How does this benchmark work?

LWWRegister stores a value + timestamp (Hybrid Logical Clock). On merge, highest timestamp wins. Merge is a simple comparison — O(1) per pair.

Measured with Criterion on optimized (release) builds. μs = microseconds. Compared against Automerge 0.7 and Yrs 0.25. Click any card for details.

Mathematical Guarantees

All CRDTs satisfy Strong Eventual Consistency (SEC). Verified by 268 tests.

a ⊕ b = b ⊕ a

Commutativity

Order of sync doesn't matter.

(a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)

Associativity

Group syncs however you want.

a ⊕ a = a

Idempotency

Safe to retry. No duplicates.

Developer CLI

status
$ crdt status app.db

Database overview: namespaces, keys, size.

inspect
$ crdt inspect app.db sensor-42

Entity detail with event log.

compact
$ crdt compact app.db

Snapshot + truncate event logs.

export
$ crdt export app.db --namespace sensors

JSON export.

generate
$ crdt generate --schema schema.toml

Generate persistence layer.

dev-ui
$ crdt dev-ui app.db

Web panel at localhost:4242.

Start building offline-first

cargo add crdt-kit --features serde