From 02413bcd0df2fd751e52363e5a6e457e18bfffb1 Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:40:34 +0300 Subject: [PATCH 01/14] add OPFS web-sys features for file system access Features added: StorageManager, FileSystemDirectoryHandle, FileSystemHandle, FileSystemRemoveOptions, FileSystemGetDirectoryOptions --- Cargo.lock | 4 ++-- Cargo.toml | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a9a9df..d003c62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4070,7 +4070,7 @@ dependencies = [ [[package]] name = "sqlite-web" -version = "0.0.1-alpha.9" +version = "0.0.1-alpha.20" dependencies = [ "base64 0.21.7", "js-sys", @@ -4086,7 +4086,7 @@ dependencies = [ [[package]] name = "sqlite-web-core" -version = "0.0.1-alpha.9" +version = "0.0.1-alpha.20" dependencies = [ "alloy", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index f130602..cfbb620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,17 +20,22 @@ wasm-bindgen-futures = "0.4" js-sys = "0.3" web-sys = { version = "0.3", features = [ "Blob", - "BlobPropertyBag", + "BlobPropertyBag", "Url", "Worker", "WorkerOptions", "WorkerType", "BroadcastChannel", - "MessageEvent", + "MessageEvent", "DedicatedWorkerGlobalScope", "Navigator", "Window", - "Location" + "Location", + "StorageManager", + "FileSystemDirectoryHandle", + "FileSystemHandle", + "FileSystemRemoveOptions", + "FileSystemGetDirectoryOptions" ]} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" From b85dcb08ed1fc17c4049bdd530ceceaa2a94dc92 Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:43:17 +0300 Subject: [PATCH 02/14] add OpfsDeletionFailed error variant --- packages/sqlite-web/src/errors.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/sqlite-web/src/errors.rs b/packages/sqlite-web/src/errors.rs index f0a924d..e45780f 100644 --- a/packages/sqlite-web/src/errors.rs +++ b/packages/sqlite-web/src/errors.rs @@ -12,6 +12,8 @@ pub enum SQLiteWasmDatabaseError { InitializationPending, #[error("Initialization failed: {0}")] InitializationFailed(String), + #[error("OPFS deletion failed: {0}")] + OpfsDeletionFailed(String), } impl From for SQLiteWasmDatabaseError { From 305a7c7de4dc7988c2292c2f8bb2c3dc329866aa Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:43:26 +0300 Subject: [PATCH 03/14] add ReadySignal::reset() method Allows resetting the signal back to pending state for reuse after wipe_and_recreate operations. --- packages/sqlite-web/src/ready.rs | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/sqlite-web/src/ready.rs b/packages/sqlite-web/src/ready.rs index 54a2bfa..c2f89e4 100644 --- a/packages/sqlite-web/src/ready.rs +++ b/packages/sqlite-web/src/ready.rs @@ -87,6 +87,14 @@ impl ReadySignal { } self.promise.borrow_mut().take(); } + + pub(crate) fn reset(&self) { + *self.state.borrow_mut() = InitializationState::Pending; + self.resolve.borrow_mut().take(); + self.reject.borrow_mut().take(); + let ready_promise = create_ready_promise(&self.resolve, &self.reject); + self.promise.borrow_mut().replace(ready_promise); + } } fn create_ready_promise( @@ -144,4 +152,44 @@ mod tests { .expect_err("promise should reject"); assert_eq!(err.as_string().as_deref(), Some("boom")); } + + #[wasm_bindgen_test(async)] + async fn ready_signal_reset_returns_to_pending() { + let signal = ReadySignal::new(); + signal.mark_ready(); + assert!(matches!(signal.current_state(), InitializationState::Ready)); + + signal.reset(); + + assert!( + matches!(signal.current_state(), InitializationState::Pending), + "state should be Pending after reset" + ); + assert!( + signal.wait_promise().is_ok(), + "promise should exist after reset" + ); + + signal.mark_ready(); + let promise = signal.wait_promise().expect("promise exists after mark_ready"); + wasm_bindgen_futures::JsFuture::from(promise) + .await + .expect("promise should resolve after mark_ready"); + } + + #[wasm_bindgen_test(async)] + async fn ready_signal_reset_from_failed_state() { + let signal = ReadySignal::new(); + signal.mark_failed("initial failure".into()); + + signal.reset(); + + assert!( + matches!(signal.current_state(), InitializationState::Pending), + "state should be Pending after reset from failed" + ); + + signal.mark_ready(); + assert!(matches!(signal.current_state(), InitializationState::Ready)); + } } From 01cecfce95dc3e1716f7913e9c5541c52d03880b Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:44:03 +0300 Subject: [PATCH 04/14] add OPFS sahpool deletion utility Provides delete_opfs_sahpool_directory() to remove the .opfs-sahpool directory used by sqlite-wasm-rs for persistent storage. --- packages/sqlite-web/src/lib.rs | 1 + packages/sqlite-web/src/opfs.rs | 160 ++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 packages/sqlite-web/src/opfs.rs diff --git a/packages/sqlite-web/src/lib.rs b/packages/sqlite-web/src/lib.rs index 86a0b38..4f355dd 100644 --- a/packages/sqlite-web/src/lib.rs +++ b/packages/sqlite-web/src/lib.rs @@ -1,6 +1,7 @@ mod db; mod errors; mod messages; +mod opfs; mod params; mod ready; mod utils; diff --git a/packages/sqlite-web/src/opfs.rs b/packages/sqlite-web/src/opfs.rs new file mode 100644 index 0000000..93aa6f3 --- /dev/null +++ b/packages/sqlite-web/src/opfs.rs @@ -0,0 +1,160 @@ +use js_sys::Reflect; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; +use web_sys::{FileSystemDirectoryHandle, FileSystemGetDirectoryOptions, FileSystemRemoveOptions}; + +use crate::errors::SQLiteWasmDatabaseError; +use crate::utils::describe_js_value; + +const SAHPOOL_DIR_NAME: &str = ".opfs-sahpool"; + +pub async fn delete_opfs_sahpool_directory() -> Result<(), SQLiteWasmDatabaseError> { + let root = get_opfs_root().await?; + + let sahpool_dir = match get_directory_if_exists(&root, SAHPOOL_DIR_NAME).await? { + Some(dir) => dir, + None => return Ok(()), + }; + + delete_directory_contents(&sahpool_dir).await?; + + let remove_options = FileSystemRemoveOptions::new(); + remove_options.set_recursive(true); + JsFuture::from(root.remove_entry_with_options(SAHPOOL_DIR_NAME, &remove_options)) + .await + .map_err(|e| { + SQLiteWasmDatabaseError::OpfsDeletionFailed(format!( + "failed to remove sahpool directory: {}", + describe_js_value(&e) + )) + })?; + + Ok(()) +} + +async fn get_opfs_root() -> Result { + let navigator = web_sys::window() + .map(|w| w.navigator()) + .or_else(|| { + let global = js_sys::global(); + Reflect::get(&global, &JsValue::from_str("navigator")) + .ok() + .and_then(|n| n.dyn_into::().ok()) + }) + .ok_or_else(|| { + SQLiteWasmDatabaseError::OpfsDeletionFailed("navigator not available".into()) + })?; + + let storage = navigator.storage(); + JsFuture::from(storage.get_directory()) + .await + .and_then(|v| v.dyn_into()) + .map_err(|e| { + SQLiteWasmDatabaseError::OpfsDeletionFailed(format!( + "failed to get OPFS root: {}", + describe_js_value(&e) + )) + }) +} + +async fn get_directory_if_exists( + parent: &FileSystemDirectoryHandle, + name: &str, +) -> Result, SQLiteWasmDatabaseError> { + let options = FileSystemGetDirectoryOptions::new(); + options.set_create(false); + + match JsFuture::from(parent.get_directory_handle_with_options(name, &options)).await { + Ok(handle) => handle.dyn_into().map(Some).map_err(|e| { + SQLiteWasmDatabaseError::OpfsDeletionFailed(format!( + "directory handle type mismatch: {}", + describe_js_value(&e) + )) + }), + Err(_) => Ok(None), + } +} + +async fn delete_directory_contents( + dir: &FileSystemDirectoryHandle, +) -> Result<(), SQLiteWasmDatabaseError> { + let entry_names = collect_entry_names(dir).await?; + + for name in entry_names { + let remove_options = FileSystemRemoveOptions::new(); + remove_options.set_recursive(true); + let _ = JsFuture::from(dir.remove_entry_with_options(&name, &remove_options)).await; + } + + Ok(()) +} + +async fn collect_entry_names( + dir: &FileSystemDirectoryHandle, +) -> Result, SQLiteWasmDatabaseError> { + let entries_iter = dir.entries(); + let mut names = Vec::new(); + + loop { + let next_fn = Reflect::get(&entries_iter, &JsValue::from_str("next")) + .ok() + .and_then(|f| f.dyn_into::().ok()) + .ok_or_else(|| { + SQLiteWasmDatabaseError::OpfsDeletionFailed( + "entries iterator missing next method".into(), + ) + })?; + + let next_promise = next_fn.call0(&entries_iter).map_err(|e| { + SQLiteWasmDatabaseError::OpfsDeletionFailed(format!( + "failed to call next(): {}", + describe_js_value(&e) + )) + })?; + + let result = JsFuture::from(js_sys::Promise::from(next_promise)) + .await + .map_err(|e| { + SQLiteWasmDatabaseError::OpfsDeletionFailed(format!( + "iterator next() failed: {}", + describe_js_value(&e) + )) + })?; + + let done = Reflect::get(&result, &JsValue::from_str("done")) + .ok() + .and_then(|v| v.as_bool()) + .unwrap_or(true); + + if done { + break; + } + + if let Some(name) = Reflect::get(&result, &JsValue::from_str("value")) + .ok() + .and_then(|v| Reflect::get(&v, &JsValue::from_f64(0.0)).ok()) + .and_then(|v| v.as_string()) + { + names.push(name); + } + } + + Ok(names) +} + +#[cfg(all(test, target_family = "wasm"))] +mod tests { + use super::*; + use wasm_bindgen_test::*; + + wasm_bindgen_test_configure!(run_in_browser); + + #[wasm_bindgen_test] + async fn delete_nonexistent_sahpool_succeeds() { + let result = delete_opfs_sahpool_directory().await; + assert!( + result.is_ok(), + "deleting nonexistent sahpool directory should succeed silently" + ); + } +} From 3ab8cf4012ba2e5d309f6beb8eebb4f1dcedb04d Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:44:19 +0300 Subject: [PATCH 05/14] add wipe_and_recreate() method to SQLiteWasmDatabase Allows completely wiping and recreating the database: - Terminates current worker - Rejects pending queries - Deletes OPFS storage - Creates fresh worker - Same instance remains valid after operation Uses interior mutability (Rc>) to allow bound callbacks to continue working. --- packages/sqlite-web/src/db.rs | 120 ++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/packages/sqlite-web/src/db.rs b/packages/sqlite-web/src/db.rs index 01f8505..c167719 100644 --- a/packages/sqlite-web/src/db.rs +++ b/packages/sqlite-web/src/db.rs @@ -11,6 +11,7 @@ use web_sys::Worker; use crate::errors::SQLiteWasmDatabaseError; use crate::messages::WORKER_ERROR_TYPE_INITIALIZATION_PENDING; +use crate::opfs::delete_opfs_sahpool_directory; use crate::params::normalize_params_js; use crate::ready::{InitializationState, ReadySignal}; use crate::utils::describe_js_value; @@ -19,7 +20,8 @@ use crate::worker_template::generate_self_contained_worker; #[wasm_bindgen] pub struct SQLiteWasmDatabase { - worker: Worker, + worker: Rc>, + db_name: String, pending_queries: Rc>>, next_request_id: Rc>, ready_signal: ReadySignal, @@ -63,7 +65,8 @@ impl SQLiteWasmDatabase { let next_request_id = Rc::new(RefCell::new(1u32)); Ok(SQLiteWasmDatabase { - worker, + worker: Rc::new(RefCell::new(worker)), + db_name: db_name.to_string(), pending_queries, next_request_id, ready_signal, @@ -112,7 +115,7 @@ impl SQLiteWasmDatabase { sql: &str, params: Option, ) -> Result { - let worker = &self.worker; + let worker = Rc::clone(&self.worker); let pending_queries = Rc::clone(&self.pending_queries); let sql = sql.to_string(); let params_array = Self::normalize_params(params)?; @@ -154,8 +157,8 @@ impl SQLiteWasmDatabase { } let rid_for_insert = request_id; - let promise = - js_sys::Promise::new(&mut |resolve, reject| match worker.post_message(&message) { + let promise = js_sys::Promise::new(&mut |resolve, reject| { + match worker.borrow().post_message(&message) { Ok(()) => { pending_queries .borrow_mut() @@ -164,7 +167,8 @@ impl SQLiteWasmDatabase { Err(err) => { let _ = reject.call1(&JsValue::NULL, &err); } - }); + } + }); let result = match JsFuture::from(promise).await { Ok(value) => value, @@ -177,6 +181,37 @@ impl SQLiteWasmDatabase { }; Ok(result.as_string().unwrap_or_else(|| format!("{result:?}"))) } + + #[wasm_export(js_name = "wipeAndRecreate", unchecked_return_type = "void")] + pub async fn wipe_and_recreate(&self) -> Result<(), SQLiteWasmDatabaseError> { + self.worker.borrow().terminate(); + + for (_, (_, reject)) in self.pending_queries.borrow_mut().drain() { + let err = JsValue::from_str("Database wipe in progress"); + let _ = reject.call1(&JsValue::NULL, &err); + } + + self.ready_signal.reset(); + + delete_opfs_sahpool_directory().await?; + + let worker_code = generate_self_contained_worker(&self.db_name); + let new_worker = + create_worker_from_code(&worker_code).map_err(SQLiteWasmDatabaseError::JsError)?; + + install_onmessage_handler( + &new_worker, + Rc::clone(&self.pending_queries), + self.ready_signal.clone(), + ); + + *self.worker.borrow_mut() = new_worker; + *self.next_request_id.borrow_mut() = 1; + + self.wait_until_ready().await?; + + Ok(()) + } } fn is_initialization_pending_error(err: &JsValue) -> bool { @@ -294,4 +329,77 @@ mod tests { let js_val = JsValue::from_str(WORKER_ERROR_TYPE_INITIALIZATION_PENDING); assert!(is_initialization_pending_error(&js_val)); } + + #[wasm_bindgen_test(async)] + async fn wipe_and_recreate_clears_all_data() { + let db = SQLiteWasmDatabase::new("test_wipe_data").await.unwrap(); + db.query( + "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", + None, + ) + .await + .unwrap(); + db.query("INSERT INTO users (name) VALUES ('Alice')", None) + .await + .unwrap(); + + let result = db + .query("SELECT COUNT(*) as count FROM users", None) + .await + .unwrap(); + assert!(result.contains("\"count\":1")); + + db.wipe_and_recreate().await.unwrap(); + + let result = db.query("SELECT * FROM users", None).await; + assert!(result.is_err() || result.unwrap().contains("no such table")); + } + + #[wasm_bindgen_test(async)] + async fn wipe_and_recreate_allows_new_operations() { + let db = SQLiteWasmDatabase::new("test_wipe_new_ops") + .await + .unwrap(); + db.query("CREATE TABLE old_table (id INTEGER)", None) + .await + .unwrap(); + + db.wipe_and_recreate().await.unwrap(); + + let create_result = db + .query( + "CREATE TABLE new_table (id INTEGER PRIMARY KEY, value TEXT)", + None, + ) + .await; + assert!(create_result.is_ok()); + + let insert_result = db + .query("INSERT INTO new_table (value) VALUES ('test')", None) + .await; + assert!(insert_result.is_ok()); + + let select_result = db.query("SELECT * FROM new_table", None).await.unwrap(); + assert!(select_result.contains("test")); + } + + #[wasm_bindgen_test(async)] + async fn wipe_and_recreate_can_be_called_multiple_times() { + let db = SQLiteWasmDatabase::new("test_multi_wipe").await.unwrap(); + + for i in 0..3 { + db.query(&format!("CREATE TABLE t{} (id INTEGER)", i), None) + .await + .unwrap(); + db.wipe_and_recreate().await.unwrap(); + } + + let result = db + .query("SELECT name FROM sqlite_master WHERE type='table'", None) + .await + .unwrap(); + assert!(!result.contains("t0")); + assert!(!result.contains("t1")); + assert!(!result.contains("t2")); + } } From 5ae510b238bfd00c4b90efd6e756455fea8085ac Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:44:27 +0300 Subject: [PATCH 06/14] add wipe_and_recreate tests and demo UI button - 8 integration tests covering data clearing, post-wipe operations, instance validity, and error recovery - Wipe & Recreate button in demo app for manual testing --- pkg/package.json | 6 +- svelte-test/package-lock.json | 384 ++++++++++-------- svelte-test/package.json | 2 +- svelte-test/src/routes/+page.svelte | 55 +++ .../integration/wipe-and-recreate.test.ts | 128 ++++++ 5 files changed, 407 insertions(+), 168 deletions(-) create mode 100644 svelte-test/tests/integration/wipe-and-recreate.test.ts diff --git a/pkg/package.json b/pkg/package.json index 3a57823..71df3f7 100644 --- a/pkg/package.json +++ b/pkg/package.json @@ -2,10 +2,6 @@ "name": "@rainlanguage/sqlite-web", "type": "module", "version": "0.0.1-alpha.20", - "repository": { - "type": "git", - "url": "https://github.com/rainlanguage/sqlite-web" - }, "files": [ "sqlite_web_bg.wasm", "sqlite_web.js", @@ -16,4 +12,4 @@ "sideEffects": [ "./snippets/*" ] -} +} \ No newline at end of file diff --git a/svelte-test/package-lock.json b/svelte-test/package-lock.json index ba2cf71..25a6000 100644 --- a/svelte-test/package-lock.json +++ b/svelte-test/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "dependencies": { "@rainlanguage/float": "^0.0.0-alpha.22", - "@rainlanguage/sqlite-web": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.9.tgz" + "@rainlanguage/sqlite-web": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.20.tgz" }, "devDependencies": { "@sveltejs/adapter-auto": "^6.0.0", @@ -35,13 +35,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -60,9 +60,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "dev": true, "license": "MIT", "engines": { @@ -512,9 +512,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -912,14 +912,14 @@ } }, "node_modules/@rainlanguage/sqlite-web": { - "version": "0.0.1-alpha.9", - "resolved": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.9.tgz", - "integrity": "sha512-pQZ0+4wV9t36PYrklecBDHX2Wg62p3rDyYJ7XlaBcBxbSEoYPKaEWSC7TFTzzPXnUDq32PhBR9GOlxM941ayBA==" + "version": "0.0.1-alpha.20", + "resolved": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.20.tgz", + "integrity": "sha512-RTG0hDpnnpcCICVttxI9F0kErfbTNczu9BW+/9GlifdyoZE6ozfu3SH0z9CAyvEGf7aLkXyLwqacg/hVevNMqA==" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", - "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", + "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", "cpu": [ "arm" ], @@ -931,9 +931,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", - "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", + "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", "cpu": [ "arm64" ], @@ -945,9 +945,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", - "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", + "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", "cpu": [ "arm64" ], @@ -959,9 +959,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", - "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", + "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", "cpu": [ "x64" ], @@ -973,9 +973,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", - "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", + "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", "cpu": [ "arm64" ], @@ -987,9 +987,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", - "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", + "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", "cpu": [ "x64" ], @@ -1001,9 +1001,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", - "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", + "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", "cpu": [ "arm" ], @@ -1015,9 +1015,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", - "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", + "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", "cpu": [ "arm" ], @@ -1029,9 +1029,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", - "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", + "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", "cpu": [ "arm64" ], @@ -1043,9 +1043,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", - "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", + "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", "cpu": [ "arm64" ], @@ -1057,9 +1057,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", - "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", + "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", + "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", "cpu": [ "loong64" ], @@ -1071,9 +1085,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", - "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", + "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", + "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", "cpu": [ "ppc64" ], @@ -1085,9 +1113,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", - "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", + "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", "cpu": [ "riscv64" ], @@ -1099,9 +1127,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", - "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", + "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", "cpu": [ "riscv64" ], @@ -1113,9 +1141,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", - "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", + "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", "cpu": [ "s390x" ], @@ -1127,9 +1155,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", - "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", + "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", "cpu": [ "x64" ], @@ -1141,9 +1169,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", - "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", + "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", "cpu": [ "x64" ], @@ -1154,10 +1182,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", + "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", - "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", + "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", "cpu": [ "arm64" ], @@ -1169,9 +1211,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", - "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", + "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", "cpu": [ "arm64" ], @@ -1183,9 +1225,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", - "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", + "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", "cpu": [ "ia32" ], @@ -1197,9 +1239,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", - "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", + "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", "cpu": [ "x64" ], @@ -1211,9 +1253,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", - "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", + "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", "cpu": [ "x64" ], @@ -1252,9 +1294,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.49.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.2.tgz", - "integrity": "sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==", + "version": "2.50.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.1.tgz", + "integrity": "sha512-XRHD2i3zC4ukhz2iCQzO4mbsts081PAZnnMAQ7LNpWeYgeBmwMsalf0FGSwhFXBbtr2XViPKnFJBDCckWqrsLw==", "dev": true, "license": "MIT", "dependencies": { @@ -1263,7 +1305,7 @@ "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", - "devalue": "^5.3.2", + "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", @@ -1282,25 +1324,29 @@ "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "peerDependenciesMeta": { "@opentelemetry/api": { "optional": true + }, + "typescript": { + "optional": true } } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.1.tgz", - "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", + "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "dev": true, "license": "MIT", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", - "debug": "^4.4.1", "deepmerge": "^4.3.1", - "magic-string": "^0.30.17", + "magic-string": "^0.30.21", + "obug": "^2.1.0", "vitefu": "^1.1.1" }, "engines": { @@ -1312,13 +1358,13 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.1.tgz", - "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz", + "integrity": "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.1" + "obug": "^2.1.0" }, "engines": { "node": "^20.19 || ^22.12 || >=24" @@ -1403,9 +1449,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", - "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", "dev": true, "license": "MIT", "dependencies": { @@ -2566,9 +2612,9 @@ } }, "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "license": "MIT", "engines": { @@ -2765,9 +2811,9 @@ } }, "node_modules/devalue": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz", - "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", + "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", "dev": true, "license": "MIT" }, @@ -3115,9 +3161,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3128,9 +3174,9 @@ } }, "node_modules/esrap": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.1.tgz", - "integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.2.tgz", + "integrity": "sha512-zA6497ha+qKvoWIK+WM9NAh5ni17sKZKhbS5B3PoYbBvaYHZWoS33zmFybmyqpn07RLUxSmn+RCls2/XF+d0oQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3862,9 +3908,9 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.12.4", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.4.tgz", - "integrity": "sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==", + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.7.tgz", + "integrity": "sha512-retd5i3xCZDVWMYjHEVuKTmhqY8lSsxujjVrZiGbbdoxxIBg5S7rCuYy/YQpfrTYIxpd/o0Kyb/3H+1udBMoYg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3921,9 +3967,9 @@ } }, "node_modules/msw/node_modules/type-fest": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz", - "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.1.tgz", + "integrity": "sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ==", "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { @@ -3972,6 +4018,17 @@ "dev": true, "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4137,13 +4194,13 @@ } }, "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.0.tgz", + "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.57.0" + "playwright-core": "1.58.0" }, "bin": { "playwright": "cli.js" @@ -4156,9 +4213,9 @@ } }, "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.0.tgz", + "integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4306,9 +4363,9 @@ } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "peer": true, @@ -4323,9 +4380,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", "dependencies": { @@ -4471,9 +4528,9 @@ } }, "node_modules/rollup": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", - "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", + "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", "dev": true, "license": "MIT", "dependencies": { @@ -4487,28 +4544,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.54.0", - "@rollup/rollup-android-arm64": "4.54.0", - "@rollup/rollup-darwin-arm64": "4.54.0", - "@rollup/rollup-darwin-x64": "4.54.0", - "@rollup/rollup-freebsd-arm64": "4.54.0", - "@rollup/rollup-freebsd-x64": "4.54.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", - "@rollup/rollup-linux-arm-musleabihf": "4.54.0", - "@rollup/rollup-linux-arm64-gnu": "4.54.0", - "@rollup/rollup-linux-arm64-musl": "4.54.0", - "@rollup/rollup-linux-loong64-gnu": "4.54.0", - "@rollup/rollup-linux-ppc64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-musl": "4.54.0", - "@rollup/rollup-linux-s390x-gnu": "4.54.0", - "@rollup/rollup-linux-x64-gnu": "4.54.0", - "@rollup/rollup-linux-x64-musl": "4.54.0", - "@rollup/rollup-openharmony-arm64": "4.54.0", - "@rollup/rollup-win32-arm64-msvc": "4.54.0", - "@rollup/rollup-win32-ia32-msvc": "4.54.0", - "@rollup/rollup-win32-x64-gnu": "4.54.0", - "@rollup/rollup-win32-x64-msvc": "4.54.0", + "@rollup/rollup-android-arm-eabi": "4.57.0", + "@rollup/rollup-android-arm64": "4.57.0", + "@rollup/rollup-darwin-arm64": "4.57.0", + "@rollup/rollup-darwin-x64": "4.57.0", + "@rollup/rollup-freebsd-arm64": "4.57.0", + "@rollup/rollup-freebsd-x64": "4.57.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", + "@rollup/rollup-linux-arm-musleabihf": "4.57.0", + "@rollup/rollup-linux-arm64-gnu": "4.57.0", + "@rollup/rollup-linux-arm64-musl": "4.57.0", + "@rollup/rollup-linux-loong64-gnu": "4.57.0", + "@rollup/rollup-linux-loong64-musl": "4.57.0", + "@rollup/rollup-linux-ppc64-gnu": "4.57.0", + "@rollup/rollup-linux-ppc64-musl": "4.57.0", + "@rollup/rollup-linux-riscv64-gnu": "4.57.0", + "@rollup/rollup-linux-riscv64-musl": "4.57.0", + "@rollup/rollup-linux-s390x-gnu": "4.57.0", + "@rollup/rollup-linux-x64-gnu": "4.57.0", + "@rollup/rollup-linux-x64-musl": "4.57.0", + "@rollup/rollup-openbsd-x64": "4.57.0", + "@rollup/rollup-openharmony-arm64": "4.57.0", + "@rollup/rollup-win32-arm64-msvc": "4.57.0", + "@rollup/rollup-win32-ia32-msvc": "4.57.0", + "@rollup/rollup-win32-x64-gnu": "4.57.0", + "@rollup/rollup-win32-x64-msvc": "4.57.0", "fsevents": "~2.3.2" } }, @@ -4733,9 +4793,9 @@ } }, "node_modules/svelte": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.0.tgz", - "integrity": "sha512-ZhLtvroYxUxr+HQJfMZEDRsGsmU46x12RvAv/zi9584f5KOX7bUrEbhPJ7cKFmUvZTJXi/CFZUYwDC6M1FigPw==", + "version": "5.48.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.48.3.tgz", + "integrity": "sha512-w7QZ398cdNherTdiQ/v3SYLLGOO4948Jgjh04PYqtTYVohmBvbmFwLmo7pp8gp4/1tceRWfSTjHgjtfpCVNJmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4747,7 +4807,7 @@ "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", - "devalue": "^5.5.0", + "devalue": "^5.6.2", "esm-env": "^1.2.1", "esrap": "^2.2.1", "is-reference": "^3.0.3", @@ -5348,9 +5408,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", - "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { @@ -6680,9 +6740,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "license": "MIT", "engines": { diff --git a/svelte-test/package.json b/svelte-test/package.json index d3777d3..5a9ad8e 100644 --- a/svelte-test/package.json +++ b/svelte-test/package.json @@ -41,6 +41,6 @@ "type": "module", "dependencies": { "@rainlanguage/float": "^0.0.0-alpha.22", - "@rainlanguage/sqlite-web": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.9.tgz" + "@rainlanguage/sqlite-web": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.20.tgz" } } diff --git a/svelte-test/src/routes/+page.svelte b/svelte-test/src/routes/+page.svelte index 26e98cd..5dbef44 100644 --- a/svelte-test/src/routes/+page.svelte +++ b/svelte-test/src/routes/+page.svelte @@ -106,6 +106,39 @@ isLoading = false; } } + + async function wipeAndRecreate() { + if (!db) return; + if (!confirm('This will completely wipe the database and recreate it from scratch. All data will be lost. Continue?')) { + return; + } + try { + isLoading = true; + status = 'Wiping database...'; + + const result = await db.wipeAndRecreate(); + if (result.error) { + throw new Error(result.error.msg); + } + + status = 'Recreating schema...'; + await db.query(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT UNIQUE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + await loadUsers(); + status = 'Ready - Database wiped and recreated ✅'; + } catch (error) { + status = `Wipe error: ${error instanceof Error ? error.message : 'Unknown error'}`; + } finally { + isLoading = false; + } + }
@@ -149,6 +182,9 @@ Clear All {/if} +
@@ -328,6 +364,25 @@ background: #c82333; } + .wipe-btn { + background: #6f42c1; + color: white; + padding: 6px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + } + + .wipe-btn:hover:not(:disabled) { + background: #5a32a3; + } + + .wipe-btn:disabled { + background: #6c757d; + cursor: not-allowed; + } + .loading { text-align: center; color: #6c757d; diff --git a/svelte-test/tests/integration/wipe-and-recreate.test.ts b/svelte-test/tests/integration/wipe-and-recreate.test.ts new file mode 100644 index 0000000..cb77a67 --- /dev/null +++ b/svelte-test/tests/integration/wipe-and-recreate.test.ts @@ -0,0 +1,128 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { createTestDatabase } from '../fixtures/test-helpers.js'; +import type { SQLiteWasmDatabase } from '@rainlanguage/sqlite-web'; + +describe.sequential('wipeAndRecreate', () => { + let db: SQLiteWasmDatabase; + + beforeAll(async () => { + db = await createTestDatabase(`test-wipe-${Date.now()}`); + }); + + it('should clear all tables after wipeAndRecreate', async () => { + await db.query('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)'); + await db.query("INSERT INTO users (name) VALUES ('Alice'), ('Bob')"); + + let result = await db.query('SELECT COUNT(*) as count FROM users'); + const data = JSON.parse(result.value!); + expect(data[0].count).toBe(2); + + const wipeResult = await db.wipeAndRecreate(); + expect(wipeResult.error).toBeUndefined(); + + result = await db.query('SELECT * FROM users'); + expect(result.error).toBeDefined(); + expect(result.error!.msg).toContain('no such table'); + }); + + it('should clear multiple tables', async () => { + await db.query('CREATE TABLE t1 (id INTEGER)'); + await db.query('CREATE TABLE t2 (id INTEGER)'); + await db.query('CREATE TABLE t3 (id INTEGER)'); + await db.query('INSERT INTO t1 VALUES (1)'); + await db.query('INSERT INTO t2 VALUES (2)'); + await db.query('INSERT INTO t3 VALUES (3)'); + + await db.wipeAndRecreate(); + + for (const table of ['t1', 't2', 't3']) { + const result = await db.query(`SELECT * FROM ${table}`); + expect(result.error).toBeDefined(); + } + }); + + it('should allow creating new tables after wipe', async () => { + await db.query('CREATE TABLE old (id INTEGER)'); + await db.wipeAndRecreate(); + + const result = await db.query( + 'CREATE TABLE new_table (id INTEGER PRIMARY KEY, value TEXT)' + ); + expect(result.error).toBeUndefined(); + expect(result.value).toContain('successfully'); + }); + + it('should allow full CRUD operations after wipe', async () => { + await db.wipeAndRecreate(); + + await db.query('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)'); + + await db.query("INSERT INTO items (name) VALUES ('item1')"); + + let result = await db.query('SELECT * FROM items'); + expect(result.value).toContain('item1'); + + await db.query("UPDATE items SET name = 'updated' WHERE id = 1"); + result = await db.query('SELECT * FROM items'); + expect(result.value).toContain('updated'); + + await db.query('DELETE FROM items WHERE id = 1'); + result = await db.query('SELECT COUNT(*) as count FROM items'); + const countData = JSON.parse(result.value!); + expect(countData[0].count).toBe(0); + }); + + it('should support parameterized queries after wipe', async () => { + await db.wipeAndRecreate(); + + await db.query('CREATE TABLE params_test (id INTEGER, value TEXT)'); + + const result = await db.query( + 'INSERT INTO params_test (id, value) VALUES (?, ?)', + [42, 'parameterized'] + ); + expect(result.error).toBeUndefined(); + + const selectResult = await db.query( + 'SELECT * FROM params_test WHERE id = ?', + [42] + ); + expect(selectResult.value).toContain('parameterized'); + }); + + it('should keep same instance valid after wipe', async () => { + const originalDb = db; + + await db.wipeAndRecreate(); + + const result = await originalDb.query('SELECT 1 + 1 as sum'); + expect(result.error).toBeUndefined(); + const sumData = JSON.parse(result.value!); + expect(sumData[0].sum).toBe(2); + }); + + it('should allow multiple consecutive wipes', async () => { + for (let i = 0; i < 5; i++) { + await db.query(`CREATE TABLE iter_${i} (id INTEGER)`); + const wipeResult = await db.wipeAndRecreate(); + expect(wipeResult.error).toBeUndefined(); + } + + const result = await db.query('SELECT sqlite_version()'); + expect(result.error).toBeUndefined(); + }); + + it('should recover from corrupted state simulation', async () => { + await db.query('CREATE TABLE important_data (id INTEGER, value TEXT)'); + await db.query("INSERT INTO important_data VALUES (1, 'critical')"); + + const result = await db.wipeAndRecreate(); + expect(result.error).toBeUndefined(); + + await db.query('CREATE TABLE important_data (id INTEGER, value TEXT)'); + await db.query("INSERT INTO important_data VALUES (1, 'recovered')"); + + const selectResult = await db.query('SELECT * FROM important_data'); + expect(selectResult.value).toContain('recovered'); + }); +}); From 1a6fa530cbe63904fea06c3844140aa1b8348b1d Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:45:00 +0300 Subject: [PATCH 07/14] ignore vitest screenshot artifacts --- svelte-test/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/svelte-test/.gitignore b/svelte-test/.gitignore index 3b462cb..0504bf7 100644 --- a/svelte-test/.gitignore +++ b/svelte-test/.gitignore @@ -21,3 +21,6 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Test screenshots +tests/**/__screenshots__ From 78c9010b8f6363c54c1479e9a678f5331c38bccb Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:45:18 +0300 Subject: [PATCH 08/14] Revert "ignore vitest screenshot artifacts" This reverts commit 1a6fa530cbe63904fea06c3844140aa1b8348b1d. --- svelte-test/.gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/svelte-test/.gitignore b/svelte-test/.gitignore index 0504bf7..3b462cb 100644 --- a/svelte-test/.gitignore +++ b/svelte-test/.gitignore @@ -21,6 +21,3 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* - -# Test screenshots -tests/**/__screenshots__ From cf1c2d715fa72e7ee35ce97c5e81c3be19203388 Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 10:46:14 +0300 Subject: [PATCH 09/14] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b013817..2bc3426 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ pkg/sqlite_web_bg.wasm.b64 # macOS .DS_Store + +.claude From b47e95d25d30e6dadb45297c45223160162774f6 Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 12:54:20 +0300 Subject: [PATCH 10/14] fix ReadySignal::reset() to reject pending promises - Reject existing promise before dropping to prevent hangs - Fix test to capture promise before calling mark_ready() --- packages/sqlite-web/src/ready.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/sqlite-web/src/ready.rs b/packages/sqlite-web/src/ready.rs index c2f89e4..8acda87 100644 --- a/packages/sqlite-web/src/ready.rs +++ b/packages/sqlite-web/src/ready.rs @@ -89,9 +89,12 @@ impl ReadySignal { } pub(crate) fn reset(&self) { - *self.state.borrow_mut() = InitializationState::Pending; self.resolve.borrow_mut().take(); - self.reject.borrow_mut().take(); + if let Some(reject) = self.reject.borrow_mut().take() { + let _ = reject.call1(&JsValue::NULL, &JsValue::from_str("Ready signal reset")); + } + self.promise.borrow_mut().take(); + *self.state.borrow_mut() = InitializationState::Pending; let ready_promise = create_ready_promise(&self.resolve, &self.reject); self.promise.borrow_mut().replace(ready_promise); } @@ -170,8 +173,8 @@ mod tests { "promise should exist after reset" ); + let promise = signal.wait_promise().expect("promise exists after reset"); signal.mark_ready(); - let promise = signal.wait_promise().expect("promise exists after mark_ready"); wasm_bindgen_futures::JsFuture::from(promise) .await .expect("promise should resolve after mark_ready"); From f41259078b246a4a76f463e69ca71f9d7b6a186c Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 12:54:26 +0300 Subject: [PATCH 11/14] improve OPFS error handling with DomException detection - Add DomException web-sys feature - Distinguish NotFoundError from other errors in get_directory_if_exists() - Remove redundant test --- Cargo.toml | 3 ++- packages/sqlite-web/src/opfs.rs | 30 ++++++++++++------------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cfbb620..26f8653 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,8 @@ web-sys = { version = "0.3", features = [ "FileSystemDirectoryHandle", "FileSystemHandle", "FileSystemRemoveOptions", - "FileSystemGetDirectoryOptions" + "FileSystemGetDirectoryOptions", + "DomException" ]} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/packages/sqlite-web/src/opfs.rs b/packages/sqlite-web/src/opfs.rs index 93aa6f3..d554906 100644 --- a/packages/sqlite-web/src/opfs.rs +++ b/packages/sqlite-web/src/opfs.rs @@ -71,7 +71,18 @@ async fn get_directory_if_exists( describe_js_value(&e) )) }), - Err(_) => Ok(None), + Err(e) => { + if let Some(dom_ex) = e.dyn_ref::() { + if dom_ex.name() == "NotFoundError" { + return Ok(None); + } + } + Err(SQLiteWasmDatabaseError::OpfsDeletionFailed(format!( + "failed to get directory '{}': {}", + name, + describe_js_value(&e) + ))) + } } } @@ -141,20 +152,3 @@ async fn collect_entry_names( Ok(names) } - -#[cfg(all(test, target_family = "wasm"))] -mod tests { - use super::*; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - #[wasm_bindgen_test] - async fn delete_nonexistent_sahpool_succeeds() { - let result = delete_opfs_sahpool_directory().await; - assert!( - result.is_ok(), - "deleting nonexistent sahpool directory should succeed silently" - ); - } -} From 57128f0eb01fe9c5f80263863ce9bde714e7c46b Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 12:54:33 +0300 Subject: [PATCH 12/14] clean up wipe_and_recreate and consolidate tests - Remove request ID reset to avoid ID collisions with stale replies - Consolidate wipe tests into single test function - Add NaN/Infinity parameter validation tests - Remove redundant tests from tests.rs --- packages/sqlite-web/src/db.rs | 62 ++++----- packages/sqlite-web/src/tests.rs | 212 +------------------------------ 2 files changed, 32 insertions(+), 242 deletions(-) diff --git a/packages/sqlite-web/src/db.rs b/packages/sqlite-web/src/db.rs index c167719..c1bded2 100644 --- a/packages/sqlite-web/src/db.rs +++ b/packages/sqlite-web/src/db.rs @@ -157,16 +157,17 @@ impl SQLiteWasmDatabase { } let rid_for_insert = request_id; - let promise = js_sys::Promise::new(&mut |resolve, reject| { - match worker.borrow().post_message(&message) { - Ok(()) => { - pending_queries - .borrow_mut() - .insert(rid_for_insert, (resolve, reject)); - } - Err(err) => { - let _ = reject.call1(&JsValue::NULL, &err); - } + let promise = js_sys::Promise::new(&mut |resolve, reject| match worker + .borrow() + .post_message(&message) + { + Ok(()) => { + pending_queries + .borrow_mut() + .insert(rid_for_insert, (resolve, reject)); + } + Err(err) => { + let _ = reject.call1(&JsValue::NULL, &err); } }); @@ -206,7 +207,6 @@ impl SQLiteWasmDatabase { ); *self.worker.borrow_mut() = new_worker; - *self.next_request_id.borrow_mut() = 1; self.wait_until_ready().await?; @@ -331,8 +331,10 @@ mod tests { } #[wasm_bindgen_test(async)] - async fn wipe_and_recreate_clears_all_data() { - let db = SQLiteWasmDatabase::new("test_wipe_data").await.unwrap(); + async fn wipe_and_recreate_tests() { + let db = SQLiteWasmDatabase::new("test_wipe").await.unwrap(); + db.wipe_and_recreate().await.unwrap(); + db.query( "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", None, @@ -347,24 +349,12 @@ mod tests { .query("SELECT COUNT(*) as count FROM users", None) .await .unwrap(); - assert!(result.contains("\"count\":1")); + assert!(result.contains("\"count\": 1")); db.wipe_and_recreate().await.unwrap(); let result = db.query("SELECT * FROM users", None).await; assert!(result.is_err() || result.unwrap().contains("no such table")); - } - - #[wasm_bindgen_test(async)] - async fn wipe_and_recreate_allows_new_operations() { - let db = SQLiteWasmDatabase::new("test_wipe_new_ops") - .await - .unwrap(); - db.query("CREATE TABLE old_table (id INTEGER)", None) - .await - .unwrap(); - - db.wipe_and_recreate().await.unwrap(); let create_result = db .query( @@ -381,11 +371,6 @@ mod tests { let select_result = db.query("SELECT * FROM new_table", None).await.unwrap(); assert!(select_result.contains("test")); - } - - #[wasm_bindgen_test(async)] - async fn wipe_and_recreate_can_be_called_multiple_times() { - let db = SQLiteWasmDatabase::new("test_multi_wipe").await.unwrap(); for i in 0..3 { db.query(&format!("CREATE TABLE t{} (id INTEGER)", i), None) @@ -401,5 +386,20 @@ mod tests { assert!(!result.contains("t0")); assert!(!result.contains("t1")); assert!(!result.contains("t2")); + + let arr = Array::new(); + arr.push(&JsValue::from_f64(f64::NAN)); + let res = db.query("SELECT ?", Some(arr)).await; + assert!(res.is_err(), "NaN should be rejected"); + + let arr = Array::new(); + arr.push(&JsValue::from_f64(f64::INFINITY)); + let res = db.query("SELECT ?", Some(arr)).await; + assert!(res.is_err(), "+Infinity should be rejected"); + + let arr = Array::new(); + arr.push(&JsValue::from_f64(f64::NEG_INFINITY)); + let res = db.query("SELECT ?", Some(arr)).await; + assert!(res.is_err(), "-Infinity should be rejected"); } } diff --git a/packages/sqlite-web/src/tests.rs b/packages/sqlite-web/src/tests.rs index 05edaa8..afefb90 100644 --- a/packages/sqlite-web/src/tests.rs +++ b/packages/sqlite-web/src/tests.rs @@ -3,13 +3,11 @@ use crate::ready::{InitializationState, ReadySignal}; use crate::worker::handle_worker_control_message; use crate::worker_template::generate_self_contained_worker; -use crate::{SQLiteWasmDatabase, SQLiteWasmDatabaseError}; +use crate::SQLiteWasmDatabaseError; use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; use wasm_bindgen_test::*; -use base64::Engine; use wasm_bindgen_utils::prelude::{serde_wasm_bindgen, WasmEncodedError}; wasm_bindgen_test_configure!(run_in_browser); @@ -65,46 +63,6 @@ fn test_sqlite_wasm_database_error_display() { assert!(error_string.contains("JavaScript error")); } -#[wasm_bindgen_test] -async fn test_sqlite_wasm_database_serialization() { - let db = SQLiteWasmDatabase::new("testdb") - .await - .expect("Should create database"); - let serialized = serde_json::to_string(&db); - - assert!(serialized.is_ok()); - let json_str = serialized.unwrap(); - assert_eq!(json_str, "{}"); -} - -#[wasm_bindgen_test] -async fn test_sqlite_wasm_database_creation() { - let result = SQLiteWasmDatabase::new("testdb").await; - - match result { - Ok(_db) => {} - Err(e) => { - let error_msg = format!("{e:?}"); - assert!(!error_msg.is_empty()); - } - } -} - -#[wasm_bindgen_test] -async fn test_query_message_format() { - if let Ok(db) = SQLiteWasmDatabase::new("testdb").await { - let result = db.query("SELECT 1", None).await; - - match result { - Ok(_) => {} - Err(e) => { - let error_msg = format!("{e:?}"); - assert!(!error_msg.is_empty()); - } - } - } -} - #[wasm_bindgen_test] fn test_error_propagation_chain() { let serde_error = serde_wasm_bindgen::Error::new("Test serde error"); @@ -186,171 +144,3 @@ async fn test_ready_signal_rejects_on_worker_error_msg() { "Rejected value should propagate worker error text" ); } - -#[wasm_bindgen_test] -async fn test_query_with_various_param_types_and_normalization() { - install_post_message_spy(); - let db = SQLiteWasmDatabase::new("testdb").await.expect("db created"); - - let arr = js_sys::Array::new(); - arr.push(&JsValue::from_f64(42.0)); - arr.push(&JsValue::from_str("hello")); - arr.push(&JsValue::from_bool(true)); - arr.push(&JsValue::NULL); - let bi = js_sys::BigInt::from(1234u32); - let bi_js: JsValue = bi.into(); - js_sys::Reflect::set(&arr, &JsValue::from_f64(5.0), &bi_js).expect("set index 5"); - let bytes: [u8; 3] = [1, 2, 3]; - let u8 = js_sys::Uint8Array::from(&bytes[..]); - let u8_js: JsValue = u8.into(); - arr.push(&u8_js); - let buf = js_sys::ArrayBuffer::new(4); - let typed = js_sys::Uint8Array::new(&buf); - typed.copy_from(&[5, 6, 7, 8]); - let buf_js: JsValue = buf.into(); - arr.push(&buf_js); - - let _ = db.query("SELECT 1", Some(arr)).await; - - if let Some(msg) = take_last_message() { - let ty = js_sys::Reflect::get(&msg, &JsValue::from_str("type")).unwrap(); - assert_eq!(ty.as_string().as_deref(), Some("execute-query")); - let sql = js_sys::Reflect::get(&msg, &JsValue::from_str("sql")).unwrap(); - assert_eq!(sql.as_string().as_deref(), Some("SELECT 1")); - let has_params = js_sys::Reflect::has(&msg, &JsValue::from_str("params")).unwrap_or(false); - assert!(has_params, "params should be present for non-empty array"); - - let params = js_sys::Reflect::get(&msg, &JsValue::from_str("params")).unwrap(); - assert!(js_sys::Array::is_array(¶ms)); - let params: js_sys::Array = params.unchecked_into(); - assert_eq!(params.length(), 8); - - let v0 = params.get(0); - assert_eq!(v0.as_f64(), Some(42.0)); - let v1 = params.get(1); - assert_eq!(v1.as_string().as_deref(), Some("hello")); - let v2 = params.get(2); - assert_eq!(v2.as_bool(), Some(true)); - let v3 = params.get(3); - assert!(v3.is_null()); - let v4 = params.get(4); - assert!(v4.is_null()); - let v5 = params.get(5); - assert!(v5.is_object()); - let t5 = js_sys::Reflect::get(&v5, &JsValue::from_str("__type")).unwrap(); - assert_eq!(t5.as_string().as_deref(), Some("bigint")); - let val5 = js_sys::Reflect::get(&v5, &JsValue::from_str("value")).unwrap(); - assert_eq!(val5.as_string().as_deref(), Some("1234")); - let v6 = params.get(6); - assert!(v6.is_object()); - let t6 = js_sys::Reflect::get(&v6, &JsValue::from_str("__type")).unwrap(); - assert_eq!(t6.as_string().as_deref(), Some("blob")); - let b64_6 = js_sys::Reflect::get(&v6, &JsValue::from_str("base64")).unwrap(); - let b64_6 = b64_6.as_string().expect("base64 string"); - let expected_6 = base64::engine::general_purpose::STANDARD.encode([1u8, 2, 3]); - assert_eq!(b64_6, expected_6); - let v7 = params.get(7); - assert!(v7.is_object()); - let t7 = js_sys::Reflect::get(&v7, &JsValue::from_str("__type")).unwrap(); - assert_eq!(t7.as_string().as_deref(), Some("blob")); - let b64_7 = js_sys::Reflect::get(&v7, &JsValue::from_str("base64")).unwrap(); - let b64_7 = b64_7.as_string().expect("base64 string"); - let expected_7 = base64::engine::general_purpose::STANDARD.encode([5u8, 6, 7, 8]); - assert_eq!(b64_7, expected_7); - } - - uninstall_post_message_spy(); -} - -#[wasm_bindgen_test] -async fn test_query_params_presence_empty_array_vs_none() { - install_post_message_spy(); - let db = SQLiteWasmDatabase::new("testdb").await.expect("db created"); - - let _ = db.query("SELECT 1", None).await; - if let Some(msg) = take_last_message() { - let has_params = js_sys::Reflect::has(&msg, &JsValue::from_str("params")).unwrap_or(true); - assert!(!has_params, "params should be absent when params=None"); - } - - let empty = js_sys::Array::new(); - let _ = db.query("SELECT 1", Some(empty)).await; - if let Some(msg) = take_last_message() { - let has_params = js_sys::Reflect::has(&msg, &JsValue::from_str("params")).unwrap_or(true); - assert!(!has_params, "params should be absent for empty array"); - } - - let one = js_sys::Array::new(); - one.push(&JsValue::from_f64(1.0)); - let _ = db.query("SELECT 1", Some(one)).await; - if let Some(msg) = take_last_message() { - let has_params = js_sys::Reflect::has(&msg, &JsValue::from_str("params")).unwrap_or(false); - assert!(has_params, "params should be present for non-empty array"); - } - - uninstall_post_message_spy(); -} - -#[wasm_bindgen_test] -async fn test_query_rejects_nan_infinity_params() { - let db = SQLiteWasmDatabase::new("testdb").await.expect("db created"); - - let arr = js_sys::Array::new(); - arr.push(&JsValue::from_f64(f64::NAN)); - let res = db.query("SELECT ?", Some(arr)).await; - assert!(res.is_err(), "NaN should be rejected"); - - let arr = js_sys::Array::new(); - arr.push(&JsValue::from_f64(f64::INFINITY)); - let res = db.query("SELECT ?", Some(arr)).await; - assert!(res.is_err(), "+Infinity should be rejected"); - - let arr = js_sys::Array::new(); - arr.push(&JsValue::from_f64(f64::NEG_INFINITY)); - let res = db.query("SELECT ?", Some(arr)).await; - assert!(res.is_err(), "-Infinity should be rejected"); -} - -fn install_post_message_spy() { - let code = r#" - (function(){ - try { - self.__lastMessage = undefined; - if (!self.__origPostMessage) { - self.__origPostMessage = Worker.prototype.postMessage; - } - Worker.prototype.postMessage = function(msg) { - self.__lastMessage = msg; - return self.__origPostMessage.call(this, msg); - }; - } catch (e) { - } - })() - "#; - let f = js_sys::Function::new_no_args(code); - let _ = f.call0(&JsValue::UNDEFINED); -} - -fn uninstall_post_message_spy() { - let code = r#" - (function(){ - try { - if (self.__origPostMessage) { - Worker.prototype.postMessage = self.__origPostMessage; - self.__origPostMessage = undefined; - } - } catch (e) { - } - })() - "#; - let f = js_sys::Function::new_no_args(code); - let _ = f.call0(&JsValue::UNDEFINED); -} - -fn take_last_message() -> Option { - let global = js_sys::global(); - let key = JsValue::from_str("__lastMessage"); - let val = js_sys::Reflect::get(&global, &key).ok(); - let _ = js_sys::Reflect::set(&global, &key, &JsValue::UNDEFINED); - val.and_then(|v| if v.is_undefined() { None } else { Some(v) }) -} From 83e48cc52f262bcce37229dc481a4f5caff8e237 Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 12:54:52 +0300 Subject: [PATCH 13/14] update package-lock.json integrity hash --- svelte-test/package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/svelte-test/package-lock.json b/svelte-test/package-lock.json index 25a6000..50fdbf1 100644 --- a/svelte-test/package-lock.json +++ b/svelte-test/package-lock.json @@ -914,7 +914,7 @@ "node_modules/@rainlanguage/sqlite-web": { "version": "0.0.1-alpha.20", "resolved": "file:../pkg/rainlanguage-sqlite-web-0.0.1-alpha.20.tgz", - "integrity": "sha512-RTG0hDpnnpcCICVttxI9F0kErfbTNczu9BW+/9GlifdyoZE6ozfu3SH0z9CAyvEGf7aLkXyLwqacg/hVevNMqA==" + "integrity": "sha512-FbZSTg/UyLPWZqyktF5q+Wj7jkR1vJXE65PPpRp2E8FXGFXfz9VkAdHYWnE9ogR6JcskuqwENgepjTAE8w8zyA==" }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.57.0", From 4fb9517779d405ecb72f8286783f84084e704e26 Mon Sep 17 00:00:00 2001 From: Arda Nakisci Date: Tue, 27 Jan 2026 13:05:07 +0300 Subject: [PATCH 14/14] always recreate worker in wipe_and_recreate even if OPFS deletion fails Capture OPFS deletion error and propagate it after worker recreation to avoid leaving the database in an unusable state. --- packages/sqlite-web/src/db.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sqlite-web/src/db.rs b/packages/sqlite-web/src/db.rs index c1bded2..0c22f56 100644 --- a/packages/sqlite-web/src/db.rs +++ b/packages/sqlite-web/src/db.rs @@ -194,7 +194,7 @@ impl SQLiteWasmDatabase { self.ready_signal.reset(); - delete_opfs_sahpool_directory().await?; + let deletion_result = delete_opfs_sahpool_directory().await; let worker_code = generate_self_contained_worker(&self.db_name); let new_worker = @@ -210,7 +210,7 @@ impl SQLiteWasmDatabase { self.wait_until_ready().await?; - Ok(()) + deletion_result } }