15 unstable releases (3 breaking)

new 0.10.0-rc.17 Nov 6, 2025
0.10.0-rc.16 Nov 3, 2025
0.10.0-rc.12 Oct 30, 2025
0.9.0-rc.1 Oct 9, 2025
0.2.1 Aug 31, 2025

#133 in Data structures

Download history 79/week @ 2025-08-26 34/week @ 2025-09-02 122/week @ 2025-09-09 9/week @ 2025-09-16 1/week @ 2025-09-23 112/week @ 2025-09-30 337/week @ 2025-10-07 31/week @ 2025-10-14 1/week @ 2025-10-21 148/week @ 2025-10-28 12/week @ 2025-11-04

199 downloads per month
Used in calimero-dag

MIT/Apache

590KB
11K SLoC

Calimero Storage - CRDT Collections

Conflict-free Replicated Data Types for Distributed Applications

Build distributed applications that automatically resolve conflicts. Write natural code with nested data structures - the storage layer handles synchronization and merge logic for you.


Quick Start

use calimero_sdk::app;
use calimero_storage::collections::{Counter, LwwRegister, UnorderedMap, Vector};
use calimero_storage_macros::Mergeable;  // For custom structs!

#[app::state(emits = MyEvent)]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct MyApp {
    // All these combinations work automatically!
    view_counts: UnorderedMap<String, Counter>,
    user_profiles: UnorderedMap<String, UserProfile>,
    recent_events: Vector<Event>,
}

// Custom struct with nested CRDTs - just add #[derive(Mergeable)]!
#[derive(Mergeable, BorshSerialize, BorshDeserialize)]
pub struct UserProfile {
    name: LwwRegister<String>,        // Last-Write-Wins with timestamps (required!)
    bio: LwwRegister<String>,         // All fields must be CRDTs
    tags: UnorderedSet<String>,        // Add-wins set
    scores: UnorderedMap<String, Counter>,  // Nested CRDTs!
}
// Zero boilerplate - macro generates merge code! ✨
// Note: Primitives (String, u64) don't work - use LwwRegister<T> instead!

// That's it! No manual merge code, no conflict resolution - it just works! ✨

What you get:

  • Zero merge code - automatic conflict resolution
  • Nested structures - maps in maps, vectors of counters, etc.
  • No divergence - all nodes converge to same state
  • Natural syntax - use like normal Rust collections

Available Collections

Collection Use Case Merge Strategy Nesting
Counter Counters, metrics Sum Leaf
LwwRegister<T> Single values Latest timestamp Leaf
ReplicatedGrowableArray Text, documents Character-level Leaf
UnorderedMap<K,V> Key-value storage Recursive per-entry ✅ Can nest
Vector<T> Ordered lists Element-wise ✅ Can nest
UnorderedSet<T> Unique values Union Simple values
Option<T> Optional CRDTs Recursive if Some ✅ Wrapper

Common Patterns

Pattern 1: Counters for Metrics

#[app::state]
pub struct Analytics {
    page_views: UnorderedMap<String, Counter>,
}

// Concurrent increments automatically sum!
// Node A: page_views["home"].increment() → 5
// Node B: page_views["home"].increment() → 7 (concurrent)
// After sync: page_views["home"] = 12 ✅

Pattern 2: User Profiles with LWW

#[derive(Mergeable, BorshSerialize, BorshDeserialize)]
pub struct UserProfile {
    name: LwwRegister<String>,     // Must use LwwRegister, not String!
    email: LwwRegister<String>,
}

#[app::state]
pub struct UserManager {
    users: UnorderedMap<String, UserProfile>,
}

// Latest update wins (by timestamp)
// Node A @ T1: users["alice"].name = "Alice A"
// Node B @ T2: users["alice"].name = "Alice B" (T2 > T1)
// After sync: users["alice"].name = "Alice B" @ T2 ✅

Pattern 3: Nested Maps

#[app::state]
pub struct DocumentEditor {
    // Map of document IDs to their metadata
    // Note: Inner values must be Mergeable - use LwwRegister<String>
    metadata: UnorderedMap<String, UnorderedMap<String, LwwRegister<String>>>,
}

// Concurrent updates to different fields preserve both!
// Node A: metadata["doc-1"]["title"] = "My Doc".into()
// Node B: metadata["doc-1"]["author"] = "Alice".into() (concurrent)
// After sync: BOTH fields present! ✅

Pattern 4: Activity Logs

#[app::state]
pub struct ActivityTracker {
    events: Vector<Event>,
    unique_users: UnorderedSet<String>,
}

// Vector: append-heavy workloads
// Set: unique membership testing

Pattern 5: Optional CRDT Fields

#[derive(Mergeable, BorshSerialize, BorshDeserialize)]
pub struct UserProfile {
    name: LwwRegister<String>,
    bio: Option<LwwRegister<String>>,  // Optional field that merges when present
    settings: LwwRegister<Option<Settings>>,  // LWW with optional value
}

// Option<T> is now Mergeable!
// - Option<LwwRegister<T>>: Optional field with LWW semantics when present
// - LwwRegister<Option<T>>: LWW decides which Option (Some/None) wins
// - Both Some: recursively merges inner values
// - One None, one Some: takes the Some value
#[app::state]
pub struct TaskList {
    tasks: Vector<Task>,
}

// Find first completed task
let completed = tasks.find(|t| t.is_complete)?
    .next();

// Get all high priority tasks
let high_priority: Vec<Task> = tasks
    .filter(|t| t.priority == Priority::High)?
    .collect();

// Count pending tasks
let pending_count = tasks
    .filter(|t| !t.is_complete)?
    .count();

Performance Characteristics

Operation Complexity When Merge Called Impact
Local insert O(1) ❌ Never None
Remote sync (diff keys) O(1) ❌ Never None
Remote sync (same key) O(1) ❌ HLC+LWW None
Root conflict O(F×E) ✅ Rare < 1%

F = number of root fields (typically 3-10)
E = entries per field (typically 10-100)

Bottom line: Merge overhead is negligible - network latency dominates (50-200ms >> 1-2ms merge time)


Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│ Your Code (Natural Rust)                                    │
│                                                             │
│ #[app::state]                                               │
│ pub struct MyApp {                                          │
│     data: UnorderedMap<String, Document>                   │
│ }                                                           │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Macro Auto-Generates                                        │
│                                                             │
│ • impl Mergeable for MyApp                                  │
│ • Registration hook                                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Storage Layer (Automatic)                                   │
│                                                             │
│ ┌──────────┐  ┌──────────┐  ┌──────────┐                  │
│ │   DAG    │  │ Elements │  │  Merge   │                  │
│ │ (99% of  │→ │ (IDs +   │→ │ (1% of   │                  │
│ │ conflicts)│  │ storage) │  │ conflicts)│                  │
│ └──────────┘  └──────────┘  └──────────┘                  │
└─────────────────────────────────────────────────────────────┘

99% of operations: DAG + element IDs handle conflicts (O(1), fast)
1% of operations: Explicit merge for root conflicts (O(F×E), rare)


When Merge is Called

Local Operation (99%):
    insert() → Storage write → ✅ Done (no merge)

Remote Sync - Different Element (90%):
    receive update → Different ID → ✅ Apply directly (no merge)

Remote Sync - Same Element (9%):
    receive update → Same IDHLC comparison → ✅ LWW (no explicit merge)

Remote Sync - Root Conflict (1%):
    receive update → Root ID + concurrent → 🔄 Call Mergeable::merge()
    → Recursive field-level merge → ✅ Both updates preserved

Decision Tree: Which Collection to Use?

Need to count/sum things?
└─> Counter

Need a single value with conflict resolution?
└─> LwwRegister<T>

Need text editing?
└─> ReplicatedGrowableArray

Need key-value storage?
├─> Simple values?
│   └─> UnorderedMap<K, V>
└─> Nested CRDTs?
    └─> UnorderedMap<K, Counter>             // Counters sum
    └─> UnorderedMap<K, LwwRegister<T>>      // Timestamps win
    └─> UnorderedMap<K, UnorderedMap<K2, V>> // Nested maps

Need ordered list?
├─> Append-heavy (logs, metrics)?
│   └─> Vector<T>
└─> Arbitrary edits (text)?
    └─> ReplicatedGrowableArray

Need unique membership?
└─> UnorderedSet<T>

Limitations & Gotchas

Collection Limitation Workaround
Vector Concurrent inserts at arbitrary positions may conflict Use for append-heavy workloads
UnorderedSet Can't contain CRDTs (no update semantics) Use UnorderedMap instead
LwwRegister Concurrent updates lose one (LWW) Expected behavior for single values
Primitives Use simple LWW without timestamps Use LwwRegister for proper timestamps
All Collections Root-level non-CRDTs use LWW Use LwwRegister for explicit timestamps

✅ Primitives Not Allowed - Compile-Time Safety!

Primitive types (String, u64, bool, etc.) do NOT implement Mergeable by design.

This prevents state divergence at compile time:

// ❌ This will NOT compile:
#[derive(Mergeable)]
pub struct UserProfile {
    name: String,  // ERROR: String doesn't implement Mergeable
    age: u64,      // ERROR: u64 doesn't implement Mergeable
}
// Compiler error: "the trait bound `String: Mergeable` is not satisfied"

Why primitives were removed:

Primitives would cause permanent state divergence:

  • Simple LWW (*self = other) is NOT commutative
  • merge(A,B)merge(B,A) → nodes end up in different states
  • Violates fundamental CRDT convergence property
  • Production applications would have inconsistent state across nodes

The solution - Use LwwRegister:

// ✅ This compiles and works correctly:
#[derive(Mergeable)]
pub struct UserProfile {
    name: LwwRegister<String>,  // ✅ Proper timestamps + convergence
    age: LwwRegister<u64>,      // ✅ Deterministic conflict resolution
}

// Usage is natural thanks to Deref/AsRef/From traits:
let s: &str = &*profile.name;       // Deref
process(profile.name.as_ref());      // AsRef  
profile.name = "Alice".to_owned().into();  // From (auto-casting!)

When can you use primitives:

Only in private data (node-local, not synchronized):

#[app::private]  // Not synchronized across nodes!
pub struct LocalCache {
    data: UnorderedMap<String, String>,  // ✅ OK - not replicated
}

#[app::state]  // Synchronized across nodes!
pub struct App {
    data: UnorderedMap<String, String>,  // ❌ ERROR: Won't compile
}

Benefits of compile-time enforcement:

  • No state divergence possible - Prevented at compile time
  • Clear error messages - Compiler suggests using LwwRegister
  • Production-safe by default - Can't accidentally break distributed guarantees
  • Ergonomic - LwwRegister has Deref/AsRef/From traits for natural usage

Documentation

For complete documentation, see the Documentation Index (if available).

For Developers (Practical Guides)

For Architects (Deep Dives)

For Contributors


Examples

E-Commerce Application

#[app::state(emits = OrderEvent)]
pub struct EcommerceApp {
    // Product inventory with counters
    inventory: UnorderedMap<String, Counter>,
    
    // User carts (nested maps!)
    carts: UnorderedMap<String, UnorderedMap<String, Counter>>,
    
    // Order history (append-only)
    orders: Vector<Order>,
    
    // Featured product IDs (unique set)
    featured: UnorderedSet<String>,
}

pub struct Order {
    id: String,
    user: LwwRegister<String>,
    total: LwwRegister<u64>,
    items: UnorderedMap<String, Counter>,
}

Collaborative Document Editor

#[app::state(emits = EditorEvent)]
pub struct CollaborativeEditor {
    documents: UnorderedMap<String, Document>,
}

pub struct Document {
    content: ReplicatedGrowableArray,  // Text with character-level CRDT
    edit_count: Counter,                 // Total edits counter
    metadata: UnorderedMap<String, LwwRegister<String>>,  // Title, author, etc.
    viewers: UnorderedSet<String>,      // Active viewers
}

Analytics Dashboard

#[app::state]
pub struct Analytics {
    // Time-series metrics
    hourly_views: Vector<Counter>,
    
    // Page-specific counters
    page_metrics: UnorderedMap<String, PageMetrics>,
    
    // Active sessions
    active_users: UnorderedSet<String>,
}

pub struct PageMetrics {
    views: Counter,
    clicks: Counter,
    time_spent: Counter,  // In seconds
}

FAQ

Q: Do I need to write merge code?
A: No! The #[app::state] macro generates it automatically.

Q: What happens with concurrent updates?
A: It depends on the collection:

  • Counter: Values sum
  • LwwRegister: Latest timestamp wins
  • Map/Set: Add-wins semantics
  • Vector: Element-wise merge

Q: Can I nest CRDTs arbitrarily deep?
A: Yes! Map<K, Map<K2, Map<K3, V>>> works fine.

Q: What about performance?
A: Negligible impact. Merge happens rarely (< 1% of operations) and is fast (1-2ms) compared to network (50-200ms).

Q: How do I migrate from manual flattening?
A: See Migration Guide. TL;DR: Both patterns work, choose what's clearer.

Q: What if I need custom conflict resolution?
A: Implement Mergeable manually. See Architecture Guide.

Q: Can I use Option with CRDTs?
A: Yes! Option<T> is now Mergeable where T is Mergeable. Use Option<LwwRegister<T>> for optional fields that merge when both are Some, or LwwRegister<Option<T>> for LWW semantics on optional values.

Q: How do I search a Vector?
A: Use find(predicate) to get the first match, or filter(predicate) to get all matches. Both return iterators for efficient lazy evaluation.

Q: Can I use LwwRegister values directly without calling .get()?
A: Yes! LwwRegister implements Deref, AsRef, Borrow, and From for seamless type conversions. Use &*reg, reg.as_ref(), or pass to functions expecting &T directly.

Q: Can I use regular types like String, u64, bool in my structs?
A: No! Primitives do NOT implement Mergeable (by design). This prevents state divergence at compile time. You must use LwwRegister<T> for synchronized state. Exception: primitives work in #[app::private] data (node-local, not synchronized). Thanks to Deref/AsRef/From traits, LwwRegister<T> usage is nearly identical to primitives: value.into(), &*reg, reg.as_ref().

Q: What's the difference between String and LwwRegister<String>?
A: String doesn't implement Mergeable and won't compile in synchronized state. LwwRegister<String> provides proper timestamp-based conflict resolution with guaranteed convergence. Use value.into() for auto-casting, and Deref for natural access: &*register. This compile-time enforcement prevents catastrophic state divergence bugs in production.


Quick Reference

// Counter - auto-sum
let mut counter = Counter::new();
counter.increment()?;  // All increments sum

// LwwRegister - timestamp wins with ergonomic auto-casting
let mut register = LwwRegister::new("initial");
register.set("updated");  // Latest timestamp wins
let s: &str = &*register;  // Deref to inner value
let len = process(register.as_ref());  // AsRef for function calls
let reg: LwwRegister<u64> = 42.into();  // From/Into for auto-casting!

// Map - entry-wise merge (values must be Mergeable!)
let mut map = UnorderedMap::<String, LwwRegister<String>>::new();
map.insert("key".to_owned(), "value".to_owned().into())?;  // .into() auto-casts!
let val: String = map.get(&"key")?.unwrap().get().clone();  // Access value

// Vector - element-wise merge with search
let mut vec = Vector::new();
vec.push(value)?;  // Concurrent appends preserved
let first = vec.find(|v| v.id == "target")?;  // Find first match
let matches: Vec<_> = vec.filter(|v| v.score > 100)?.collect();  // Filter all

// Set - union merge
let mut set = UnorderedSet::new();
set.insert("value")?;  // All adds preserved (union)

// Option<T> - recursive merge
let mut opt: Option<LwwRegister<String>> = Some(LwwRegister::new("value"));
opt.merge(&other)?;  // Merges inner values when both Some

// ❌ Primitives in synchronized state DON'T COMPILE:
// let map = UnorderedMap::<String, String>::new();  // ERROR!
// ✅ Use LwwRegister:
let map = UnorderedMap::<String, LwwRegister<String>>::new();  //

License

See root LICENSE file.

Contributing

See root CONTRIBUTING.md.


Dependencies

~4.5–6MB
~130K SLoC