spacedb-crdt 0.5.2

SpaceDB Layer 1 — convergent collections: the Y-CRDT (yrs) document model with a typed field->CRDT mapping (LWW-Register, PN-Counter, ...), local-first mutation, and order-independent merge. Phase 1 SpaceDB Mission, M2. See 'docs/plans/(working) phase 1 spacedb mission.md'.
Documentation

spacedb-crdt

crates.io docs.rs License: MIT OR Apache-2.0 Remade With Rust By Mata Network

SpaceDB Layer 1 — convergent collections.

The default data tier, and the reason SpaceDB survives a partition-prone mesh: data is modeled as Y-CRDT (via yrs), so every write is locally available and merges conflict-free with no coordination. Writing offline on a Starlink-partitioned home is a non-event, not an error.

Part of SpaceDB. Dual-licensed MIT OR Apache-2.0.

[dependencies]
spacedb-crdt = "0.5"

The model

A CrdtDoc is a document with a typed field → CRDT-type mapping. Pick the CRDT per field; the merge rule follows from the type.

use spacedb_crdt::CrdtDoc;

let laptop = CrdtDoc::new(1);   // actor id
let phone  = CrdtDoc::new(2);

laptop.set_register("display_name", &"Ada").unwrap();  // LWW-Register
laptop.increment("visits", 1);                          // PN-Counter
laptop.text_push("bio", "building on SpaceDB");         // Y.Text
laptop.set_add("tags", "rust");                         // OR-Set

// Sync: exchange state vectors, ship only the delta they imply.
let delta = laptop.encode_update_since(&phone.state_vector()).unwrap();
phone.apply_update(&delta).unwrap();

assert_eq!(phone.get_register::<String>("display_name").unwrap().as_deref(), Some("Ada"));
assert_eq!(phone.counter("visits"), 1);
CRDT type Field API Merge rule
LWW-Register set_register / get_register / remove_register last writer wins
PN-Counter increment / counter sum of per-actor increments
Y.Text text_push / text_insert / text_remove / text character-level intent preservation
OR-Set set_add / set_remove / set_contains / set_members add wins over concurrent remove

Reactive queries

use spacedb_crdt::CrdtDoc;
let doc = CrdtDoc::new(1);
let watcher = doc.watch();
// ... after any local or merged write:
if watcher.drain_changed() { /* re-render */ }

ReactiveQuery::poll gives the same thing with a derived value: it recomputes only when the document revision moves.

Encrypted persistence

CrdtStore writes documents into a spacedb-store engine behind the KeyProvider AEAD boundary — save, load, apply_remote, contains. compact_updates folds an update log into one minimal update so a long-lived document doesn't grow without bound.

The convergence property

The same updates, applied in any order, produce the same state. That is the guarantee everything above this layer depends on, and it is proven — not asserted — by the fuzzed suite in tests/convergence.rs, which shuffles update orderings across replicas and requires byte-identical final state.

ops_behind and estimated_state_size expose honest lag / size, which spacedb-replica turns into a Freshness verdict.

Open-core boundary

Depends on yrs and spacedb-store — never on a MATA crate.

Testing

The workspace defaults to wasm32; this crate is native. Test on your host triple:

cargo test -p spacedb-crdt --target aarch64-apple-darwin   # or your host triple

Suites: convergence.rs (fuzzed), fields.rs, reactive.rs, persistence.rs, compact.rs, lag.rs.

License

MIT OR Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.