1use std::collections::{HashMap, VecDeque};
28use std::sync::Arc;
29use std::time::SystemTime;
30
31use async_trait::async_trait;
32use serde::{Deserialize, Serialize};
33use tokio::sync::RwLock;
34
35pub const DEFAULT_MAX_EVENTS_PER_SESSION: usize = 1000;
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40#[non_exhaustive]
41pub struct EventRecord {
42 pub id: u64,
44 pub data: String,
46 pub timestamp: SystemTime,
48}
49
50impl EventRecord {
51 pub fn new(id: u64, data: impl Into<String>) -> Self {
53 Self {
54 id,
55 data: data.into(),
56 timestamp: SystemTime::now(),
57 }
58 }
59}
60
61#[derive(Debug, thiserror::Error)]
63#[non_exhaustive]
64pub enum EventStoreError {
65 #[error("encode error: {0}")]
67 Encode(String),
68 #[error("decode error: {0}")]
70 Decode(String),
71 #[error("backend error: {0}")]
73 Backend(String),
74}
75
76pub type Result<T> = std::result::Result<T, EventStoreError>;
78
79#[async_trait]
85pub trait EventStore: Send + Sync + 'static {
86 async fn append(&self, session_id: &str, event: EventRecord) -> Result<()>;
88
89 async fn replay_after(&self, session_id: &str, after_id: u64) -> Result<Vec<EventRecord>>;
91
92 async fn purge_session(&self, session_id: &str) -> Result<()>;
94}
95
96#[derive(Debug, Clone)]
102pub struct MemoryEventStore {
103 inner: Arc<RwLock<HashMap<String, VecDeque<EventRecord>>>>,
104 max_events_per_session: usize,
105}
106
107impl Default for MemoryEventStore {
108 fn default() -> Self {
109 Self::with_capacity(DEFAULT_MAX_EVENTS_PER_SESSION)
110 }
111}
112
113impl MemoryEventStore {
114 pub fn new() -> Self {
116 Self::default()
117 }
118
119 pub fn with_capacity(max_events_per_session: usize) -> Self {
121 Self {
122 inner: Arc::new(RwLock::new(HashMap::new())),
123 max_events_per_session,
124 }
125 }
126
127 pub async fn total_events(&self) -> usize {
129 self.inner.read().await.values().map(|v| v.len()).sum()
130 }
131
132 pub async fn session_count(&self) -> usize {
134 self.inner.read().await.len()
135 }
136}
137
138#[async_trait]
139impl EventStore for MemoryEventStore {
140 async fn append(&self, session_id: &str, event: EventRecord) -> Result<()> {
141 let mut map = self.inner.write().await;
142 let buf = map.entry(session_id.to_string()).or_default();
143 if buf.len() >= self.max_events_per_session {
144 buf.pop_front();
145 }
146 buf.push_back(event);
147 Ok(())
148 }
149
150 async fn replay_after(&self, session_id: &str, after_id: u64) -> Result<Vec<EventRecord>> {
151 let map = self.inner.read().await;
152 Ok(match map.get(session_id) {
153 Some(buf) => buf.iter().filter(|e| e.id > after_id).cloned().collect(),
154 None => Vec::new(),
155 })
156 }
157
158 async fn purge_session(&self, session_id: &str) -> Result<()> {
159 self.inner.write().await.remove(session_id);
160 Ok(())
161 }
162}
163
164#[derive(Debug, Clone)]
170pub struct CachingEventStore<Cache, Store> {
171 cache: Cache,
172 store: Store,
173}
174
175impl<Cache, Store> CachingEventStore<Cache, Store> {
176 pub fn new(cache: Cache, store: Store) -> Self {
178 Self { cache, store }
179 }
180}
181
182#[async_trait]
183impl<Cache, Store> EventStore for CachingEventStore<Cache, Store>
184where
185 Cache: EventStore,
186 Store: EventStore,
187{
188 async fn append(&self, session_id: &str, event: EventRecord) -> Result<()> {
189 self.store.append(session_id, event.clone()).await?;
191 self.cache.append(session_id, event).await?;
192 Ok(())
193 }
194
195 async fn replay_after(&self, session_id: &str, after_id: u64) -> Result<Vec<EventRecord>> {
196 let cached = self.cache.replay_after(session_id, after_id).await?;
197 if !cached.is_empty() {
198 return Ok(cached);
199 }
200 let from_store = self.store.replay_after(session_id, after_id).await?;
201 for event in &from_store {
203 let _ = self.cache.append(session_id, event.clone()).await;
204 }
205 Ok(from_store)
206 }
207
208 async fn purge_session(&self, session_id: &str) -> Result<()> {
209 let cache_result = self.cache.purge_session(session_id).await;
210 let store_result = self.store.purge_session(session_id).await;
211 cache_result.and(store_result)
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[tokio::test]
220 async fn memory_store_append_and_replay() {
221 let store = MemoryEventStore::new();
222 for i in 0..3 {
223 store
224 .append("s", EventRecord::new(i, format!("e{i}")))
225 .await
226 .unwrap();
227 }
228
229 let all = store.replay_after("s", 0).await.unwrap();
230 assert_eq!(all.len(), 2); let none = store.replay_after("s", 5).await.unwrap();
233 assert!(none.is_empty());
234
235 let after_first = store.replay_after("s", 0).await.unwrap();
236 assert_eq!(after_first[0].id, 1);
237 assert_eq!(after_first[1].id, 2);
238 }
239
240 #[tokio::test]
241 async fn memory_store_respects_capacity() {
242 let store = MemoryEventStore::with_capacity(3);
243 for i in 0..5 {
244 store
245 .append("s", EventRecord::new(i, format!("e{i}")))
246 .await
247 .unwrap();
248 }
249
250 let events = store.replay_after("s", 0).await.unwrap();
252 assert_eq!(events.len(), 3);
253 assert_eq!(events[0].id, 2);
254 assert_eq!(events[2].id, 4);
255 }
256
257 #[tokio::test]
258 async fn memory_store_isolates_sessions() {
259 let store = MemoryEventStore::new();
260 store.append("a", EventRecord::new(0, "a0")).await.unwrap();
261 store.append("b", EventRecord::new(0, "b0")).await.unwrap();
262
263 let a = store.replay_after("a", 0).await.unwrap();
264 let b = store.replay_after("b", 0).await.unwrap();
265 assert!(a.is_empty() && b.is_empty(), "after_id filters out id 0");
266
267 store.append("a", EventRecord::new(1, "a1")).await.unwrap();
270 let a1 = store.replay_after("a", 0).await.unwrap();
271 assert_eq!(a1.len(), 1);
272 assert_eq!(a1[0].data, "a1");
273 }
274
275 #[tokio::test]
276 async fn memory_store_purge_removes_session() {
277 let store = MemoryEventStore::new();
278 store.append("s", EventRecord::new(0, "x")).await.unwrap();
279 assert_eq!(store.session_count().await, 1);
280
281 store.purge_session("s").await.unwrap();
282 assert_eq!(store.session_count().await, 0);
283 }
284
285 #[tokio::test]
286 async fn memory_store_purge_is_idempotent() {
287 MemoryEventStore::new()
288 .purge_session("nonexistent")
289 .await
290 .unwrap();
291 }
292
293 #[tokio::test]
294 async fn dyn_event_store_object_safe() {
295 let store: Arc<dyn EventStore> = Arc::new(MemoryEventStore::new());
296 store.append("s", EventRecord::new(0, "x")).await.unwrap();
297 }
298
299 #[tokio::test]
300 async fn caching_store_writes_to_both_tiers() {
301 let cache = MemoryEventStore::new();
302 let backend = MemoryEventStore::new();
303 let store = CachingEventStore::new(cache.clone(), backend.clone());
304
305 store.append("s", EventRecord::new(0, "x")).await.unwrap();
306
307 assert_eq!(cache.total_events().await, 1);
308 assert_eq!(backend.total_events().await, 1);
309 }
310
311 #[tokio::test]
312 async fn caching_store_reads_from_cache_first() {
313 let cache = MemoryEventStore::new();
314 let backend = MemoryEventStore::new();
315
316 backend
318 .append("s", EventRecord::new(0, "b0"))
319 .await
320 .unwrap();
321 backend
322 .append("s", EventRecord::new(1, "b1"))
323 .await
324 .unwrap();
325
326 let store = CachingEventStore::new(cache.clone(), backend);
327
328 let first = store.replay_after("s", 0).await.unwrap();
330 assert_eq!(first.len(), 1); assert_eq!(cache.total_events().await, 1);
334 }
335
336 #[tokio::test]
337 async fn caching_store_purge_clears_both() {
338 let cache = MemoryEventStore::new();
339 let backend = MemoryEventStore::new();
340 let store = CachingEventStore::new(cache.clone(), backend.clone());
341
342 store.append("s", EventRecord::new(0, "x")).await.unwrap();
343 store.purge_session("s").await.unwrap();
344
345 assert_eq!(cache.total_events().await, 0);
346 assert_eq!(backend.total_events().await, 0);
347 }
348}