Skip to main content

diamond_types_extended/
refs.rs

1//! Read-only reference types for Diamond Types Extended CRDTs.
2//!
3//! These types provide read-only access to CRDT data without requiring
4//! an agent or transaction.
5
6
7
8use crate::branch::btree_range_for_crdt;
9use crate::value::{Conflicted, CrdtId, Value};
10use crate::{OpLog, Primitive, ROOT_CRDT_ID, LV};
11
12/// Read-only reference to a Map CRDT.
13///
14/// Maps are key-value containers where each key maps to an LWW Register.
15/// Values can be primitives or references to nested CRDTs.
16pub struct MapRef<'a> {
17    oplog: &'a OpLog,
18    crdt_id: LV,
19}
20
21impl<'a> MapRef<'a> {
22    pub(crate) fn new(oplog: &'a OpLog, crdt_id: LV) -> Self {
23        Self { oplog, crdt_id }
24    }
25
26    /// Get the CRDT ID for this map.
27    pub fn id(&self) -> CrdtId {
28        CrdtId(self.crdt_id)
29    }
30
31    /// Check if this is the root map.
32    pub fn is_root(&self) -> bool {
33        self.crdt_id == ROOT_CRDT_ID
34    }
35
36    /// Get a value by key.
37    ///
38    /// Returns the "winning" value according to LWW semantics.
39    /// Use `get_conflicted` if you need to see concurrent conflicts.
40    pub fn get(&self, key: &str) -> Option<Value> {
41        let info = self.oplog.map_keys.get(&(self.crdt_id, key.into()))?;
42        let state = self.oplog.get_state_for_register(info);
43        Some(state.value.into())
44    }
45
46    /// Get a value by key with conflict information.
47    ///
48    /// If there are concurrent writes to this key, the conflicts are included.
49    pub fn get_conflicted(&self, key: &str) -> Option<Conflicted<Value>> {
50        let info = self.oplog.map_keys.get(&(self.crdt_id, key.into()))?;
51        let state = self.oplog.get_state_for_register(info);
52        Some(Conflicted {
53            value: state.value.into(),
54            conflicts: state.conflicts_with.into_iter().map(|rv| rv.into()).collect(),
55        })
56    }
57
58    /// Check if a key exists.
59    pub fn contains_key(&self, key: &str) -> bool {
60        self.oplog.map_keys.contains_key(&(self.crdt_id, key.into()))
61    }
62
63    /// Get all keys in this map.
64    pub fn keys(&self) -> impl Iterator<Item = &str> {
65        btree_range_for_crdt(&self.oplog.map_keys, self.crdt_id)
66            .map(|((_, key), _)| key.as_str())
67    }
68
69    /// Get the number of keys.
70    pub fn len(&self) -> usize {
71        btree_range_for_crdt(&self.oplog.map_keys, self.crdt_id).count()
72    }
73
74    /// Check if the map is empty.
75    pub fn is_empty(&self) -> bool {
76        self.len() == 0
77    }
78
79    /// Get a nested map by key.
80    pub fn get_map(&self, key: &str) -> Option<MapRef<'a>> {
81        let value = self.get(key)?;
82        match value {
83            Value::Map(id) => Some(MapRef::new(self.oplog, id.0)),
84            _ => None,
85        }
86    }
87
88    /// Get a nested text CRDT by key.
89    pub fn get_text(&self, key: &str) -> Option<TextRef<'a>> {
90        let value = self.get(key)?;
91        match value {
92            Value::Text(id) => Some(TextRef::new(self.oplog, id.0)),
93            _ => None,
94        }
95    }
96
97    /// Get a nested set CRDT by key.
98    pub fn get_set(&self, key: &str) -> Option<SetRef<'a>> {
99        let value = self.get(key)?;
100        match value {
101            Value::Set(id) => Some(SetRef::new(self.oplog, id.0)),
102            _ => None,
103        }
104    }
105
106    /// Get a nested register CRDT by key.
107    pub fn get_register(&self, key: &str) -> Option<RegisterRef<'a>> {
108        let value = self.get(key)?;
109        match value {
110            Value::Register(id) => Some(RegisterRef::new(self.oplog, id.0)),
111            _ => None,
112        }
113    }
114
115    /// Iterate over all entries.
116    pub fn iter(&self) -> impl Iterator<Item = (&str, Value)> + 'a {
117        btree_range_for_crdt(&self.oplog.map_keys, self.crdt_id)
118            .map(|((_, key), info)| {
119                let state = self.oplog.get_state_for_register(info);
120                (key.as_str(), state.value.into())
121            })
122    }
123
124    /// Collect keys as owned Strings (avoids borrow-checker friction in FFI/WASM).
125    pub fn keys_owned(&self) -> Vec<String> {
126        self.keys().map(|s| s.to_string()).collect()
127    }
128
129    /// Collect entries as owned pairs (avoids borrow-checker friction in FFI/WASM).
130    pub fn entries_owned(&self) -> Vec<(String, Value)> {
131        self.iter().map(|(k, v)| (k.to_string(), v)).collect()
132    }
133}
134
135/// Read-only reference to a Text CRDT.
136///
137/// Text is a sequence CRDT optimized for collaborative text editing.
138/// For large documents, prefer `slice()` or `chars()` over `content()` to avoid
139/// allocating the entire string.
140pub struct TextRef<'a> {
141    oplog: &'a OpLog,
142    crdt_id: LV,
143}
144
145impl<'a> TextRef<'a> {
146    pub(crate) fn new(oplog: &'a OpLog, crdt_id: LV) -> Self {
147        Self { oplog, crdt_id }
148    }
149
150    /// Get the CRDT ID for this text.
151    pub fn id(&self) -> CrdtId {
152        CrdtId(self.crdt_id)
153    }
154
155    /// Get the full text content as a String.
156    ///
157    /// For large documents, consider using `slice()` or `chars()` instead
158    /// to avoid allocating the entire string.
159    pub fn content(&self) -> String {
160        self.oplog.checkout_text(self.crdt_id).to_string()
161    }
162
163    /// Get a slice of the text as a String.
164    ///
165    /// Range is in Unicode characters (not bytes). This is more efficient than
166    /// `content()` for extracting portions of large documents.
167    ///
168    /// # Example
169    ///
170    /// ```
171    /// use diamond_types_extended::{Document, Uuid};
172    ///
173    /// let mut doc = Document::new();
174    /// let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
175    ///
176    /// doc.transact(alice, |tx| {
177    ///     let id = tx.root().create_text("content");
178    ///     tx.text_by_id(id).unwrap().insert(0, "Hello, world!");
179    /// });
180    ///
181    /// let text = doc.root().get_text("content").unwrap();
182    /// assert_eq!(text.slice(0..5), "Hello");
183    /// assert_eq!(text.slice(7..12), "world");
184    /// ```
185    pub fn slice(&self, range: std::ops::Range<usize>) -> String {
186        let rope = self.oplog.checkout_text(self.crdt_id);
187        let borrowed = rope.borrow();
188        borrowed.slice_chars(range).collect()
189    }
190
191    /// Iterate over characters in the text.
192    ///
193    /// More memory-efficient than `content()` for large documents when you
194    /// only need to iterate.
195    pub fn chars(&self) -> impl Iterator<Item = char> {
196        // Note: We collect into a Vec because the JumpRope iterator borrows
197        // the rope, and we can't return a borrowed iterator from a temporary.
198        // For truly streaming access on huge documents, use slice() with ranges.
199        let rope = self.oplog.checkout_text(self.crdt_id);
200        let borrowed = rope.borrow();
201        borrowed.chars().collect::<Vec<_>>().into_iter()
202    }
203
204    /// Get the length in Unicode characters.
205    pub fn len(&self) -> usize {
206        self.oplog.checkout_text(self.crdt_id).len_chars()
207    }
208
209    /// Check if the text is empty.
210    pub fn is_empty(&self) -> bool {
211        self.len() == 0
212    }
213}
214
215/// Read-only reference to a Set CRDT.
216///
217/// Sets use OR-Set (Observed-Remove) semantics with add-wins conflict resolution.
218pub struct SetRef<'a> {
219    oplog: &'a OpLog,
220    crdt_id: LV,
221}
222
223impl<'a> SetRef<'a> {
224    pub(crate) fn new(oplog: &'a OpLog, crdt_id: LV) -> Self {
225        Self { oplog, crdt_id }
226    }
227
228    /// Get the CRDT ID for this set.
229    pub fn id(&self) -> CrdtId {
230        CrdtId(self.crdt_id)
231    }
232
233    /// Check if the set contains a value.
234    pub fn contains(&self, value: &Value) -> bool {
235        let primitive: Primitive = value.clone().into();
236        let set_data = self.oplog.checkout_set(self.crdt_id);
237        set_data.contains(&primitive)
238    }
239
240    /// Check if the set contains a primitive string.
241    pub fn contains_str(&self, s: &str) -> bool {
242        let primitive = Primitive::Str(s.into());
243        let set_data = self.oplog.checkout_set(self.crdt_id);
244        set_data.contains(&primitive)
245    }
246
247    /// Check if the set contains a primitive integer.
248    pub fn contains_int(&self, n: i64) -> bool {
249        let primitive = Primitive::I64(n);
250        let set_data = self.oplog.checkout_set(self.crdt_id);
251        set_data.contains(&primitive)
252    }
253
254    /// Get the number of elements.
255    pub fn len(&self) -> usize {
256        self.oplog.checkout_set(self.crdt_id).len()
257    }
258
259    /// Check if the set is empty.
260    pub fn is_empty(&self) -> bool {
261        self.len() == 0
262    }
263
264    /// Iterate over all values in the set.
265    pub fn iter(&self) -> impl Iterator<Item = Value> + '_ {
266        self.oplog.checkout_set(self.crdt_id)
267            .into_iter()
268            .map(|p| p.into())
269    }
270
271    /// Get all values as a Vec.
272    pub fn to_vec(&self) -> Vec<Value> {
273        self.iter().collect()
274    }
275}
276
277/// Read-only reference to a Register CRDT.
278///
279/// Registers are single-value containers with LWW semantics.
280pub struct RegisterRef<'a> {
281    oplog: &'a OpLog,
282    crdt_id: LV,
283}
284
285impl<'a> RegisterRef<'a> {
286    pub(crate) fn new(oplog: &'a OpLog, crdt_id: LV) -> Self {
287        Self { oplog, crdt_id }
288    }
289
290    /// Get the CRDT ID for this register.
291    pub fn id(&self) -> CrdtId {
292        CrdtId(self.crdt_id)
293    }
294
295    /// Get the current value.
296    ///
297    /// Returns the "winning" value according to LWW semantics.
298    pub fn get(&self) -> Option<Value> {
299        let state = self.oplog.checkout_register(self.crdt_id);
300        Some(state.value.into())
301    }
302
303    /// Get the current value with conflict information.
304    pub fn get_conflicted(&self) -> Option<Conflicted<Value>> {
305        let state = self.oplog.checkout_register(self.crdt_id);
306        Some(Conflicted {
307            value: state.value.into(),
308            conflicts: state.conflicts_with.into_iter().map(|rv| rv.into()).collect(),
309        })
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use uuid::Uuid;
316    use crate::Document;
317
318    #[test]
319    fn test_map_ref_basic() {
320        let mut doc = Document::new();
321        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
322
323        doc.transact(alice, |tx| {
324            tx.root().set("key", "value");
325            tx.root().set("num", 42);
326        });
327
328        let root = doc.root();
329        assert!(root.contains_key("key"));
330        assert!(!root.contains_key("missing"));
331
332        let val = root.get("key").unwrap();
333        assert_eq!(val.as_str(), Some("value"));
334
335        let num = root.get("num").unwrap();
336        assert_eq!(num.as_int(), Some(42));
337    }
338
339    #[test]
340    fn test_map_ref_nested() {
341        let mut doc = Document::new();
342        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
343
344        doc.transact(alice, |tx| {
345            tx.root().create_map("nested");
346        });
347
348        doc.transact(alice, |tx| {
349            if let Some(mut nested) = tx.get_map_mut(&["nested"]) {
350                nested.set("inner", "value");
351            }
352        });
353
354        let nested = doc.root().get_map("nested").unwrap();
355        let inner = nested.get("inner").unwrap();
356        assert_eq!(inner.as_str(), Some("value"));
357    }
358
359    #[test]
360    fn test_text_ref_slice() {
361        let mut doc = Document::new();
362        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
363
364        doc.transact(alice, |tx| {
365            let id = tx.root().create_text("content");
366            tx.text_by_id(id).unwrap().insert(0, "Hello, world!");
367        });
368
369        let text = doc.root().get_text("content").unwrap();
370
371        // Full content
372        assert_eq!(text.content(), "Hello, world!");
373
374        // Slices
375        assert_eq!(text.slice(0..5), "Hello");
376        assert_eq!(text.slice(7..12), "world");
377        assert_eq!(text.slice(0..0), "");
378        assert_eq!(text.slice(5..7), ", ");
379    }
380
381    #[test]
382    fn test_text_ref_slice_unicode() {
383        let mut doc = Document::new();
384        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
385
386        doc.transact(alice, |tx| {
387            let id = tx.root().create_text("emoji");
388            // Each emoji is 1 unicode character but multiple bytes
389            tx.text_by_id(id).unwrap().insert(0, "🎉🎊🎈");
390        });
391
392        let text = doc.root().get_text("emoji").unwrap();
393
394        // Length is in unicode characters, not bytes
395        assert_eq!(text.len(), 3);
396
397        // Slice by character position
398        assert_eq!(text.slice(0..1), "🎉");
399        assert_eq!(text.slice(1..2), "🎊");
400        assert_eq!(text.slice(2..3), "🎈");
401    }
402
403    #[test]
404    fn test_text_ref_chars() {
405        let mut doc = Document::new();
406        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
407
408        doc.transact(alice, |tx| {
409            let id = tx.root().create_text("content");
410            tx.text_by_id(id).unwrap().insert(0, "abc");
411        });
412
413        let text = doc.root().get_text("content").unwrap();
414        let chars: Vec<char> = text.chars().collect();
415
416        assert_eq!(chars, vec!['a', 'b', 'c']);
417    }
418
419    #[test]
420    fn test_map_ref_keys_owned() {
421        let mut doc = Document::new();
422        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
423
424        doc.transact(alice, |tx| {
425            tx.root().set("b", 2i64);
426            tx.root().set("a", 1i64);
427            tx.root().create_map("c");
428        });
429
430        let keys = doc.root().keys_owned();
431        assert_eq!(keys, vec!["a".to_string(), "b".to_string(), "c".to_string()]);
432    }
433
434    #[test]
435    fn test_map_ref_entries_owned() {
436        let mut doc = Document::new();
437        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
438
439        doc.transact(alice, |tx| {
440            tx.root().set("name", "Alice");
441            tx.root().set("age", 30i64);
442        });
443
444        let entries = doc.root().entries_owned();
445        assert_eq!(entries.len(), 2);
446
447        // BTreeMap iteration is sorted by key
448        assert_eq!(entries[0].0, "age");
449        assert_eq!(entries[0].1.as_int(), Some(30));
450        assert_eq!(entries[1].0, "name");
451        assert_eq!(entries[1].1.as_str(), Some("Alice"));
452    }
453}