Skip to main content

breez_sdk_spark/persist/backend/
mod.rs

1//! Storage backend abstraction.
2//!
3//! A [`StorageBackend`] produces the four per-tenant stores the SDK needs: the
4//! main [`Storage`], plus a [`TreeStore`], [`TokenOutputStore`] and
5//! [`SessionStore`]. Each built-in backend is an independent module — adding
6//! one touches nothing else, and there is no central enum of storage options.
7//!
8//! [`SdkBuilder::with_storage_backend`](crate::SdkBuilder::with_storage_backend)
9//! takes an `Arc<dyn StorageBackend>`; build one with [`default_storage`],
10//! [`postgres_storage`], [`mysql_storage`] or
11//! [`custom_storage`].
12
13use std::sync::Arc;
14
15use macros::async_trait;
16use spark_wallet::{SessionStore, TokenOutputStore, TreeStore};
17
18use crate::{Network, SdkError, persist::Storage};
19
20mod prebuilt;
21
22pub use prebuilt::PrebuiltBackend;
23
24#[cfg(feature = "sqlite")]
25mod sqlite;
26
27#[cfg(feature = "postgres")]
28mod postgres;
29
30#[cfg(feature = "mysql")]
31mod mysql;
32
33/// The four per-tenant stores produced by a [`StorageBackend`].
34///
35/// An opaque handle: the SDK reads its stores internally; the fields never
36/// cross the FFI boundary.
37#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
38pub struct ResolvedStores {
39    pub(crate) storage: Arc<dyn Storage>,
40    pub(crate) tree_store: Option<Arc<dyn TreeStore>>,
41    pub(crate) token_output_store: Option<Arc<dyn TokenOutputStore>>,
42    pub(crate) session_store: Option<Arc<dyn SessionStore>>,
43}
44
45/// A factory for a tenant's storage.
46///
47/// A single backend may back many SDK instances; each
48/// [`create_stores`](Self::create_stores) call yields the store set scoped to
49/// one tenant `identity` (a serialized public key). `network` lets file-based
50/// backends segregate tenants by network; database backends ignore it.
51#[cfg_attr(feature = "uniffi", uniffi::export)]
52#[async_trait]
53pub trait StorageBackend: Send + Sync {
54    async fn create_stores(
55        &self,
56        network: Network,
57        identity: Vec<u8>,
58    ) -> Result<Arc<ResolvedStores>, SdkError>;
59}
60
61/// Wraps a caller-supplied [`Storage`] implementation as a [`StorageBackend`].
62/// The tree, token-output and session stores use the in-memory defaults.
63#[cfg_attr(feature = "uniffi", uniffi::export)]
64#[must_use]
65pub fn custom_storage(storage: Arc<dyn Storage>) -> Arc<dyn StorageBackend> {
66    Arc::new(prebuilt::PrebuiltBackend::new(storage, None, None, None))
67}
68
69/// The session store `backend` provides for `identity` on `network` (its own
70/// persistence: `PostgreSQL`/`MySQL` when the backend is DB-backed, else an
71/// in-memory store).
72///
73/// Returned so it can be wrapped in a decorating [`SessionStore`] and passed to
74/// [`SdkBuilder::with_session_store`](crate::SdkBuilder::with_session_store),
75/// keeping the backend's persistence. A typical use is at-rest encryption (the
76/// SDK does not encrypt tokens itself): wrap it in a store that encrypts on
77/// write and decrypts on read. `identity` is the wallet identity public key
78/// bytes (the same value the SDK derives from the signer).
79#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
80pub async fn default_session_store(
81    backend: Arc<dyn StorageBackend>,
82    network: Network,
83    identity: Vec<u8>,
84) -> Result<Arc<dyn crate::session_store::SessionStore>, SdkError> {
85    let stores = backend.create_stores(network, identity).await?;
86    let inner = stores
87        .session_store
88        .clone()
89        .unwrap_or_else(|| Arc::new(spark_wallet::InMemorySessionStore::default()));
90    Ok(Arc::new(crate::session_store::SparkSessionStoreAdapter(
91        inner,
92    )))
93}
94
95/// File-based `SQLite` storage rooted at `storage_dir` — the default for
96/// mobile and desktop apps. Each tenant gets its own database file under the
97/// directory.
98#[cfg(feature = "sqlite")]
99#[cfg_attr(feature = "uniffi", uniffi::export)]
100#[must_use]
101pub fn default_storage(storage_dir: String) -> Arc<dyn StorageBackend> {
102    Arc::new(sqlite::SqliteBackend::new(storage_dir))
103}
104
105/// `PostgreSQL`-backed storage built from `config`. Opens the connection pool;
106/// fails if `config` is invalid.
107#[cfg(feature = "postgres")]
108#[cfg_attr(feature = "uniffi", uniffi::export)]
109#[allow(clippy::needless_pass_by_value)]
110pub fn postgres_storage(
111    config: crate::persist::postgres::PostgresStorageConfig,
112) -> Result<Arc<dyn StorageBackend>, SdkError> {
113    let run_migration = config.run_migration;
114    let pool = crate::persist::postgres::create_pool(&config)?;
115    Ok(Arc::new(postgres::PostgresBackend::new(
116        pool,
117        run_migration,
118    )))
119}
120
121/// `MySQL`-backed storage built from `config`. Opens the connection pool;
122/// fails if `config` is invalid.
123#[cfg(feature = "mysql")]
124#[cfg_attr(feature = "uniffi", uniffi::export)]
125#[allow(clippy::needless_pass_by_value)]
126pub fn mysql_storage(
127    config: crate::persist::mysql::MysqlStorageConfig,
128) -> Result<Arc<dyn StorageBackend>, SdkError> {
129    let run_migration = config.run_migration;
130    let foreign_key_mode = config.foreign_key_mode;
131    let pool = crate::persist::mysql::create_pool(&config)?;
132    Ok(Arc::new(mysql::MysqlBackend::new(
133        pool,
134        run_migration,
135        foreign_key_mode,
136    )))
137}