modkit_db/db_provider.rs
1use std::{future::Future, marker::PhantomData, pin::Pin, sync::Arc};
2
3use crate::secure::{DbConn, DbTx};
4use crate::{Db, DbError};
5
6/// Thin, reusable DB entrypoint for application services.
7///
8/// This wraps a module-scoped `Db` and provides:
9/// - `conn()` for non-transactional operations
10/// - `transaction(...)` for transactional operations without exposing `DbHandle`
11///
12/// Services can store this behind an `Arc` and use:
13///
14/// ```ignore
15/// let conn = self.db.conn()?;
16/// let out = self.db.transaction(|tx| Box::pin(async move { /* ... */ })).await?;
17/// ```
18pub struct DBProvider<E> {
19 db: Arc<Db>,
20 _error: PhantomData<fn() -> E>,
21}
22
23impl<E> Clone for DBProvider<E> {
24 fn clone(&self) -> Self {
25 Self {
26 db: Arc::clone(&self.db),
27 _error: PhantomData,
28 }
29 }
30}
31
32impl<E> DBProvider<E>
33where
34 E: From<DbError> + Send + 'static,
35{
36 #[must_use]
37 pub fn new(db: Db) -> Self {
38 Self {
39 db: Arc::new(db),
40 _error: PhantomData,
41 }
42 }
43
44 /// Create a non-transactional database runner.
45 ///
46 /// # Errors
47 ///
48 /// Returns `E` if `Db::conn()` fails (including the transaction-bypass guard).
49 pub fn conn(&self) -> Result<DbConn<'_>, E> {
50 self.db.conn().map_err(E::from)
51 }
52
53 /// Execute a closure inside a database transaction.
54 ///
55 /// # Errors
56 ///
57 /// Returns `E` if:
58 /// - starting the transaction fails (mapped from `DbError`)
59 /// - the closure returns an error
60 /// - commit fails (mapped from `DbError`)
61 pub async fn transaction<T, F>(&self, f: F) -> Result<T, E>
62 where
63 T: Send + 'static,
64 F: for<'a> FnOnce(&'a DbTx<'a>) -> Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>
65 + Send,
66 {
67 self.db.transaction_ref_mapped(f).await
68 }
69}