app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 9526bd48f30c5b3da3d739c8a4b8296a91f9b791
parent 93790dae176aca559c1e587a0c0198e5bc1a9051
Author: triesap <triesap@radroots.dev>
Date:   Mon, 19 Jan 2026 00:56:04 +0000

app-core: add sql engine types

- define sql exec outcome, params, and row types
- add sql engine and encrypted store traits
- model migration state and engine configuration structs
- add unit test for positional sql params

Diffstat:
MCargo.lock | 1+
MCargo.toml | 1+
Mcrates/core/Cargo.toml | 1+
Mcrates/core/src/sql/mod.rs | 13+++++++++++++
Acrates/core/src/sql/types.rs | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 114 insertions(+), 0 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1151,6 +1151,7 @@ version = "0.1.0" dependencies = [ "async-trait", "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml @@ -16,6 +16,7 @@ license = "AGPL-3.0" leptos = { version = "0.8.5", default-features = false } wasm-bindgen = "=0.2.100" serde = { version = "1", features = ["derive"] } +serde_json = "1" [profile.release] codegen-units = 1 diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml @@ -12,3 +12,4 @@ crate-type = ["rlib"] [dependencies] async-trait = "0.1.83" serde = { workspace = true } +serde_json = { workspace = true } diff --git a/crates/core/src/sql/mod.rs b/crates/core/src/sql/mod.rs @@ -1,3 +1,16 @@ pub mod error; +pub mod types; pub use error::{RadrootsClientSqlError, RadrootsClientSqlErrorMessage}; +pub use types::{ + RadrootsClientSqlEncryptedStore, + RadrootsClientSqlEngine, + RadrootsClientSqlEngineConfig, + RadrootsClientSqlExecOutcome, + RadrootsClientSqlMigrationRow, + RadrootsClientSqlMigrationState, + RadrootsClientSqlParams, + RadrootsClientSqlResultRow, + RadrootsClientSqlResult, + RadrootsClientSqlValue, +}; diff --git a/crates/core/src/sql/types.rs b/crates/core/src/sql/types.rs @@ -0,0 +1,98 @@ +use std::collections::BTreeMap; + +use async_trait::async_trait; +use serde_json::Value; + +use crate::backup::RadrootsClientBackupSqlPayload; +use crate::idb::RadrootsClientIdbConfig; + +use super::RadrootsClientSqlError; + +pub type RadrootsClientSqlResult<T> = Result<T, RadrootsClientSqlError>; +pub type RadrootsClientSqlValue = Value; +pub type RadrootsClientSqlResultRow = BTreeMap<String, Value>; + +#[derive(Debug, Clone, PartialEq)] +pub struct RadrootsClientSqlExecOutcome { + pub changes: i64, + pub last_insert_id: i64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RadrootsClientSqlMigrationRow { + pub id: i64, + pub name: String, + pub applied_at: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RadrootsClientSqlMigrationState { + pub applied_names: Vec<String>, + pub applied_count: usize, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum RadrootsClientSqlParams { + Named(BTreeMap<String, Value>), + Positional(Vec<Value>), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RadrootsClientSqlEngineConfig { + pub store_key: String, + pub idb_config: RadrootsClientIdbConfig, + pub cipher_config: Option<RadrootsClientIdbConfig>, + pub sql_wasm_path: Option<String>, +} + +#[async_trait(?Send)] +pub trait RadrootsClientSqlEncryptedStore { + async fn load(&self) -> RadrootsClientSqlResult<Option<Vec<u8>>>; + async fn save(&self, bytes: &[u8]) -> RadrootsClientSqlResult<()>; + async fn remove(&self) -> RadrootsClientSqlResult<()>; +} + +#[async_trait(?Send)] +pub trait RadrootsClientSqlEngine { + async fn close(&self) -> RadrootsClientSqlResult<()>; + async fn purge_storage(&self) -> RadrootsClientSqlResult<()>; + fn exec( + &self, + sql: &str, + params: RadrootsClientSqlParams, + ) -> RadrootsClientSqlResult<RadrootsClientSqlExecOutcome>; + fn query( + &self, + sql: &str, + params: RadrootsClientSqlParams, + ) -> RadrootsClientSqlResult<Vec<RadrootsClientSqlResultRow>>; + fn export_bytes(&self) -> RadrootsClientSqlResult<Vec<u8>>; + async fn import_bytes(&self, bytes: &[u8]) -> RadrootsClientSqlResult<()>; + async fn export_backup( + &self, + ) -> RadrootsClientSqlResult<RadrootsClientBackupSqlPayload>; + async fn import_backup( + &self, + payload: RadrootsClientBackupSqlPayload, + ) -> RadrootsClientSqlResult<()>; + fn get_store_id(&self) -> &str; +} + +#[cfg(test)] +mod tests { + use super::{RadrootsClientSqlParams, RadrootsClientSqlValue}; + + #[test] + fn params_accept_positional_values() { + let params = RadrootsClientSqlParams::Positional(vec![ + RadrootsClientSqlValue::from(1), + RadrootsClientSqlValue::from("two"), + ]); + match params { + RadrootsClientSqlParams::Positional(values) => { + assert_eq!(values.len(), 2); + } + RadrootsClientSqlParams::Named(_) => panic!("expected positional params"), + } + } +}