Skip to main content

stateset_embedded/events/
store.rs

1//! Event store implementations for persisting events
2
3use 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/// Stored event with metadata
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub(super) struct StoredEvent {
14    /// Sequence number (monotonically increasing)
15    pub sequence: u64,
16    /// Unique event ID
17    pub id: Uuid,
18    /// Event type string
19    pub event_type: String,
20    /// Aggregate type (e.g., "order", "customer")
21    pub aggregate_type: Option<String>,
22    /// Aggregate ID
23    pub aggregate_id: Option<String>,
24    /// Serialized event data
25    pub data: String,
26    /// When the event was stored
27    pub stored_at: DateTime<Utc>,
28}
29
30/// In-memory event store for testing
31pub 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    /// Create a new in-memory event store
47    #[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    /// Get aggregate info from event
57    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            // x402 Payment Intent events
114            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            // Agent Card events
123            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            // A2A Commerce events
130            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            // Cart events
141            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            // Payment events
148            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            // Shipment events
154            CommerceEvent::ShipmentCreated { shipment_id, .. }
155            | CommerceEvent::ShipmentDelivered { shipment_id, .. } => {
156                (Some("shipment".to_string()), Some(shipment_id.to_string()))
157            }
158            // Invoice events
159            CommerceEvent::InvoiceCreated { invoice_id, .. }
160            | CommerceEvent::InvoiceStatusChanged { invoice_id, .. } => {
161                (Some("invoice".to_string()), Some(invoice_id.to_string()))
162            }
163            // Subscription events
164            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            // Gift card events
171            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            // Store credit events
176            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            // Loyalty events
181            CommerceEvent::LoyaltyPointsEarned { program_id, .. }
182            | CommerceEvent::LoyaltyPointsRedeemed { program_id, .. } => {
183                (Some("loyalty".to_string()), Some(program_id.to_string()))
184            }
185            // Fixed asset events
186            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            // Revenue recognition events
193            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            // Warehouse events
200            CommerceEvent::CycleCountCompleted { cycle_count_id, .. } => {
201                (Some("cycle_count".to_string()), Some(cycle_count_id.to_string()))
202            }
203            // Accounts payable events
204            CommerceEvent::ThreeWayMatchVarianceDetected { bill_id, .. } => {
205                (Some("bill".to_string()), Some(bill_id.to_string()))
206            }
207            // General ledger events
208            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/// SQLite event store implementation
306#[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    /// Create a new SQLite event store
321    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/// PostgreSQL event store implementation
526#[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    /// Create a new PostgreSQL event store
541    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    /// Append an event (async version)
590    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    /// Get events since sequence (async version)
618    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    /// Get events for aggregate (async version)
653    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    /// Get latest sequence (async version)
687    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        // Should only keep last 2 events
783        let events = store.get_events_since(0, 10).unwrap();
784        assert_eq!(events.len(), 2);
785    }
786}