1use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10
11use eventcore_types::{
12 CheckpointStore, CommandStateSnapshot, CommandStateSnapshotId, Event, EventFilter, EventPage,
13 EventReader, EventStore, EventStoreError, EventStream, EventStreamSlice, Operation,
14 ProjectorCoordinator, StreamId, StreamPosition, StreamVersion, StreamWriteEntry, StreamWrites,
15};
16use uuid::Uuid;
17
18type StreamData = (Vec<Box<dyn std::any::Any + Send>>, StreamVersion);
19
20#[derive(Debug, Clone)]
28struct GlobalLogEntry {
29 event_id: Uuid,
31 stream_id: String,
33 event_type: String,
35 event_data: String,
37}
38
39struct StoreData {
41 streams: HashMap<StreamId, StreamData>,
42 global_log: Vec<GlobalLogEntry>,
44 checkpoints: HashMap<String, StreamPosition>,
46 command_state_snapshots: HashMap<CommandStateSnapshotId, CommandStateSnapshot>,
48 locks: Arc<RwLock<HashMap<String, ()>>>,
50}
51
52pub struct InMemoryEventStore {
73 data: std::sync::Mutex<StoreData>,
74}
75
76impl InMemoryEventStore {
77 pub fn new() -> Self {
82 Self {
83 data: std::sync::Mutex::new(StoreData {
84 streams: HashMap::new(),
85 global_log: Vec::new(),
86 checkpoints: HashMap::new(),
87 command_state_snapshots: HashMap::new(),
88 locks: Arc::new(RwLock::new(HashMap::new())),
89 }),
90 }
91 }
92}
93
94impl Default for InMemoryEventStore {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl EventStore for InMemoryEventStore {
101 async fn read_stream<E: Event>(
102 &self,
103 stream_id: StreamId,
104 ) -> Result<EventStream<E>, EventStoreError> {
105 let items: Vec<Result<E, EventStoreError>> = {
113 let data = self
114 .data
115 .lock()
116 .map_err(|_| EventStoreError::StoreFailure {
117 operation: Operation::ReadStream,
118 })?;
119 match data.streams.get(&stream_id) {
120 None => Vec::new(),
121 Some((boxed_events, _version)) => boxed_events
122 .iter()
123 .map(|boxed| match boxed.downcast_ref::<E>() {
124 Some(event) => Ok(event.clone()),
125 None => Err(EventStoreError::DeserializationFailed {
126 stream_id: stream_id.clone(),
127 detail: format!(
128 "event could not be downcast to {}",
129 std::any::type_name::<E>()
130 ),
131 }),
132 })
133 .collect(),
134 }
135 };
136
137 Ok(EventStream::new(futures::stream::iter(items)))
138 }
139
140 async fn read_stream_after<E: Event>(
141 &self,
142 stream_id: StreamId,
143 exclusive_version: StreamVersion,
144 ) -> Result<EventStream<E>, EventStoreError> {
145 let count: usize = exclusive_version.into();
146 let items: Vec<Result<E, EventStoreError>> = {
147 let data = self
148 .data
149 .lock()
150 .map_err(|_| EventStoreError::StoreFailure {
151 operation: Operation::ReadStream,
152 })?;
153 match data.streams.get(&stream_id) {
154 None => Vec::new(),
155 Some((boxed_events, _version)) => boxed_events
156 .iter()
157 .skip(count)
158 .map(|boxed| match boxed.downcast_ref::<E>() {
159 Some(event) => Ok(event.clone()),
160 None => Err(EventStoreError::DeserializationFailed {
161 stream_id: stream_id.clone(),
162 detail: format!(
163 "event could not be downcast to {}",
164 std::any::type_name::<E>()
165 ),
166 }),
167 })
168 .collect(),
169 }
170 };
171
172 Ok(EventStream::new(futures::stream::iter(items)))
173 }
174
175 async fn append_events(
176 &self,
177 writes: StreamWrites,
178 ) -> Result<EventStreamSlice, EventStoreError> {
179 let mut data = self
180 .data
181 .lock()
182 .map_err(|_| EventStoreError::StoreFailure {
183 operation: Operation::AppendEvents,
184 })?;
185 let expected_versions = writes.expected_versions().clone();
186
187 for (stream_id, expected_version) in &expected_versions {
189 let current_version = data
190 .streams
191 .get(stream_id)
192 .map(|(_events, version)| *version)
193 .unwrap_or_else(|| StreamVersion::new(0));
194
195 if current_version != *expected_version {
196 return Err(EventStoreError::VersionConflict {
197 stream_id: stream_id.clone(),
198 expected: *expected_version,
199 actual: current_version,
200 });
201 }
202 }
203
204 for entry in writes.into_entries() {
206 let StreamWriteEntry {
207 stream_id,
208 event,
209 event_type,
210 event_data,
211 } = entry;
212
213 let event_id = Uuid::now_v7();
215
216 data.global_log.push(GlobalLogEntry {
219 event_id,
220 stream_id: stream_id.as_ref().to_string(),
221 event_type: event_type.to_string(),
222 event_data: event_data.get().to_owned(),
223 });
224
225 let (events, version) = data
226 .streams
227 .entry(stream_id)
228 .or_insert_with(|| (Vec::new(), StreamVersion::new(0)));
229 events.push(event);
230 *version = version.increment();
231 }
232
233 Ok(EventStreamSlice)
234 }
235
236 async fn load_command_state_snapshot(
237 &self,
238 snapshot_id: CommandStateSnapshotId,
239 ) -> Result<Option<CommandStateSnapshot>, EventStoreError> {
240 let data = self
241 .data
242 .lock()
243 .map_err(|_| EventStoreError::StoreFailure {
244 operation: Operation::ReadStream,
245 })?;
246 Ok(data.command_state_snapshots.get(&snapshot_id).cloned())
247 }
248
249 async fn save_command_state_snapshot(
250 &self,
251 snapshot_id: CommandStateSnapshotId,
252 snapshot: CommandStateSnapshot,
253 ) -> Result<(), EventStoreError> {
254 let mut data = self
255 .data
256 .lock()
257 .map_err(|_| EventStoreError::StoreFailure {
258 operation: Operation::AppendEvents,
259 })?;
260 match data.command_state_snapshots.get(&snapshot_id) {
261 Some(current) if !snapshot.covers(current) => {}
262 _ => {
263 let _ = data.command_state_snapshots.insert(snapshot_id, snapshot);
264 }
265 }
266 Ok(())
267 }
268}
269
270impl EventReader for InMemoryEventStore {
271 type Error = EventStoreError;
272
273 async fn read_events<E: Event>(
274 &self,
275 filter: EventFilter,
276 page: EventPage,
277 ) -> Result<Vec<(E, StreamPosition)>, Self::Error> {
278 let data = self
279 .data
280 .lock()
281 .map_err(|_| EventStoreError::StoreFailure {
282 operation: Operation::ReadStream,
283 })?;
284
285 let after_event_id = page.after_position().map(|p| p.into_inner());
286
287 let events: Vec<(E, StreamPosition)> = data
288 .global_log
289 .iter()
290 .filter(|entry| {
291 match after_event_id {
293 None => true,
294 Some(after_id) => entry.event_id > after_id,
295 }
296 })
297 .filter(|entry| {
298 match filter.stream_prefix() {
300 None => true,
301 Some(prefix) => entry.stream_id.starts_with(prefix.as_ref()),
302 }
303 })
304 .filter(|entry| {
305 match filter.stream_pattern() {
308 None => true,
309 Some(pattern) => pattern.matches(&entry.stream_id),
310 }
311 })
312 .filter(|entry| {
313 let type_filter = filter.event_type().unwrap_or_else(|| E::event_type_name());
317 entry.event_type == type_filter
318 })
319 .take(page.limit().into_inner())
320 .filter_map(|entry| {
321 serde_json::from_str::<E>(&entry.event_data)
322 .ok()
323 .map(|e| (e, StreamPosition::new(entry.event_id)))
324 })
325 .collect();
326
327 Ok(events)
328 }
329}
330
331impl CheckpointStore for InMemoryEventStore {
332 type Error = InMemoryCheckpointError;
333
334 async fn load(&self, name: &str) -> Result<Option<StreamPosition>, Self::Error> {
335 let data = self
336 .data
337 .lock()
338 .map_err(|e| InMemoryCheckpointError::LockFailed(e.to_string()))?;
339 Ok(data.checkpoints.get(name).copied())
340 }
341
342 async fn save(&self, name: &str, position: StreamPosition) -> Result<(), Self::Error> {
343 let mut data = self
344 .data
345 .lock()
346 .map_err(|e| InMemoryCheckpointError::LockFailed(e.to_string()))?;
347 let _ = data.checkpoints.insert(name.to_string(), position);
348 Ok(())
349 }
350}
351
352impl ProjectorCoordinator for InMemoryEventStore {
353 type Error = InMemoryCoordinationError;
354 type Guard = InMemoryCoordinationGuard;
355
356 async fn try_acquire(&self, subscription_name: &str) -> Result<Self::Guard, Self::Error> {
357 let data = self
358 .data
359 .lock()
360 .map_err(|e| InMemoryCoordinationError::LockPoisoned {
361 message: e.to_string(),
362 })?;
363
364 let mut guard =
365 data.locks
366 .write()
367 .map_err(|e| InMemoryCoordinationError::LockPoisoned {
368 message: e.to_string(),
369 })?;
370
371 if guard.contains_key(subscription_name) {
372 return Err(InMemoryCoordinationError::LeadershipNotAcquired {
373 subscription_name: subscription_name.to_string(),
374 });
375 }
376
377 let _ = guard.insert(subscription_name.to_string(), ());
378
379 Ok(InMemoryCoordinationGuard {
380 subscription_name: subscription_name.to_string(),
381 locks: Arc::clone(&data.locks),
382 })
383 }
384}
385
386#[derive(Debug, Clone, Default)]
404pub struct InMemoryCheckpointStore {
405 checkpoints: Arc<RwLock<HashMap<String, StreamPosition>>>,
406}
407
408impl InMemoryCheckpointStore {
409 pub fn new() -> Self {
411 Self::default()
412 }
413}
414
415#[derive(Debug, Clone, thiserror::Error)]
420pub enum InMemoryCheckpointError {
421 #[error("failed to acquire lock: {0}")]
422 LockFailed(String),
423}
424
425#[derive(Debug, Clone, thiserror::Error)]
427pub enum InMemoryCoordinationError {
428 #[error(
430 "leadership not acquired for subscription '{subscription_name}': another instance holds the lock"
431 )]
432 LeadershipNotAcquired { subscription_name: String },
433 #[error("lock poisoned: {message}")]
435 LockPoisoned { message: String },
436}
437
438#[derive(Debug)]
440pub struct InMemoryCoordinationGuard {
441 subscription_name: String,
442 locks: Arc<RwLock<HashMap<String, ()>>>,
443}
444
445impl Drop for InMemoryCoordinationGuard {
446 fn drop(&mut self) {
447 if let Ok(mut guard) = self.locks.write() {
448 let _ = guard.remove(&self.subscription_name);
449 } else {
450 tracing::error!(
451 subscription_name = %self.subscription_name,
452 "failed to release coordination lock: RwLock poisoned"
453 );
454 }
455 }
456}
457
458#[derive(Debug, Clone, Default)]
467pub struct InMemoryProjectorCoordinator {
468 locks: Arc<RwLock<HashMap<String, ()>>>,
469}
470
471impl InMemoryProjectorCoordinator {
472 pub fn new() -> Self {
474 Self::default()
475 }
476}
477
478impl ProjectorCoordinator for InMemoryProjectorCoordinator {
479 type Error = InMemoryCoordinationError;
480 type Guard = InMemoryCoordinationGuard;
481
482 async fn try_acquire(&self, subscription_name: &str) -> Result<Self::Guard, Self::Error> {
483 let mut guard =
484 self.locks
485 .write()
486 .map_err(|e| InMemoryCoordinationError::LockPoisoned {
487 message: e.to_string(),
488 })?;
489
490 if guard.contains_key(subscription_name) {
491 return Err(InMemoryCoordinationError::LeadershipNotAcquired {
492 subscription_name: subscription_name.to_string(),
493 });
494 }
495
496 let _ = guard.insert(subscription_name.to_string(), ());
497
498 Ok(InMemoryCoordinationGuard {
499 subscription_name: subscription_name.to_string(),
500 locks: Arc::clone(&self.locks),
501 })
502 }
503}
504
505impl CheckpointStore for InMemoryCheckpointStore {
506 type Error = InMemoryCheckpointError;
507
508 async fn load(&self, name: &str) -> Result<Option<StreamPosition>, Self::Error> {
509 let guard = self
510 .checkpoints
511 .read()
512 .map_err(|e| InMemoryCheckpointError::LockFailed(e.to_string()))?;
513 Ok(guard.get(name).copied())
514 }
515
516 async fn save(&self, name: &str, position: StreamPosition) -> Result<(), Self::Error> {
517 let mut guard = self
518 .checkpoints
519 .write()
520 .map_err(|e| InMemoryCheckpointError::LockFailed(e.to_string()))?;
521 let _ = guard.insert(name.to_string(), position);
522 Ok(())
523 }
524}
525
526#[cfg(test)]
527#[path = "lib.test.rs"]
528mod tests;