crdt-kit
Conflict-free Replicated Data Types for Rust
Build offline-first, real-time, and distributed applications that just work.
Documentation • Crate • Examples • Contributing
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 specifically for resource-constrained, latency-sensitive environments where existing solutions (Automerge, Yjs) add too much overhead:
+-----------+ +-----------+ +-----------+
| 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
Key Advantages
crdt-kit |
Automerge | Yjs | |
|---|---|---|---|
| Zero dependencies (core) | Yes | No (30+) | No (N/A — JS) |
no_std / embedded |
Yes | No | No |
| WASM-ready | Yes | Partial | Native JS |
| Persistent storage | SQLite, redb, memory | Custom | N/A |
| Schema migrations | Automatic (lazy) | Manual | N/A |
| Delta sync | Yes | Yes | Yes |
| Serde integration | Yes | Custom | N/A |
| Pure Rust | Yes | Yes | No (JS) |
Built For
| Environment | Why it matters |
|---|---|
| IoT / Embedded | no_std support — runs on bare metal, Raspberry Pi, ESP32 |
| Mobile apps | Offline-first with automatic conflict resolution on reconnect |
| Edge computing | Delta sync minimizes bandwidth between edge nodes |
| P2P networks | No central server needed — every peer is equal |
| Real-time collaboration | Concurrent edits merge without coordination |
| WASM / Browser | First-class WebAssembly bindings for web apps |
Quick Start
[]
= "0.3"
use *;
// Two devices, working offline
let mut phone = new;
phone.increment;
phone.increment;
let mut laptop = new;
laptop.increment;
// When they reconnect — merge. Always converges.
phone.merge;
assert_eq!;
With Persistence
[]
= { = "0.3", = ["serde"] }
= { = "0.1", = ["sqlite"] }
use ;
use ;
let store = open.unwrap;
let mut db = with_store;
// Save — automatically wrapped in a version envelope
db.save.unwrap;
// Load — automatically migrated if the schema changed
let reading: = db.load.unwrap;
Workspace Architecture
crdt-kit is a multi-crate workspace. Use only what you need:
crdt-kit/
├── crates/
│ ├── crdt-kit/ 9 CRDTs + HLC + traits (the core library)
│ ├── crdt-store/ Persistence: SQLite, redb, memory + CrdtDb API
│ ├── crdt-migrate/ Version envelopes + migration engine
│ ├── crdt-migrate-macros/ #[crdt_schema] + #[migration] proc macros
│ ├── crdt-codegen/ Code generation from TOML schemas
│ ├── crdt-cli/ CLI: status/inspect/compact/export/generate/dev-ui
│ ├── crdt-dev-ui/ Embedded web panel for database inspection
│ └── crdt-example-tasks/ Full example: codegen + migrations + events
| Crate | Description | Feature flags |
|---|---|---|
| crdt-kit | 9 CRDT types, HLC, Crdt/DeltaCrdt traits | std, serde, wasm |
| crdt-store | Unified storage abstraction + high-level CrdtDb | sqlite, redb |
| crdt-migrate | Transparent schema migrations (lazy, on-read) | macros |
| crdt-codegen | Generate structs, migrations, and helpers from TOML | — |
| crdt-cli | Developer CLI tool (crdt binary) |
— |
| crdt-dev-ui | Web inspection panel (Axum, dark theme) | — |
Persistence & Storage
Three backends, one trait surface:
| Backend | Feature | Use case | Dependencies |
|---|---|---|---|
MemoryStore |
(always) | Testing, prototyping | None |
SqliteStore |
sqlite |
Edge Linux, mobile, desktop | rusqlite (bundled) |
RedbStore |
redb |
Pure-Rust edge (no C deps) | redb |
All backends implement StateStore + EventStore with event sourcing, snapshots, and compaction:
use ;
let mut store = new;
// State persistence
store.put.unwrap;
// Event sourcing
let seq = store.append_event.unwrap;
// Snapshots + compaction
store.save_snapshot.unwrap;
store.truncate_events_before.unwrap;
Schema Migrations
When your data evolves between app versions, crdt-migrate handles it transparently:
use ;
- Lazy: Data migrates on read, not on startup
- Deterministic: Two devices migrating the same data produce identical results
- Linear chain: v1 → v2 → v3 → current, never skipping steps
- Write-back: Migrated data is re-persisted automatically
Code Generation
Define your entities in a crdt-schema.toml file and generate all the Rust boilerplate:
[]
= "src/generated"
# Entity with CRDT-wrapped fields (conflict-free replicated)
[[]]
= "Project"
= "projects"
[[]]
= 1
= [
{ = "name", = "String", = "LWWRegister" },
{ = "members", = "String", = "ORSet" },
]
# Entity with plain fields, versioned schema, and a relation
[[]]
= "Task"
= "tasks"
[[]]
= 1
= [
{ = "title", = "String" },
{ = "done", = "bool" },
]
[[]]
= 2
= [
{ = "title", = "String" },
{ = "done", = "bool" },
{ = "priority", = "Option<u8>", = "None" },
{ = "tags", = "Vec<String>", = "Vec::new()" },
{ = "project_id", = "String", = "String::new()", = "Project" },
]
Field options:
| Option | Example | Effect |
|---|---|---|
type |
"String", "Vec<u8>" |
Rust type for the field |
default |
"None", "Vec::new()" |
Default value for migrations |
crdt |
"LWWRegister", "ORSet", "GCounter" |
Wraps the type with a CRDT (e.g., LWWRegister<String>) |
relation |
"Project" |
Documents a reference to another entity |
Supported CRDT types: GCounter, PNCounter, LWWRegister, MVRegister, GSet, TwoPSet, ORSet
What gets generated:
project.rs/task.rs— Versioned structs with#[crdt_schema], CRDT-wrapped field types, andtype Project = ProjectV1task_migrations.rs— Migration functions with#[migration](auto-generated for field additions, CRDT fields get auto-defaults)helpers.rs—create_db()andcreate_memory_db()with all migrations pre-registeredmod.rs— Module declarations and re-exports
All files are marked AUTO-GENERATED by crdt-codegen -- DO NOT EDIT.
See crdt-example-tasks for a complete working example.
Developer Tools
CLI
Dev UI
A dark-themed web panel for visual database inspection during development:
Browse namespaces, entities, event logs, version envelopes, and snapshots — all from your browser.
Edge Computing & IoT
crdt-kit is purpose-built for edge environments. Import it with no_std for bare metal or with serde for network serialization:
# Raspberry Pi / ESP32 / bare metal (no standard library)
[]
= { = "0.3", = false }
# Edge node with JSON sync over MQTT/HTTP
[]
= { = "0.3", = ["serde"] }
= "1"
use *;
// Edge sensor node collects temperature readings
let mut sensor_a = new;
let mut sensor_b = new;
// Each sensor counts events independently (no network needed)
sensor_a.increment_by; // 142 events detected
sensor_b.increment_by; // 89 events detected
// When the gateway collects data — merge. Order doesn't matter.
sensor_a.merge;
assert_eq!; // exact total, no double-counting
// Delta sync: only send what changed (saves bandwidth on LoRa/BLE)
let mut gateway = new;
let delta = sensor_a.delta; // minimal payload
gateway.apply_delta; // gateway is up to date
assert_eq!;
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 | 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 |
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 |
Examples
# CRDT basics
# Persistence & migration
# Full-stack example (codegen + migrations + event sourcing)
Performance
Measured with Criterion on optimized builds:
| Operation | Time | Throughput |
|---|---|---|
| GCounter increment x1000 | 53 µs | ~19M ops/sec |
| GCounter merge 10 replicas | 1.1 µs | ~9M merges/sec |
| GCounter merge 100 replicas | 17.8 µs | — |
| PNCounter inc+dec x1000 | 60 µs | ~16M ops/sec |
| ORSet insert x1000 | 187 µs | ~5M ops/sec |
| ORSet merge 500+500 elements | 191 µs | — |
| GSet merge 1000+1000 elements | 102 µs | — |
| LWWRegister merge 100 replicas | 11.5 µs | ~8M merges/sec |
Guarantees
All 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 256 tests across the workspace.
Roadmap
- G-Counter, PN-Counter
- LWW-Register, MV-Register
- G-Set, 2P-Set, OR-Set
- RGA List (ordered sequence)
- Text CRDT (collaborative text editing)
-
no_stdsupport (embedded / bare metal) -
serdeserialization support - Delta-state optimization
- WASM bindings
- Persistent storage (SQLite + redb)
- High-level CrdtDb API with version envelopes
- Transparent schema migrations (
#[crdt_schema]+#[migration]) - Developer CLI (
crdt status/inspect/compact/export/generate) - Dev UI web panel
- Code generation from TOML schemas (
crdt generate) - Network transport layer (TCP, WebSocket, QUIC)
- Sync protocol (delta-based replication)
- Benchmarks against Automerge / Yrs
Contributing
Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.
License
Dual-licensed under your choice of:
- MIT — LICENSE-MIT
- Apache 2.0 — LICENSE-APACHE