armature_collab/
lib.rs

1//! Real-time Collaboration Module for Armature Framework
2//!
3//! Provides CRDTs (Conflict-free Replicated Data Types) and collaboration
4//! primitives for building real-time collaborative applications.
5//!
6//! ## Overview
7//!
8//! ```text
9//! ┌─────────────────────────────────────────────────────────────────┐
10//! │                    Collaboration Architecture                    │
11//! │                                                                  │
12//! │  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │
13//! │  │ Client A │───▶│  CRDT    │───▶│  Sync    │◀───│ Client B │  │
14//! │  └──────────┘    │  State   │    │  Engine  │    └──────────┘  │
15//! │                  └──────────┘    └──────────┘                   │
16//! │                       │               │                         │
17//! │                       ▼               ▼                         │
18//! │                  ┌──────────┐    ┌──────────┐                   │
19//! │                  │ Document │    │ Presence │                   │
20//! │                  │  State   │    │  State   │                   │
21//! │                  └──────────┘    └──────────┘                   │
22//! └─────────────────────────────────────────────────────────────────┘
23//! ```
24//!
25//! ## Quick Start
26//!
27//! ```rust,ignore
28//! use armature_collab::{Document, TextCrdt, CollabSession};
29//!
30//! // Create a collaborative document
31//! let doc = Document::new("doc-123");
32//!
33//! // Add a text field with CRDT
34//! let text = doc.add_text("content");
35//!
36//! // Make edits (automatically synced)
37//! text.insert(0, "Hello, ");
38//! text.insert(7, "World!");
39//!
40//! // Subscribe to changes
41//! doc.on_change(|change| {
42//!     println!("Document updated: {:?}", change);
43//! });
44//! ```
45//!
46//! ## CRDT Types
47//!
48//! | Type | Use Case | Merge Strategy |
49//! |------|----------|----------------|
50//! | `LwwRegister` | Single values | Last-Writer-Wins |
51//! | `GCounter` | Increment-only counters | Max per replica |
52//! | `PnCounter` | Inc/dec counters | G-Counter pair |
53//! | `GSet` | Append-only sets | Union |
54//! | `OrSet` | Add/remove sets | Observed-Remove |
55//! | `LwwMap` | Key-value stores | LWW per key |
56//! | `RgaText` | Collaborative text | RGA algorithm |
57//!
58//! ## Features
59//!
60//! - **`text`** - Text CRDT with RGA algorithm (default)
61//! - **`websocket`** - WebSocket sync integration
62//! - **`full`** - All features
63
64pub mod crdt;
65pub mod document;
66pub mod error;
67pub mod presence;
68pub mod session;
69pub mod sync;
70
71#[cfg(feature = "text")]
72pub mod text;
73
74pub use crdt::*;
75pub use document::*;
76pub use error::*;
77pub use presence::*;
78pub use session::*;
79pub use sync::*;
80
81#[cfg(feature = "text")]
82pub use text::*;
83
84use serde::{Deserialize, Serialize};
85use uuid::Uuid;
86
87/// Unique identifier for a replica (client/node)
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
89pub struct ReplicaId(pub Uuid);
90
91impl ReplicaId {
92    /// Create a new random replica ID
93    pub fn new() -> Self {
94        Self(Uuid::new_v4())
95    }
96
97    /// Create from a UUID
98    pub fn from_uuid(uuid: Uuid) -> Self {
99        Self(uuid)
100    }
101}
102
103impl Default for ReplicaId {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109impl std::fmt::Display for ReplicaId {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        write!(f, "{}", self.0)
112    }
113}
114
115/// Logical timestamp for ordering operations
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
117pub struct LogicalClock {
118    /// Counter value
119    pub counter: u64,
120    /// Replica that created this timestamp
121    pub replica: ReplicaId,
122}
123
124impl LogicalClock {
125    /// Create a new logical clock
126    pub fn new(counter: u64, replica: ReplicaId) -> Self {
127        Self { counter, replica }
128    }
129
130    /// Increment the clock
131    pub fn tick(&mut self) -> Self {
132        self.counter += 1;
133        *self
134    }
135
136    /// Merge with another clock (take max)
137    pub fn merge(&mut self, other: &Self) {
138        self.counter = self.counter.max(other.counter);
139    }
140}
141
142/// Vector clock for tracking causality
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct VectorClock {
145    clocks: std::collections::HashMap<ReplicaId, u64>,
146}
147
148impl VectorClock {
149    /// Create a new empty vector clock
150    pub fn new() -> Self {
151        Self {
152            clocks: std::collections::HashMap::new(),
153        }
154    }
155
156    /// Increment the clock for a replica
157    pub fn increment(&mut self, replica: ReplicaId) -> u64 {
158        let counter = self.clocks.entry(replica).or_insert(0);
159        *counter += 1;
160        *counter
161    }
162
163    /// Get the counter for a replica
164    pub fn get(&self, replica: &ReplicaId) -> u64 {
165        *self.clocks.get(replica).unwrap_or(&0)
166    }
167
168    /// Merge with another vector clock
169    pub fn merge(&mut self, other: &Self) {
170        for (replica, counter) in &other.clocks {
171            let entry = self.clocks.entry(*replica).or_insert(0);
172            *entry = (*entry).max(*counter);
173        }
174    }
175
176    /// Check if this clock is concurrent with another
177    pub fn is_concurrent(&self, other: &Self) -> bool {
178        !self.happens_before(other) && !other.happens_before(self)
179    }
180
181    /// Check if this clock happens before another
182    pub fn happens_before(&self, other: &Self) -> bool {
183        let mut dominated = false;
184        for (replica, &counter) in &self.clocks {
185            let other_counter = other.get(replica);
186            if counter > other_counter {
187                return false;
188            }
189            if counter < other_counter {
190                dominated = true;
191            }
192        }
193        // Check for any replicas in other but not in self
194        for replica in other.clocks.keys() {
195            if !self.clocks.contains_key(replica) && other.get(replica) > 0 {
196                dominated = true;
197            }
198        }
199        dominated
200    }
201}
202
203impl Default for VectorClock {
204    fn default() -> Self {
205        Self::new()
206    }
207}
208
209/// An operation that can be applied to a CRDT
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct Operation<T> {
212    /// Unique operation ID
213    pub id: Uuid,
214    /// Replica that created the operation
215    pub replica: ReplicaId,
216    /// Logical timestamp
217    pub timestamp: LogicalClock,
218    /// The actual operation data
219    pub data: T,
220    /// Dependencies (operations this depends on)
221    pub deps: Vec<Uuid>,
222}
223
224impl<T> Operation<T> {
225    /// Create a new operation
226    pub fn new(replica: ReplicaId, timestamp: LogicalClock, data: T) -> Self {
227        Self {
228            id: Uuid::new_v4(),
229            replica,
230            timestamp,
231            data,
232            deps: Vec::new(),
233        }
234    }
235
236    /// Add a dependency
237    pub fn with_dep(mut self, dep: Uuid) -> Self {
238        self.deps.push(dep);
239        self
240    }
241
242    /// Add multiple dependencies
243    pub fn with_deps(mut self, deps: impl IntoIterator<Item = Uuid>) -> Self {
244        self.deps.extend(deps);
245        self
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn test_replica_id() {
255        let id1 = ReplicaId::new();
256        let id2 = ReplicaId::new();
257        assert_ne!(id1, id2);
258    }
259
260    #[test]
261    fn test_logical_clock() {
262        let replica = ReplicaId::new();
263        let mut clock = LogicalClock::new(0, replica);
264
265        assert_eq!(clock.counter, 0);
266        clock.tick();
267        assert_eq!(clock.counter, 1);
268    }
269
270    #[test]
271    fn test_vector_clock() {
272        let replica1 = ReplicaId::new();
273        let replica2 = ReplicaId::new();
274
275        let mut vc1 = VectorClock::new();
276        vc1.increment(replica1);
277        vc1.increment(replica1);
278
279        let mut vc2 = VectorClock::new();
280        vc2.increment(replica2);
281
282        assert!(vc1.is_concurrent(&vc2));
283
284        vc1.merge(&vc2);
285        assert_eq!(vc1.get(&replica1), 2);
286        assert_eq!(vc1.get(&replica2), 1);
287    }
288
289    #[test]
290    fn test_happens_before() {
291        let replica = ReplicaId::new();
292
293        let mut vc1 = VectorClock::new();
294        vc1.increment(replica);
295
296        let mut vc2 = vc1.clone();
297        vc2.increment(replica);
298
299        assert!(vc1.happens_before(&vc2));
300        assert!(!vc2.happens_before(&vc1));
301    }
302}
303