Skip to content

Commit 4f0b9f1

Browse files
feat: add builder extension trait for sqlite store (0xMiden#1416)
* feat: add builder extension trait for sqlite store * review: update error message * chore: lint
1 parent 527b913 commit 4f0b9f1

File tree

8 files changed

+75
-32
lines changed

8 files changed

+75
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* [BREAKING] Removed WebClient's `compileNoteScript` method and both `TransactionScript` and `NoteScript` compile methods; the new `ScriptBuilder` should be used instead ([#1331](https://github.com/0xMiden/miden-client/pull/1274)).
3535
* [BREAKING] Implemented `AccountFile` in the WebClient ([#1258](https://github.com/0xMiden/miden-client/pull/1258)).
3636
* [BREAKING] Added remote key storage and signature requesting to the `WebKeyStore` ([#1371](https://github.com/0xMiden/miden-client/pull/1371))
37+
* Added `sqlite_store` under `ClientBuilderSqliteExt` method to the `ClientBuilder` ([#1416](https://github.com/0xMiden/miden-client/pull/1416))
3738

3839
## 0.11.8 (2025-09-29)
3940

bin/integration-tests/src/tests/client.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use miden_client::transaction::{
2727
TransactionRequestBuilder,
2828
TransactionStatus,
2929
};
30-
use miden_client_sqlite_store::SqliteStore;
30+
use miden_client_sqlite_store::ClientBuilderSqliteExt;
3131

3232
use crate::tests::config::ClientConfig;
3333

@@ -36,12 +36,10 @@ pub async fn test_client_builder_initializes_client_with_endpoint(
3636
) -> Result<()> {
3737
let (endpoint, _, store_config, auth_path) = client_config.as_parts();
3838

39-
let sqlite_store = SqliteStore::new(store_config).await?;
40-
4139
let mut client = ClientBuilder::<FilesystemKeyStore<_>>::new()
4240
.grpc_client(&endpoint, Some(10_000))
4341
.filesystem_keystore(auth_path.to_str().context("failed to convert auth path to string")?)
44-
.store(Arc::new(sqlite_store))
42+
.sqlite_store(store_config)
4543
.in_debug_mode(miden_client::DebugMode::Enabled)
4644
.build()
4745
.await?;

bin/integration-tests/src/tests/config.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use miden_client::keystore::FilesystemKeyStore;
99
use miden_client::rpc::{Endpoint, GrpcClient};
1010
use miden_client::testing::common::{TestClient, TestClientKeyStore, create_test_store_path};
1111
use miden_client::{DebugMode, Felt};
12-
use miden_client_sqlite_store::SqliteStore;
12+
use miden_client_sqlite_store::ClientBuilderSqliteExt;
1313
use rand::Rng;
1414
use uuid::Uuid;
1515

@@ -64,21 +64,14 @@ impl ClientConfig {
6464

6565
let rng = RpoRandomCoin::new(coin_seed.map(Felt::new).into());
6666

67-
let store = {
68-
let sqlite_store = SqliteStore::new(store_config)
69-
.await
70-
.with_context(|| "failed to create SQLite store")?;
71-
Arc::new(sqlite_store)
72-
};
73-
7467
let keystore = FilesystemKeyStore::new(auth_path.clone()).with_context(|| {
7568
format!("failed to create keystore at path: {}", auth_path.to_string_lossy())
7669
})?;
7770

7871
let builder = ClientBuilder::new()
7972
.rpc(Arc::new(GrpcClient::new(&rpc_endpoint, rpc_timeout)))
8073
.rng(Box::new(rng))
81-
.store(store)
74+
.sqlite_store(store_config)
8275
.filesystem_keystore(auth_path.to_str().with_context(|| {
8376
format!("failed to convert auth path to string: {}", auth_path.to_string_lossy())
8477
})?)

bin/miden-cli/src/lib.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use miden_client::keystore::FilesystemKeyStore;
1212
use miden_client::note_transport::grpc::GrpcNoteTransportClient;
1313
use miden_client::store::{NoteFilter as ClientNoteFilter, OutputNoteRecord};
1414
use miden_client::{Client, DebugMode, IdPrefixFetchError};
15-
use miden_client_sqlite_store::SqliteStore;
15+
use miden_client_sqlite_store::ClientBuilderSqliteExt;
1616
use rand::rngs::StdRng;
1717
mod commands;
1818
use commands::account::AccountCmd;
@@ -173,12 +173,8 @@ impl Cli {
173173
let keystore = CliKeyStore::new(cli_config.secret_keys_directory.clone())
174174
.map_err(CliError::KeyStore)?;
175175

176-
let sqlite_store = SqliteStore::new(cli_config.store_filepath.clone())
177-
.await
178-
.map_err(|e| CliError::Internal(Box::new(e)))?;
179-
180176
let mut builder = ClientBuilder::new()
181-
.store(Arc::new(sqlite_store))
177+
.sqlite_store(cli_config.store_filepath.clone())
182178
.grpc_client(&cli_config.rpc.endpoint.clone().into(), Some(cli_config.rpc.timeout_ms))
183179
.authenticator(Arc::new(keystore.clone()))
184180
.in_debug_mode(in_debug_mode)

crates/rust-client/src/builder.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rand::Rng;
1111
use crate::keystore::FilesystemKeyStore;
1212
use crate::note_transport::NoteTransportClient;
1313
use crate::rpc::NodeRpcClient;
14-
use crate::store::Store;
14+
use crate::store::{Store, StoreError};
1515
use crate::{Client, ClientError, DebugMode};
1616

1717
// CONSTANTS
@@ -35,6 +35,23 @@ enum AuthenticatorConfig<AUTH> {
3535
Instance(Arc<AUTH>),
3636
}
3737

38+
// STORE BUILDER
39+
// ================================================================================================
40+
41+
/// Allows the [`ClientBuilder`] to accept either an already built store instance or a factory for
42+
/// deferring the store instantiation.
43+
pub enum StoreBuilder {
44+
Store(Arc<dyn Store>),
45+
Factory(Box<dyn StoreFactory>),
46+
}
47+
48+
/// Trait for building a store instance.
49+
#[async_trait::async_trait]
50+
pub trait StoreFactory {
51+
/// Returns a new store instance.
52+
async fn build(&self) -> Result<Arc<dyn Store>, StoreError>;
53+
}
54+
3855
// CLIENT BUILDER
3956
// ================================================================================================
4057

@@ -47,7 +64,7 @@ pub struct ClientBuilder<AUTH> {
4764
/// An optional custom RPC client. If provided, this takes precedence over `rpc_endpoint`.
4865
rpc_api: Option<Arc<dyn NodeRpcClient>>,
4966
/// An optional store provided by the user.
50-
store: Option<Arc<dyn Store>>,
67+
pub store: Option<StoreBuilder>,
5168
/// An optional RNG provided by the user.
5269
rng: Option<Box<dyn FeltRng>>,
5370
/// The keystore configuration provided by the user.
@@ -70,7 +87,6 @@ impl<AUTH> Default for ClientBuilder<AUTH> {
7087
rpc_api: None,
7188
store: None,
7289
rng: None,
73-
7490
keystore: None,
7591
in_debug_mode: DebugMode::Disabled,
7692
tx_graceful_blocks: Some(TX_GRACEFUL_BLOCKS),
@@ -113,10 +129,10 @@ where
113129
self
114130
}
115131

116-
/// Optionally provide a store directly.
132+
/// Provide a store to be used by the client.
117133
#[must_use]
118134
pub fn store(mut self, store: Arc<dyn Store>) -> Self {
119-
self.store = Some(store);
135+
self.store = Some(StoreBuilder::Store(store));
120136
self
121137
}
122138

@@ -188,12 +204,14 @@ where
188204
};
189205

190206
// Ensure a store was provided.
191-
let store: Arc<dyn Store> = if let Some(store) = self.store {
192-
store
207+
let store = if let Some(store_builder) = self.store {
208+
match store_builder {
209+
StoreBuilder::Store(store) => store,
210+
StoreBuilder::Factory(factory) => factory.build().await?,
211+
}
193212
} else {
194213
return Err(ClientError::ClientInitializationError(
195-
"Store must be specified. Call `.store(...)` or `.sqlite_store(...)` with a store path if `sqlite` is enabled."
196-
.into(),
214+
"Store must be specified. Call `.store(...)`.".into(),
197215
));
198216
};
199217

crates/sqlite-store/src/builder.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use std::path::PathBuf;
2+
use std::sync::Arc;
3+
4+
use miden_client::builder::{BuilderAuthenticator, ClientBuilder, StoreBuilder, StoreFactory};
5+
use miden_client::store::{Store, StoreError};
6+
7+
use crate::SqliteStore;
8+
9+
/// Extends the [`ClientBuilder`] with a method to add a [`SqliteStore`].
10+
pub trait ClientBuilderSqliteExt<AUTH> {
11+
fn sqlite_store(self, database_filepath: PathBuf) -> ClientBuilder<AUTH>;
12+
}
13+
14+
impl<AUTH: BuilderAuthenticator> ClientBuilderSqliteExt<AUTH> for ClientBuilder<AUTH> {
15+
/// Sets a [`SqliteStore`] to the [`ClientBuilder`]. The store will be instantiated when the
16+
/// [`build`](ClientBuilder::build) method is called.
17+
fn sqlite_store(mut self, database_filepath: PathBuf) -> ClientBuilder<AUTH> {
18+
self.store =
19+
Some(StoreBuilder::Factory(Box::new(SqliteStoreFactory { database_filepath })));
20+
self
21+
}
22+
}
23+
24+
/// Factory for building a [`SqliteStore`].
25+
struct SqliteStoreFactory {
26+
database_filepath: PathBuf,
27+
}
28+
29+
#[async_trait::async_trait]
30+
impl StoreFactory for SqliteStoreFactory {
31+
async fn build(&self) -> Result<Arc<dyn Store>, StoreError> {
32+
let sqlite_store = SqliteStore::new(self.database_filepath.clone()).await?;
33+
Ok(Arc::new(sqlite_store))
34+
}
35+
}

crates/sqlite-store/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use rusqlite::types::Value;
5454
use crate::merkle_store::{insert_asset_nodes, insert_storage_map_nodes};
5555

5656
mod account;
57+
mod builder;
5758
mod chain_data;
5859
mod db_management;
5960
mod merkle_store;
@@ -62,6 +63,8 @@ mod sql_error;
6263
mod sync;
6364
mod transaction;
6465

66+
pub use builder::ClientBuilderSqliteExt;
67+
6568
// SQLITE STORE
6669
// ================================================================================================
6770

@@ -80,7 +83,7 @@ impl SqliteStore {
8083

8184
/// Returns a new instance of [Store] instantiated with the specified configuration options.
8285
pub async fn new(database_filepath: PathBuf) -> Result<Self, StoreError> {
83-
let sqlite_pool_manager = SqlitePoolManager::new(database_filepath.clone());
86+
let sqlite_pool_manager = SqlitePoolManager::new(database_filepath);
8487
let pool = Pool::builder(sqlite_pool_manager)
8588
.build()
8689
.map_err(|e| StoreError::DatabaseError(e.to_string()))?;

crates/testing/miden-client-tests/src/tests.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use miden_client::transaction::{
4545
};
4646
use miden_client::utils::RwLock;
4747
use miden_client::{ClientError, DebugMode};
48-
use miden_client_sqlite_store::SqliteStore;
48+
use miden_client_sqlite_store::ClientBuilderSqliteExt;
4949
use miden_lib::account::auth::AuthRpoFalcon512;
5050
use miden_lib::account::faucets::BasicFungibleFaucet;
5151
use miden_lib::account::interface::AccountInterfaceError;
@@ -2103,11 +2103,10 @@ pub async fn create_test_client_builder()
21032103
let rpc_api = MockRpcApi::new(Box::pin(create_prebuilt_mock_chain()).await);
21042104
let arc_rpc_api = Arc::new(rpc_api.clone());
21052105

2106-
let sqlite_store = SqliteStore::new(create_test_store_path()).await.unwrap();
21072106
let builder = ClientBuilder::new()
21082107
.rpc(arc_rpc_api)
21092108
.rng(Box::new(rng))
2110-
.store(Arc::new(sqlite_store))
2109+
.sqlite_store(create_test_store_path())
21112110
.filesystem_keystore(keystore_path.to_str().unwrap())
21122111
.in_debug_mode(DebugMode::Enabled)
21132112
.tx_graceful_blocks(None);

0 commit comments

Comments
 (0)