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
11pub 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
95pub trait TransactionContext: Send + Sync {
104 fn orders(&self) -> Box<dyn OrderRepository + '_>;
106 fn inventory(&self) -> Box<dyn InventoryRepository + '_>;
108 fn customers(&self) -> Box<dyn CustomerRepository + '_>;
110 fn products(&self) -> Box<dyn ProductRepository + '_>;
112}
113
114#[derive(Debug, Clone, Default)]
116#[must_use]
117pub struct TransactionOptions {
118 pub timeout_ms: Option<u64>,
120 pub isolation: TransactionIsolation,
122 pub retry_on_conflict: bool,
124 pub max_retries: u32,
126}
127
128const DEFAULT_TRANSACTION_TIMEOUT_MS: u64 = 30_000;
129
130impl TransactionOptions {
131 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub const fn timeout_ms(mut self, ms: u64) -> Self {
138 self.timeout_ms = Some(ms);
139 self
140 }
141
142 pub const fn isolation(mut self, level: TransactionIsolation) -> Self {
144 self.isolation = level;
145 self
146 }
147
148 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
161pub enum TransactionIsolation {
162 ReadUncommitted,
164 ReadCommitted,
166 RepeatableRead,
168 #[default]
170 Serializable,
171}
172
173#[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
253pub trait Database: Send + Sync {
260 fn backend_name(&self) -> &'static str {
262 "external"
263 }
264
265 fn supports_capability(&self, _capability: DatabaseCapability) -> bool {
267 true
268 }
269
270 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 fn orders(&self) -> Box<dyn OrderRepository + '_>;
285 fn inventory(&self) -> Box<dyn InventoryRepository + '_>;
287 fn customers(&self) -> Box<dyn CustomerRepository + '_>;
289 fn products(&self) -> Box<dyn ProductRepository + '_>;
291 fn custom_objects(&self) -> Box<dyn CustomObjectRepository + '_>;
293 fn returns(&self) -> Box<dyn ReturnRepository + '_>;
295 fn bom(&self) -> Box<dyn BomRepository + '_>;
297 fn work_orders(&self) -> Box<dyn WorkOrderRepository + '_>;
299 fn shipments(&self) -> Box<dyn ShipmentRepository + '_>;
301 fn payments(&self) -> Box<dyn PaymentRepository + '_>;
303 fn warranties(&self) -> Box<dyn WarrantyRepository + '_>;
305 fn purchase_orders(&self) -> Box<dyn PurchaseOrderRepository + '_>;
307 fn invoices(&self) -> Box<dyn InvoiceRepository + '_>;
309 fn carts(&self) -> Box<dyn CartRepository + '_>;
311 fn analytics(&self) -> Box<dyn AnalyticsRepository + '_>;
313 fn currency(&self) -> Box<dyn CurrencyRepository + '_>;
315 fn tax(&self) -> Box<dyn TaxRepository + '_>;
317 fn promotions(&self) -> Box<dyn PromotionRepository + '_>;
319 fn subscriptions(&self) -> Box<dyn SubscriptionRepository + '_>;
321 fn quality(&self) -> Box<dyn QualityRepository + '_>;
323 fn lots(&self) -> Box<dyn LotRepository + '_>;
325 fn serials(&self) -> Box<dyn SerialRepository + '_>;
327 fn warehouse(&self) -> Box<dyn WarehouseRepository + '_>;
329 fn receiving(&self) -> Box<dyn ReceivingRepository + '_>;
331 fn fulfillment(&self) -> Box<dyn FulfillmentRepository + '_>;
333 fn accounts_payable(&self) -> Box<dyn AccountsPayableRepository + '_>;
335 fn cost_accounting(&self) -> Box<dyn CostAccountingRepository + '_>;
337 fn credit(&self) -> Box<dyn CreditRepository + '_>;
339 fn backorder(&self) -> Box<dyn BackorderRepository + '_>;
341 fn accounts_receivable(&self) -> Box<dyn AccountsReceivableRepository + '_>;
343 fn general_ledger(&self) -> Box<dyn GeneralLedgerRepository + '_>;
345 fn x402_payment_intents(&self) -> Box<dyn X402PaymentIntentRepository + '_>;
347 fn x402_credits(&self) -> Box<dyn X402CreditRepository + '_>;
349 fn a2a_quotes(&self) -> Box<dyn A2ACommerceRepository + '_>;
351 fn a2a_purchases(&self) -> Box<dyn A2ACommerceRepository + '_>;
353 fn agent_cards(&self) -> Box<dyn AgentCardRepository + '_>;
355 fn agent_identities(&self) -> Box<dyn AgentIdentityRepository + '_>;
357 fn agent_reputation(&self) -> Box<dyn AgentReputationRepository + '_>;
359 fn agent_validation(&self) -> Box<dyn AgentValidationRepository + '_>;
361
362 fn gift_cards(&self) -> Box<dyn GiftCardRepository + '_>;
366 fn store_credits(&self) -> Box<dyn StoreCreditRepository + '_>;
368 fn segments(&self) -> Box<dyn SegmentRepository + '_>;
370 fn shipping_zones(&self) -> Box<dyn ShippingZoneRepository + '_>;
372 fn zone_shipping_methods(&self) -> Box<dyn ZoneShippingMethodRepository + '_>;
374 fn reviews(&self) -> Box<dyn ReviewRepository + '_>;
376 fn wishlists(&self) -> Box<dyn WishlistRepository + '_>;
378 fn loyalty_programs(&self) -> Box<dyn LoyaltyProgramRepository + '_>;
380 fn rewards(&self) -> Box<dyn RewardRepository + '_>;
382 fn fraud(&self) -> Box<dyn FraudRepository + '_>;
384 fn search_configs(&self) -> Box<dyn SearchConfigRepository + '_>;
386 fn channels(&self) -> Box<dyn ChannelRepository + '_>;
388 fn companies(&self) -> Box<dyn CompanyRepository + '_>;
390 fn transfer_orders(&self) -> Box<dyn TransferOrderRepository + '_>;
392 fn units_of_measure(&self) -> Box<dyn UnitOfMeasureRepository + '_>;
394 fn production_batches(&self) -> Box<dyn ProductionBatchRepository + '_>;
396 fn supplier_skus(&self) -> Box<dyn SupplierSkuRepository + '_>;
398 fn vendor_returns(&self) -> Box<dyn VendorReturnRepository + '_>;
400 fn vendor_credits(&self) -> Box<dyn VendorCreditRepository + '_>;
402 fn payment_obligations(&self) -> Box<dyn PaymentObligationRepository + '_>;
404 fn price_levels(&self) -> Box<dyn PriceLevelRepository + '_>;
406 fn prepayments(&self) -> Box<dyn PrepaymentRepository + '_>;
408 fn price_schedules(&self) -> Box<dyn PriceScheduleRepository + '_>;
410 fn activity_logs(&self) -> Box<dyn ActivityLogRepository + '_>;
412 fn integration_mappings(&self) -> Box<dyn IntegrationMappingRepository + '_>;
414 fn inbound_shipments(&self) -> Box<dyn InboundShipmentRepository + '_>;
416 fn purgatory(&self) -> Box<dyn PurgatoryRepository + '_>;
418 fn print_stations(&self) -> Box<dyn PrintStationRepository + '_>;
420 fn edi_documents(&self) -> Box<dyn EdiDocumentRepository + '_>;
422 fn integration_field_mappings(&self) -> Box<dyn IntegrationFieldMappingRepository + '_>;
424 fn topology_snapshots(&self) -> Box<dyn TopologySnapshotRepository + '_>;
426 fn stock_snapshots(&self) -> Box<dyn StockSnapshotRepository + '_>;
428 fn fixed_assets(&self) -> Box<dyn FixedAssetRepository + '_>;
430 fn revenue_recognition(&self) -> Box<dyn RevenueRecognitionRepository + '_>;
432 fn http_idempotency(&self) -> Option<Box<dyn HttpIdempotencyRepository + '_>> {
436 None
437 }
438}
439
440#[cfg(feature = "sqlite")]
469pub trait DatabaseExt {
470 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 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 self.with_transaction(f)
487 }
488}
489
490#[cfg(feature = "postgres")]
507#[allow(async_fn_in_trait)]
508pub trait AsyncDatabaseExt {
509 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 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
852macro_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 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#[cfg(feature = "sqlite")]
1182impl_database_accessors!(SqliteDatabase);
1183
1184#[cfg(feature = "postgres")]
1185impl_database_accessors!(PostgresDatabase);
1186
1187pub const DEFAULT_POSTGRES_POOL_SIZE: u32 = 10;
1189
1190#[derive(Debug, Clone)]
1192pub struct DatabaseConfig {
1193 pub url: String,
1195 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 #[must_use]
1208 pub fn sqlite(path: &str) -> Self {
1209 Self { url: path.to_string(), max_connections: 5 }
1210 }
1211
1212 #[must_use]
1214 pub fn in_memory() -> Self {
1215 Self {
1216 url: ":memory:".to_string(),
1217 max_connections: 4,
1221 }
1222 }
1223
1224 #[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 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 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 assert_eq!(
1282 DatabaseConfig::postgres("postgres://localhost/x").max_connections,
1283 DEFAULT_POSTGRES_POOL_SIZE
1284 );
1285 }
1286}
1287
1288#[cfg(doctest)]
1291#[doc = include_str!("../README.md")]
1292struct ReadmeDoctests;