# spacedb-crdt
**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`](https://crates.io/crates/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](../README.md). Dual-licensed **MIT OR Apache-2.0**.
```toml
[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.
```rust
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);
```
| 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
```rust
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`](../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`](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`](../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:
```bash
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](../LICENSE-MIT) and
[LICENSE-APACHE](../LICENSE-APACHE).