1use serde::{Deserialize, Serialize};
2use tsify::Tsify;
3use wasm_bindgen::prelude::*;
4
5#[derive(Tsify, Serialize, Deserialize, Debug, Clone)]
7#[tsify(into_wasm_abi, from_wasm_abi)]
8pub struct DatabaseConfig {
9 pub name: String,
10 pub version: Option<u32>,
11 pub cache_size: Option<usize>,
12 pub page_size: Option<usize>,
13 pub auto_vacuum: Option<bool>,
14 pub journal_mode: Option<String>,
34 pub max_export_size_bytes: Option<u64>,
39}
40
41impl Default for DatabaseConfig {
42 fn default() -> Self {
43 Self {
44 name: "default.db".to_string(),
45 version: Some(1),
46 cache_size: Some(10_000),
47 page_size: Some(4096),
48 auto_vacuum: Some(true),
49 journal_mode: Some("MEMORY".to_string()),
52 max_export_size_bytes: Some(2 * 1024 * 1024 * 1024), }
54 }
55}
56
57impl DatabaseConfig {
58 pub fn mobile_optimized(name: impl Into<String>) -> Self {
76 Self {
77 name: name.into(),
78 version: Some(1),
79 cache_size: Some(20_000), page_size: Some(4096),
81 auto_vacuum: Some(true),
82 journal_mode: Some("WAL".to_string()), max_export_size_bytes: Some(2 * 1024 * 1024 * 1024),
84 }
85 }
86}
87#[derive(Tsify, Serialize, Deserialize, Debug)]
89#[tsify(into_wasm_abi, from_wasm_abi)]
90#[serde(rename_all = "camelCase")]
91pub struct QueryResult {
92 pub columns: Vec<String>,
93 pub rows: Vec<Row>,
94 pub affected_rows: u32,
95 pub last_insert_id: Option<i64>,
96 pub execution_time_ms: f64,
97}
98
99#[derive(Tsify, Serialize, Deserialize, Debug, Clone)]
100#[tsify(into_wasm_abi, from_wasm_abi)]
101pub struct Row {
102 pub values: Vec<ColumnValue>,
103}
104
105#[derive(Tsify, Serialize, Deserialize, Debug, Clone, PartialEq)]
106#[tsify(into_wasm_abi, from_wasm_abi)]
107#[serde(tag = "type", content = "value")]
108pub enum ColumnValue {
109 Null,
110 Integer(i64),
111 Real(f64),
112 Text(String),
113 Blob(Vec<u8>),
114 Date(i64), BigInt(String), }
117
118impl ColumnValue {
119 #[cfg(not(target_arch = "wasm32"))]
120 pub fn from_rusqlite_value(value: &rusqlite::types::Value) -> Self {
121 match value {
122 rusqlite::types::Value::Null => ColumnValue::Null,
123 rusqlite::types::Value::Integer(i) => ColumnValue::Integer(*i),
124 rusqlite::types::Value::Real(f) => ColumnValue::Real(*f),
125 rusqlite::types::Value::Text(s) => {
126 if s.len() >= 20 && s.starts_with("20") && s.contains('T') && s.contains('Z') {
128 if let Ok(dt) = time::OffsetDateTime::parse(
129 s,
130 &time::format_description::well_known::Rfc3339,
131 ) {
132 return ColumnValue::Date((dt.unix_timestamp_nanos() / 1_000_000) as i64);
133 }
134 }
135 if s.len() > 18
137 && s.chars()
138 .all(|c| c.is_ascii_digit() || c == '-' || c == '+')
139 {
140 return ColumnValue::BigInt(s.clone());
141 }
142 ColumnValue::Text(s.clone())
143 }
144 rusqlite::types::Value::Blob(b) => ColumnValue::Blob(b.clone()),
145 }
146 }
147
148 #[cfg(not(target_arch = "wasm32"))]
149 pub fn to_rusqlite_value(&self) -> rusqlite::types::Value {
150 match self {
151 ColumnValue::Null => rusqlite::types::Value::Null,
152 ColumnValue::Integer(i) => rusqlite::types::Value::Integer(*i),
153 ColumnValue::Real(f) => rusqlite::types::Value::Real(*f),
154 ColumnValue::Text(s) => rusqlite::types::Value::Text(s.clone()),
155 ColumnValue::Blob(b) => rusqlite::types::Value::Blob(b.clone()),
156 ColumnValue::Date(ts) => {
157 let dt = time::OffsetDateTime::from_unix_timestamp_nanos((*ts as i128) * 1_000_000)
159 .unwrap_or(time::OffsetDateTime::UNIX_EPOCH);
160 let formatted = dt
161 .format(&time::format_description::well_known::Rfc3339)
162 .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string());
163 rusqlite::types::Value::Text(formatted)
164 }
165 ColumnValue::BigInt(s) => rusqlite::types::Value::Text(s.clone()),
166 }
167 }
168}
169
170#[derive(Tsify, Serialize, Deserialize, Debug)]
172#[tsify(into_wasm_abi, from_wasm_abi)]
173pub struct TransactionOptions {
174 pub isolation_level: IsolationLevel,
175 pub timeout_ms: Option<u32>,
176}
177
178#[derive(Tsify, Serialize, Deserialize, Debug)]
179#[tsify(into_wasm_abi, from_wasm_abi)]
180pub enum IsolationLevel {
181 ReadUncommitted,
182 ReadCommitted,
183 RepeatableRead,
184 Serializable,
185}
186
187#[derive(Tsify, Serialize, Deserialize, Debug, Clone, thiserror::Error)]
189#[tsify(into_wasm_abi, from_wasm_abi)]
190#[error("Database error: {message}")]
191pub struct DatabaseError {
192 pub code: String,
193 pub message: String,
194 pub sql: Option<String>,
195}
196
197impl DatabaseError {
198 pub fn new(code: &str, message: &str) -> Self {
199 Self {
200 code: code.to_string(),
201 message: message.to_string(),
202 sql: None,
203 }
204 }
205
206 pub fn with_sql(mut self, sql: &str) -> Self {
207 self.sql = Some(sql.to_string());
208 self
209 }
210}
211
212#[cfg(not(target_arch = "wasm32"))]
213impl From<rusqlite::Error> for DatabaseError {
214 fn from(err: rusqlite::Error) -> Self {
215 DatabaseError::new("SQLITE_ERROR", &err.to_string())
216 }
217}
218
219impl From<JsValue> for DatabaseError {
220 fn from(err: JsValue) -> Self {
221 let message = err
222 .as_string()
223 .unwrap_or_else(|| "Unknown JavaScript error".to_string());
224 DatabaseError::new("JS_ERROR", &message)
225 }
226}