1use std::sync::{Arc, Mutex};
12
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct LiteChangeEvent {
18 pub collection: String,
20 pub document_id: String,
22 pub op: LiteChangeOp,
24 pub timestamp_ms: u64,
26 pub from_sync: bool,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum LiteChangeOp {
33 Insert,
34 Update,
35 Delete,
36}
37
38pub struct Subscription {
40 id: u64,
41 stream: Arc<LiteChangeStreamInner>,
42}
43
44impl Drop for Subscription {
45 fn drop(&mut self) {
46 let mut cbs = self
47 .stream
48 .callbacks
49 .lock()
50 .unwrap_or_else(|p| p.into_inner());
51 cbs.retain(|(id, _)| *id != self.id);
52 }
53}
54
55type ChangeCallback = Box<dyn Fn(&LiteChangeEvent) + Send + Sync>;
58
59struct LiteChangeStreamInner {
60 callbacks: Mutex<Vec<(u64, ChangeCallback)>>,
61 next_id: std::sync::atomic::AtomicU64,
62 events_published: std::sync::atomic::AtomicU64,
63}
64
65pub struct LiteChangeStream {
72 inner: Arc<LiteChangeStreamInner>,
73}
74
75impl LiteChangeStream {
76 pub fn new() -> Self {
77 Self {
78 inner: Arc::new(LiteChangeStreamInner {
79 callbacks: Mutex::new(Vec::new()),
80 next_id: std::sync::atomic::AtomicU64::new(1),
81 events_published: std::sync::atomic::AtomicU64::new(0),
82 }),
83 }
84 }
85
86 pub fn subscribe<F>(&self, callback: F) -> Subscription
91 where
92 F: Fn(&LiteChangeEvent) + Send + Sync + 'static,
93 {
94 let id = self
95 .inner
96 .next_id
97 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
98 let mut cbs = self
99 .inner
100 .callbacks
101 .lock()
102 .unwrap_or_else(|p| p.into_inner());
103 cbs.push((id, Box::new(callback)));
104 Subscription {
105 id,
106 stream: Arc::clone(&self.inner),
107 }
108 }
109
110 pub fn publish(&self, event: &LiteChangeEvent) {
112 self.inner
113 .events_published
114 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
115 let cbs = self
116 .inner
117 .callbacks
118 .lock()
119 .unwrap_or_else(|p| p.into_inner());
120 for (_, cb) in cbs.iter() {
121 cb(event);
122 }
123 }
124
125 pub fn publish_write(&self, collection: &str, document_id: &str, op: LiteChangeOp) {
127 let event = LiteChangeEvent {
128 collection: collection.to_string(),
129 document_id: document_id.to_string(),
130 op,
131 timestamp_ms: crate::runtime::now_millis(),
132 from_sync: false,
133 };
134 self.publish(&event);
135 }
136
137 pub fn publish_sync(&self, collection: &str, document_id: &str, op: LiteChangeOp) {
139 let event = LiteChangeEvent {
140 collection: collection.to_string(),
141 document_id: document_id.to_string(),
142 op,
143 timestamp_ms: crate::runtime::now_millis(),
144 from_sync: true,
145 };
146 self.publish(&event);
147 }
148
149 pub fn events_published(&self) -> u64 {
151 self.inner
152 .events_published
153 .load(std::sync::atomic::Ordering::Relaxed)
154 }
155
156 pub fn subscriber_count(&self) -> usize {
158 self.inner
159 .callbacks
160 .lock()
161 .unwrap_or_else(|p| p.into_inner())
162 .len()
163 }
164}
165
166impl Default for LiteChangeStream {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use std::sync::atomic::{AtomicU64, Ordering};
176
177 #[test]
178 fn publish_and_receive() {
179 let stream = LiteChangeStream::new();
180 let received = Arc::new(AtomicU64::new(0));
181 let received_clone = Arc::clone(&received);
182
183 let _sub = stream.subscribe(move |_event| {
184 received_clone.fetch_add(1, Ordering::Relaxed);
185 });
186
187 stream.publish_write("users", "u1", LiteChangeOp::Insert);
188 assert_eq!(received.load(Ordering::Relaxed), 1);
189 }
190
191 #[test]
192 fn sync_events_flagged() {
193 let stream = LiteChangeStream::new();
194 let was_sync = Arc::new(std::sync::atomic::AtomicBool::new(false));
195 let was_sync_clone = Arc::clone(&was_sync);
196
197 let _sub = stream.subscribe(move |event| {
198 was_sync_clone.store(event.from_sync, std::sync::atomic::Ordering::Relaxed);
199 });
200
201 stream.publish_sync("orders", "o1", LiteChangeOp::Update);
202 assert!(was_sync.load(std::sync::atomic::Ordering::Relaxed));
203 }
204
205 #[test]
206 fn unsubscribe_on_drop() {
207 let stream = LiteChangeStream::new();
208 let count = Arc::new(AtomicU64::new(0));
209 let count_clone = Arc::clone(&count);
210
211 let sub = stream.subscribe(move |_| {
212 count_clone.fetch_add(1, Ordering::Relaxed);
213 });
214 assert_eq!(stream.subscriber_count(), 1);
215
216 stream.publish_write("a", "1", LiteChangeOp::Insert);
217 assert_eq!(count.load(Ordering::Relaxed), 1);
218
219 drop(sub);
220 assert_eq!(stream.subscriber_count(), 0);
221
222 stream.publish_write("a", "2", LiteChangeOp::Insert);
223 assert_eq!(count.load(Ordering::Relaxed), 1); }
225
226 #[test]
227 fn multiple_subscribers() {
228 let stream = LiteChangeStream::new();
229 let count1 = Arc::new(AtomicU64::new(0));
230 let count2 = Arc::new(AtomicU64::new(0));
231 let c1 = Arc::clone(&count1);
232 let c2 = Arc::clone(&count2);
233
234 let _sub1 = stream.subscribe(move |_| {
235 c1.fetch_add(1, Ordering::Relaxed);
236 });
237 let _sub2 = stream.subscribe(move |_| {
238 c2.fetch_add(1, Ordering::Relaxed);
239 });
240
241 stream.publish_write("x", "1", LiteChangeOp::Delete);
242 assert_eq!(count1.load(Ordering::Relaxed), 1);
243 assert_eq!(count2.load(Ordering::Relaxed), 1);
244 }
245
246 #[test]
247 fn no_subscriber_doesnt_panic() {
248 let stream = LiteChangeStream::new();
249 stream.publish_write("test", "t1", LiteChangeOp::Delete);
250 assert_eq!(stream.events_published(), 1);
251 }
252}