Skip to main content

stateset_db/
lib.rs

1#![deny(unsafe_code)]
2#![cfg_attr(not(test), deny(clippy::unwrap_used))]
3#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(
6    html_logo_url = "https://raw.githubusercontent.com/stateset/stateset-icommerce/main/assets/stateset.png",
7    html_favicon_url = "https://raw.githubusercontent.com/stateset/stateset-icommerce/main/assets/favicon.ico",
8    issue_tracker_base_url = "https://github.com/stateset/stateset-icommerce/issues/"
9)]
10
11//! # StateSet DB
12//!
13//! Database implementations for StateSet iCommerce.
14//!
15//! ## Features
16//!
17//! - `sqlite` (default): SQLite database support via rusqlite
18//! - `postgres`: PostgreSQL database support via sqlx (async)
19//! - `vector`: Vector search support via sqlite-vec extension
20//! - `saga`: Experimental persisted saga coordinator (PostgreSQL-only)
21//!
22//! ## Usage
23//!
24//! ### SQLite (default)
25//! ```ignore
26//! use stateset_db::{SqliteDatabase, DatabaseConfig};
27//! let db = SqliteDatabase::new(&DatabaseConfig::sqlite("./store.db"))?;
28//! ```
29//!
30//! ### PostgreSQL
31//! ```ignore
32//! use stateset_db::{PostgresDatabase, DatabaseConfig};
33//! let db = PostgresDatabase::connect(&DatabaseConfig::postgres("postgres://localhost/stateset")).await?;
34//! ```
35//!
36//! ## Error Handling
37//!
38//! This crate uses typed errors via `stateset_core::DbError` for better
39//! debugging and error categorization. Use the error helper functions
40//! in the `error_helpers` module for converting backend-specific errors.
41
42pub mod error_helpers;
43pub mod http_idempotency;
44pub use http_idempotency::{HttpIdempotencyRecord, HttpIdempotencyRepository};
45
46#[cfg(feature = "sqlite")]
47pub mod audit;
48#[cfg(feature = "sqlite")]
49pub mod backup;
50#[cfg(feature = "sqlite")]
51pub mod maintenance;
52#[cfg(feature = "sqlite")]
53pub mod migrations;
54pub mod portability;
55#[cfg(feature = "sqlite")]
56pub mod sqlite;
57
58#[cfg(feature = "postgres")]
59pub mod postgres;
60
61#[cfg(all(feature = "postgres", feature = "saga"))]
62pub mod saga;
63
64pub mod transactions;
65
66#[cfg(feature = "sqlite")]
67pub use sqlite::SqliteDatabase;
68
69#[cfg(feature = "postgres")]
70pub use postgres::PostgresDatabase;
71
72use stateset_core::{
73    A2ACommerceRepository, AccountsPayableRepository, AccountsReceivableRepository,
74    ActivityLogRepository, AgentCardRepository, AgentIdentityRepository, AgentReputationRepository,
75    AgentValidationRepository, AnalyticsRepository, BackorderRepository, BomRepository,
76    CartRepository, ChannelRepository, CommerceError, CompanyRepository, CostAccountingRepository,
77    CreditRepository, CurrencyRepository, CustomObjectRepository, CustomerRepository,
78    EdiDocumentRepository, FixedAssetRepository, FraudRepository, FulfillmentRepository,
79    GeneralLedgerRepository, GiftCardRepository, InboundShipmentRepository,
80    IntegrationFieldMappingRepository, IntegrationMappingRepository, InventoryRepository,
81    InvoiceRepository, LotRepository, LoyaltyProgramRepository, OrderRepository,
82    PaymentObligationRepository, PaymentRepository, PrepaymentRepository, PriceLevelRepository,
83    PriceScheduleRepository, PrintStationRepository, ProductRepository, ProductionBatchRepository,
84    PromotionRepository, PurchaseOrderRepository, PurgatoryRepository, QualityRepository,
85    ReceivingRepository, Result, ReturnRepository, RevenueRecognitionRepository, ReviewRepository,
86    RewardRepository, SearchConfigRepository, SegmentRepository, SerialRepository,
87    ShipmentRepository, ShippingZoneRepository, StockSnapshotRepository, StoreCreditRepository,
88    SubscriptionRepository, SupplierSkuRepository, TaxRepository, TopologySnapshotRepository,
89    TransferOrderRepository, UnitOfMeasureRepository, VendorCreditRepository,
90    VendorReturnRepository, WarehouseRepository, WarrantyRepository, WishlistRepository,
91    WorkOrderRepository, X402CreditRepository, X402PaymentIntentRepository,
92    ZoneShippingMethodRepository,
93};
94
95// ============================================================================
96// Transaction Support
97// ============================================================================
98
99/// Context for operations within a transaction
100///
101/// This trait provides access to repositories within a transaction scope.
102/// All operations performed through the context are part of the same transaction.
103pub trait TransactionContext: Send + Sync {
104    /// Get the order repository within this transaction
105    fn orders(&self) -> Box<dyn OrderRepository + '_>;
106    /// Get the inventory repository within this transaction
107    fn inventory(&self) -> Box<dyn InventoryRepository + '_>;
108    /// Get the customer repository within this transaction
109    fn customers(&self) -> Box<dyn CustomerRepository + '_>;
110    /// Get the product repository within this transaction
111    fn products(&self) -> Box<dyn ProductRepository + '_>;
112}
113
114/// Options for transaction execution
115#[derive(Debug, Clone, Default)]
116#[must_use]
117pub struct TransactionOptions {
118    /// Timeout for the transaction in milliseconds (default: 30000)
119    pub timeout_ms: Option<u64>,
120    /// Isolation level for the transaction
121    pub isolation: TransactionIsolation,
122    /// Whether to retry on transient failures
123    pub retry_on_conflict: bool,
124    /// Maximum number of retries
125    pub max_retries: u32,
126}
127
128const DEFAULT_TRANSACTION_TIMEOUT_MS: u64 = 30_000;
129
130impl TransactionOptions {
131    /// Create options with default settings
132    pub fn new() -> Self {
133        Self::default()
134    }
135
136    /// Set the timeout
137    pub const fn timeout_ms(mut self, ms: u64) -> Self {
138        self.timeout_ms = Some(ms);
139        self
140    }
141
142    /// Set the isolation level
143    pub const fn isolation(mut self, level: TransactionIsolation) -> Self {
144        self.isolation = level;
145        self
146    }
147
148    /// Enable retry on conflict
149    ///
150    /// If enabled, the transaction closure may re-run on transient database failures.
151    /// Ensure the closure body is idempotent (or safely handles retry) when enabling this option.
152    pub const fn with_retries(mut self, max_retries: u32) -> Self {
153        self.retry_on_conflict = true;
154        self.max_retries = max_retries;
155        self
156    }
157}
158
159/// Transaction isolation levels
160#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
161pub enum TransactionIsolation {
162    /// Read uncommitted.
163    ReadUncommitted,
164    /// Read committed.
165    ReadCommitted,
166    /// Repeatable read.
167    RepeatableRead,
168    /// Serializable.
169    #[default]
170    Serializable,
171}
172
173/// Optional domain capability exposed by a database backend.
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum DatabaseCapability {
176    GiftCards,
177    StoreCredits,
178    Segments,
179    ShippingZones,
180    ZoneShippingMethods,
181    Reviews,
182    Wishlists,
183    LoyaltyPrograms,
184    Rewards,
185    Fraud,
186    SearchConfigs,
187    Channels,
188    Companies,
189    TransferOrders,
190    UnitsOfMeasure,
191    ProductionBatches,
192    SupplierSkus,
193    VendorReturns,
194    VendorCredits,
195    PaymentObligations,
196    PriceLevels,
197    Prepayments,
198    PriceSchedules,
199    ActivityLogs,
200    IntegrationMappings,
201    InboundShipments,
202    Purgatory,
203    PrintStations,
204    EdiDocuments,
205    FixedAssets,
206    RevenueRecognition,
207    IntegrationFieldMappings,
208    TopologySnapshots,
209    StockSnapshots,
210}
211
212impl DatabaseCapability {
213    const fn repository_name(self) -> &'static str {
214        match self {
215            Self::GiftCards => "gift_cards",
216            Self::StoreCredits => "store_credits",
217            Self::Segments => "segments",
218            Self::ShippingZones => "shipping_zones",
219            Self::ZoneShippingMethods => "zone_shipping_methods",
220            Self::Reviews => "reviews",
221            Self::Wishlists => "wishlists",
222            Self::LoyaltyPrograms => "loyalty_programs",
223            Self::Rewards => "rewards",
224            Self::Fraud => "fraud",
225            Self::SearchConfigs => "search_configs",
226            Self::Channels => "channels",
227            Self::Companies => "companies",
228            Self::TransferOrders => "transfer_orders",
229            Self::UnitsOfMeasure => "units_of_measure",
230            Self::ProductionBatches => "production_batches",
231            Self::SupplierSkus => "supplier_skus",
232            Self::VendorReturns => "vendor_returns",
233            Self::VendorCredits => "vendor_credits",
234            Self::PaymentObligations => "payment_obligations",
235            Self::PriceLevels => "price_levels",
236            Self::Prepayments => "prepayments",
237            Self::PriceSchedules => "price_schedules",
238            Self::ActivityLogs => "activity_logs",
239            Self::IntegrationMappings => "integration_mappings",
240            Self::InboundShipments => "inbound_shipments",
241            Self::Purgatory => "purgatory",
242            Self::PrintStations => "print_stations",
243            Self::EdiDocuments => "edi_documents",
244            Self::FixedAssets => "fixed_assets",
245            Self::RevenueRecognition => "revenue_recognition",
246            Self::IntegrationFieldMappings => "integration_field_mappings",
247            Self::TopologySnapshots => "topology_snapshots",
248            Self::StockSnapshots => "stock_snapshots",
249        }
250    }
251}
252
253// ============================================================================
254// Database Trait
255// ============================================================================
256
257/// Unified database trait that both SQLite and PostgreSQL implement.
258/// This allows stateset-embedded to work with either backend.
259pub trait Database: Send + Sync {
260    /// Human-readable backend name.
261    fn backend_name(&self) -> &'static str {
262        "external"
263    }
264
265    /// Whether the backend supports a given optional repository domain.
266    fn supports_capability(&self, _capability: DatabaseCapability) -> bool {
267        true
268    }
269
270    /// Fail fast when a caller requests an unsupported optional repository domain.
271    fn ensure_capability(&self, capability: DatabaseCapability) -> Result<()> {
272        if self.supports_capability(capability) {
273            Ok(())
274        } else {
275            Err(CommerceError::NotPermitted(format!(
276                "{} repository is not implemented for {} backend",
277                capability.repository_name(),
278                self.backend_name()
279            )))
280        }
281    }
282
283    /// Get the order repository
284    fn orders(&self) -> Box<dyn OrderRepository + '_>;
285    /// Get the inventory repository
286    fn inventory(&self) -> Box<dyn InventoryRepository + '_>;
287    /// Get the customer repository
288    fn customers(&self) -> Box<dyn CustomerRepository + '_>;
289    /// Get the product repository
290    fn products(&self) -> Box<dyn ProductRepository + '_>;
291    /// Get the custom objects repository (custom states / metaobjects)
292    fn custom_objects(&self) -> Box<dyn CustomObjectRepository + '_>;
293    /// Get the return repository
294    fn returns(&self) -> Box<dyn ReturnRepository + '_>;
295    /// Get the BOM (Bill of Materials) repository
296    fn bom(&self) -> Box<dyn BomRepository + '_>;
297    /// Get the work order repository
298    fn work_orders(&self) -> Box<dyn WorkOrderRepository + '_>;
299    /// Get the shipment repository
300    fn shipments(&self) -> Box<dyn ShipmentRepository + '_>;
301    /// Get the payment repository
302    fn payments(&self) -> Box<dyn PaymentRepository + '_>;
303    /// Get the warranty repository
304    fn warranties(&self) -> Box<dyn WarrantyRepository + '_>;
305    /// Get the purchase order repository
306    fn purchase_orders(&self) -> Box<dyn PurchaseOrderRepository + '_>;
307    /// Get the invoice repository
308    fn invoices(&self) -> Box<dyn InvoiceRepository + '_>;
309    /// Get the cart/checkout repository
310    fn carts(&self) -> Box<dyn CartRepository + '_>;
311    /// Get the analytics repository
312    fn analytics(&self) -> Box<dyn AnalyticsRepository + '_>;
313    /// Get the currency repository
314    fn currency(&self) -> Box<dyn CurrencyRepository + '_>;
315    /// Get the tax repository
316    fn tax(&self) -> Box<dyn TaxRepository + '_>;
317    /// Get the promotions repository
318    fn promotions(&self) -> Box<dyn PromotionRepository + '_>;
319    /// Get the subscriptions repository
320    fn subscriptions(&self) -> Box<dyn SubscriptionRepository + '_>;
321    /// Get the quality repository
322    fn quality(&self) -> Box<dyn QualityRepository + '_>;
323    /// Get the lots repository
324    fn lots(&self) -> Box<dyn LotRepository + '_>;
325    /// Get the serials repository
326    fn serials(&self) -> Box<dyn SerialRepository + '_>;
327    /// Get the warehouse repository
328    fn warehouse(&self) -> Box<dyn WarehouseRepository + '_>;
329    /// Get the receiving repository
330    fn receiving(&self) -> Box<dyn ReceivingRepository + '_>;
331    /// Get the fulfillment repository
332    fn fulfillment(&self) -> Box<dyn FulfillmentRepository + '_>;
333    /// Get the accounts payable repository
334    fn accounts_payable(&self) -> Box<dyn AccountsPayableRepository + '_>;
335    /// Get the cost accounting repository
336    fn cost_accounting(&self) -> Box<dyn CostAccountingRepository + '_>;
337    /// Get the credit repository
338    fn credit(&self) -> Box<dyn CreditRepository + '_>;
339    /// Get the backorder repository
340    fn backorder(&self) -> Box<dyn BackorderRepository + '_>;
341    /// Get the accounts receivable repository
342    fn accounts_receivable(&self) -> Box<dyn AccountsReceivableRepository + '_>;
343    /// Get the general ledger repository
344    fn general_ledger(&self) -> Box<dyn GeneralLedgerRepository + '_>;
345    /// Get the x402 payment intent repository
346    fn x402_payment_intents(&self) -> Box<dyn X402PaymentIntentRepository + '_>;
347    /// Get the x402 credit ledger repository
348    fn x402_credits(&self) -> Box<dyn X402CreditRepository + '_>;
349    /// Get the agent-to-agent commerce repository (quotes and purchases)
350    fn a2a_quotes(&self) -> Box<dyn A2ACommerceRepository + '_>;
351    /// Get the agent-to-agent commerce repository (quotes and purchases)
352    fn a2a_purchases(&self) -> Box<dyn A2ACommerceRepository + '_>;
353    /// Get the agent card repository
354    fn agent_cards(&self) -> Box<dyn AgentCardRepository + '_>;
355    /// Get the agent identity registry repository (ERC-8004)
356    fn agent_identities(&self) -> Box<dyn AgentIdentityRepository + '_>;
357    /// Get the agent reputation registry repository (ERC-8004)
358    fn agent_reputation(&self) -> Box<dyn AgentReputationRepository + '_>;
359    /// Get the agent validation registry repository (ERC-8004)
360    fn agent_validation(&self) -> Box<dyn AgentValidationRepository + '_>;
361
362    // --- New domain repositories ---
363
364    /// Get the gift card repository
365    fn gift_cards(&self) -> Box<dyn GiftCardRepository + '_>;
366    /// Get the store credit repository
367    fn store_credits(&self) -> Box<dyn StoreCreditRepository + '_>;
368    /// Get the customer segment repository
369    fn segments(&self) -> Box<dyn SegmentRepository + '_>;
370    /// Get the shipping zone repository
371    fn shipping_zones(&self) -> Box<dyn ShippingZoneRepository + '_>;
372    /// Get the zone shipping method repository
373    fn zone_shipping_methods(&self) -> Box<dyn ZoneShippingMethodRepository + '_>;
374    /// Get the product review repository
375    fn reviews(&self) -> Box<dyn ReviewRepository + '_>;
376    /// Get the wishlist repository
377    fn wishlists(&self) -> Box<dyn WishlistRepository + '_>;
378    /// Get the loyalty program repository
379    fn loyalty_programs(&self) -> Box<dyn LoyaltyProgramRepository + '_>;
380    /// Get the reward catalog repository
381    fn rewards(&self) -> Box<dyn RewardRepository + '_>;
382    /// Get the fraud detection repository
383    fn fraud(&self) -> Box<dyn FraudRepository + '_>;
384    /// Get the search configuration repository
385    fn search_configs(&self) -> Box<dyn SearchConfigRepository + '_>;
386    /// Get the sales/fulfillment channel repository
387    fn channels(&self) -> Box<dyn ChannelRepository + '_>;
388    /// Get the B2B company repository
389    fn companies(&self) -> Box<dyn CompanyRepository + '_>;
390    /// Get the transfer order repository
391    fn transfer_orders(&self) -> Box<dyn TransferOrderRepository + '_>;
392    /// Get the units-of-measure repository
393    fn units_of_measure(&self) -> Box<dyn UnitOfMeasureRepository + '_>;
394    /// Get the production batch repository
395    fn production_batches(&self) -> Box<dyn ProductionBatchRepository + '_>;
396    /// Get the supplier SKU repository
397    fn supplier_skus(&self) -> Box<dyn SupplierSkuRepository + '_>;
398    /// Get the vendor return repository
399    fn vendor_returns(&self) -> Box<dyn VendorReturnRepository + '_>;
400    /// Get the vendor credit repository
401    fn vendor_credits(&self) -> Box<dyn VendorCreditRepository + '_>;
402    /// Get the payment obligation repository
403    fn payment_obligations(&self) -> Box<dyn PaymentObligationRepository + '_>;
404    /// Get the price level repository
405    fn price_levels(&self) -> Box<dyn PriceLevelRepository + '_>;
406    /// Get the prepayment repository
407    fn prepayments(&self) -> Box<dyn PrepaymentRepository + '_>;
408    /// Get the price schedule repository
409    fn price_schedules(&self) -> Box<dyn PriceScheduleRepository + '_>;
410    /// Get the activity log repository
411    fn activity_logs(&self) -> Box<dyn ActivityLogRepository + '_>;
412    /// Get the integration mapping repository
413    fn integration_mappings(&self) -> Box<dyn IntegrationMappingRepository + '_>;
414    /// Get the inbound shipment repository
415    fn inbound_shipments(&self) -> Box<dyn InboundShipmentRepository + '_>;
416    /// Get the purgatory repository
417    fn purgatory(&self) -> Box<dyn PurgatoryRepository + '_>;
418    /// Get the print station repository
419    fn print_stations(&self) -> Box<dyn PrintStationRepository + '_>;
420    /// Get the EDI document repository
421    fn edi_documents(&self) -> Box<dyn EdiDocumentRepository + '_>;
422    /// Get the integration field-mapping repository
423    fn integration_field_mappings(&self) -> Box<dyn IntegrationFieldMappingRepository + '_>;
424    /// Get the topology snapshot repository
425    fn topology_snapshots(&self) -> Box<dyn TopologySnapshotRepository + '_>;
426    /// Get the stock snapshot repository
427    fn stock_snapshots(&self) -> Box<dyn StockSnapshotRepository + '_>;
428    /// Get the fixed asset repository
429    fn fixed_assets(&self) -> Box<dyn FixedAssetRepository + '_>;
430    /// Get the revenue recognition repository
431    fn revenue_recognition(&self) -> Box<dyn RevenueRecognitionRepository + '_>;
432    /// Get the durable HTTP idempotency repository, if the backend provides
433    /// one. Backends without durable idempotency support return `None`, in
434    /// which case callers fall back to in-memory behavior.
435    fn http_idempotency(&self) -> Option<Box<dyn HttpIdempotencyRepository + '_>> {
436        None
437    }
438}
439
440/// Extension trait for database transaction support.
441///
442/// Provides closure-based transaction management with automatic commit/rollback.
443/// Note: This is a simplified transaction API. For complex transactions spanning
444/// multiple repositories, use the raw connection approach via `SqliteDatabase::conn()`.
445///
446/// # Example
447/// ```ignore
448/// use stateset_db::{SqliteDatabase, DatabaseExt, TransactionOptions};
449///
450/// let db = SqliteDatabase::in_memory()?;
451///
452/// // Simple transaction using raw SQL
453/// db.with_transaction(|conn| {
454///     conn.execute("UPDATE inventory_balances SET quantity_on_hand = 100 WHERE item_id = 1", [])?;
455///     conn.execute("INSERT INTO inventory_transactions (...) VALUES (...)", [...])?;
456///     Ok(())
457/// })?;
458///
459/// // Transaction with options
460/// db.with_transaction_opts(
461///     TransactionOptions::new().with_retries(3),
462///     |conn| {
463///         conn.execute("UPDATE orders SET status = 'completed' WHERE id = ?", [&order_id])?;
464///         Ok(())
465///     },
466/// )?;
467/// ```
468#[cfg(feature = "sqlite")]
469pub trait DatabaseExt {
470    /// Execute a closure within a database transaction.
471    ///
472    /// The transaction is automatically committed if the closure returns `Ok`,
473    /// and rolled back if it returns `Err` or panics.
474    fn with_transaction<F, T>(&self, f: F) -> Result<T>
475    where
476        F: FnMut(&rusqlite::Connection) -> std::result::Result<T, rusqlite::Error>;
477
478    /// Execute a closure within a database transaction with custom options.
479    ///
480    /// This method allows setting transaction options like timeout and retry behavior.
481    fn with_transaction_opts<F, T>(&self, _opts: TransactionOptions, f: F) -> Result<T>
482    where
483        F: FnMut(&rusqlite::Connection) -> std::result::Result<T, rusqlite::Error>,
484    {
485        // Default implementation ignores options and delegates to with_transaction
486        self.with_transaction(f)
487    }
488}
489
490/// Async extension trait for PostgreSQL transaction support.
491///
492/// # Example
493/// ```ignore
494/// use stateset_db::{PostgresDatabase, AsyncDatabaseExt, TransactionOptions};
495///
496/// let db = PostgresDatabase::connect("postgres://localhost/db").await?;
497///
498/// db.with_transaction_async(|tx| async move {
499///     sqlx::query("UPDATE orders SET status = 'completed' WHERE id = $1")
500///         .bind(order_id)
501///         .execute(&mut *tx)
502///         .await?;
503///     Ok(())
504/// }).await?;
505/// ```
506#[cfg(feature = "postgres")]
507#[allow(async_fn_in_trait)]
508pub trait AsyncDatabaseExt {
509    /// Execute an async closure within a database transaction.
510    ///
511    /// The transaction is automatically committed if the closure returns `Ok`,
512    /// and rolled back if it returns `Err` or panics.
513    /// If `retry_on_conflict` is enabled, the closure may run more than once.
514    async fn with_transaction_async<'a, F, T, Fut>(&'a self, f: F) -> Result<T>
515    where
516        F: FnMut(&mut sqlx::Transaction<'a, sqlx::Postgres>) -> Fut + Send,
517        Fut: std::future::Future<Output = std::result::Result<T, sqlx::Error>> + Send,
518        T: Send,
519    {
520        self.with_transaction_async_opts(crate::TransactionOptions::new(), f).await
521    }
522
523    /// Execute an async closure within a transaction with custom options.
524    ///
525    /// If `opts.retry_on_conflict` is enabled, the closure may run multiple times.
526    async fn with_transaction_async_opts<'a, F, T, Fut>(
527        &'a self,
528        opts: TransactionOptions,
529        f: F,
530    ) -> Result<T>
531    where
532        F: FnMut(&mut sqlx::Transaction<'a, sqlx::Postgres>) -> Fut + Send,
533        Fut: std::future::Future<Output = std::result::Result<T, sqlx::Error>> + Send,
534        T: Send;
535}
536
537trait NewDomainRepositoryFactory {
538    fn gift_cards_repo(&self) -> Box<dyn GiftCardRepository + '_>;
539    fn store_credits_repo(&self) -> Box<dyn StoreCreditRepository + '_>;
540    fn segments_repo(&self) -> Box<dyn SegmentRepository + '_>;
541    fn shipping_zones_repo(&self) -> Box<dyn ShippingZoneRepository + '_>;
542    fn zone_shipping_methods_repo(&self) -> Box<dyn ZoneShippingMethodRepository + '_>;
543    fn reviews_repo(&self) -> Box<dyn ReviewRepository + '_>;
544    fn wishlists_repo(&self) -> Box<dyn WishlistRepository + '_>;
545    fn loyalty_programs_repo(&self) -> Box<dyn LoyaltyProgramRepository + '_>;
546    fn rewards_repo(&self) -> Box<dyn RewardRepository + '_>;
547    fn fraud_repo(&self) -> Box<dyn FraudRepository + '_>;
548    fn search_configs_repo(&self) -> Box<dyn SearchConfigRepository + '_>;
549    fn channels_repo(&self) -> Box<dyn ChannelRepository + '_>;
550    fn companies_repo(&self) -> Box<dyn CompanyRepository + '_>;
551    fn transfer_orders_repo(&self) -> Box<dyn TransferOrderRepository + '_>;
552    fn units_of_measure_repo(&self) -> Box<dyn UnitOfMeasureRepository + '_>;
553    fn production_batches_repo(&self) -> Box<dyn ProductionBatchRepository + '_>;
554    fn supplier_skus_repo(&self) -> Box<dyn SupplierSkuRepository + '_>;
555    fn vendor_returns_repo(&self) -> Box<dyn VendorReturnRepository + '_>;
556    fn vendor_credits_repo(&self) -> Box<dyn VendorCreditRepository + '_>;
557    fn payment_obligations_repo(&self) -> Box<dyn PaymentObligationRepository + '_>;
558    fn price_levels_repo(&self) -> Box<dyn PriceLevelRepository + '_>;
559    fn prepayments_repo(&self) -> Box<dyn PrepaymentRepository + '_>;
560    fn price_schedules_repo(&self) -> Box<dyn PriceScheduleRepository + '_>;
561    fn activity_logs_repo(&self) -> Box<dyn ActivityLogRepository + '_>;
562    fn integration_mappings_repo(&self) -> Box<dyn IntegrationMappingRepository + '_>;
563    fn inbound_shipments_repo(&self) -> Box<dyn InboundShipmentRepository + '_>;
564    fn purgatory_repo(&self) -> Box<dyn PurgatoryRepository + '_>;
565    fn print_stations_repo(&self) -> Box<dyn PrintStationRepository + '_>;
566    fn edi_documents_repo(&self) -> Box<dyn EdiDocumentRepository + '_>;
567    fn integration_field_mappings_repo(&self) -> Box<dyn IntegrationFieldMappingRepository + '_>;
568    fn topology_snapshots_repo(&self) -> Box<dyn TopologySnapshotRepository + '_>;
569    fn stock_snapshots_repo(&self) -> Box<dyn StockSnapshotRepository + '_>;
570    fn fixed_assets_repo(&self) -> Box<dyn FixedAssetRepository + '_>;
571    fn revenue_recognition_repo(&self) -> Box<dyn RevenueRecognitionRepository + '_>;
572}
573
574#[cfg(feature = "sqlite")]
575impl NewDomainRepositoryFactory for SqliteDatabase {
576    fn gift_cards_repo(&self) -> Box<dyn GiftCardRepository + '_> {
577        Box::new(self.gift_cards())
578    }
579
580    fn store_credits_repo(&self) -> Box<dyn StoreCreditRepository + '_> {
581        Box::new(self.store_credits())
582    }
583
584    fn segments_repo(&self) -> Box<dyn SegmentRepository + '_> {
585        Box::new(self.segments())
586    }
587
588    fn shipping_zones_repo(&self) -> Box<dyn ShippingZoneRepository + '_> {
589        Box::new(self.shipping_zones())
590    }
591
592    fn zone_shipping_methods_repo(&self) -> Box<dyn ZoneShippingMethodRepository + '_> {
593        Box::new(self.zone_shipping_methods())
594    }
595
596    fn reviews_repo(&self) -> Box<dyn ReviewRepository + '_> {
597        Box::new(self.reviews())
598    }
599
600    fn wishlists_repo(&self) -> Box<dyn WishlistRepository + '_> {
601        Box::new(self.wishlists())
602    }
603
604    fn loyalty_programs_repo(&self) -> Box<dyn LoyaltyProgramRepository + '_> {
605        Box::new(self.loyalty_programs())
606    }
607
608    fn rewards_repo(&self) -> Box<dyn RewardRepository + '_> {
609        Box::new(self.rewards())
610    }
611
612    fn fraud_repo(&self) -> Box<dyn FraudRepository + '_> {
613        Box::new(self.fraud())
614    }
615
616    fn search_configs_repo(&self) -> Box<dyn SearchConfigRepository + '_> {
617        Box::new(self.search_configs())
618    }
619
620    fn channels_repo(&self) -> Box<dyn ChannelRepository + '_> {
621        Box::new(self.channels())
622    }
623
624    fn companies_repo(&self) -> Box<dyn CompanyRepository + '_> {
625        Box::new(self.companies())
626    }
627
628    fn transfer_orders_repo(&self) -> Box<dyn TransferOrderRepository + '_> {
629        Box::new(self.transfer_orders())
630    }
631
632    fn units_of_measure_repo(&self) -> Box<dyn UnitOfMeasureRepository + '_> {
633        Box::new(self.units_of_measure())
634    }
635
636    fn production_batches_repo(&self) -> Box<dyn ProductionBatchRepository + '_> {
637        Box::new(self.production_batches())
638    }
639
640    fn supplier_skus_repo(&self) -> Box<dyn SupplierSkuRepository + '_> {
641        Box::new(self.supplier_skus())
642    }
643
644    fn vendor_returns_repo(&self) -> Box<dyn VendorReturnRepository + '_> {
645        Box::new(self.vendor_returns())
646    }
647
648    fn vendor_credits_repo(&self) -> Box<dyn VendorCreditRepository + '_> {
649        Box::new(self.vendor_credits())
650    }
651
652    fn payment_obligations_repo(&self) -> Box<dyn PaymentObligationRepository + '_> {
653        Box::new(self.payment_obligations())
654    }
655
656    fn price_levels_repo(&self) -> Box<dyn PriceLevelRepository + '_> {
657        Box::new(self.price_levels())
658    }
659
660    fn prepayments_repo(&self) -> Box<dyn PrepaymentRepository + '_> {
661        Box::new(self.prepayments())
662    }
663
664    fn price_schedules_repo(&self) -> Box<dyn PriceScheduleRepository + '_> {
665        Box::new(self.price_schedules())
666    }
667
668    fn activity_logs_repo(&self) -> Box<dyn ActivityLogRepository + '_> {
669        Box::new(self.activity_logs())
670    }
671
672    fn integration_mappings_repo(&self) -> Box<dyn IntegrationMappingRepository + '_> {
673        Box::new(self.integration_mappings())
674    }
675
676    fn inbound_shipments_repo(&self) -> Box<dyn InboundShipmentRepository + '_> {
677        Box::new(self.inbound_shipments())
678    }
679
680    fn purgatory_repo(&self) -> Box<dyn PurgatoryRepository + '_> {
681        Box::new(self.purgatory())
682    }
683
684    fn print_stations_repo(&self) -> Box<dyn PrintStationRepository + '_> {
685        Box::new(self.print_stations())
686    }
687
688    fn edi_documents_repo(&self) -> Box<dyn EdiDocumentRepository + '_> {
689        Box::new(self.edi_documents())
690    }
691
692    fn integration_field_mappings_repo(&self) -> Box<dyn IntegrationFieldMappingRepository + '_> {
693        Box::new(self.integration_field_mappings())
694    }
695
696    fn topology_snapshots_repo(&self) -> Box<dyn TopologySnapshotRepository + '_> {
697        Box::new(self.topology_snapshots())
698    }
699
700    fn stock_snapshots_repo(&self) -> Box<dyn StockSnapshotRepository + '_> {
701        Box::new(self.stock_snapshots())
702    }
703
704    fn fixed_assets_repo(&self) -> Box<dyn FixedAssetRepository + '_> {
705        Box::new(self.fixed_assets())
706    }
707
708    fn revenue_recognition_repo(&self) -> Box<dyn RevenueRecognitionRepository + '_> {
709        Box::new(self.revenue_recognition())
710    }
711}
712
713#[cfg(feature = "postgres")]
714impl NewDomainRepositoryFactory for PostgresDatabase {
715    fn gift_cards_repo(&self) -> Box<dyn GiftCardRepository + '_> {
716        Box::new(postgres::PgGiftCardRepository::new(self.pool().clone()))
717    }
718
719    fn store_credits_repo(&self) -> Box<dyn StoreCreditRepository + '_> {
720        Box::new(postgres::PgStoreCreditRepository::new(self.pool().clone()))
721    }
722
723    fn segments_repo(&self) -> Box<dyn SegmentRepository + '_> {
724        Box::new(self.segments())
725    }
726
727    fn shipping_zones_repo(&self) -> Box<dyn ShippingZoneRepository + '_> {
728        Box::new(self.shipping_zones())
729    }
730
731    fn zone_shipping_methods_repo(&self) -> Box<dyn ZoneShippingMethodRepository + '_> {
732        Box::new(self.zone_shipping_methods())
733    }
734
735    fn reviews_repo(&self) -> Box<dyn ReviewRepository + '_> {
736        Box::new(postgres::PgReviewRepository::new(self.pool().clone()))
737    }
738
739    fn wishlists_repo(&self) -> Box<dyn WishlistRepository + '_> {
740        Box::new(postgres::PgWishlistRepository::new(self.pool().clone()))
741    }
742
743    fn loyalty_programs_repo(&self) -> Box<dyn LoyaltyProgramRepository + '_> {
744        Box::new(self.loyalty())
745    }
746
747    fn rewards_repo(&self) -> Box<dyn RewardRepository + '_> {
748        Box::new(self.rewards())
749    }
750
751    fn fraud_repo(&self) -> Box<dyn FraudRepository + '_> {
752        Box::new(self.fraud())
753    }
754
755    fn search_configs_repo(&self) -> Box<dyn SearchConfigRepository + '_> {
756        Box::new(self.search_configs())
757    }
758
759    fn channels_repo(&self) -> Box<dyn ChannelRepository + '_> {
760        Box::new(self.channels())
761    }
762
763    fn companies_repo(&self) -> Box<dyn CompanyRepository + '_> {
764        Box::new(self.companies())
765    }
766
767    fn transfer_orders_repo(&self) -> Box<dyn TransferOrderRepository + '_> {
768        Box::new(self.transfer_orders())
769    }
770
771    fn units_of_measure_repo(&self) -> Box<dyn UnitOfMeasureRepository + '_> {
772        Box::new(self.units_of_measure())
773    }
774
775    fn production_batches_repo(&self) -> Box<dyn ProductionBatchRepository + '_> {
776        Box::new(self.production_batches())
777    }
778
779    fn supplier_skus_repo(&self) -> Box<dyn SupplierSkuRepository + '_> {
780        Box::new(self.supplier_skus())
781    }
782
783    fn vendor_returns_repo(&self) -> Box<dyn VendorReturnRepository + '_> {
784        Box::new(self.vendor_returns())
785    }
786
787    fn vendor_credits_repo(&self) -> Box<dyn VendorCreditRepository + '_> {
788        Box::new(self.vendor_credits())
789    }
790
791    fn payment_obligations_repo(&self) -> Box<dyn PaymentObligationRepository + '_> {
792        Box::new(self.payment_obligations())
793    }
794
795    fn price_levels_repo(&self) -> Box<dyn PriceLevelRepository + '_> {
796        Box::new(self.price_levels())
797    }
798
799    fn prepayments_repo(&self) -> Box<dyn PrepaymentRepository + '_> {
800        Box::new(self.prepayments())
801    }
802
803    fn price_schedules_repo(&self) -> Box<dyn PriceScheduleRepository + '_> {
804        Box::new(self.price_schedules())
805    }
806
807    fn activity_logs_repo(&self) -> Box<dyn ActivityLogRepository + '_> {
808        Box::new(self.activity_logs())
809    }
810
811    fn integration_mappings_repo(&self) -> Box<dyn IntegrationMappingRepository + '_> {
812        Box::new(self.integration_mappings())
813    }
814
815    fn inbound_shipments_repo(&self) -> Box<dyn InboundShipmentRepository + '_> {
816        Box::new(self.inbound_shipments())
817    }
818
819    fn purgatory_repo(&self) -> Box<dyn PurgatoryRepository + '_> {
820        Box::new(self.purgatory())
821    }
822
823    fn print_stations_repo(&self) -> Box<dyn PrintStationRepository + '_> {
824        Box::new(self.print_stations())
825    }
826
827    fn edi_documents_repo(&self) -> Box<dyn EdiDocumentRepository + '_> {
828        Box::new(self.edi_documents())
829    }
830
831    fn integration_field_mappings_repo(&self) -> Box<dyn IntegrationFieldMappingRepository + '_> {
832        Box::new(self.integration_field_mappings())
833    }
834
835    fn topology_snapshots_repo(&self) -> Box<dyn TopologySnapshotRepository + '_> {
836        Box::new(self.topology_snapshots())
837    }
838
839    fn stock_snapshots_repo(&self) -> Box<dyn StockSnapshotRepository + '_> {
840        Box::new(self.stock_snapshots())
841    }
842
843    fn fixed_assets_repo(&self) -> Box<dyn FixedAssetRepository + '_> {
844        Box::new(self.fixed_assets())
845    }
846
847    fn revenue_recognition_repo(&self) -> Box<dyn RevenueRecognitionRepository + '_> {
848        Box::new(self.revenue_recognition())
849    }
850}
851
852/// Macro to eliminate duplicate Database implementations
853/// Generates all 32 repository accessor methods for any concrete Database type
854macro_rules! impl_database_accessors {
855    ($db_type:ident) => {
856        impl Database for $db_type {
857            fn backend_name(&self) -> &'static str {
858                impl_database_accessors!(@backend_name $db_type)
859            }
860
861            fn supports_capability(&self, capability: DatabaseCapability) -> bool {
862                impl_database_accessors!(@supports_capability $db_type, capability)
863            }
864
865            fn orders(&self) -> Box<dyn OrderRepository + '_> {
866                Box::new(<$db_type>::orders(self))
867            }
868
869            fn inventory(&self) -> Box<dyn InventoryRepository + '_> {
870                Box::new(<$db_type>::inventory(self))
871            }
872
873            fn customers(&self) -> Box<dyn CustomerRepository + '_> {
874                Box::new(<$db_type>::customers(self))
875            }
876
877            fn products(&self) -> Box<dyn ProductRepository + '_> {
878                Box::new(<$db_type>::products(self))
879            }
880
881            fn custom_objects(&self) -> Box<dyn CustomObjectRepository + '_> {
882                Box::new(<$db_type>::custom_objects(self))
883            }
884
885            fn returns(&self) -> Box<dyn ReturnRepository + '_> {
886                Box::new(<$db_type>::returns(self))
887            }
888
889            fn bom(&self) -> Box<dyn BomRepository + '_> {
890                Box::new(<$db_type>::bom(self))
891            }
892
893            fn work_orders(&self) -> Box<dyn WorkOrderRepository + '_> {
894                Box::new(<$db_type>::work_orders(self))
895            }
896
897            fn shipments(&self) -> Box<dyn ShipmentRepository + '_> {
898                Box::new(<$db_type>::shipments(self))
899            }
900
901            fn payments(&self) -> Box<dyn PaymentRepository + '_> {
902                Box::new(<$db_type>::payments(self))
903            }
904
905            fn warranties(&self) -> Box<dyn WarrantyRepository + '_> {
906                Box::new(<$db_type>::warranties(self))
907            }
908
909            fn purchase_orders(&self) -> Box<dyn PurchaseOrderRepository + '_> {
910                Box::new(<$db_type>::purchase_orders(self))
911            }
912
913            fn invoices(&self) -> Box<dyn InvoiceRepository + '_> {
914                Box::new(<$db_type>::invoices(self))
915            }
916
917            fn carts(&self) -> Box<dyn CartRepository + '_> {
918                Box::new(<$db_type>::carts(self))
919            }
920
921            fn analytics(&self) -> Box<dyn AnalyticsRepository + '_> {
922                Box::new(<$db_type>::analytics(self))
923            }
924
925            fn currency(&self) -> Box<dyn CurrencyRepository + '_> {
926                Box::new(<$db_type>::currency(self))
927            }
928
929            fn tax(&self) -> Box<dyn TaxRepository + '_> {
930                Box::new(<$db_type>::tax(self))
931            }
932
933            fn promotions(&self) -> Box<dyn PromotionRepository + '_> {
934                Box::new(<$db_type>::promotions(self))
935            }
936
937            fn subscriptions(&self) -> Box<dyn SubscriptionRepository + '_> {
938                Box::new(<$db_type>::subscriptions(self))
939            }
940
941            fn quality(&self) -> Box<dyn QualityRepository + '_> {
942                Box::new(<$db_type>::quality(self))
943            }
944
945            fn lots(&self) -> Box<dyn LotRepository + '_> {
946                Box::new(<$db_type>::lots(self))
947            }
948
949            fn serials(&self) -> Box<dyn SerialRepository + '_> {
950                Box::new(<$db_type>::serials(self))
951            }
952
953            fn warehouse(&self) -> Box<dyn WarehouseRepository + '_> {
954                Box::new(<$db_type>::warehouse(self))
955            }
956
957            fn receiving(&self) -> Box<dyn ReceivingRepository + '_> {
958                Box::new(<$db_type>::receiving(self))
959            }
960
961            fn fulfillment(&self) -> Box<dyn FulfillmentRepository + '_> {
962                Box::new(<$db_type>::fulfillment(self))
963            }
964
965            fn accounts_payable(&self) -> Box<dyn AccountsPayableRepository + '_> {
966                Box::new(<$db_type>::accounts_payable(self))
967            }
968
969            fn cost_accounting(&self) -> Box<dyn CostAccountingRepository + '_> {
970                Box::new(<$db_type>::cost_accounting(self))
971            }
972
973            fn credit(&self) -> Box<dyn CreditRepository + '_> {
974                Box::new(<$db_type>::credit(self))
975            }
976
977            fn backorder(&self) -> Box<dyn BackorderRepository + '_> {
978                Box::new(<$db_type>::backorder(self))
979            }
980
981            fn accounts_receivable(&self) -> Box<dyn AccountsReceivableRepository + '_> {
982                Box::new(<$db_type>::accounts_receivable(self))
983            }
984
985            fn general_ledger(&self) -> Box<dyn GeneralLedgerRepository + '_> {
986                Box::new(<$db_type>::general_ledger(self))
987            }
988
989            fn x402_payment_intents(&self) -> Box<dyn X402PaymentIntentRepository + '_> {
990                Box::new(<$db_type>::x402_payment_intents(self))
991            }
992
993            fn x402_credits(&self) -> Box<dyn X402CreditRepository + '_> {
994                Box::new(<$db_type>::x402_credits(self))
995            }
996
997            fn a2a_quotes(&self) -> Box<dyn A2ACommerceRepository + '_> {
998                Box::new(<$db_type>::a2a_quotes(self))
999            }
1000
1001            fn a2a_purchases(&self) -> Box<dyn A2ACommerceRepository + '_> {
1002                Box::new(<$db_type>::a2a_purchases(self))
1003            }
1004
1005            fn agent_cards(&self) -> Box<dyn AgentCardRepository + '_> {
1006                Box::new(<$db_type>::agent_cards(self))
1007            }
1008
1009            fn agent_identities(&self) -> Box<dyn AgentIdentityRepository + '_> {
1010                Box::new(<$db_type>::agent_identities(self))
1011            }
1012
1013            fn agent_reputation(&self) -> Box<dyn AgentReputationRepository + '_> {
1014                Box::new(<$db_type>::agent_reputation(self))
1015            }
1016
1017            fn agent_validation(&self) -> Box<dyn AgentValidationRepository + '_> {
1018                Box::new(<$db_type>::agent_validation(self))
1019            }
1020
1021            // --- New domain repositories ---
1022
1023            fn gift_cards(&self) -> Box<dyn GiftCardRepository + '_> {
1024                crate::NewDomainRepositoryFactory::gift_cards_repo(self)
1025            }
1026
1027            fn store_credits(&self) -> Box<dyn StoreCreditRepository + '_> {
1028                crate::NewDomainRepositoryFactory::store_credits_repo(self)
1029            }
1030
1031            fn segments(&self) -> Box<dyn SegmentRepository + '_> {
1032                crate::NewDomainRepositoryFactory::segments_repo(self)
1033            }
1034
1035            fn shipping_zones(&self) -> Box<dyn ShippingZoneRepository + '_> {
1036                crate::NewDomainRepositoryFactory::shipping_zones_repo(self)
1037            }
1038
1039            fn zone_shipping_methods(&self) -> Box<dyn ZoneShippingMethodRepository + '_> {
1040                crate::NewDomainRepositoryFactory::zone_shipping_methods_repo(self)
1041            }
1042
1043            fn reviews(&self) -> Box<dyn ReviewRepository + '_> {
1044                crate::NewDomainRepositoryFactory::reviews_repo(self)
1045            }
1046
1047            fn wishlists(&self) -> Box<dyn WishlistRepository + '_> {
1048                crate::NewDomainRepositoryFactory::wishlists_repo(self)
1049            }
1050
1051            fn loyalty_programs(&self) -> Box<dyn LoyaltyProgramRepository + '_> {
1052                crate::NewDomainRepositoryFactory::loyalty_programs_repo(self)
1053            }
1054
1055            fn rewards(&self) -> Box<dyn RewardRepository + '_> {
1056                crate::NewDomainRepositoryFactory::rewards_repo(self)
1057            }
1058
1059            fn fraud(&self) -> Box<dyn FraudRepository + '_> {
1060                crate::NewDomainRepositoryFactory::fraud_repo(self)
1061            }
1062
1063            fn search_configs(&self) -> Box<dyn SearchConfigRepository + '_> {
1064                crate::NewDomainRepositoryFactory::search_configs_repo(self)
1065            }
1066
1067            fn channels(&self) -> Box<dyn ChannelRepository + '_> {
1068                crate::NewDomainRepositoryFactory::channels_repo(self)
1069            }
1070
1071            fn companies(&self) -> Box<dyn CompanyRepository + '_> {
1072                crate::NewDomainRepositoryFactory::companies_repo(self)
1073            }
1074
1075            fn transfer_orders(&self) -> Box<dyn TransferOrderRepository + '_> {
1076                crate::NewDomainRepositoryFactory::transfer_orders_repo(self)
1077            }
1078
1079            fn units_of_measure(&self) -> Box<dyn UnitOfMeasureRepository + '_> {
1080                crate::NewDomainRepositoryFactory::units_of_measure_repo(self)
1081            }
1082
1083            fn production_batches(&self) -> Box<dyn ProductionBatchRepository + '_> {
1084                crate::NewDomainRepositoryFactory::production_batches_repo(self)
1085            }
1086
1087            fn supplier_skus(&self) -> Box<dyn SupplierSkuRepository + '_> {
1088                crate::NewDomainRepositoryFactory::supplier_skus_repo(self)
1089            }
1090
1091            fn vendor_returns(&self) -> Box<dyn VendorReturnRepository + '_> {
1092                crate::NewDomainRepositoryFactory::vendor_returns_repo(self)
1093            }
1094
1095            fn vendor_credits(&self) -> Box<dyn VendorCreditRepository + '_> {
1096                crate::NewDomainRepositoryFactory::vendor_credits_repo(self)
1097            }
1098
1099            fn payment_obligations(&self) -> Box<dyn PaymentObligationRepository + '_> {
1100                crate::NewDomainRepositoryFactory::payment_obligations_repo(self)
1101            }
1102
1103            fn price_levels(&self) -> Box<dyn PriceLevelRepository + '_> {
1104                crate::NewDomainRepositoryFactory::price_levels_repo(self)
1105            }
1106
1107            fn prepayments(&self) -> Box<dyn PrepaymentRepository + '_> {
1108                crate::NewDomainRepositoryFactory::prepayments_repo(self)
1109            }
1110
1111            fn price_schedules(&self) -> Box<dyn PriceScheduleRepository + '_> {
1112                crate::NewDomainRepositoryFactory::price_schedules_repo(self)
1113            }
1114
1115            fn activity_logs(&self) -> Box<dyn ActivityLogRepository + '_> {
1116                crate::NewDomainRepositoryFactory::activity_logs_repo(self)
1117            }
1118
1119            fn integration_mappings(&self) -> Box<dyn IntegrationMappingRepository + '_> {
1120                crate::NewDomainRepositoryFactory::integration_mappings_repo(self)
1121            }
1122
1123            fn inbound_shipments(&self) -> Box<dyn InboundShipmentRepository + '_> {
1124                crate::NewDomainRepositoryFactory::inbound_shipments_repo(self)
1125            }
1126
1127            fn purgatory(&self) -> Box<dyn PurgatoryRepository + '_> {
1128                crate::NewDomainRepositoryFactory::purgatory_repo(self)
1129            }
1130
1131            fn print_stations(&self) -> Box<dyn PrintStationRepository + '_> {
1132                crate::NewDomainRepositoryFactory::print_stations_repo(self)
1133            }
1134
1135            fn edi_documents(&self) -> Box<dyn EdiDocumentRepository + '_> {
1136                crate::NewDomainRepositoryFactory::edi_documents_repo(self)
1137            }
1138
1139            fn integration_field_mappings(&self) -> Box<dyn IntegrationFieldMappingRepository + '_> {
1140                crate::NewDomainRepositoryFactory::integration_field_mappings_repo(self)
1141            }
1142
1143            fn topology_snapshots(&self) -> Box<dyn TopologySnapshotRepository + '_> {
1144                crate::NewDomainRepositoryFactory::topology_snapshots_repo(self)
1145            }
1146
1147            fn stock_snapshots(&self) -> Box<dyn StockSnapshotRepository + '_> {
1148                crate::NewDomainRepositoryFactory::stock_snapshots_repo(self)
1149            }
1150
1151            fn fixed_assets(&self) -> Box<dyn FixedAssetRepository + '_> {
1152                crate::NewDomainRepositoryFactory::fixed_assets_repo(self)
1153            }
1154
1155            fn revenue_recognition(&self) -> Box<dyn RevenueRecognitionRepository + '_> {
1156                crate::NewDomainRepositoryFactory::revenue_recognition_repo(self)
1157            }
1158
1159            fn http_idempotency(&self) -> Option<Box<dyn HttpIdempotencyRepository + '_>> {
1160                Some(Box::new(<$db_type>::http_idempotency(self)))
1161            }
1162        }
1163    };
1164    (@backend_name SqliteDatabase) => {
1165        "sqlite"
1166    };
1167    (@backend_name PostgresDatabase) => {
1168        "postgres"
1169    };
1170    (@supports_capability SqliteDatabase, $capability:expr) => {{
1171        let _ = $capability;
1172        true
1173    }};
1174    (@supports_capability PostgresDatabase, $capability:expr) => {{
1175        let _ = $capability;
1176        true
1177    }};
1178}
1179
1180// Apply the macro to generate Database implementations
1181#[cfg(feature = "sqlite")]
1182impl_database_accessors!(SqliteDatabase);
1183
1184#[cfg(feature = "postgres")]
1185impl_database_accessors!(PostgresDatabase);
1186
1187/// Default PostgreSQL pool size when `STATESET_DB_MAX_CONNECTIONS` is unset.
1188pub const DEFAULT_POSTGRES_POOL_SIZE: u32 = 10;
1189
1190/// Database configuration
1191#[derive(Debug, Clone)]
1192pub struct DatabaseConfig {
1193    /// Path to database file (SQLite) or connection string (PostgreSQL)
1194    pub url: String,
1195    /// Maximum number of connections in pool
1196    pub max_connections: u32,
1197}
1198
1199impl Default for DatabaseConfig {
1200    fn default() -> Self {
1201        Self { url: "stateset.db".to_string(), max_connections: 5 }
1202    }
1203}
1204
1205impl DatabaseConfig {
1206    /// Create config for SQLite with path
1207    #[must_use]
1208    pub fn sqlite(path: &str) -> Self {
1209        Self { url: path.to_string(), max_connections: 5 }
1210    }
1211
1212    /// Create config for in-memory SQLite (useful for testing)
1213    #[must_use]
1214    pub fn in_memory() -> Self {
1215        Self {
1216            url: ":memory:".to_string(),
1217            // Use multiple connections with FULL_MUTEX mode for serialized access.
1218            // This avoids connection pool exhaustion in flows that legitimately need more
1219            // than one connection, such as nested repository calls in tests.
1220            max_connections: 4,
1221        }
1222    }
1223
1224    /// Create config for PostgreSQL connection
1225    ///
1226    /// # Example
1227    /// ```ignore
1228    /// let config = DatabaseConfig::postgres("postgres://user:pass@localhost/stateset");
1229    /// ```
1230    #[must_use]
1231    pub fn postgres(connection_string: &str) -> Self {
1232        Self {
1233            url: connection_string.to_string(),
1234            max_connections: Self::pool_size_from_env(DEFAULT_POSTGRES_POOL_SIZE),
1235        }
1236    }
1237
1238    /// Resolve a pool size from `STATESET_DB_MAX_CONNECTIONS`, falling back to
1239    /// `default` when the variable is unset or unparsable.
1240    ///
1241    /// The compiled-in defaults suit embedded and single-node use; server
1242    /// deployments serving concurrent traffic will usually need a larger pool
1243    /// than the default, hence the environment override.
1244    fn pool_size_from_env(default: u32) -> u32 {
1245        Self::parse_pool_size(std::env::var("STATESET_DB_MAX_CONNECTIONS").ok().as_deref(), default)
1246    }
1247
1248    /// Pure half of [`Self::pool_size_from_env`], split out so the parsing
1249    /// rules are testable without mutating process-global environment state.
1250    fn parse_pool_size(raw: Option<&str>, default: u32) -> u32 {
1251        raw.and_then(|value| value.trim().parse::<u32>().ok())
1252            .filter(|size| *size > 0)
1253            .unwrap_or(default)
1254    }
1255}
1256
1257#[cfg(test)]
1258mod config_tests {
1259    use super::{DEFAULT_POSTGRES_POOL_SIZE, DatabaseConfig};
1260
1261    #[test]
1262    fn pool_size_falls_back_on_absent_or_invalid_values() {
1263        for raw in [None, Some(""), Some("   "), Some("not-a-number"), Some("0"), Some("-4")] {
1264            assert_eq!(
1265                DatabaseConfig::parse_pool_size(raw, DEFAULT_POSTGRES_POOL_SIZE),
1266                DEFAULT_POSTGRES_POOL_SIZE,
1267                "{raw:?} must fall back to the default pool size"
1268            );
1269        }
1270    }
1271
1272    #[test]
1273    fn pool_size_honors_valid_override() {
1274        assert_eq!(DatabaseConfig::parse_pool_size(Some("64"), DEFAULT_POSTGRES_POOL_SIZE), 64);
1275        assert_eq!(DatabaseConfig::parse_pool_size(Some(" 32 "), DEFAULT_POSTGRES_POOL_SIZE), 32);
1276    }
1277
1278    #[test]
1279    fn postgres_config_defaults_to_documented_pool_size() {
1280        // No override set in the test environment.
1281        assert_eq!(
1282            DatabaseConfig::postgres("postgres://localhost/x").max_connections,
1283            DEFAULT_POSTGRES_POOL_SIZE
1284        );
1285    }
1286}
1287
1288/// Compiles the code examples in `README.md` as doctests, so the crates.io
1289/// landing page can never drift from the real API.
1290#[cfg(doctest)]
1291#[doc = include_str!("../README.md")]
1292struct ReadmeDoctests;