26 releases
| new 0.9.3 | Mar 6, 2026 |
|---|---|
| 0.9.2 | Feb 28, 2026 |
| 0.8.19 | Feb 16, 2026 |
| 0.7.0 | Feb 10, 2026 |
| 0.6.0 | Jan 17, 2026 |
#2495 in Math
Used in 4 crates
(2 directly)
135KB
2.5K
SLoC
logicaffeine-data
WASM-safe data structures and CRDTs for distributed systems.
Part of the Logicaffeine project.
The Lamport Invariant
This crate enforces a strict boundary: no IO, no system time, no network access. It compiles cleanly for both native and wasm32-unknown-unknown targets.
All timestamps must be injected by callers. This means:
LWWRegisterrequires explicit timestamp parameters- Replica IDs are generated using
getrandom(works in WASM) - The
Synced<T>networking wrapper lives inlogicaffeine_system, not here
Features
- WASM-compatible: Compiles for native and WebAssembly targets
- Pure data structures: No tokio, no libp2p, no SystemTime dependencies
- Serializable: All types implement
serde::SerializeandDeserialize - Delta sync: Efficient incremental synchronization via
DeltaCrdttrait
CRDT Types
CRDTs (Conflict-free Replicated Data Types) provide automatic conflict resolution. Any two replicas can merge to produce the same result, regardless of message order.
Counters
| Type | Description | Use Case |
|---|---|---|
GCounter |
Grow-only counter | View counts, page hits |
PNCounter |
Positive-negative counter | Bidirectional counters (upvotes/downvotes) |
use logicaffeine_data::{GCounter, PNCounter, Merge};
let mut counter = GCounter::new();
counter.increment(5);
assert_eq!(counter.value(), 5);
let mut pn = PNCounter::new();
pn.increment(10);
pn.decrement(3);
assert_eq!(pn.value(), 7);
Registers
| Type | Description | Use Case |
|---|---|---|
LWWRegister<T> |
Last-write-wins | Single values where latest update should win |
MVRegister<T> |
Multi-value | Track conflicts for manual resolution |
use logicaffeine_data::{LWWRegister, MVRegister, Merge};
// LWW: Timestamp determines winner
let mut reg = LWWRegister::new("initial", 100);
reg.set("updated", 200); // Higher timestamp wins
// MV: Preserves concurrent writes for conflict detection
let mut mv: MVRegister<String> = MVRegister::new(1);
mv.set("value".into());
if mv.has_conflict() {
mv.resolve("resolved".into());
}
Sets
| Type | Description | Use Case |
|---|---|---|
ORSet<T, AddWins> |
Concurrent add beats remove | Collaborative collections (default) |
ORSet<T, RemoveWins> |
Concurrent remove beats add | Access revocation, cleanup operations |
use logicaffeine_data::{ORSet, AddWins, RemoveWins, Merge};
let mut set: ORSet<String, AddWins> = ORSet::new(1);
set.add("item".into());
assert!(set.contains(&"item".into()));
// With remove-wins bias
let mut strict: ORSet<String, RemoveWins> = ORSet::new(1);
Maps
| Type | Description | Use Case |
|---|---|---|
ORMap<K, V> |
Key-value map with nested CRDTs | Structured data, nested counters/sets |
use logicaffeine_data::{ORMap, PNCounter, Merge};
let mut scores: ORMap<String, PNCounter> = ORMap::new(1);
scores.get_or_insert("player1".into()).increment(100);
assert_eq!(scores.get(&"player1".into()).unwrap().value(), 100);
Sequences
| Type | Description | Use Case |
|---|---|---|
RGA |
Replicated Growable Array | Collaborative lists |
YATA |
Yet Another Text Algorithm | Collaborative text editing |
use logicaffeine_data::{RGA, YATA, Merge};
let mut list: RGA<String> = RGA::new(1);
list.append("first".into());
list.append("second".into());
assert_eq!(list.to_vec(), vec!["first", "second"]);
let mut text: YATA<char> = YATA::new(1);
text.append('H');
text.append('i');
Causal Infrastructure
These types track causality and enable conflict detection.
| Type | Description |
|---|---|
VClock |
Vector clock for causal ordering |
Dot |
Unique event identifier (replica ID + sequence number) |
DotContext |
Combines clock + cloud for out-of-order message handling |
DeltaBuffer<D> |
Ring buffer for recent deltas (efficient sync) |
use logicaffeine_data::{VClock, Dot, DotContext};
let mut clock = VClock::new();
let seq = clock.increment(42); // Returns sequence number
let dot = Dot::new(42, seq);
let mut ctx = DotContext::new();
let next_dot = ctx.next(42); // Generate and track
assert!(ctx.has_seen(&next_dot));
Runtime Types
Type aliases for LOGOS programs:
| LOGOS Type | Rust Type | Description |
|---|---|---|
Nat |
u64 |
Natural numbers |
Int |
i64 |
Signed integers |
Real |
f64 |
Floating-point |
Text |
String |
UTF-8 strings |
Bool |
bool |
Boolean values |
Unit |
() |
Unit type |
Char |
char |
Unicode scalar |
Byte |
u8 |
Raw bytes |
Seq<T> |
Vec<T> |
Ordered sequences |
Set<T> |
HashSet<T> |
Unique elements |
Map<K,V> |
HashMap<K,V> |
Key-value pairs |
Tuple |
Vec<Value> |
Heterogeneous tuples |
Value |
enum | Dynamic type for mixed collections |
Key Traits
Merge
The core CRDT trait. Must satisfy:
- Commutative:
a.merge(b) == b.merge(a) - Associative:
a.merge(b.merge(c)) == a.merge(b).merge(c) - Idempotent:
a.merge(a) == a
use logicaffeine_data::Merge;
// All CRDT types implement Merge
fn sync<T: Merge>(local: &mut T, remote: &T) {
local.merge(remote);
}
DeltaCrdt
For efficient incremental synchronization:
use logicaffeine_data::{DeltaCrdt, VClock};
// Extract changes since a known version
fn get_updates<T: DeltaCrdt>(crdt: &T, since: &VClock) -> Option<T::Delta> {
crdt.delta_since(since)
}
LogosIndex / LogosIndexMut
1-based indexing for natural language conventions:
use logicaffeine_data::{LogosIndex, LogosIndexMut};
let v = vec![10, 20, 30];
assert_eq!(v.logos_get(1i64), 10); // Index 1 = first element
assert_eq!(v.logos_get(3i64), 30); // Index 3 = third element
// Index 0 panics!
LogosContains
Unified containment testing:
use logicaffeine_data::LogosContains;
let v = vec![1, 2, 3];
assert!(v.logos_contains(&2));
let s = String::from("hello");
assert!(s.logos_contains(&"ell")); // Substring
assert!(s.logos_contains(&'o')); // Character
Usage Example
use logicaffeine_data::{ORSet, PNCounter, ORMap, Merge, AddWins};
// Create state on replica 1
let mut replica1: ORMap<String, PNCounter> = ORMap::new(1);
replica1.get_or_insert("score".into()).increment(100);
// Create state on replica 2
let mut replica2: ORMap<String, PNCounter> = ORMap::new(2);
replica2.get_or_insert("score".into()).increment(50);
// Merge - order doesn't matter, result is always the same
replica1.merge(&replica2);
assert_eq!(replica1.get(&"score".into()).unwrap().value(), 150);
Important Constraints
- No IO - Timestamps and network sync are the caller's responsibility
- 1-based indexing -
LogosIndexpanics on index 0 or out-of-bounds - For networking - Use
logicaffeine_system'sSynced<T>wrapper
License
Business Source License 1.1 (BUSL-1.1)
- Free for individuals and organizations with <25 employees
- Commercial license required for organizations with 25+ employees offering Logic Services
- Converts to MIT on December 24, 2029
See LICENSE for full terms.
Dependencies
~0.4–1.3MB
~25K SLoC