Skip to main content

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}