1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use stateset_core::{CommerceEvent, EventStore, Result};
6use std::collections::VecDeque;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{Arc, RwLock};
9use uuid::Uuid;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub(super) struct StoredEvent {
14 pub sequence: u64,
16 pub id: Uuid,
18 pub event_type: String,
20 pub aggregate_type: Option<String>,
22 pub aggregate_id: Option<String>,
24 pub data: String,
26 pub stored_at: DateTime<Utc>,
28}
29
30pub struct InMemoryEventStore {
32 events: Arc<RwLock<VecDeque<StoredEvent>>>,
33 sequence: AtomicU64,
34 max_events: usize,
35}
36
37impl std::fmt::Debug for InMemoryEventStore {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("InMemoryEventStore")
40 .field("max_events", &self.max_events)
41 .finish_non_exhaustive()
42 }
43}
44
45impl InMemoryEventStore {
46 #[must_use]
48 pub fn new(max_events: usize) -> Self {
49 Self {
50 events: Arc::new(RwLock::new(VecDeque::with_capacity(max_events))),
51 sequence: AtomicU64::new(0),
52 max_events,
53 }
54 }
55
56 fn extract_aggregate(event: &CommerceEvent) -> (Option<String>, Option<String>) {
58 match event {
59 CommerceEvent::OrderCreated { order_id, .. }
60 | CommerceEvent::OrderStatusChanged { order_id, .. }
61 | CommerceEvent::OrderPaymentStatusChanged { order_id, .. }
62 | CommerceEvent::OrderFulfillmentStatusChanged { order_id, .. }
63 | CommerceEvent::OrderCancelled { order_id, .. }
64 | CommerceEvent::OrderItemAdded { order_id, .. }
65 | CommerceEvent::OrderItemRemoved { order_id, .. } => {
66 (Some("order".to_string()), Some(order_id.to_string()))
67 }
68 CommerceEvent::CustomerCreated { customer_id, .. }
69 | CommerceEvent::CustomerUpdated { customer_id, .. }
70 | CommerceEvent::CustomerStatusChanged { customer_id, .. }
71 | CommerceEvent::CustomerAddressAdded { customer_id, .. } => {
72 (Some("customer".to_string()), Some(customer_id.to_string()))
73 }
74 CommerceEvent::ProductCreated { product_id, .. }
75 | CommerceEvent::ProductUpdated { product_id, .. }
76 | CommerceEvent::ProductStatusChanged { product_id, .. } => {
77 (Some("product".to_string()), Some(product_id.to_string()))
78 }
79 CommerceEvent::ProductVariantAdded { variant_id, .. }
80 | CommerceEvent::ProductVariantUpdated { variant_id, .. } => {
81 (Some("variant".to_string()), Some(variant_id.to_string()))
82 }
83 CommerceEvent::CustomObjectTypeCreated { type_id, .. }
84 | CommerceEvent::CustomObjectTypeUpdated { type_id, .. }
85 | CommerceEvent::CustomObjectTypeDeleted { type_id, .. } => {
86 (Some("custom_object_type".to_string()), Some(type_id.to_string()))
87 }
88 CommerceEvent::CustomObjectCreated { object_id, .. }
89 | CommerceEvent::CustomObjectUpdated { object_id, .. }
90 | CommerceEvent::CustomObjectDeleted { object_id, .. } => {
91 (Some("custom_object".to_string()), Some(object_id.to_string()))
92 }
93 CommerceEvent::InventoryItemCreated { item_id, .. }
94 | CommerceEvent::InventoryAdjusted { item_id, .. } => {
95 (Some("inventory".to_string()), Some(item_id.to_string()))
96 }
97 CommerceEvent::InventoryReserved { reservation_id, .. }
98 | CommerceEvent::InventoryReservationReleased { reservation_id, .. }
99 | CommerceEvent::InventoryReservationConfirmed { reservation_id, .. } => {
100 (Some("reservation".to_string()), Some(reservation_id.to_string()))
101 }
102 CommerceEvent::LowStockAlert { sku, .. } => {
103 (Some("inventory".to_string()), Some(sku.clone()))
104 }
105 CommerceEvent::ReturnRequested { return_id, .. }
106 | CommerceEvent::ReturnStatusChanged { return_id, .. }
107 | CommerceEvent::ReturnApproved { return_id, .. }
108 | CommerceEvent::ReturnRejected { return_id, .. }
109 | CommerceEvent::ReturnCompleted { return_id, .. }
110 | CommerceEvent::RefundIssued { return_id, .. } => {
111 (Some("return".to_string()), Some(return_id.to_string()))
112 }
113 CommerceEvent::X402IntentCreated { intent_id, .. }
115 | CommerceEvent::X402IntentSigned { intent_id, .. }
116 | CommerceEvent::X402IntentSequenced { intent_id, .. }
117 | CommerceEvent::X402IntentSettled { intent_id, .. }
118 | CommerceEvent::X402IntentFailed { intent_id, .. }
119 | CommerceEvent::X402IntentExpired { intent_id, .. } => {
120 (Some("x402_intent".to_string()), Some(intent_id.to_string()))
121 }
122 CommerceEvent::AgentCardCreated { agent_id, .. }
124 | CommerceEvent::AgentCardVerified { agent_id, .. }
125 | CommerceEvent::AgentCardSuspended { agent_id, .. }
126 | CommerceEvent::AgentCardReactivated { agent_id, .. } => {
127 (Some("agent_card".to_string()), Some(agent_id.to_string()))
128 }
129 CommerceEvent::A2AQuoteRequested { quote_id, .. }
131 | CommerceEvent::A2AQuoteAccepted { quote_id, .. }
132 | CommerceEvent::A2AQuoteRejected { quote_id, .. } => {
133 (Some("a2a_quote".to_string()), Some(quote_id.to_string()))
134 }
135 CommerceEvent::A2APurchaseInitiated { purchase_id, .. }
136 | CommerceEvent::A2APurchasePaid { purchase_id, .. }
137 | CommerceEvent::A2ADeliveryConfirmed { purchase_id, .. } => {
138 (Some("a2a_purchase".to_string()), Some(purchase_id.to_string()))
139 }
140 CommerceEvent::CartCreated { cart_id, .. }
142 | CommerceEvent::CartItemAdded { cart_id, .. }
143 | CommerceEvent::CartStatusChanged { cart_id, .. }
144 | CommerceEvent::CartCheckoutCompleted { cart_id, .. } => {
145 (Some("cart".to_string()), Some(cart_id.to_string()))
146 }
147 CommerceEvent::PaymentCreated { payment_id, .. }
149 | CommerceEvent::PaymentStatusChanged { payment_id, .. }
150 | CommerceEvent::PaymentCompleted { payment_id, .. } => {
151 (Some("payment".to_string()), Some(payment_id.to_string()))
152 }
153 CommerceEvent::ShipmentCreated { shipment_id, .. }
155 | CommerceEvent::ShipmentDelivered { shipment_id, .. } => {
156 (Some("shipment".to_string()), Some(shipment_id.to_string()))
157 }
158 CommerceEvent::InvoiceCreated { invoice_id, .. }
160 | CommerceEvent::InvoiceStatusChanged { invoice_id, .. } => {
161 (Some("invoice".to_string()), Some(invoice_id.to_string()))
162 }
163 CommerceEvent::SubscriptionCreated { subscription_id, .. }
165 | CommerceEvent::SubscriptionStatusChanged { subscription_id, .. }
166 | CommerceEvent::SubscriptionRenewed { subscription_id, .. }
167 | CommerceEvent::SubscriptionCancelled { subscription_id, .. } => {
168 (Some("subscription".to_string()), Some(subscription_id.to_string()))
169 }
170 CommerceEvent::GiftCardCreated { gift_card_id, .. }
172 | CommerceEvent::GiftCardRedeemed { gift_card_id, .. } => {
173 (Some("gift_card".to_string()), Some(gift_card_id.to_string()))
174 }
175 CommerceEvent::StoreCreditIssued { store_credit_id, .. }
177 | CommerceEvent::StoreCreditApplied { store_credit_id, .. } => {
178 (Some("store_credit".to_string()), Some(store_credit_id.to_string()))
179 }
180 CommerceEvent::LoyaltyPointsEarned { program_id, .. }
182 | CommerceEvent::LoyaltyPointsRedeemed { program_id, .. } => {
183 (Some("loyalty".to_string()), Some(program_id.to_string()))
184 }
185 CommerceEvent::FixedAssetPlacedInService { asset_id, .. }
187 | CommerceEvent::FixedAssetDisposed { asset_id, .. }
188 | CommerceEvent::FixedAssetWrittenOff { asset_id, .. }
189 | CommerceEvent::DepreciationPosted { asset_id, .. } => {
190 (Some("fixed_asset".to_string()), Some(asset_id.to_string()))
191 }
192 CommerceEvent::RevenueRecognized { obligation_id, .. } => {
194 (Some("performance_obligation".to_string()), Some(obligation_id.to_string()))
195 }
196 CommerceEvent::RevenueContractCompleted { contract_id, .. } => {
197 (Some("revenue_contract".to_string()), Some(contract_id.to_string()))
198 }
199 CommerceEvent::CycleCountCompleted { cycle_count_id, .. } => {
201 (Some("cycle_count".to_string()), Some(cycle_count_id.to_string()))
202 }
203 CommerceEvent::ThreeWayMatchVarianceDetected { bill_id, .. } => {
205 (Some("bill".to_string()), Some(bill_id.to_string()))
206 }
207 CommerceEvent::FxRevaluationPosted { as_of_date, .. } => {
209 (Some("fx_revaluation".to_string()), Some(as_of_date.to_string()))
210 }
211 CommerceEvent::MonthEndCloseCompleted { period_id, .. } => {
212 (Some("gl_period".to_string()), Some(period_id.to_string()))
213 }
214 }
215 }
216}
217
218impl EventStore for InMemoryEventStore {
219 fn append(&self, event: &CommerceEvent) -> Result<u64> {
220 let (aggregate_type, aggregate_id) = Self::extract_aggregate(event);
221 let data = event.to_json().map_err(|e| {
222 stateset_core::CommerceError::Internal(format!("Failed to serialize event: {e}"))
223 })?;
224
225 let seq = self.sequence.fetch_add(1, Ordering::Relaxed) + 1;
226
227 let stored = StoredEvent {
228 sequence: seq,
229 id: Uuid::new_v4(),
230 event_type: event.event_type().to_string(),
231 aggregate_type,
232 aggregate_id,
233 data,
234 stored_at: Utc::now(),
235 };
236
237 let mut events = self.events.write().map_err(|_| {
238 stateset_core::CommerceError::Internal(
239 "InMemoryEventStore events lock poisoned".to_string(),
240 )
241 })?;
242 if events.len() >= self.max_events {
243 events.pop_front();
244 }
245 events.push_back(stored);
246
247 Ok(seq)
248 }
249
250 fn get_events_since(&self, sequence: u64, limit: u32) -> Result<Vec<(u64, CommerceEvent)>> {
251 let events = self.events.read().map_err(|_| {
252 stateset_core::CommerceError::Internal(
253 "InMemoryEventStore events lock poisoned".to_string(),
254 )
255 })?;
256 let result = events
257 .iter()
258 .filter(|e| e.sequence > sequence)
259 .take(limit as usize)
260 .map(|e| {
261 CommerceEvent::from_json(&e.data).map(|event| (e.sequence, event)).map_err(|err| {
262 stateset_core::CommerceError::Internal(format!(
263 "Failed to deserialize event {}: {}",
264 e.id, err
265 ))
266 })
267 })
268 .collect::<Result<Vec<_>>>()?;
269 Ok(result)
270 }
271
272 fn get_events_for_aggregate(
273 &self,
274 aggregate_type: &str,
275 aggregate_id: &str,
276 ) -> Result<Vec<CommerceEvent>> {
277 let events = self.events.read().map_err(|_| {
278 stateset_core::CommerceError::Internal(
279 "InMemoryEventStore events lock poisoned".to_string(),
280 )
281 })?;
282 let result = events
283 .iter()
284 .filter(|e| {
285 e.aggregate_type.as_deref() == Some(aggregate_type)
286 && e.aggregate_id.as_deref() == Some(aggregate_id)
287 })
288 .map(|e| {
289 CommerceEvent::from_json(&e.data).map_err(|err| {
290 stateset_core::CommerceError::Internal(format!(
291 "Failed to deserialize event {}: {}",
292 e.id, err
293 ))
294 })
295 })
296 .collect::<Result<Vec<_>>>()?;
297 Ok(result)
298 }
299
300 fn latest_sequence(&self) -> Result<u64> {
301 Ok(self.sequence.load(Ordering::Relaxed))
302 }
303}
304
305#[cfg(feature = "sqlite-events")]
307pub struct SqliteEventStore {
308 pool: r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>,
309}
310
311#[cfg(feature = "sqlite-events")]
312impl std::fmt::Debug for SqliteEventStore {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 f.debug_struct("SqliteEventStore").finish_non_exhaustive()
315 }
316}
317
318#[cfg(feature = "sqlite-events")]
319impl SqliteEventStore {
320 pub fn new(pool: r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>) -> Result<Self> {
322 let store = Self { pool };
323 store.create_table()?;
324 Ok(store)
325 }
326
327 fn create_table(&self) -> Result<()> {
328 let conn = self.pool.get().map_err(|e| {
329 stateset_core::CommerceError::DatabaseError(format!("Failed to get connection: {}", e))
330 })?;
331
332 conn.execute(
333 r#"
334 CREATE TABLE IF NOT EXISTS commerce_events (
335 sequence INTEGER PRIMARY KEY AUTOINCREMENT,
336 id TEXT NOT NULL UNIQUE,
337 event_type TEXT NOT NULL,
338 aggregate_type TEXT,
339 aggregate_id TEXT,
340 data TEXT NOT NULL,
341 stored_at TEXT NOT NULL
342 )
343 "#,
344 [],
345 )
346 .map_err(|e| {
347 stateset_core::CommerceError::DatabaseError(format!("Failed to create table: {}", e))
348 })?;
349
350 conn.execute(
351 "CREATE INDEX IF NOT EXISTS idx_events_aggregate ON commerce_events(aggregate_type, aggregate_id)",
352 [],
353 ).map_err(|e| {
354 stateset_core::CommerceError::DatabaseError(format!("Failed to create index: {}", e))
355 })?;
356
357 conn.execute(
358 "CREATE INDEX IF NOT EXISTS idx_events_type ON commerce_events(event_type)",
359 [],
360 )
361 .map_err(|e| {
362 stateset_core::CommerceError::DatabaseError(format!("Failed to create index: {}", e))
363 })?;
364
365 Ok(())
366 }
367}
368
369#[cfg(feature = "sqlite-events")]
370impl EventStore for SqliteEventStore {
371 fn append(&self, event: &CommerceEvent) -> Result<u64> {
372 let conn = self.pool.get().map_err(|e| {
373 stateset_core::CommerceError::DatabaseError(format!("Failed to get connection: {}", e))
374 })?;
375
376 let (aggregate_type, aggregate_id) = InMemoryEventStore::extract_aggregate(event);
377 let data = event.to_json().map_err(|e| {
378 stateset_core::CommerceError::Internal(format!("Failed to serialize event: {}", e))
379 })?;
380
381 conn.execute(
382 r#"
383 INSERT INTO commerce_events (id, event_type, aggregate_type, aggregate_id, data, stored_at)
384 VALUES (?1, ?2, ?3, ?4, ?5, ?6)
385 "#,
386 rusqlite::params![
387 Uuid::new_v4().to_string(),
388 event.event_type(),
389 aggregate_type,
390 aggregate_id,
391 data,
392 Utc::now().to_rfc3339(),
393 ],
394 )
395 .map_err(|e| {
396 stateset_core::CommerceError::DatabaseError(format!("Failed to insert event: {}", e))
397 })?;
398
399 let sequence = conn.last_insert_rowid() as u64;
400 Ok(sequence)
401 }
402
403 fn get_events_since(&self, sequence: u64, limit: u32) -> Result<Vec<(u64, CommerceEvent)>> {
404 let conn = self.pool.get().map_err(|e| {
405 stateset_core::CommerceError::DatabaseError(format!("Failed to get connection: {}", e))
406 })?;
407
408 let mut stmt = conn
409 .prepare(
410 r#"
411 SELECT sequence, data FROM commerce_events
412 WHERE sequence > ?1
413 ORDER BY sequence ASC
414 LIMIT ?2
415 "#,
416 )
417 .map_err(|e| {
418 stateset_core::CommerceError::DatabaseError(format!(
419 "Failed to prepare statement: {}",
420 e
421 ))
422 })?;
423
424 let rows = stmt
425 .query_map(rusqlite::params![sequence as i64, limit], |row| {
426 let seq: i64 = row.get(0)?;
427 let data: String = row.get(1)?;
428 Ok((seq as u64, data))
429 })
430 .map_err(|e| {
431 stateset_core::CommerceError::DatabaseError(format!(
432 "Failed to query events: {}",
433 e
434 ))
435 })?;
436
437 let mut events = Vec::new();
438 for row in rows {
439 let (seq, data) = row.map_err(|e| {
440 stateset_core::CommerceError::DatabaseError(format!("Failed to read row: {}", e))
441 })?;
442 let event = CommerceEvent::from_json(&data).map_err(|e| {
443 stateset_core::CommerceError::DatabaseError(format!(
444 "Failed to deserialize event at sequence {}: {}",
445 seq, e
446 ))
447 })?;
448 events.push((seq, event));
449 }
450
451 Ok(events)
452 }
453
454 fn get_events_for_aggregate(
455 &self,
456 aggregate_type: &str,
457 aggregate_id: &str,
458 ) -> Result<Vec<CommerceEvent>> {
459 let conn = self.pool.get().map_err(|e| {
460 stateset_core::CommerceError::DatabaseError(format!("Failed to get connection: {}", e))
461 })?;
462
463 let mut stmt = conn
464 .prepare(
465 r#"
466 SELECT data FROM commerce_events
467 WHERE aggregate_type = ?1 AND aggregate_id = ?2
468 ORDER BY sequence ASC
469 "#,
470 )
471 .map_err(|e| {
472 stateset_core::CommerceError::DatabaseError(format!(
473 "Failed to prepare statement: {}",
474 e
475 ))
476 })?;
477
478 let rows = stmt
479 .query_map(rusqlite::params![aggregate_type, aggregate_id], |row| {
480 let data: String = row.get(0)?;
481 Ok(data)
482 })
483 .map_err(|e| {
484 stateset_core::CommerceError::DatabaseError(format!(
485 "Failed to query events: {}",
486 e
487 ))
488 })?;
489
490 let mut events = Vec::new();
491 for row in rows {
492 let data = row.map_err(|e| {
493 stateset_core::CommerceError::DatabaseError(format!("Failed to read row: {}", e))
494 })?;
495 let event = CommerceEvent::from_json(&data).map_err(|e| {
496 stateset_core::CommerceError::DatabaseError(format!(
497 "Failed to deserialize event for {}:{}: {}",
498 aggregate_type, aggregate_id, e
499 ))
500 })?;
501 events.push(event);
502 }
503
504 Ok(events)
505 }
506
507 fn latest_sequence(&self) -> Result<u64> {
508 let conn = self.pool.get().map_err(|e| {
509 stateset_core::CommerceError::DatabaseError(format!("Failed to get connection: {}", e))
510 })?;
511
512 let sequence: Option<i64> = conn
513 .query_row("SELECT MAX(sequence) FROM commerce_events", [], |row| row.get(0))
514 .map_err(|e| {
515 stateset_core::CommerceError::DatabaseError(format!(
516 "Failed to get latest sequence: {}",
517 e
518 ))
519 })?;
520
521 Ok(sequence.unwrap_or(0) as u64)
522 }
523}
524
525#[cfg(feature = "postgres")]
527pub struct PostgresEventStore {
528 pool: sqlx::PgPool,
529}
530
531#[cfg(feature = "postgres")]
532impl std::fmt::Debug for PostgresEventStore {
533 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
534 f.debug_struct("PostgresEventStore").finish_non_exhaustive()
535 }
536}
537
538#[cfg(feature = "postgres")]
539impl PostgresEventStore {
540 pub async fn new(pool: sqlx::PgPool) -> Result<Self> {
542 let store = Self { pool };
543 store.create_table().await?;
544 Ok(store)
545 }
546
547 async fn create_table(&self) -> Result<()> {
548 sqlx::query(
549 r#"
550 CREATE TABLE IF NOT EXISTS commerce_events (
551 sequence BIGSERIAL PRIMARY KEY,
552 id UUID NOT NULL UNIQUE,
553 event_type TEXT NOT NULL,
554 aggregate_type TEXT,
555 aggregate_id TEXT,
556 data JSONB NOT NULL,
557 stored_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
558 )
559 "#,
560 )
561 .execute(&self.pool)
562 .await
563 .map_err(|e| {
564 stateset_core::CommerceError::DatabaseError(format!("Failed to create table: {}", e))
565 })?;
566
567 sqlx::query(
568 "CREATE INDEX IF NOT EXISTS idx_events_aggregate ON commerce_events(aggregate_type, aggregate_id)",
569 )
570 .execute(&self.pool)
571 .await
572 .map_err(|e| {
573 stateset_core::CommerceError::DatabaseError(format!("Failed to create index: {}", e))
574 })?;
575
576 sqlx::query("CREATE INDEX IF NOT EXISTS idx_events_type ON commerce_events(event_type)")
577 .execute(&self.pool)
578 .await
579 .map_err(|e| {
580 stateset_core::CommerceError::DatabaseError(format!(
581 "Failed to create index: {}",
582 e
583 ))
584 })?;
585
586 Ok(())
587 }
588
589 pub async fn append_async(&self, event: &CommerceEvent) -> Result<u64> {
591 let (aggregate_type, aggregate_id) = InMemoryEventStore::extract_aggregate(event);
592 let data = serde_json::to_value(event).map_err(|e| {
593 stateset_core::CommerceError::Internal(format!("Failed to serialize event: {}", e))
594 })?;
595
596 let row: (i64,) = sqlx::query_as(
597 r#"
598 INSERT INTO commerce_events (id, event_type, aggregate_type, aggregate_id, data)
599 VALUES ($1, $2, $3, $4, $5)
600 RETURNING sequence
601 "#,
602 )
603 .bind(Uuid::new_v4())
604 .bind(event.event_type())
605 .bind(aggregate_type)
606 .bind(aggregate_id)
607 .bind(data)
608 .fetch_one(&self.pool)
609 .await
610 .map_err(|e| {
611 stateset_core::CommerceError::DatabaseError(format!("Failed to insert event: {}", e))
612 })?;
613
614 Ok(row.0 as u64)
615 }
616
617 pub async fn get_events_since_async(
619 &self,
620 sequence: u64,
621 limit: u32,
622 ) -> Result<Vec<(u64, CommerceEvent)>> {
623 let rows: Vec<(i64, Uuid, serde_json::Value)> = sqlx::query_as(
624 r#"
625 SELECT sequence, id, data FROM commerce_events
626 WHERE sequence > $1
627 ORDER BY sequence ASC
628 LIMIT $2
629 "#,
630 )
631 .bind(sequence as i64)
632 .bind(limit as i32)
633 .fetch_all(&self.pool)
634 .await
635 .map_err(|e| {
636 stateset_core::CommerceError::DatabaseError(format!("Failed to query events: {}", e))
637 })?;
638
639 let mut events = Vec::new();
640 for (seq, id, data) in rows {
641 let event: CommerceEvent = serde_json::from_value(data).map_err(|e| {
642 stateset_core::CommerceError::DatabaseError(format!(
643 "Failed to deserialize event {id} at sequence {seq}: {e}"
644 ))
645 })?;
646 events.push((seq as u64, event));
647 }
648
649 Ok(events)
650 }
651
652 pub async fn get_events_for_aggregate_async(
654 &self,
655 aggregate_type: &str,
656 aggregate_id: &str,
657 ) -> Result<Vec<CommerceEvent>> {
658 let rows: Vec<(i64, Uuid, serde_json::Value)> = sqlx::query_as(
659 r#"
660 SELECT sequence, id, data FROM commerce_events
661 WHERE aggregate_type = $1 AND aggregate_id = $2
662 ORDER BY sequence ASC
663 "#,
664 )
665 .bind(aggregate_type)
666 .bind(aggregate_id)
667 .fetch_all(&self.pool)
668 .await
669 .map_err(|e| {
670 stateset_core::CommerceError::DatabaseError(format!("Failed to query events: {}", e))
671 })?;
672
673 let mut events = Vec::new();
674 for (seq, id, data) in rows {
675 let event: CommerceEvent = serde_json::from_value(data).map_err(|e| {
676 stateset_core::CommerceError::DatabaseError(format!(
677 "Failed to deserialize event {id} at sequence {seq}: {e}"
678 ))
679 })?;
680 events.push(event);
681 }
682
683 Ok(events)
684 }
685
686 pub async fn latest_sequence_async(&self) -> Result<u64> {
688 let row: Option<(Option<i64>,)> =
689 sqlx::query_as("SELECT MAX(sequence) FROM commerce_events")
690 .fetch_optional(&self.pool)
691 .await
692 .map_err(|e| {
693 stateset_core::CommerceError::DatabaseError(format!(
694 "Failed to get latest sequence: {}",
695 e
696 ))
697 })?;
698
699 Ok(row.and_then(|(s,)| s).unwrap_or(0) as u64)
700 }
701}
702
703#[cfg(feature = "postgres")]
704fn block_on<F, T>(fut: F) -> Result<T>
705where
706 F: std::future::Future<Output = Result<T>>,
707{
708 if tokio::runtime::Handle::try_current().is_ok() {
709 return Err(stateset_core::CommerceError::NotPermitted(
710 "Blocking Postgres event store call inside an async runtime; use the async methods instead"
711 .to_string(),
712 ));
713 }
714
715 let rt = tokio::runtime::Runtime::new().map_err(|e| {
716 stateset_core::CommerceError::Internal(format!("Failed to create runtime: {}", e))
717 })?;
718 rt.block_on(fut)
719}
720
721#[cfg(feature = "postgres")]
722impl EventStore for PostgresEventStore {
723 fn append(&self, event: &CommerceEvent) -> Result<u64> {
724 block_on(self.append_async(event))
725 }
726
727 fn get_events_since(&self, sequence: u64, limit: u32) -> Result<Vec<(u64, CommerceEvent)>> {
728 block_on(self.get_events_since_async(sequence, limit))
729 }
730
731 fn get_events_for_aggregate(
732 &self,
733 aggregate_type: &str,
734 aggregate_id: &str,
735 ) -> Result<Vec<CommerceEvent>> {
736 block_on(self.get_events_for_aggregate_async(aggregate_type, aggregate_id))
737 }
738
739 fn latest_sequence(&self) -> Result<u64> {
740 block_on(self.latest_sequence_async())
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747 use rust_decimal_macros::dec;
748
749 #[test]
750 fn test_in_memory_store() {
751 let store = InMemoryEventStore::new(100);
752
753 let event = CommerceEvent::OrderCreated {
754 order_id: stateset_core::OrderId::new(),
755 customer_id: stateset_core::CustomerId::new(),
756 total_amount: dec!(100.00),
757 item_count: 2,
758 timestamp: Utc::now(),
759 };
760
761 let seq = store.append(&event).unwrap();
762 assert_eq!(seq, 1);
763
764 let events = store.get_events_since(0, 10).unwrap();
765 assert_eq!(events.len(), 1);
766 assert_eq!(events[0].0, 1);
767 }
768
769 #[test]
770 fn test_in_memory_store_max_events() {
771 let store = InMemoryEventStore::new(2);
772
773 for i in 0..5 {
774 let event = CommerceEvent::CustomerCreated {
775 customer_id: stateset_core::CustomerId::new(),
776 email: format!("test{}@example.com", i),
777 timestamp: Utc::now(),
778 };
779 store.append(&event).unwrap();
780 }
781
782 let events = store.get_events_since(0, 10).unwrap();
784 assert_eq!(events.len(), 2);
785 }
786}