Skip to main content

abyo_crdt/
lib.rs

1//! # abyo-crdt
2//!
3//! Pure Rust CRDT library implementing an [Eg-walker]-style event log over a
4//! [Fugue-Maximal] list, with companion [`Map`], [`Counter`], and [`Set`]
5//! CRDTs and [Peritext] rich text planned for v0.3.
6//!
7//! See the [`README`](https://github.com/abyo-software/abyo-crdt) for design
8//! background and the [crate-level plan](https://github.com/abyo-software/abyo-crdt/blob/main/abyo_crdt_plan.md)
9//! for the roadmap.
10//!
11//! [Eg-walker]: https://arxiv.org/abs/2409.14252
12//! [Fugue-Maximal]: https://arxiv.org/abs/2305.00583
13//! [Peritext]: https://www.inkandswitch.com/peritext/
14//!
15//! ## Available data types
16//!
17//! | Type           | Algorithm        | Status     |
18//! |----------------|------------------|------------|
19//! | [`List<T>`]    | Fugue-Maximal    | v0.1 ✅    |
20//! | [`Map<K, V>`]  | LWW with Lamport | v0.2 ✅    |
21//! | [`Counter`]    | PN-Counter       | v0.2 ✅    |
22//! | [`Set<T>`]     | OR-Set, add-wins | v0.2 ✅    |
23//! | `Text`         | Peritext         | v0.3 🚧    |
24//!
25//! Every CRDT in the crate ships an event log (`ops()`), supports incremental
26//! sync via [`VersionVector`] (`ops_since(&version)`), and is `Serialize +
27//! Deserialize` under the default `serde` feature.
28//!
29//! ## Quick start
30//!
31//! ```
32//! use abyo_crdt::List;
33//!
34//! let mut alice = List::<char>::new(1);
35//! alice.insert(0, 'H');
36//! alice.insert(1, 'i');
37//!
38//! let mut bob = List::<char>::new(2);
39//! bob.merge(&alice);
40//!
41//! bob.insert(2, '!');
42//! alice.merge(&bob);
43//!
44//! assert_eq!(alice.to_vec(), vec!['H', 'i', '!']);
45//! assert_eq!(bob.to_vec(), vec!['H', 'i', '!']);
46//! ```
47//!
48//! ## Concurrent edits never interleave
49//!
50//! Fugue-Maximal guarantees that contiguous bursts of typing stay contiguous
51//! after merge, regardless of timing.
52//!
53//! ```
54//! use abyo_crdt::List;
55//!
56//! let mut alice = List::<char>::new(1);
57//! let mut bob = List::<char>::new(2);
58//!
59//! // Both start from a shared "ab" doc
60//! alice.insert(0, 'a');
61//! alice.insert(1, 'b');
62//! bob.merge(&alice);
63//!
64//! // Concurrent inserts at position 1 — Alice types "Hello", Bob types "World"
65//! for (i, c) in "Hello".chars().enumerate() {
66//!     alice.insert(1 + i, c);
67//! }
68//! for (i, c) in "World".chars().enumerate() {
69//!     bob.insert(1 + i, c);
70//! }
71//!
72//! alice.merge(&bob);
73//! bob.merge(&alice);
74//!
75//! let merged: String = alice.iter().collect();
76//! // Either "aHelloWorldb" or "aWorldHellob" — never interleaved.
77//! assert!(merged == "aHelloWorldb" || merged == "aWorldHellob");
78//! assert_eq!(merged, bob.iter().collect::<String>());
79//! ```
80
81#![warn(missing_docs)]
82#![warn(rust_2018_idioms)]
83#![forbid(unsafe_code)]
84
85mod counter;
86mod cursor;
87mod error;
88mod id;
89mod list;
90mod map;
91mod ost;
92mod set;
93#[cfg(feature = "storage")]
94pub mod storage;
95mod text;
96mod version;
97pub mod yjs_compat;
98
99pub use counter::{Counter, CounterOp};
100pub use cursor::{Cursor, CursorSide, Selection};
101pub use error::Error;
102pub use id::{new_replica_id, OpId, ReplicaId};
103pub use list::{List, ListOp, Side};
104pub use map::{Map, MapOp};
105pub use set::{Set, SetOp};
106pub use text::{
107    Anchor, AnchorSide, AttrValue, DeltaOp, ExpandRule, MarkSet, MarkValue, Span, SpanValue, Text,
108    TextOp,
109};
110pub use version::VersionVector;