From c36d6b9ded8b365b02039109545c5a98ccd9e9d9 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Thu, 23 Apr 2026 00:07:13 +0200 Subject: [PATCH] 0.5.0 --- CHANGELOG.md | 1 + Cargo.toml | 2 +- kb_lib/Cargo.toml | 2 + kb_lib/src/config.rs | 32 +++++ kb_lib/src/db.rs | 23 ++++ kb_lib/src/db/connection.rs | 140 ++++++++++++++++++++ kb_lib/src/db/dtos.rs | 7 + kb_lib/src/db/dtos/db_metadata.rs | 57 ++++++++ kb_lib/src/db/entities.rs | 9 ++ kb_lib/src/db/entities/db_metadata.rs | 14 ++ kb_lib/src/db/queries.rs | 9 ++ kb_lib/src/db/queries/db_metadata.rs | 164 ++++++++++++++++++++++++ kb_lib/src/db/schema.rs | 39 ++++++ kb_lib/src/db/sqlite.rs | 67 ++++++++++ kb_lib/src/db/types.rs | 7 + kb_lib/src/db/types/database_backend.rs | 11 ++ kb_lib/src/error.rs | 28 ++-- kb_lib/src/lib.rs | 11 ++ 18 files changed, 609 insertions(+), 14 deletions(-) create mode 100644 kb_lib/src/db.rs create mode 100644 kb_lib/src/db/connection.rs create mode 100644 kb_lib/src/db/dtos.rs create mode 100644 kb_lib/src/db/dtos/db_metadata.rs create mode 100644 kb_lib/src/db/entities.rs create mode 100644 kb_lib/src/db/entities/db_metadata.rs create mode 100644 kb_lib/src/db/queries.rs create mode 100644 kb_lib/src/db/queries/db_metadata.rs create mode 100644 kb_lib/src/db/schema.rs create mode 100644 kb_lib/src/db/sqlite.rs create mode 100644 kb_lib/src/db/types.rs create mode 100644 kb_lib/src/db/types/database_backend.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c3971..ac7792e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,3 +16,4 @@ 0.4.2 - Préparation de la politique HTTP avancée : états de pause avant envoi, quotas par famille de méthodes et futur pool d’endpoints 0.4.3 - Pool d’endpoints HTTP 0.4.4 - Ajout de la fenêtre Demo Http dans kb_app, exécution manuelle des méthodes HTTP via le pool, snapshot des endpoints et amélioration des presets UI +0.5.0 - Début du socle SQLite : configuration database, ouverture/validation de la base et premières briques de persistance diff --git a/Cargo.toml b/Cargo.toml index 95f2b07..cdc7e0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ ] [workspace.package] -version = "0.4.4" +version = "0.5.0" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bobobot" diff --git a/kb_lib/Cargo.toml b/kb_lib/Cargo.toml index 55c8b2b..6c05746 100644 --- a/kb_lib/Cargo.toml +++ b/kb_lib/Cargo.toml @@ -20,6 +20,7 @@ solana-sdk-ids.workspace = true spl-associated-token-account-interface.workspace = true spl-token-2022-interface.workspace = true spl-token-interface.workspace = true +sqlx.workspace = true tokio.workspace = true tokio-tungstenite.workspace = true tracing.workspace = true @@ -27,4 +28,5 @@ tracing-appender.workspace = true tracing-subscriber.workspace = true [dev-dependencies] +tempfile.workspace = true diff --git a/kb_lib/src/config.rs b/kb_lib/src/config.rs index b49b5d8..e73d485 100644 --- a/kb_lib/src/config.rs +++ b/kb_lib/src/config.rs @@ -13,6 +13,8 @@ pub struct KbConfig { pub data: KbDataConfig, /// Solana endpoint configuration. pub solana: KbSolanaConfig, + /// Database configuration. + pub database: KbDatabaseConfig, } impl KbConfig { @@ -489,6 +491,36 @@ impl KbWsEndpointConfig { } } +/// SQLite configuration. +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KbSqliteDatabaseConfig { + /// SQLite database path. + pub path: std::string::String, + /// Whether the file should be created if missing. + pub create_if_missing: bool, + /// SQLite busy timeout in milliseconds. + pub busy_timeout_ms: u64, + /// Maximum pool connections. + pub max_connections: u32, + /// Whether the schema should be initialized automatically at startup. + pub auto_initialize_schema: bool, + /// Whether WAL journal mode should be enabled. + pub use_wal: bool, +} + +/// Database configuration. +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KbDatabaseConfig { + /// Whether the database layer is enabled. + pub enabled: bool, + /// Selected backend. + pub backend: crate::KbDatabaseBackend, + /// SQLite-specific configuration. + pub sqlite: KbSqliteDatabaseConfig, +} + fn kb_workspace_root_dir() -> std::path::PathBuf { let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); match manifest_dir.parent() { diff --git a/kb_lib/src/db.rs b/kb_lib/src/db.rs new file mode 100644 index 0000000..f067c00 --- /dev/null +++ b/kb_lib/src/db.rs @@ -0,0 +1,23 @@ +// file: kb_lib/src/db.rs + +//! Database facade. +//! +//! This module centralizes the database layer and exposes a storage API that is +//! intentionally structured to remain evolutive across backends. + +mod connection; +mod dtos; +mod entities; +mod queries; +mod schema; +mod sqlite; +mod types; + +pub use crate::db::connection::KbDatabase; +pub use crate::db::connection::KbDatabaseConnection; +pub use crate::db::dtos::KbDbMetadataDto; +pub use crate::db::entities::KbDbMetadataEntity; +pub use crate::db::queries::get_db_metadata; +pub use crate::db::queries::list_db_metadata; +pub use crate::db::queries::upsert_db_metadata; +pub use crate::db::types::KbDatabaseBackend; diff --git a/kb_lib/src/db/connection.rs b/kb_lib/src/db/connection.rs new file mode 100644 index 0000000..2092e04 --- /dev/null +++ b/kb_lib/src/db/connection.rs @@ -0,0 +1,140 @@ +// file: kb_lib/src/db/connection.rs + +//! Database connection facade. + +/// Concrete database connection. +#[derive(Debug, Clone)] +pub enum KbDatabaseConnection { + /// SQLite connection pool. + Sqlite(sqlx::SqlitePool), +} + +/// Database facade. +#[derive(Debug, Clone)] +pub struct KbDatabase { + backend: crate::KbDatabaseBackend, + database_url: std::string::String, + connection: KbDatabaseConnection, +} + +impl KbDatabase { + /// Opens a database connection without initializing the schema. + pub async fn connect( + config: &crate::KbDatabaseConfig, + ) -> Result { + if !config.enabled { + return Err(crate::KbError::Config( + "database is disabled in configuration".to_string(), + )); + } + match config.backend { + crate::KbDatabaseBackend::Sqlite => { + let database_url_result = + crate::db::sqlite::sqlite_database_url_from_config(config); + let database_url = match database_url_result { + Ok(database_url) => database_url, + Err(error) => return Err(error), + }; + let pool_result = crate::db::sqlite::connect_sqlite(config).await; + let pool = match pool_result { + Ok(pool) => pool, + Err(error) => return Err(error), + }; + Ok(Self { + backend: crate::KbDatabaseBackend::Sqlite, + database_url, + connection: KbDatabaseConnection::Sqlite(pool), + }) + }, + } + } + + /// Opens a database connection and initializes the schema if configured. + pub async fn connect_and_initialize( + config: &crate::KbDatabaseConfig, + ) -> Result { + let connect_result = Self::connect(config).await; + let database = match connect_result { + Ok(database) => database, + Err(error) => return Err(error), + }; + if config.sqlite.auto_initialize_schema { + let init_result = crate::db::schema::ensure_schema(&database).await; + if let Err(error) = init_result { + return Err(error); + } + } + Ok(database) + } + + /// Returns the configured backend. + pub fn backend( + &self, + ) -> crate::KbDatabaseBackend { + self.backend + } + + /// Returns a displayable database URL-like string. + pub fn database_url( + &self, + ) -> &str { + &self.database_url + } + + /// Pings the database. + pub async fn ping( + &self, + ) -> Result<(), crate::KbError> { + match &self.connection { + KbDatabaseConnection::Sqlite(pool) => { + let ping_result = sqlx::query("SELECT 1").execute(pool).await; + match ping_result { + Ok(_) => Ok(()), + Err(error) => Err(crate::KbError::Db(format!( + "cannot ping sqlite database '{}': {}", + self.database_url, + error + ))), + } + }, + } + } + + /// Returns the underlying connection enum. + pub(crate) fn connection( + &self, + ) -> &KbDatabaseConnection { + &self.connection + } +} + +#[cfg(test)] +mod tests { + #[tokio::test] + async fn connect_and_ping_sqlite_database_works() { + let tempdir = tempfile::tempdir().expect("tempdir must succeed"); + let database_path = tempdir.path().join("connection.sqlite3"); + let config = crate::KbDatabaseConfig { + enabled: true, + backend: crate::KbDatabaseBackend::Sqlite, + sqlite: crate::KbSqliteDatabaseConfig { + path: database_path.to_string_lossy().to_string(), + create_if_missing: true, + busy_timeout_ms: 5000, + max_connections: 1, + auto_initialize_schema: true, + use_wal: true, + }, + }; + let database = crate::KbDatabase::connect_and_initialize(&config) + .await + .expect("database init must succeed"); + assert_eq!(database.backend(), crate::KbDatabaseBackend::Sqlite); + assert!(database.database_url().starts_with("sqlite://")); + database.ping().await.expect("ping must succeed"); + let metadata = crate::get_db_metadata(&database, "schema_version") + .await + .expect("metadata fetch must succeed"); + assert!(metadata.is_some()); + } +} diff --git a/kb_lib/src/db/dtos.rs b/kb_lib/src/db/dtos.rs new file mode 100644 index 0000000..d5b180e --- /dev/null +++ b/kb_lib/src/db/dtos.rs @@ -0,0 +1,7 @@ +// file: kb_lib/src/db/dtos.rs + +//! Database data transfer objects. + +mod db_metadata; + +pub use crate::db::dtos::db_metadata::KbDbMetadataDto; diff --git a/kb_lib/src/db/dtos/db_metadata.rs b/kb_lib/src/db/dtos/db_metadata.rs new file mode 100644 index 0000000..2416ca5 --- /dev/null +++ b/kb_lib/src/db/dtos/db_metadata.rs @@ -0,0 +1,57 @@ +// file: kb_lib/src/db/dtos/db_metadata.rs + +//! Metadata DTOs. + +/// Metadata DTO used by the application layer. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct KbDbMetadataDto { + /// Metadata key. + pub key: std::string::String, + /// Metadata value. + pub value: std::string::String, + /// Last update timestamp. + pub updated_at: chrono::DateTime, +} + +impl KbDbMetadataDto { + /// Creates a new metadata DTO with the current UTC timestamp. + pub fn new(key: std::string::String, value: std::string::String) -> Self { + Self { + key, + value, + updated_at: chrono::Utc::now(), + } + } +} + +impl TryFrom for KbDbMetadataDto { + type Error = crate::KbError; + + fn try_from(entity: crate::KbDbMetadataEntity) -> Result { + let parsed_result = chrono::DateTime::parse_from_rfc3339(&entity.updated_at); + let parsed = match parsed_result { + Ok(parsed) => parsed, + Err(error) => { + return Err(crate::KbError::Db(format!( + "cannot parse db metadata timestamp '{}': {}", + entity.updated_at, error + ))); + } + }; + Ok(Self { + key: entity.key, + value: entity.value, + updated_at: parsed.with_timezone(&chrono::Utc), + }) + } +} + +impl From for crate::KbDbMetadataEntity { + fn from(dto: KbDbMetadataDto) -> Self { + Self { + key: dto.key, + value: dto.value, + updated_at: dto.updated_at.to_rfc3339(), + } + } +} diff --git a/kb_lib/src/db/entities.rs b/kb_lib/src/db/entities.rs new file mode 100644 index 0000000..7e04f9d --- /dev/null +++ b/kb_lib/src/db/entities.rs @@ -0,0 +1,9 @@ +// file: kb_lib/src/db/entities.rs + +//! Database entities. +//! +//! These types are close to persisted rows and SQL query results. + +mod db_metadata; + +pub use crate::db::entities::db_metadata::KbDbMetadataEntity; diff --git a/kb_lib/src/db/entities/db_metadata.rs b/kb_lib/src/db/entities/db_metadata.rs new file mode 100644 index 0000000..75b0f06 --- /dev/null +++ b/kb_lib/src/db/entities/db_metadata.rs @@ -0,0 +1,14 @@ +// file: kb_lib/src/db/entities/db_metadata.rs + +//! Metadata table entity. + +/// Persisted metadata row. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, sqlx::FromRow)] +pub struct KbDbMetadataEntity { + /// Metadata key. + pub key: std::string::String, + /// Metadata value. + pub value: std::string::String, + /// Last update timestamp encoded as RFC3339 UTC text. + pub updated_at: std::string::String, +} diff --git a/kb_lib/src/db/queries.rs b/kb_lib/src/db/queries.rs new file mode 100644 index 0000000..2d2a824 --- /dev/null +++ b/kb_lib/src/db/queries.rs @@ -0,0 +1,9 @@ +// file: kb_lib/src/db/queries.rs + +//! Database queries. + +mod db_metadata; + +pub use crate::db::queries::db_metadata::get_db_metadata; +pub use crate::db::queries::db_metadata::list_db_metadata; +pub use crate::db::queries::db_metadata::upsert_db_metadata; diff --git a/kb_lib/src/db/queries/db_metadata.rs b/kb_lib/src/db/queries/db_metadata.rs new file mode 100644 index 0000000..46d163b --- /dev/null +++ b/kb_lib/src/db/queries/db_metadata.rs @@ -0,0 +1,164 @@ +// file: kb_lib/src/db/queries/db_metadata.rs + +//! Queries for `kb_db_metadata`. + +/// Inserts or updates one metadata row. +pub async fn upsert_db_metadata( + database: &crate::KbDatabase, + dto: &crate::KbDbMetadataDto, +) -> Result<(), crate::KbError> { + let entity = crate::KbDbMetadataEntity::from(dto.clone()); + match database.connection() { + crate::KbDatabaseConnection::Sqlite(pool) => { + let query_result = sqlx::query( + r#" +INSERT INTO kb_db_metadata ( + key, + value, + updated_at +) +VALUES (?, ?, ?) +ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + updated_at = excluded.updated_at + "#, + ) + .bind(entity.key) + .bind(entity.value) + .bind(entity.updated_at) + .execute(pool) + .await; + match query_result { + Ok(_) => Ok(()), + Err(error) => Err(crate::KbError::Db(format!( + "cannot upsert kb_db_metadata on sqlite: {}", + error + ))), + } + } + } +} + +/// Reads one metadata row by key. +pub async fn get_db_metadata( + database: &crate::KbDatabase, + key: &str, +) -> Result, crate::KbError> { + match database.connection() { + crate::KbDatabaseConnection::Sqlite(pool) => { + let query_result = sqlx::query_as::( + r#" +SELECT + key, + value, + updated_at +FROM kb_db_metadata +WHERE key = ? +LIMIT 1 + "#, + ) + .bind(key) + .fetch_optional(pool) + .await; + let entity_option = match query_result { + Ok(entity_option) => entity_option, + Err(error) => { + return Err(crate::KbError::Db(format!( + "cannot read kb_db_metadata '{}' on sqlite: {}", + key, error + ))); + } + }; + match entity_option { + Some(entity) => { + let dto_result = crate::KbDbMetadataDto::try_from(entity); + match dto_result { + Ok(dto) => Ok(Some(dto)), + Err(error) => Err(error), + } + } + None => Ok(None), + } + } + } +} + +/// Lists all metadata rows. +pub async fn list_db_metadata( + database: &crate::KbDatabase, +) -> Result, crate::KbError> { + match database.connection() { + crate::KbDatabaseConnection::Sqlite(pool) => { + let query_result = sqlx::query_as::( + r#" +SELECT + key, + value, + updated_at +FROM kb_db_metadata +ORDER BY key ASC + "#, + ) + .fetch_all(pool) + .await; + let entities = match query_result { + Ok(entities) => entities, + Err(error) => { + return Err(crate::KbError::Db(format!( + "cannot list kb_db_metadata on sqlite: {}", + error + ))); + } + }; + let mut dtos = std::vec::Vec::new(); + for entity in entities { + let dto_result = crate::KbDbMetadataDto::try_from(entity); + let dto = match dto_result { + Ok(dto) => dto, + Err(error) => return Err(error), + }; + dtos.push(dto); + } + Ok(dtos) + } + } +} + +#[cfg(test)] +mod tests { + #[tokio::test] + async fn db_metadata_roundtrip_works() { + let tempdir = tempfile::tempdir().expect("tempdir must succeed"); + let database_path = tempdir.path().join("roundtrip.sqlite3"); + let config = crate::KbDatabaseConfig { + enabled: true, + backend: crate::KbDatabaseBackend::Sqlite, + sqlite: crate::KbSqliteDatabaseConfig { + path: database_path.to_string_lossy().to_string(), + create_if_missing: true, + busy_timeout_ms: 5000, + max_connections: 1, + auto_initialize_schema: true, + use_wal: true, + }, + }; + let database = crate::KbDatabase::connect_and_initialize(&config) + .await + .expect("database init must succeed"); + let dto = crate::KbDbMetadataDto::new("schema_version".to_string(), "0.5.0".to_string()); + crate::upsert_db_metadata(&database, &dto) + .await + .expect("upsert must succeed"); + let fetched = crate::get_db_metadata(&database, "schema_version") + .await + .expect("fetch must succeed"); + assert!(fetched.is_some()); + let fetched = fetched.expect("metadata must exist"); + assert_eq!(fetched.key, "schema_version"); + assert_eq!(fetched.value, "0.5.0"); + let listed = crate::list_db_metadata(&database) + .await + .expect("list must succeed"); + assert!(!listed.is_empty()); + } +} diff --git a/kb_lib/src/db/schema.rs b/kb_lib/src/db/schema.rs new file mode 100644 index 0000000..763e553 --- /dev/null +++ b/kb_lib/src/db/schema.rs @@ -0,0 +1,39 @@ +// file: kb_lib/src/db/schema.rs + +//! Database schema initialization. + +/// Ensures that the database schema exists. +pub(super) async fn ensure_schema( + database: &crate::KbDatabase, +) -> Result<(), crate::KbError> { + match database.connection() { + crate::KbDatabaseConnection::Sqlite(pool) => { + let metadata_table_result = sqlx::query( + r#" +CREATE TABLE IF NOT EXISTS kb_db_metadata ( + key TEXT NOT NULL PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT NOT NULL +) + "#, + ) + .execute(pool) + .await; + if let Err(error) = metadata_table_result { + return Err(crate::KbError::Db(format!( + "cannot create table kb_db_metadata on sqlite: {}", + error + ))); + } + let schema_version = crate::KbDbMetadataDto::new( + "schema_version".to_string(), + env!("CARGO_PKG_VERSION").to_string(), + ); + let upsert_result = crate::upsert_db_metadata(database, &schema_version).await; + if let Err(error) = upsert_result { + return Err(error); + } + Ok(()) + }, + } +} diff --git a/kb_lib/src/db/sqlite.rs b/kb_lib/src/db/sqlite.rs new file mode 100644 index 0000000..7c43d1e --- /dev/null +++ b/kb_lib/src/db/sqlite.rs @@ -0,0 +1,67 @@ +// file: kb_lib/src/db/sqlite.rs + +//! SQLite backend helpers. + +/// Returns a displayable SQLite database URL-like string. +pub(crate) fn sqlite_database_url_from_config( + config: &crate::KbDatabaseConfig, +) -> Result { + let path = config.sqlite.path.trim(); + if path.is_empty() { + return Err(crate::KbError::Config( + "database.sqlite.path must not be empty".to_string(), + )); + } + Ok(format!("sqlite://{}", path)) +} + +/// Opens a SQLite pool according to configuration. +pub(crate) async fn connect_sqlite( + config: &crate::KbDatabaseConfig, +) -> Result { + let path = config.sqlite.path.trim(); + if path.is_empty() { + return Err(crate::KbError::Config( + "database.sqlite.path must not be empty".to_string(), + )); + } + if config.sqlite.max_connections == 0 { + return Err(crate::KbError::Config( + "database.sqlite.max_connections must be > 0".to_string(), + )); + } + let database_path = std::path::Path::new(path); + let parent_option = database_path.parent(); + if let Some(parent) = parent_option { + if !parent.as_os_str().is_empty() { + let create_result = std::fs::create_dir_all(parent); + if let Err(error) = create_result { + return Err(crate::KbError::Db(format!( + "cannot create sqlite parent directory '{}': {}", + parent.display(), + error + ))); + } + } + } + let mut connect_options = sqlx::sqlite::SqliteConnectOptions::new() + .filename(database_path) + .create_if_missing(config.sqlite.create_if_missing) + .foreign_keys(true) + .busy_timeout(std::time::Duration::from_millis( + config.sqlite.busy_timeout_ms, + )); + if config.sqlite.use_wal { + connect_options = connect_options.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal); + } + let pool_options = + sqlx::sqlite::SqlitePoolOptions::new().max_connections(config.sqlite.max_connections); + let connect_result = pool_options.connect_with(connect_options).await; + match connect_result { + Ok(pool) => Ok(pool), + Err(error) => Err(crate::KbError::Db(format!( + "cannot open sqlite database '{}': {}", + path, error + ))), + } +} diff --git a/kb_lib/src/db/types.rs b/kb_lib/src/db/types.rs new file mode 100644 index 0000000..d90e326 --- /dev/null +++ b/kb_lib/src/db/types.rs @@ -0,0 +1,7 @@ +// file: kb_lib/src/db/types.rs + +//! Database shared types. + +mod database_backend; + +pub use crate::db::types::database_backend::KbDatabaseBackend; diff --git a/kb_lib/src/db/types/database_backend.rs b/kb_lib/src/db/types/database_backend.rs new file mode 100644 index 0000000..7aa45ef --- /dev/null +++ b/kb_lib/src/db/types/database_backend.rs @@ -0,0 +1,11 @@ +// file: kb_lib/src/db/types/database_backend.rs + +//! Database backend discriminator. + +/// Supported database backends. +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum KbDatabaseBackend { + /// SQLite backend. + Sqlite, +} diff --git a/kb_lib/src/error.rs b/kb_lib/src/error.rs index 444e9c0..a7950d0 100644 --- a/kb_lib/src/error.rs +++ b/kb_lib/src/error.rs @@ -20,6 +20,8 @@ pub enum KbError { Http(std::string::String), /// WebSocket transport error. Ws(std::string::String), + /// Database error. + Db(std::string::String), /// Invalid internal state error. InvalidState(std::string::String), /// Operation requested while the client is not connected. @@ -29,38 +31,38 @@ pub enum KbError { } impl std::fmt::Display for KbError { - fn fmt( - &self, - formatter: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Config(message) => { write!(formatter, "configuration error: {message}") - }, + } Self::Io(message) => { write!(formatter, "io error: {message}") - }, + } Self::Json(message) => { write!(formatter, "json error: {message}") - }, + } Self::Tracing(message) => { write!(formatter, "tracing error: {message}") - }, + } Self::Http(message) => { write!(formatter, "http error: {message}") - }, + } Self::Ws(message) => { write!(formatter, "websocket error: {message}") - }, + } + Self::Db(message) => { + write!(formatter, "db error: {}", message) + } Self::InvalidState(message) => { write!(formatter, "invalid state: {message}") - }, + } Self::NotConnected(message) => { write!(formatter, "not connected: {message}") - }, + } Self::NotImplemented(message) => { write!(formatter, "not implemented: {message}") - }, + } } } } diff --git a/kb_lib/src/lib.rs b/kb_lib/src/lib.rs index 385d241..8ce7ed2 100644 --- a/kb_lib/src/lib.rs +++ b/kb_lib/src/lib.rs @@ -18,6 +18,7 @@ mod types; mod ws_client; mod rpc_ws_solana; mod http_pool; +mod db; pub use crate::config::KbAppConfig; pub use crate::config::KbConfig; @@ -26,6 +27,8 @@ pub use crate::config::KbHttpEndpointConfig; pub use crate::config::KbLoggingConfig; pub use crate::config::KbSolanaConfig; pub use crate::config::KbWsEndpointConfig; +pub use crate::config::KbDatabaseConfig; +pub use crate::config::KbSqliteDatabaseConfig; pub use crate::constants::*; pub use crate::error::KbError; pub use crate::rpc_ws::KbJsonRpcWsErrorObject; @@ -60,4 +63,12 @@ pub use crate::rpc_ws_solana::parse_kb_solana_ws_typed_notification; pub use crate::rpc_ws_solana::parse_kb_solana_ws_typed_notification_from_event; pub use crate::http_pool::HttpEndpointPool; pub use crate::http_pool::KbHttpPoolClientSnapshot; +pub use crate::db::KbDatabase; +pub use crate::db::KbDatabaseBackend; +pub use crate::db::KbDatabaseConnection; +pub use crate::db::KbDbMetadataDto; +pub use crate::db::KbDbMetadataEntity; +pub use crate::db::get_db_metadata; +pub use crate::db::list_db_metadata; +pub use crate::db::upsert_db_metadata;