Skip to content

Commit 9a86594

Browse files
committed
feat: refactor to separate pure Rust
1 parent 2fb37b9 commit 9a86594

29 files changed

Lines changed: 1796 additions & 765 deletions

Cargo.lock

Lines changed: 535 additions & 398 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ resolver = "3"
33
members = [
44
"crates/sqlx-sqlite-conn-mgr",
55
"crates/sqlx-sqlite-observer",
6+
"crates/sqlx-sqlite-toolkit",
67
]
78

89
[package]
@@ -33,6 +34,15 @@ sqlx = { version = "0.8.6", features = ["sqlite", "json", "time", "runtime-tokio
3334
# Connection manager
3435
sqlx-sqlite-conn-mgr = { path = "crates/sqlx-sqlite-conn-mgr" }
3536

37+
# Toolkit (high-level API — builders, transactions, decoding)
38+
sqlx-sqlite-toolkit = { path = "crates/sqlx-sqlite-toolkit", features = ["observer"] }
39+
40+
# Observer types (for payload conversion)
41+
sqlx-sqlite-observer = { path = "crates/sqlx-sqlite-observer", features = ["conn-mgr"] }
42+
43+
# Async stream support for observer subscriptions
44+
futures = "0.3"
45+
3646
[build-dependencies]
3747
tauri-plugin = { version = "2.5.1", features = ["build"] }
3848

api-iife.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ fn main() {
1111
"close",
1212
"close_all",
1313
"remove",
14+
"get_migration_events",
15+
"observe",
16+
"subscribe",
17+
"unsubscribe",
18+
"unobserve",
1419
])
1520
.build();
1621
}

crates/sqlx-sqlite-observer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ keywords = ["sqlite", "sqlx", "reactive", "observer", "database"]
1313
categories = ["database", "asynchronous"]
1414

1515
[features]
16-
conn-mgr = ["dep:sqlx-sqlite-conn-mgr"]
1716
# Bundle SQLite by default - preupdate hooks require SQLITE_ENABLE_PREUPDATE_HOOK
1817
# which most system SQLite libraries don't have enabled.
1918
default = ["bundled"]
2019
bundled = ["libsqlite3-sys/bundled"]
20+
conn-mgr = ["dep:sqlx-sqlite-conn-mgr"]
2121

2222
[dependencies]
2323
tokio = { version = "1.49.0", features = ["sync"] }

crates/sqlx-sqlite-observer/README.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,19 @@ The library uses SQLite's native hooks for transaction-safe change tracking:
6262
│ (captures data) │ │ (Vec<Event>) │ │ │
6363
└─────────────────┘ └────────┬────────┘ └─────────────────┘
6464
│ ▲
65-
┌────────────┼────────────┐
66-
│ │
67-
┌─────▼────┐ ┌────▼─────┐
68-
│ COMMIT │ │ ROLLBACK │
69-
└─────┬────┘ └────┬─────┘
70-
│ │
71-
▼ ▼
72-
on_commit() on_rollback()
73-
│ │
74-
│ buffer.clear()
75-
│ (discard)
76-
77-
└───────────────────────────────────┘
65+
┌────────────|
66+
│ │
67+
┌─────▼────┐ ┌────▼─────┐
68+
│ COMMIT │ │ ROLLBACK │
69+
└─────┬────┘ └────┬─────┘
70+
│ │
71+
▼ ▼
72+
on_commit() on_rollback()
73+
│ │
74+
│ buffer.clear()
75+
│ (discard)
76+
77+
└───────────────────────────────────┘
7878
change_tx.send()
7979
(publish)
8080
```
@@ -283,6 +283,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
283283
let config = ObserverConfig::new()
284284
.with_tables(["users"])
285285
.with_capture_values(false);
286+
286287
let observer = SqliteObserver::new(
287288
SqlitePool::connect("sqlite:mydb.db").await?,
288289
config,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "sqlx-sqlite-toolkit"
3+
# Sync major.minor with major.minor of SQLx crate
4+
version = "0.8.6"
5+
license = "MIT"
6+
edition = "2024"
7+
rust-version = "1.89"
8+
9+
[features]
10+
default = []
11+
observer = ["dep:sqlx-sqlite-observer"]
12+
13+
[dependencies]
14+
sqlx-sqlite-conn-mgr = { path = "../sqlx-sqlite-conn-mgr" }
15+
sqlx-sqlite-observer = { path = "../sqlx-sqlite-observer", features = ["conn-mgr"], optional = true }
16+
sqlx = { version = "0.8.6", features = ["sqlite", "json", "time", "runtime-tokio"] }
17+
serde = { version = "1.0", features = ["derive"] }
18+
serde_json = "1.0"
19+
thiserror = "2.0"
20+
indexmap = { version = "2.12", features = ["serde"] }
21+
base64 = "0.22"
22+
time = "0.3"
23+
uuid = { version = "1.11", features = ["v4"] }
24+
tokio = { version = "1.48.0", features = ["sync", "rt"] }
25+
tracing = { version = "0.1", default-features = false, features = ["std", "release_max_level_off"] }
26+
27+
[dev-dependencies]
28+
tempfile = "3.23.0"
29+
tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros"] }

src/builders.rs renamed to crates/sqlx-sqlite-toolkit/src/builders.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use serde_json::Value as JsonValue;
99
use sqlx_sqlite_conn_mgr::AttachedSpec;
1010

1111
use crate::Error;
12-
use crate::wrapper::{WriteQueryResult, bind_value};
12+
use crate::wrapper::{DatabaseWrapper, WriteQueryResult, bind_value};
1313

1414
/// Builder for SELECT queries returning multiple rows
1515
pub struct FetchAllBuilder {
@@ -155,18 +155,14 @@ impl IntoFuture for FetchOneBuilder {
155155

156156
/// Builder for write queries (INSERT/UPDATE/DELETE)
157157
pub struct ExecuteBuilder {
158-
db: Arc<sqlx_sqlite_conn_mgr::SqliteDatabase>,
158+
db: DatabaseWrapper,
159159
query: String,
160160
values: Vec<JsonValue>,
161161
attached: Vec<AttachedSpec>,
162162
}
163163

164164
impl ExecuteBuilder {
165-
pub(crate) fn new(
166-
db: Arc<sqlx_sqlite_conn_mgr::SqliteDatabase>,
167-
query: String,
168-
values: Vec<JsonValue>,
169-
) -> Self {
165+
pub(crate) fn new(db: DatabaseWrapper, query: String, values: Vec<JsonValue>) -> Self {
170166
Self {
171167
db,
172168
query,
@@ -184,7 +180,7 @@ impl ExecuteBuilder {
184180
/// Execute the write operation
185181
pub async fn execute(self) -> Result<WriteQueryResult, Error> {
186182
if self.attached.is_empty() {
187-
// No attached databases - use regular writer
183+
// No attached databases - use wrapper's writer (routes through observer when enabled)
188184
let mut writer = self.db.acquire_writer().await?;
189185
let mut q = sqlx::query(&self.query);
190186
for value in self.values {
@@ -198,7 +194,8 @@ impl ExecuteBuilder {
198194
} else {
199195
// With attached database(s) - acquire writer with attached database(s)
200196
let mut conn =
201-
sqlx_sqlite_conn_mgr::acquire_writer_with_attached(&self.db, self.attached).await?;
197+
sqlx_sqlite_conn_mgr::acquire_writer_with_attached(self.db.inner(), self.attached)
198+
.await?;
202199

203200
let mut q = sqlx::query(&self.query);
204201
for value in self.values {
@@ -227,7 +224,7 @@ impl IntoFuture for ExecuteBuilder {
227224
}
228225

229226
/// Helper to decode SQLite rows to JSON
230-
fn decode_rows(
227+
pub(crate) fn decode_rows(
231228
rows: Vec<sqlx::sqlite::SqliteRow>,
232229
) -> Result<Vec<IndexMap<String, JsonValue>>, Error> {
233230
use sqlx::{Column, Row};
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/// Result type alias for toolkit operations.
2+
pub type Result<T> = std::result::Result<T, Error>;
3+
4+
/// Error types for SQLite toolkit operations.
5+
///
6+
/// These are pure database-operation errors with no Tauri dependencies.
7+
#[derive(Debug, thiserror::Error)]
8+
pub enum Error {
9+
/// Error from SQLx operations.
10+
#[error(transparent)]
11+
Sqlx(#[from] sqlx::Error),
12+
13+
/// Error from the connection manager.
14+
#[error(transparent)]
15+
ConnectionManager(#[from] sqlx_sqlite_conn_mgr::Error),
16+
17+
/// SQLite type that cannot be mapped to JSON.
18+
#[error("unsupported datatype: {0}")]
19+
UnsupportedDatatype(String),
20+
21+
/// Multiple rows returned from fetchOne query.
22+
#[error("fetchOne() query returned {0} rows, expected 0 or 1")]
23+
MultipleRowsReturned(usize),
24+
25+
/// Transaction failed and rollback also failed.
26+
#[error("transaction failed: {transaction_error}; rollback also failed: {rollback_error}")]
27+
TransactionRollbackFailed {
28+
transaction_error: String,
29+
rollback_error: String,
30+
},
31+
32+
/// Transaction has already been committed or rolled back.
33+
#[error("transaction has already been finalized (committed or rolled back)")]
34+
TransactionAlreadyFinalized,
35+
36+
/// Transaction already active for this database.
37+
#[error("transaction already active for database: {0}")]
38+
TransactionAlreadyActive(String),
39+
40+
/// No active transaction for this database.
41+
#[error("no active transaction for database: {0}")]
42+
NoActiveTransaction(String),
43+
44+
/// Invalid transaction token provided.
45+
#[error("invalid transaction token")]
46+
InvalidTransactionToken,
47+
48+
/// I/O error when accessing database files.
49+
#[error("io error: {0}")]
50+
Io(#[from] std::io::Error),
51+
52+
/// Generic error for operations that don't fit other categories.
53+
#[error("{0}")]
54+
Other(String),
55+
}
56+
57+
impl Error {
58+
/// Extract a structured error code from the error type.
59+
///
60+
/// This provides machine-readable error codes for error handling.
61+
pub fn error_code(&self) -> String {
62+
match self {
63+
Error::Sqlx(e) => {
64+
if let Some(code) = e.as_database_error().and_then(|db_err| db_err.code()) {
65+
return format!("SQLITE_{}", code);
66+
}
67+
"SQLX_ERROR".to_string()
68+
}
69+
Error::ConnectionManager(_) => "CONNECTION_ERROR".to_string(),
70+
Error::UnsupportedDatatype(_) => "UNSUPPORTED_DATATYPE".to_string(),
71+
Error::MultipleRowsReturned(_) => "MULTIPLE_ROWS_RETURNED".to_string(),
72+
Error::TransactionRollbackFailed { .. } => "TRANSACTION_ROLLBACK_FAILED".to_string(),
73+
Error::TransactionAlreadyFinalized => "TRANSACTION_ALREADY_FINALIZED".to_string(),
74+
Error::TransactionAlreadyActive(_) => "TRANSACTION_ALREADY_ACTIVE".to_string(),
75+
Error::NoActiveTransaction(_) => "NO_ACTIVE_TRANSACTION".to_string(),
76+
Error::InvalidTransactionToken => "INVALID_TRANSACTION_TOKEN".to_string(),
77+
Error::Io(_) => "IO_ERROR".to_string(),
78+
Error::Other(_) => "ERROR".to_string(),
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)