1use std::collections::{HashMap, VecDeque};
4use std::pin::Pin;
5use std::sync::Arc;
6
7use async_stream::stream;
8use async_trait::async_trait;
9use chrono::Utc;
10use dashmap::DashMap;
11use futures::stream::Stream;
12use serde_json::Value;
13use tokio::sync::{broadcast, RwLock};
14use uuid::Uuid;
15
16use super::partition::topic_filter_matches;
17use super::port::{StorageCapabilities, StoragePort};
18use crate::error::Result;
19use crate::event::TransportCrypto;
20use crate::models::Event;
21
22const DEFAULT_REPLAY_BUFFER_SIZE: usize = 1000;
23
24fn partition_key(topic_name: &str, topic_key: Option<&str>) -> String {
25 format!("{}:{}", topic_name, topic_key.unwrap_or("__null__"))
26}
27
28fn replay_key(topic_name: &str, topic_key_filter: Option<&str>) -> String {
29 partition_key(topic_name, topic_key_filter)
30}
31
32pub struct InProcStoragePort {
34 crypto: TransportCrypto,
35 tx: broadcast::Sender<Event>,
36 replay_buffer: Arc<RwLock<HashMap<String, VecDeque<Event>>>>,
37 replay_buffer_size: usize,
38 events: Arc<DashMap<String, Event>>,
39 seq_counters: Arc<DashMap<String, i64>>,
40 checkpoints: Arc<DashMap<String, i64>>,
41 delivery_pins: Arc<DashMap<String, i64>>,
42}
43
44impl InProcStoragePort {
45 #[must_use]
47 pub fn new(crypto: TransportCrypto) -> Self {
48 let (tx, _) = broadcast::channel(1024);
49 Self {
50 crypto,
51 tx,
52 replay_buffer: Arc::new(RwLock::new(HashMap::new())),
53 replay_buffer_size: DEFAULT_REPLAY_BUFFER_SIZE,
54 events: Arc::new(DashMap::new()),
55 seq_counters: Arc::new(DashMap::new()),
56 checkpoints: Arc::new(DashMap::new()),
57 delivery_pins: Arc::new(DashMap::new()),
58 }
59 }
60
61 fn checkpoint_key(sub: &str, topic: &str, topic_key: Option<&str>) -> String {
62 format!("{sub}:{}:{}", topic, topic_key.unwrap_or("__null__"))
63 }
64
65 #[allow(clippy::significant_drop_tightening)]
66 async fn push_replay(&self, event: &Event) {
67 let key = replay_key(&event.topic_name, event.topic_key.as_deref());
68 let event = event.clone();
69 let mut buf = self.replay_buffer.write().await;
70 let queue = buf.entry(key).or_default();
71 queue.push_back(event);
72 while queue.len() > self.replay_buffer_size {
73 queue.pop_front();
74 }
75 }
76
77 fn next_seq(&self, topic_name: &str, topic_key: Option<&str>) -> i64 {
78 let key = partition_key(topic_name, topic_key);
79 let mut entry = self.seq_counters.entry(key).or_insert(0);
80 *entry += 1;
81 *entry
82 }
83}
84
85#[async_trait]
86impl StoragePort for InProcStoragePort {
87 fn capabilities(&self) -> StorageCapabilities {
88 StorageCapabilities::mem()
89 }
90
91 async fn append(
92 &self,
93 topic_name: &str,
94 topic_key: Option<&str>,
95 actor_json: Value,
96 payload_json: Value,
97 ) -> Result<Event> {
98 let _ = self.crypto.encrypt(&actor_json, &payload_json)?;
99 let seq = self.next_seq(topic_name, topic_key);
100 let event = Event {
101 event_id: Uuid::new_v4().to_string(),
102 topic_name: topic_name.to_string(),
103 topic_key: topic_key.map(String::from),
104 seq,
105 actor_json,
106 payload_json,
107 created_at: Utc::now(),
108 };
109 self.events
110 .insert(event.event_id.clone(), event.clone());
111 self.push_replay(&event).await;
112 let _ = self.tx.send(event.clone());
113 Ok(event)
114 }
115
116 fn subscribe(
117 &self,
118 topic_name: String,
119 topic_key_filter: Option<String>,
120 after_seq: Option<i64>,
121 ) -> Pin<Box<dyn Stream<Item = Result<Event>> + Send>> {
122 let replay_key = replay_key(&topic_name, topic_key_filter.as_deref());
123 let replay_buffer = Arc::clone(&self.replay_buffer);
124 let mut live_rx = self.tx.subscribe();
125 let topic = topic_name;
126 let filter = topic_key_filter;
127 let delivery_pins = Arc::clone(&self.delivery_pins);
128
129 Box::pin(stream! {
130 if let Some(seq) = after_seq {
131 let buf = replay_buffer.read().await;
132 if let Some(queue) = buf.get(&replay_key) {
133 for evt in queue {
134 if evt.seq > seq && topic_filter_matches(evt, &topic, filter.as_ref()) {
135 yield Ok(evt.clone());
136 }
137 }
138 }
139 }
140
141 loop {
142 match live_rx.recv().await {
143 Ok(ev) => {
144 if !topic_filter_matches(&ev, &topic, filter.as_ref()) {
145 continue;
146 }
147 if after_seq.is_some_and(|s| ev.seq <= s) {
148 continue;
149 }
150 let pin_key = partition_key(&ev.topic_name, ev.topic_key.as_deref());
151 delivery_pins.insert(pin_key, ev.seq);
152 yield Ok(ev);
153 }
154 Err(broadcast::error::RecvError::Lagged(_)) => {
155 let pin_key = partition_key(&topic, filter.as_deref());
157 let after = delivery_pins
158 .get(&pin_key)
159 .map(|v| *v)
160 .or(after_seq)
161 .unwrap_or(0);
162 let buf = replay_buffer.read().await;
163 if let Some(queue) = buf.get(&replay_key) {
164 for evt in queue {
165 if evt.seq > after
166 && topic_filter_matches(evt, &topic, filter.as_ref())
167 {
168 let pk = partition_key(
169 &evt.topic_name,
170 evt.topic_key.as_deref(),
171 );
172 delivery_pins.insert(pk, evt.seq);
173 yield Ok(evt.clone());
174 }
175 }
176 }
177 }
178 Err(broadcast::error::RecvError::Closed) => break,
179 }
180 }
181 })
182 }
183
184 async fn get_event(&self, event_id: &str) -> Result<Option<Event>> {
185 Ok(self.events.get(event_id).map(|e| e.clone()))
186 }
187
188 async fn load_checkpoint(
189 &self,
190 subscription_name: &str,
191 topic_name: &str,
192 topic_key: Option<&str>,
193 ) -> Result<Option<i64>> {
194 let key = Self::checkpoint_key(subscription_name, topic_name, topic_key);
195 Ok(self.checkpoints.get(&key).map(|v| *v))
196 }
197
198 async fn commit_checkpoint(
199 &self,
200 subscription_name: &str,
201 topic_name: &str,
202 topic_key: Option<&str>,
203 last_seq: i64,
204 ) -> Result<()> {
205 let key = Self::checkpoint_key(subscription_name, topic_name, topic_key);
206 self.checkpoints.insert(key, last_seq);
207 Ok(())
208 }
209
210 async fn truncate_before(
211 &self,
212 topic_name: &str,
213 topic_key: Option<&str>,
214 truncate_bound: i64,
215 ) -> Result<u64> {
216 let key = replay_key(topic_name, topic_key);
217 let mut removed = 0u64;
218 let mut buf = self.replay_buffer.write().await;
219 if let Some(queue) = buf.get_mut(&key) {
220 while queue
221 .front()
222 .is_some_and(|e| e.seq < truncate_bound)
223 {
224 if let Some(ev) = queue.pop_front() {
225 self.events.remove(&ev.event_id);
226 removed += 1;
227 }
228 }
229 }
230 drop(buf);
231 Ok(removed)
232 }
233
234 async fn delivery_seq_pin(&self, topic_name: &str, topic_key: Option<&str>) -> Option<i64> {
235 let key = partition_key(topic_name, topic_key);
236 self.delivery_pins.get(&key).map(|v| *v)
237 }
238}