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    /// Returns a clone of the inner [`Db`] handle.
45    ///
46    /// Cheap (clones an inner `Arc`). Useful when the caller needs the raw
47    /// `Db` — e.g. to pass to [`Outbox::builder`](crate::outbox::Outbox::builder).
48    #[must_use]
49    pub fn db(&self) -> Db {
50        (*self.db).clone()
51    }
52
53    /// Create a non-transactional database runner.
54    ///
55    /// # Errors
56    ///
57    /// Returns `E` if `Db::conn()` fails (including the transaction-bypass guard).
58    pub fn conn(&self) -> Result<DbConn<'_>, E> {
59        self.db.conn().map_err(E::from)
60    }
61
62    /// Execute a closure inside a database transaction.
63    ///
64    /// # Errors
65    ///
66    /// Returns `E` if:
67    /// - starting the transaction fails (mapped from `DbError`)
68    /// - the closure returns an error
69    /// - commit fails (mapped from `DbError`)
70    pub async fn transaction<T, F>(&self, f: F) -> Result<T, E>
71    where
72        T: Send + 'static,
73        F: for<'a> FnOnce(&'a DbTx<'a>) -> Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>
74            + Send,
75    {
76        self.db.transaction_ref_mapped(f).await
77    }
78}