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(s, &time::format_description::well_known::Rfc3339) {
129 return ColumnValue::Date((dt.unix_timestamp_nanos() / 1_000_000) as i64);
130 }
131 }
132 if s.len() > 18 && s.chars().all(|c| c.is_digit(10) || c == '-' || c == '+') {
134 return ColumnValue::BigInt(s.clone());
135 }
136 ColumnValue::Text(s.clone())
137 },
138 rusqlite::types::Value::Blob(b) => ColumnValue::Blob(b.clone()),
139 }
140 }
141
142 #[cfg(not(target_arch = "wasm32"))]
143 pub fn to_rusqlite_value(&self) -> rusqlite::types::Value {
144 match self {
145 ColumnValue::Null => rusqlite::types::Value::Null,
146 ColumnValue::Integer(i) => rusqlite::types::Value::Integer(*i),
147 ColumnValue::Real(f) => rusqlite::types::Value::Real(*f),
148 ColumnValue::Text(s) => rusqlite::types::Value::Text(s.clone()),
149 ColumnValue::Blob(b) => rusqlite::types::Value::Blob(b.clone()),
150 ColumnValue::Date(ts) => {
151 let dt = time::OffsetDateTime::from_unix_timestamp_nanos((*ts as i128) * 1_000_000)
153 .unwrap_or_else(|_| time::OffsetDateTime::UNIX_EPOCH);
154 let formatted = dt.format(&time::format_description::well_known::Rfc3339)
155 .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string());
156 rusqlite::types::Value::Text(formatted)
157 },
158 ColumnValue::BigInt(s) => rusqlite::types::Value::Text(s.clone()),
159 }
160 }
161}
162
163#[derive(Tsify, Serialize, Deserialize, Debug)]
165#[tsify(into_wasm_abi, from_wasm_abi)]
166pub struct TransactionOptions {
167 pub isolation_level: IsolationLevel,
168 pub timeout_ms: Option<u32>,
169}
170
171#[derive(Tsify, Serialize, Deserialize, Debug)]
172#[tsify(into_wasm_abi, from_wasm_abi)]
173pub enum IsolationLevel {
174 ReadUncommitted,
175 ReadCommitted,
176 RepeatableRead,
177 Serializable,
178}
179
180#[derive(Tsify, Serialize, Deserialize, Debug, Clone, thiserror::Error)]
182#[tsify(into_wasm_abi, from_wasm_abi)]
183#[error("Database error: {message}")]
184pub struct DatabaseError {
185 pub code: String,
186 pub message: String,
187 pub sql: Option<String>,
188}
189
190impl DatabaseError {
191 pub fn new(code: &str, message: &str) -> Self {
192 Self {
193 code: code.to_string(),
194 message: message.to_string(),
195 sql: None,
196 }
197 }
198
199 pub fn with_sql(mut self, sql: &str) -> Self {
200 self.sql = Some(sql.to_string());
201 self
202 }
203}
204
205#[cfg(not(target_arch = "wasm32"))]
206impl From<rusqlite::Error> for DatabaseError {
207 fn from(err: rusqlite::Error) -> Self {
208 DatabaseError::new("SQLITE_ERROR", &err.to_string())
209 }
210}
211
212impl From<JsValue> for DatabaseError {
213 fn from(err: JsValue) -> Self {
214 let message = err.as_string().unwrap_or_else(|| "Unknown JavaScript error".to_string());
215 DatabaseError::new("JS_ERROR", &message)
216 }
217}