dx_forge/crdt/
operations.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Operation {
7 pub id: Uuid,
8 pub timestamp: DateTime<Utc>,
9 pub actor_id: String,
10 pub file_path: String,
11 pub op_type: OperationType,
12 pub parent_ops: Vec<Uuid>, }
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum OperationType {
17 Insert {
18 position: Position,
19 content: String,
20 length: usize,
21 },
22 Delete {
23 position: Position,
24 length: usize,
25 },
26 Replace {
27 position: Position,
28 old_content: String,
29 new_content: String,
30 },
31 FileCreate {
32 content: String,
33 },
34 FileDelete,
35 FileRename {
36 old_path: String,
37 new_path: String,
38 },
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42pub struct Position {
43 pub lamport_timestamp: u64,
45 pub actor_id: String,
46 pub offset: usize,
47
48 pub line: usize,
50 pub column: usize,
51}
52
53impl Position {
54 pub fn new(line: usize, column: usize, offset: usize, actor_id: String, lamport: u64) -> Self {
55 Self {
56 lamport_timestamp: lamport,
57 actor_id,
58 offset,
59 line,
60 column,
61 }
62 }
63
64 pub fn stable_id(&self) -> String {
66 format!(
67 "{}:{}:{}",
68 self.actor_id, self.lamport_timestamp, self.offset
69 )
70 }
71}
72
73impl Operation {
74 pub fn new(file_path: String, op_type: OperationType, actor_id: String) -> Self {
75 Self {
76 id: Uuid::new_v4(),
77 timestamp: Utc::now(),
78 actor_id,
79 file_path,
80 op_type,
81 parent_ops: Vec::new(),
82 }
83 }
84
85 pub fn with_parents(mut self, parents: Vec<Uuid>) -> Self {
86 self.parent_ops = parents;
87 self
88 }
89
90 pub fn lamport(&self) -> Option<u64> {
91 match &self.op_type {
92 OperationType::Insert { position, .. }
93 | OperationType::Delete { position, .. }
94 | OperationType::Replace { position, .. } => Some(position.lamport_timestamp),
95 _ => None,
96 }
97 }
98
99 pub fn can_batch_with(&self, other: &Operation) -> bool {
101 if self.file_path != other.file_path {
103 return false;
104 }
105
106 if self.actor_id != other.actor_id {
108 return false;
109 }
110
111 match (&self.op_type, &other.op_type) {
113 (OperationType::Insert { .. }, OperationType::Insert { .. }) => true,
114 (OperationType::Delete { .. }, OperationType::Delete { .. }) => true,
115 _ => false,
116 }
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct OperationBatch {
123 pub operations: Vec<Operation>,
124 pub batch_id: Uuid,
125 pub created_at: DateTime<Utc>,
126}
127
128impl OperationBatch {
129 pub fn new(operations: Vec<Operation>) -> Self {
131 Self {
132 operations,
133 batch_id: Uuid::new_v4(),
134 created_at: Utc::now(),
135 }
136 }
137
138 pub fn optimize(&mut self) {
140 self.operations.dedup_by(|a, b| {
142 a.file_path == b.file_path &&
143 a.actor_id == b.actor_id &&
144 a.timestamp == b.timestamp
145 });
146 }
147
148 pub fn len(&self) -> usize {
150 self.operations.len()
151 }
152
153 pub fn is_empty(&self) -> bool {
155 self.operations.is_empty()
156 }
157}