Skip to main content

diamond_types_extended/
muts.rs

1//! Mutable reference types for Facet CRDTs.
2//!
3//! These types provide write access to CRDT data within a Transaction.
4
5use std::ops::Range;
6
7
8use crate::value::{CrdtId, PrimitiveValue};
9use crate::list::operation::TextOperation;
10use crate::{AgentId, CRDTKind, CreateValue, OpLog, Primitive, ROOT_CRDT_ID, LV};
11
12/// Mutable reference to a Map CRDT.
13///
14/// Obtained through `Transaction::root()` or `Transaction::get_map_mut()`.
15pub struct MapMut<'a> {
16    oplog: &'a mut OpLog,
17    agent: AgentId,
18    crdt_id: LV,
19}
20
21impl<'a> MapMut<'a> {
22    pub(crate) fn new(oplog: &'a mut OpLog, agent: AgentId, crdt_id: LV) -> Self {
23        Self { oplog, agent, 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    // ============ Write operations ============
37
38    /// Set a key to a primitive value.
39    ///
40    /// Accepts any type that implements `Into<PrimitiveValue>`:
41    /// `bool`, `i64`, `i32`, `String`, `&str`, or `()` for nil.
42    ///
43    /// To create nested CRDTs, use `create_map()`, `create_text()`,
44    /// `create_set()`, or `create_register()` instead.
45    pub fn set(&mut self, key: &str, value: impl Into<PrimitiveValue>) {
46        let create_value: CreateValue = value.into().into();
47        self.oplog.local_map_set(self.agent, self.crdt_id, key, create_value);
48    }
49
50    /// Set a key to nil (tombstone).
51    ///
52    /// In CRDT semantics, this creates a tombstone - the key still exists but
53    /// has a nil value. Use `get()` to check if a value is nil, or use
54    /// `get_conflicted()` to see if there are concurrent non-nil values.
55    ///
56    /// Note: `contains_key()` will still return true after this operation.
57    pub fn set_nil(&mut self, key: &str) {
58        self.oplog.local_map_set(
59            self.agent,
60            self.crdt_id,
61            key,
62            CreateValue::Primitive(Primitive::Nil),
63        );
64    }
65
66    /// Create a nested Map at the given key.
67    ///
68    /// Returns the CRDT ID of the new map, which can be used with
69    /// `Transaction::map_by_id()` to get a mutable reference.
70    pub fn create_map(&mut self, key: &str) -> CrdtId {
71        let lv = self.oplog.local_map_set(
72            self.agent,
73            self.crdt_id,
74            key,
75            CreateValue::NewCRDT(CRDTKind::Map),
76        );
77        CrdtId(lv)
78    }
79
80    /// Create a nested Text CRDT at the given key.
81    ///
82    /// Returns the CRDT ID of the new text.
83    pub fn create_text(&mut self, key: &str) -> CrdtId {
84        let lv = self.oplog.local_map_set(
85            self.agent,
86            self.crdt_id,
87            key,
88            CreateValue::NewCRDT(CRDTKind::Text),
89        );
90        CrdtId(lv)
91    }
92
93    /// Create a nested Set CRDT at the given key.
94    ///
95    /// Returns the CRDT ID of the new set.
96    pub fn create_set(&mut self, key: &str) -> CrdtId {
97        let lv = self.oplog.local_map_set(
98            self.agent,
99            self.crdt_id,
100            key,
101            CreateValue::NewCRDT(CRDTKind::Set),
102        );
103        CrdtId(lv)
104    }
105
106    /// Create a nested Register CRDT at the given key.
107    ///
108    /// Returns the CRDT ID of the new register.
109    pub fn create_register(&mut self, key: &str) -> CrdtId {
110        let lv = self.oplog.local_map_set(
111            self.agent,
112            self.crdt_id,
113            key,
114            CreateValue::NewCRDT(CRDTKind::Register),
115        );
116        CrdtId(lv)
117    }
118}
119
120/// Mutable reference to a Text CRDT.
121///
122/// Obtained through `Transaction::get_text_mut()` or `Transaction::text_by_id()`.
123pub struct TextMut<'a> {
124    oplog: &'a mut OpLog,
125    agent: AgentId,
126    crdt_id: LV,
127}
128
129impl<'a> TextMut<'a> {
130    pub(crate) fn new(oplog: &'a mut OpLog, agent: AgentId, crdt_id: LV) -> Self {
131        Self { oplog, agent, crdt_id }
132    }
133
134    /// Get the CRDT ID for this text.
135    pub fn id(&self) -> CrdtId {
136        CrdtId(self.crdt_id)
137    }
138
139    /// Insert text at a position.
140    ///
141    /// Position is in Unicode characters (not bytes).
142    pub fn insert(&mut self, pos: usize, content: &str) {
143        let op = TextOperation::new_insert(pos, content);
144        self.oplog.local_text_op(self.agent, self.crdt_id, op);
145    }
146
147    /// Delete a range of text.
148    ///
149    /// Range is in Unicode characters (not bytes).
150    pub fn delete(&mut self, range: Range<usize>) {
151        if !range.is_empty() {
152            let op = TextOperation::new_delete(range);
153            self.oplog.local_text_op(self.agent, self.crdt_id, op);
154        }
155    }
156
157    /// Replace text in a range.
158    ///
159    /// This is equivalent to delete + insert, but may be optimized.
160    pub fn replace(&mut self, range: Range<usize>, content: &str) {
161        if !range.is_empty() {
162            let del_op = TextOperation::new_delete(range.clone());
163            self.oplog.local_text_op(self.agent, self.crdt_id, del_op);
164        }
165        if !content.is_empty() {
166            let ins_op = TextOperation::new_insert(range.start, content);
167            self.oplog.local_text_op(self.agent, self.crdt_id, ins_op);
168        }
169    }
170
171    /// Append text to the end.
172    pub fn push(&mut self, content: &str) {
173        let len = self.oplog.checkout_text(self.crdt_id).len_chars();
174        let op = TextOperation::new_insert(len, content);
175        self.oplog.local_text_op(self.agent, self.crdt_id, op);
176    }
177
178    /// Clear all text.
179    pub fn clear(&mut self) {
180        let len = self.oplog.checkout_text(self.crdt_id).len_chars();
181        if len > 0 {
182            let op = TextOperation::new_delete(0..len);
183            self.oplog.local_text_op(self.agent, self.crdt_id, op);
184        }
185    }
186}
187
188/// Mutable reference to a Set CRDT.
189///
190/// Obtained through `Transaction::get_set_mut()` or `Transaction::set_by_id()`.
191pub struct SetMut<'a> {
192    oplog: &'a mut OpLog,
193    agent: AgentId,
194    crdt_id: LV,
195}
196
197impl<'a> SetMut<'a> {
198    pub(crate) fn new(oplog: &'a mut OpLog, agent: AgentId, crdt_id: LV) -> Self {
199        Self { oplog, agent, crdt_id }
200    }
201
202    /// Get the CRDT ID for this set.
203    pub fn id(&self) -> CrdtId {
204        CrdtId(self.crdt_id)
205    }
206
207    /// Add a primitive value to the set.
208    ///
209    /// Accepts any type that implements `Into<PrimitiveValue>`:
210    /// `bool`, `i64`, `i32`, `String`, `&str`, or `()` for nil.
211    pub fn add(&mut self, value: impl Into<PrimitiveValue>) {
212        let primitive: Primitive = value.into().into();
213        self.oplog.local_set_add(self.agent, self.crdt_id, primitive);
214    }
215
216    /// Add a string to the set.
217    pub fn add_str(&mut self, s: &str) {
218        self.oplog.local_set_add(self.agent, self.crdt_id, Primitive::Str(s.into()));
219    }
220
221    /// Add an integer to the set.
222    pub fn add_int(&mut self, n: i64) {
223        self.oplog.local_set_add(self.agent, self.crdt_id, Primitive::I64(n));
224    }
225
226    /// Add a boolean to the set.
227    pub fn add_bool(&mut self, b: bool) {
228        self.oplog.local_set_add(self.agent, self.crdt_id, Primitive::Bool(b));
229    }
230
231    /// Remove a primitive value from the set.
232    ///
233    /// With OR-Set semantics, this removes all observed instances of the value.
234    ///
235    /// Accepts any type that implements `Into<PrimitiveValue>`.
236    pub fn remove(&mut self, value: impl Into<PrimitiveValue>) {
237        let primitive: Primitive = value.into().into();
238        self.oplog.local_set_remove(self.agent, self.crdt_id, primitive);
239    }
240
241    /// Remove a string from the set.
242    pub fn remove_str(&mut self, s: &str) {
243        self.oplog.local_set_remove(self.agent, self.crdt_id, Primitive::Str(s.into()));
244    }
245
246    /// Remove an integer from the set.
247    pub fn remove_int(&mut self, n: i64) {
248        self.oplog.local_set_remove(self.agent, self.crdt_id, Primitive::I64(n));
249    }
250}
251
252/// Mutable reference to a Register CRDT (not yet implemented).
253///
254/// In v0.1, standalone registers are accessed through map keys.
255/// Each map key is effectively an LWW register. Use `MapMut::set()` to update
256/// register values.
257// Hidden from public API until standalone register mutation is implemented
258#[allow(dead_code)]
259pub(crate) struct RegisterMut<'a> {
260    oplog: &'a mut OpLog,
261    agent: AgentId,
262    crdt_id: LV,
263}
264
265#[allow(dead_code)]
266impl<'a> RegisterMut<'a> {
267    pub(crate) fn new(oplog: &'a mut OpLog, agent: AgentId, crdt_id: LV) -> Self {
268        Self { oplog, agent, crdt_id }
269    }
270
271    /// Get the CRDT ID for this register.
272    pub fn id(&self) -> CrdtId {
273        CrdtId(self.crdt_id)
274    }
275
276    // Note: Standalone register mutation is not yet implemented.
277    // Use map keys as registers via MapMut::set().
278}
279
280#[cfg(test)]
281mod tests {
282    use uuid::Uuid;
283    use crate::Document;
284
285    #[test]
286    fn test_map_mut_set() {
287        let mut doc = Document::new();
288        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
289
290        doc.transact(alice, |tx| {
291            tx.root().set("string", "hello");
292            tx.root().set("int", 42);
293            tx.root().set("bool", true);
294            tx.root().set("nil", ());
295        });
296
297        assert_eq!(doc.root().get("string").unwrap().as_str(), Some("hello"));
298        assert_eq!(doc.root().get("int").unwrap().as_int(), Some(42));
299        assert_eq!(doc.root().get("bool").unwrap().as_bool(), Some(true));
300        assert!(doc.root().get("nil").unwrap().is_nil());
301    }
302
303    #[test]
304    fn test_map_mut_create_nested() {
305        let mut doc = Document::new();
306        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
307
308        let text_id = doc.transact(alice, |tx| {
309            tx.root().create_text("content")
310        });
311
312        doc.transact(alice, |tx| {
313            if let Some(mut text) = tx.text_by_id(text_id) {
314                text.insert(0, "Hello, world!");
315            }
316        });
317
318        let text = doc.root().get_text("content").unwrap();
319        assert_eq!(text.content(), "Hello, world!");
320    }
321
322    #[test]
323    fn test_text_mut() {
324        let mut doc = Document::new();
325        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
326
327        let text_id = doc.transact(alice, |tx| {
328            tx.root().create_text("doc")
329        });
330
331        doc.transact(alice, |tx| {
332            let mut text = tx.text_by_id(text_id).unwrap();
333            text.insert(0, "Hello");
334            text.insert(5, ", world!");
335        });
336
337        assert_eq!(doc.root().get_text("doc").unwrap().content(), "Hello, world!");
338
339        doc.transact(alice, |tx| {
340            let mut text = tx.text_by_id(text_id).unwrap();
341            text.delete(5..13); // Remove ", world!"
342        });
343
344        assert_eq!(doc.root().get_text("doc").unwrap().content(), "Hello");
345    }
346
347    #[test]
348    fn test_set_mut() {
349        let mut doc = Document::new();
350        let alice = doc.create_agent(Uuid::from_u128(0xA11CE));
351
352        let set_id = doc.transact(alice, |tx| {
353            tx.root().create_set("tags")
354        });
355
356        doc.transact(alice, |tx| {
357            let mut set = tx.set_by_id(set_id).unwrap();
358            set.add_str("rust");
359            set.add_str("crdt");
360            set.add_int(42);
361        });
362
363        let set = doc.root().get_set("tags").unwrap();
364        assert!(set.contains_str("rust"));
365        assert!(set.contains_str("crdt"));
366        assert!(set.contains_int(42));
367        assert!(!set.contains_str("missing"));
368
369        doc.transact(alice, |tx| {
370            let mut set = tx.set_by_id(set_id).unwrap();
371            set.remove_str("crdt");
372        });
373
374        let set = doc.root().get_set("tags").unwrap();
375        assert!(set.contains_str("rust"));
376        assert!(!set.contains_str("crdt"));
377    }
378}