From d29825e6479b6bd1c41f9ce01a1c1afd6d582b30 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 2 Sep 2025 12:22:21 -0400 Subject: [PATCH 01/13] feat: add support for configuring behavior of a transaction when it is dropped --- src/transaction.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/transaction.rs b/src/transaction.rs index 3ce7ae8..5cb63ad 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -35,6 +35,27 @@ pub struct Transaction<'a> { listeners: TxListeners<'a>, done: bool, + on_drop: OnTransactionDrop, +} + +/// An enum representing the possible behavior which a [`Transaction`] may exhibit +/// when it is dropped. +/// +/// Note that unlike JavaScript's [`IDBTransaction`][1], this crate's [`Transaction`] +/// defaults to aborting - i.e., [`OnTransactionDrop::Abort`] - instead of +/// committing - i.e., [`OnTransactionDrop::Commit`] - the transaction! +/// +/// [1]: https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction +#[derive(Debug, Copy, Clone)] +pub enum OnTransactionDrop { + /// Abort the [`Transaction`] when it is dropped. This is the default + /// behavior of [`Transaction`]. + Abort, + /// Commit the [`Transaction`] when it is dropped. This is the default + /// behavior of an [`IDBTransaction`][1] in JavaScript. + /// + /// [1]: https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction + Commit, } /// A [transaction's](Transaction) result. @@ -65,9 +86,16 @@ impl<'a> Transaction<'a> { Self { listeners: TxListeners::new(db, inner), done: false, + on_drop: OnTransactionDrop::Abort, } } + /// Set the behavior for when the [`Transaction`] is dropped + pub fn on_drop(mut self, on_drop: OnTransactionDrop) -> Self { + self.on_drop = on_drop; + self + } + /// Rolls back all the changes to objects in the database associated with this transaction. /// /// # Browser compatibility note @@ -115,7 +143,10 @@ impl Drop for Transaction<'_> { self.listeners.free_listeners(); if !self.done { - let _ = self.as_sys().abort(); + let _ = match self.on_drop { + OnTransactionDrop::Abort => self.as_sys().abort(), + OnTransactionDrop::Commit => self.as_sys().do_commit(), + }; } } } @@ -126,6 +157,7 @@ impl Debug for Transaction<'_> { .field("transaction", self.as_sys()) .field("db", self.db()) .field("done", &self.done) + .field("on_drop", &self.on_drop) .finish() } } From bc34fc3bbf6f0dc9d0cac633bae70fe73a8a9e47 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 2 Sep 2025 12:31:13 -0400 Subject: [PATCH 02/13] refactor: add fn for creating a transaction from a version change event --- src/error/unexpected_data.rs | 4 ++++ src/transaction.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/error/unexpected_data.rs b/src/error/unexpected_data.rs index 49da5e3..16fb24b 100644 --- a/src/error/unexpected_data.rs +++ b/src/error/unexpected_data.rs @@ -22,6 +22,10 @@ pub enum UnexpectedDataError { #[error("`Future` polled unexpectedly.")] PollState, + /// Expected a Transaction to exist, but it was not found. + #[error("Expected the Transaction to exist, but it was not found.")] + TransactionNotFound, + /// Expected a Transaction to be aborted, but it was committed. #[error("Expected the Transaction to be aborted, but it was committed.")] TransactionCommitted, diff --git a/src/transaction.rs b/src/transaction.rs index 5cb63ad..d6a4664 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,7 +1,7 @@ //! An [`IDBTransaction`](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction) implementation. use crate::database::Database; -use crate::error::Error; +use crate::error::{Error, SimpleValueError, UnexpectedDataError}; use crate::internal_utils::{StructName, SystemRepr}; pub use base::TransactionRef; use listeners::TxListeners; @@ -10,6 +10,7 @@ pub use options::{TransactionDurability, TransactionOptions}; use std::fmt::{Debug, Formatter}; use std::ops::Deref; pub(crate) use tx_sys::TransactionSys; +use wasm_bindgen::JsCast; pub use web_sys::IdbTransactionMode as TransactionMode; mod base; @@ -90,10 +91,30 @@ impl<'a> Transaction<'a> { } } + /// Create a [`Transaction`] from an [`web_sys::IdbVersionChangeEvent`]. + /// + /// This is useful for extracting the transaction being used to upgrade + /// the database. + #[allow(unused)] + pub(crate) fn from_raw_version_change_event( + db: &'a Database, + event: &web_sys::IdbVersionChangeEvent, + ) -> crate::Result { + let inner = match event.target() { + Some(target) => match target.dyn_ref::() { + Some(req) => req + .transaction() + .ok_or(Error::from(UnexpectedDataError::TransactionNotFound)), + None => Err(SimpleValueError::DynCast(target.unchecked_into()).into()), + }, + None => Err(UnexpectedDataError::NoEventTarget.into()), + }?; + Ok(Self::new(db, inner)) + } + /// Set the behavior for when the [`Transaction`] is dropped - pub fn on_drop(mut self, on_drop: OnTransactionDrop) -> Self { + pub fn on_drop(&mut self, on_drop: OnTransactionDrop) { self.on_drop = on_drop; - self } /// Rolls back all the changes to objects in the database associated with this transaction. From c6d90ee36fb8f207a0ec6945b9cc1a625d763e6a Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 2 Sep 2025 13:24:57 -0400 Subject: [PATCH 03/13] feat: provide active transaction (instead of database) to upgrade needed callback BREAKING CHANGE: The callback provided to `OpenDbRequestBuilder::with_on_upgrade_needed` must now accept a reference to a `Transaction` rather than a `Database`. For most cases, existing code can be made compatible by calling `Transaction::database` in order to get access to the underlying database. The caveat is that the `Transaction::database` provides a reference, rather than ownership of the `Database`. --- internal_macros/src/generic_bounds.rs | 2 +- src/future/open_db/listener.rs | 14 +++++++++++--- src/transaction.rs | 1 - tests/tests/database/delete_obj_store.rs | 6 ++++-- tests/tests/database/obj_store_create.rs | 6 ++++-- tests/tests/example_reproductions.rs | 15 ++++++++++----- tests/tests/index/create.rs | 3 ++- tests/tests/object_store/add_put.rs | 6 ++++-- tests/tests/object_store/query_source/key_path.rs | 9 ++++++--- tests/tests/transaction/mod.rs | 3 ++- tests/tests/utils/init.rs | 12 ++++++++---- 11 files changed, 52 insertions(+), 25 deletions(-) diff --git a/internal_macros/src/generic_bounds.rs b/internal_macros/src/generic_bounds.rs index cf2d0e3..a6c4cb4 100644 --- a/internal_macros/src/generic_bounds.rs +++ b/internal_macros/src/generic_bounds.rs @@ -53,7 +53,7 @@ make_opts!(Opts => { db_name|index_name|store_name|key_path => ::core::convert::AsRef, db_version => crate::factory::DBVersion, blocked_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent) -> crate::Result<()> + 'static, - upgrade_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent, crate::database::Database) -> crate::Result<()> + 'static, + upgrade_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent, &crate::transaction::Transaction<'_>) -> crate::Result<()> + 'static, [custom] => { upgrade_async_cb => UpgradeAsyncCb, }, diff --git a/src/future/open_db/listener.rs b/src/future/open_db/listener.rs index a5ea5cb..365beec 100644 --- a/src/future/open_db/listener.rs +++ b/src/future/open_db/listener.rs @@ -1,5 +1,6 @@ use crate::database::{Database, VersionChangeEvent}; use crate::error::{Error, UnexpectedDataError}; +use crate::transaction::{OnTransactionDrop, Transaction}; use internal_macros::generic_bounds; use std::fmt::{Debug, Display, Formatter}; use std::mem; @@ -56,9 +57,16 @@ impl OpenDbListener { #[cfg(feature = "async-upgrade")] async_notify: Self::fake_rx(), listener: Closure::once(move |evt: web_sys::IdbVersionChangeEvent| { - let res = Database::from_event(&evt) - .and_then(move |db| callback(VersionChangeEvent::new(evt), db)); - + let res = Database::from_event(&evt).and_then(|db| { + Transaction::from_raw_version_change_event(&db, &evt).and_then(|mut tx| { + callback(VersionChangeEvent::new(evt), &tx).inspect(|_| { + // If the callback succeeded, we want to ensure that + // the transaction is committed when dropped and not + // aborted. + tx.on_drop(OnTransactionDrop::Commit); + }) + }) + }); Self::handle_result(LBL_UPGRADE, &status, res) }), } diff --git a/src/transaction.rs b/src/transaction.rs index d6a4664..5def5c1 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -95,7 +95,6 @@ impl<'a> Transaction<'a> { /// /// This is useful for extracting the transaction being used to upgrade /// the database. - #[allow(unused)] pub(crate) fn from_raw_version_change_event( db: &'a Database, event: &web_sys::IdbVersionChangeEvent, diff --git a/tests/tests/database/delete_obj_store.rs b/tests/tests/database/delete_obj_store.rs index 8660ba1..7eb2a79 100644 --- a/tests/tests/database/delete_obj_store.rs +++ b/tests/tests/database/delete_obj_store.rs @@ -12,7 +12,8 @@ pub async fn invalid_state_error() { #[wasm_bindgen_test] pub async fn not_found_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.delete_object_store(&db.name())?; Ok(()) }) @@ -28,7 +29,8 @@ pub async fn happy_path() { let n1_clone = n1.clone(); let db = Database::open(&n1) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&n1_clone).build()?; db.create_object_store(&random_str()) .build()? diff --git a/tests/tests/database/obj_store_create.rs b/tests/tests/database/obj_store_create.rs index 7667bfe..f090ecf 100644 --- a/tests/tests/database/obj_store_create.rs +++ b/tests/tests/database/obj_store_create.rs @@ -12,7 +12,8 @@ pub async fn happy_path() { #[wasm_bindgen_test] pub async fn constraint_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); let name = random_str(); db.create_object_store(&name).build()?; db.create_object_store(&name).build()?; @@ -27,7 +28,8 @@ pub async fn constraint_error() { #[wasm_bindgen_test] pub async fn invalid_access_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_auto_increment(true) .with_key_path("".into()) diff --git a/tests/tests/example_reproductions.rs b/tests/tests/example_reproductions.rs index 1d45b9f..f4f1d18 100644 --- a/tests/tests/example_reproductions.rs +++ b/tests/tests/example_reproductions.rs @@ -28,7 +28,8 @@ pub async fn multi_threaded_executor() { } let db = Database::open("my_db_multi_threaded_executor") - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); db.create_object_store("my_store") .with_auto_increment(true) .build()?; @@ -111,7 +112,8 @@ pub async fn rw_serde() { } let db = Database::open("example_rw_serde") - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); db.create_object_store("users") .with_key_path("id".into()) .build()?; @@ -159,7 +161,8 @@ pub async fn readme_example() { async fn main() -> indexed_db_futures::OpenDbResult<()> { let db = Database::open("my_db_readme_example") .with_version(2u8) - .with_on_upgrade_needed(|event, db| { + .with_on_upgrade_needed(|event, tx| { + let db = tx.db(); // Convert versions from floats to integers to allow using them in match expressions let old_version = event.old_version() as u64; let new_version = event.new_version().map(|v| v as u64); @@ -262,7 +265,8 @@ pub async fn iterating_a_cursor() { let db = Database::open("example_iterating_a_cursor") .with_version(2u8) - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); db.create_object_store("my_store").build()?; Ok(()) }) @@ -329,7 +333,8 @@ pub async fn iterating_index_as_a_stream() { } let db = Database::open("example_iterating_index_as_a_stream") - .with_on_upgrade_needed(|_, db| { + .with_on_upgrade_needed(|_, tx| { + let db = tx.db(); let store = db .create_object_store("my_store") .with_key_path("id".into()) diff --git a/tests/tests/index/create.rs b/tests/tests/index/create.rs index 4690a48..6818f1c 100644 --- a/tests/tests/index/create.rs +++ b/tests/tests/index/create.rs @@ -4,7 +4,8 @@ use idb_fut::database::Database; #[wasm_bindgen_test] pub async fn constraint_error() { let err = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); let name = db.name(); let store = db .create_object_store(&name) diff --git a/tests/tests/object_store/add_put.rs b/tests/tests/object_store/add_put.rs index 1c26e43..7e43947 100644 --- a/tests/tests/object_store/add_put.rs +++ b/tests/tests/object_store/add_put.rs @@ -11,7 +11,8 @@ macro_rules! common_tests { #[wasm_bindgen_test] pub async fn data_error_inline_key() { - let db = random_db_with_init(move |_, db| { + let db = random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_key_path("foo".into()) .build()?; @@ -64,7 +65,8 @@ macro_rules! common_tests { #[cfg(feature = "serde")] #[wasm_bindgen_test] pub async fn serde_object_nesting() { - let db = random_db_with_init(move |_, db| { + let db = random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_key_path("foo".into()) .build()?; diff --git a/tests/tests/object_store/query_source/key_path.rs b/tests/tests/object_store/query_source/key_path.rs index 9522485..12a699d 100644 --- a/tests/tests/object_store/query_source/key_path.rs +++ b/tests/tests/object_store/query_source/key_path.rs @@ -5,7 +5,8 @@ use idb_fut::KeyPath; #[wasm_bindgen_test] pub async fn auto_incremented() { let db = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_auto_increment(true) .build()?; @@ -21,7 +22,8 @@ pub async fn auto_incremented() { #[wasm_bindgen_test] pub async fn none() { let db = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()).build()?; Ok(()) }) @@ -35,7 +37,8 @@ pub async fn none() { #[wasm_bindgen_test] pub async fn explicit() { let db = Database::open(random_str()) - .with_on_upgrade_needed(move |_, db| { + .with_on_upgrade_needed(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_key_path(Key::KEY_PATH) .build()?; diff --git a/tests/tests/transaction/mod.rs b/tests/tests/transaction/mod.rs index 54afa41..6e1868e 100644 --- a/tests/tests/transaction/mod.rs +++ b/tests/tests/transaction/mod.rs @@ -7,7 +7,8 @@ pub mod on_done; #[wasm_bindgen_test] pub async fn multi_store() { - let db = random_db_with_init(move |_, db| { + let db = random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store("s1").build()?; db.create_object_store("s2").build()?; Ok(()) diff --git a/tests/tests/utils/init.rs b/tests/tests/utils/init.rs index 4601248..849c9ec 100644 --- a/tests/tests/utils/init.rs +++ b/tests/tests/utils/init.rs @@ -1,9 +1,10 @@ use crate::prelude::*; use idb_fut::database::{Database, VersionChangeEvent}; +use indexed_db_futures::transaction::Transaction; pub async fn random_db_with_init(on_upgrade_needed: F) -> Database where - F: Fn(VersionChangeEvent, Database) -> idb_fut::Result<()> + 'static, + F: Fn(VersionChangeEvent, &Transaction<'_>) -> idb_fut::Result<()> + 'static, { Database::open(random_str()) .with_on_upgrade_needed(on_upgrade_needed) @@ -13,7 +14,8 @@ where /// Crate a DB with and an object store with a matching name and default params. pub async fn random_db_with_store() -> Database { - random_db_with_init(move |_, db| { + random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()).build()?; Ok(()) }) @@ -22,7 +24,8 @@ pub async fn random_db_with_store() -> Database { /// Create a random DB and a store with a matching name that expect [`KeyVal`] inputs. pub async fn random_db_keyval() -> Database { - random_db_with_init(move |_, db| { + random_db_with_init(move |_, tx| { + let db = tx.db(); db.create_object_store(&db.name()) .with_auto_increment(false) .with_key_path(Key::KEY_PATH) @@ -35,7 +38,8 @@ pub async fn random_db_keyval() -> Database { /// [`random_db_keyval`] + an index with default params. #[cfg(feature = "indices")] pub async fn random_db_idx_keyval() -> Database { - random_db_with_init(move |_, db| { + random_db_with_init(move |_, tx| { + let db = tx.db(); let name = db.name(); let store = db .create_object_store(&name) From 64ebb16e266109edcc1d5c7a815f03e2fe0a0238 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Fri, 5 Sep 2025 11:40:55 -0400 Subject: [PATCH 04/13] feat: use `AsyncFnOnce` for async upgrade needed callback BREAKING CHANGE: `AsyncFnOnce` was stabilized in version 1.85.0 of Rust, so the MSRV had to be bumped to accommodate this. Additionally, synchronous closures that use `async move` blocks are not drop-in replacements for `async` closures, so users of `OpenDbRequestBuilder::with_on_upgrade_needed_fut` will have to make minor changes to their invocations. --- Cargo.toml | 2 +- internal_macros/src/generic_bounds.rs | 33 ++------------------------- src/factory/req_builder.rs | 4 ++-- src/future/open_db/listener.rs | 4 ++-- tests/tests/example_reproductions.rs | 4 ++-- tests/tests/transaction/on_done.rs | 4 ++-- 6 files changed, 11 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33f2ed4..a71db38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ name = "indexed_db_futures" version = "0.6.4" authors = ["Arturas Molcanovas "] edition = "2021" -rust-version = "1.75.0" +rust-version = "1.85.0" license = "MIT" description = "Future bindings for IndexedDB via web_sys" repository = "https://github.com/Alorel/rust-indexed-db" diff --git a/internal_macros/src/generic_bounds.rs b/internal_macros/src/generic_bounds.rs index a6c4cb4..1b959ff 100644 --- a/internal_macros/src/generic_bounds.rs +++ b/internal_macros/src/generic_bounds.rs @@ -1,7 +1,5 @@ use crate::commons::FnTarget; use crate::TokenStream1; -use macroific::prelude::*; -use proc_macro2::Ident; use quote::ToTokens; use syn::{parse_quote, Error, WherePredicate}; @@ -22,7 +20,7 @@ macro_rules! make_opts { /// /// | Option | Type | /// |--------|-----------| - $($(#[doc = concat!(" | `", stringify!($extra_opt), "` | `", stringify!($extra_ty), "` |")])+)+ + $($(#[doc = concat!(" | `", stringify!($extra_opt), "` | `", stringify!($extra_ty), "` |")])+)* #[derive(::macroific::attr_parse::AttributeOptions)] pub(super) struct $struct_name { $($($opt: ::syn::punctuated::Punctuated,)+)+ @@ -54,9 +52,7 @@ make_opts!(Opts => { db_version => crate::factory::DBVersion, blocked_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent) -> crate::Result<()> + 'static, upgrade_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent, &crate::transaction::Transaction<'_>) -> crate::Result<()> + 'static, - [custom] => { - upgrade_async_cb => UpgradeAsyncCb, - }, + upgrade_async_cb => ::core::ops::AsyncFnOnce(crate::database::VersionChangeEvent, crate::database::Database) -> crate::Result<()> + 'static, }); #[inline] @@ -73,31 +69,6 @@ pub(super) fn exec(spec: TokenStream1, target: TokenStream1) -> TokenStream1 { } } -#[derive(ParseOption)] -struct UpgradeAsyncCb { - #[attr_opts(default = false)] - fun: Ident, - - #[attr_opts(default = false)] - fut: Ident, -} - -impl UpgradeAsyncCb { - fn extend_target(self, target: &mut FnTarget) { - let Self { fun, fut } = self; - let wheres = [ - parse_quote!(#fun: ::core::ops::FnOnce(crate::database::VersionChangeEvent, crate::database::Database) -> #fut + 'static), - parse_quote!(#fut: ::core::future::Future> + 'static), - ]; - - target - .generics_mut() - .make_where_clause() - .predicates - .extend::<[WherePredicate; 2]>(wheres); - } -} - fn on_err(mut target: TokenStream1, e: Error) -> TokenStream1 { let e: TokenStream1 = e.into_compile_error().into(); target.extend(e); diff --git a/src/factory/req_builder.rs b/src/factory/req_builder.rs index 57f5343..eedf7fc 100644 --- a/src/factory/req_builder.rs +++ b/src/factory/req_builder.rs @@ -102,9 +102,9 @@ impl OpenDbRequestBuilder { /// Set the [upgradeneeded](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) /// event handler that returns a `Future`. - #[generic_bounds(upgrade_async_cb(fun(U2), fut(U2Fut)))] + #[generic_bounds(upgrade_async_cb(U2))] #[cfg(feature = "async-upgrade")] - pub fn with_on_upgrade_needed_fut( + pub fn with_on_upgrade_needed_fut( self, on_upgrade_needed: U2, ) -> OpenDbRequestBuilder { diff --git a/src/future/open_db/listener.rs b/src/future/open_db/listener.rs index 365beec..40f29b2 100644 --- a/src/future/open_db/listener.rs +++ b/src/future/open_db/listener.rs @@ -162,8 +162,8 @@ const _: () = { tokio::sync::mpsc::unbounded_channel().1 } - #[generic_bounds(upgrade_async_cb(fun(Fn), fut(Fut)))] - pub(crate) fn new_upgrade_fut(callback: Fn) -> Self { + #[generic_bounds(upgrade_async_cb(Fn))] + pub(crate) fn new_upgrade_fut(callback: Fn) -> Self { let status = Status::new(); let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); Self { diff --git a/tests/tests/example_reproductions.rs b/tests/tests/example_reproductions.rs index f4f1d18..3951fcb 100644 --- a/tests/tests/example_reproductions.rs +++ b/tests/tests/example_reproductions.rs @@ -44,14 +44,14 @@ pub async fn multi_threaded_executor() { #[wasm_bindgen_test] #[cfg(all(feature = "tx-done", feature = "async-upgrade"))] pub async fn opening_a_database_and_making_some_schema_changes() { - use indexed_db_futures::database::Database; + use indexed_db_futures::database::{Database, VersionChangeEvent}; use indexed_db_futures::prelude::*; use indexed_db_futures::transaction::TransactionMode; let _ = Database::open("opening_a_database_and_making_some_schema_changes") .with_version(2u8) .with_on_blocked(|_| Ok(())) - .with_on_upgrade_needed_fut(|event, db| { + .with_on_upgrade_needed_fut(|event: VersionChangeEvent, db: Database| { // Convert versions from floats to integers to allow using them in match expressions let old_version = event.old_version() as u64; let new_version = event.new_version().map(|v| v as u64); diff --git a/tests/tests/transaction/on_done.rs b/tests/tests/transaction/on_done.rs index febb2e8..c40483a 100644 --- a/tests/tests/transaction/on_done.rs +++ b/tests/tests/transaction/on_done.rs @@ -40,7 +40,7 @@ pub mod async_upgrade { let err = Database::open(random_str()) .with_version(2u8) - .with_on_upgrade_needed_fut(move |_, db| async move { + .with_on_upgrade_needed_fut(move |_, db: Database| async move { // Create an object store and await its transaction db.create_object_store(STORE_NAME) .with_auto_increment(true) @@ -74,7 +74,7 @@ pub mod async_upgrade { .with_version(2u8) .with_on_upgrade_needed_fut({ let events = Arc::clone(&events); - move |_, db| async move { + move |_, db: Database| async move { events.lock().unwrap().push(Event::CallbackStart); // Create an object store and await its transaction From 3e1384aa47737caba7a91a6f0b7cd55c506029e1 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Fri, 5 Sep 2025 11:50:43 -0400 Subject: [PATCH 05/13] ci: update the MSRV toolchain so that it uses 1.85.0 --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 404cba9..fd3f87d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: Test on: - workflow_call: { } + workflow_call: {} push: branches: - master @@ -104,7 +104,7 @@ jobs: include: - toolchain: nightly-2025-05-05 os: ubuntu-latest - - toolchain: 1.75.0 + - toolchain: 1.85.0 os: ubuntu-latest - toolchain: stable os: ubuntu-latest @@ -120,7 +120,7 @@ jobs: fail-fast: false matrix: flags: - - '' + - "" - --features cursors - --features dates - --features indices From 3d8fa337f43f8ffc08ce8a297d3ea375d5fa84e2 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Fri, 5 Sep 2025 12:01:32 -0400 Subject: [PATCH 06/13] feat: provide active transaction (instead of database) to async upgrade needed callback BREAKING CHANGE: The callback provided to `OpenDbRequestBuilder::with_on_upgrade_needed_fut` must now accept a reference to a `Transaction` rather than a `Database`. For most cases, existing code can be made compatible by calling `Transaction::database` in order to get access to the underlying database. The caveat is that the `Transaction::database` provides a reference, rather than ownership of the `Database`. --- internal_macros/src/generic_bounds.rs | 2 +- src/future/open_db/listener.rs | 18 +++++-- tests/tests/example_reproductions.rs | 67 +++++++++++++-------------- tests/tests/transaction/on_done.rs | 6 ++- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/internal_macros/src/generic_bounds.rs b/internal_macros/src/generic_bounds.rs index 1b959ff..a2e646d 100644 --- a/internal_macros/src/generic_bounds.rs +++ b/internal_macros/src/generic_bounds.rs @@ -52,7 +52,7 @@ make_opts!(Opts => { db_version => crate::factory::DBVersion, blocked_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent) -> crate::Result<()> + 'static, upgrade_cb => ::core::ops::FnOnce(crate::database::VersionChangeEvent, &crate::transaction::Transaction<'_>) -> crate::Result<()> + 'static, - upgrade_async_cb => ::core::ops::AsyncFnOnce(crate::database::VersionChangeEvent, crate::database::Database) -> crate::Result<()> + 'static, + upgrade_async_cb => ::core::ops::AsyncFnOnce(crate::database::VersionChangeEvent, &crate::transaction::Transaction<'_>) -> crate::Result<()> + 'static, }); #[inline] diff --git a/src/future/open_db/listener.rs b/src/future/open_db/listener.rs index 40f29b2..63567ce 100644 --- a/src/future/open_db/listener.rs +++ b/src/future/open_db/listener.rs @@ -176,11 +176,21 @@ const _: () = { }; Self::set_status(&status, Status::Pending, LBL_UPGRADE)?; - let fut = callback(VersionChangeEvent::new(evt), db); - wasm_bindgen_futures::spawn_local(async move { - let result = match fut.await { - Ok(()) => Status::Ok, + let db = db; + let result = match Transaction::from_raw_version_change_event(&db, &evt) { + Ok(mut transaction) => { + match callback(VersionChangeEvent::new(evt), &transaction).await { + Ok(_) => { + // If the callback succeeded, we want to ensure that + // the transaction is committed when dropped and not + // aborted. + transaction.on_drop(OnTransactionDrop::Commit); + Status::Ok + } + Err(e) => Status::Err(e), + } + } Err(e) => Status::Err(e), }; let _ = Self::set_status(&status, result, LBL_UPGRADE); diff --git a/tests/tests/example_reproductions.rs b/tests/tests/example_reproductions.rs index 3951fcb..9488fff 100644 --- a/tests/tests/example_reproductions.rs +++ b/tests/tests/example_reproductions.rs @@ -44,54 +44,53 @@ pub async fn multi_threaded_executor() { #[wasm_bindgen_test] #[cfg(all(feature = "tx-done", feature = "async-upgrade"))] pub async fn opening_a_database_and_making_some_schema_changes() { - use indexed_db_futures::database::{Database, VersionChangeEvent}; + use indexed_db_futures::database::Database; use indexed_db_futures::prelude::*; use indexed_db_futures::transaction::TransactionMode; let _ = Database::open("opening_a_database_and_making_some_schema_changes") .with_version(2u8) .with_on_blocked(|_| Ok(())) - .with_on_upgrade_needed_fut(|event: VersionChangeEvent, db: Database| { + .with_on_upgrade_needed_fut(async |event, tx| { + let db = tx.db(); // Convert versions from floats to integers to allow using them in match expressions let old_version = event.old_version() as u64; let new_version = event.new_version().map(|v| v as u64); - async move { - match (old_version, new_version) { - (0, Some(1)) => { - db.create_object_store("my_store") - .with_auto_increment(true) - .build()?; + match (old_version, new_version) { + (0, Some(1)) => { + db.create_object_store("my_store") + .with_auto_increment(true) + .build()?; + } + (prev, Some(2)) => { + if prev == 1 { + db.delete_object_store("my_store")?; } - (prev, Some(2)) => { - if prev == 1 { - db.delete_object_store("my_store")?; - } - // Create an object store and await its transaction before inserting data. - db.create_object_store("my_other_store") - .with_auto_increment(true) - .build()? - .transaction() - .on_done()? - .await - .into_result()?; - - //- Start a new transaction & add some data - let tx = db - .transaction("my_other_store") - .with_mode(TransactionMode::Readwrite) - .build()?; - let store = tx.object_store("my_other_store")?; - store.add("foo").await?; - store.add("bar").await?; - tx.commit().await?; - } - _ => {} + // Create an object store and await its transaction before inserting data. + db.create_object_store("my_other_store") + .with_auto_increment(true) + .build()? + .transaction() + .on_done()? + .await + .into_result()?; + + //- Start a new transaction & add some data + let tx = db + .transaction("my_other_store") + .with_mode(TransactionMode::Readwrite) + .build()?; + let store = tx.object_store("my_other_store")?; + store.add("foo").await?; + store.add("bar").await?; + tx.commit().await?; } - - Ok(()) + _ => {} } + + Ok(()) }) .await .expect("Error opening DB"); diff --git a/tests/tests/transaction/on_done.rs b/tests/tests/transaction/on_done.rs index c40483a..b6aefc0 100644 --- a/tests/tests/transaction/on_done.rs +++ b/tests/tests/transaction/on_done.rs @@ -40,7 +40,8 @@ pub mod async_upgrade { let err = Database::open(random_str()) .with_version(2u8) - .with_on_upgrade_needed_fut(move |_, db: Database| async move { + .with_on_upgrade_needed_fut(async |_, tx| { + let db = tx.db(); // Create an object store and await its transaction db.create_object_store(STORE_NAME) .with_auto_increment(true) @@ -74,7 +75,8 @@ pub mod async_upgrade { .with_version(2u8) .with_on_upgrade_needed_fut({ let events = Arc::clone(&events); - move |_, db: Database| async move { + async move |_, tx| { + let db = tx.db(); events.lock().unwrap().push(Event::CallbackStart); // Create an object store and await its transaction From bcd95506ad1481558e32998b30b8fc66c7e692d3 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Sun, 7 Sep 2025 10:33:03 -0400 Subject: [PATCH 07/13] ci: bump nightly toolchain for generating docs --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fd3f87d..4ce6dca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: uses: alorel-actions/cargo/init@v2 id: toolchain with: - toolchain: nightly-2024-10-18 + toolchain: nightly-2025-05-05 cache-prefix: doc local: true From 3428679c10396b2ad194fa9b3cf085a166754963 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Sun, 7 Sep 2025 11:12:01 -0400 Subject: [PATCH 08/13] docs: update example using OpenDbRequestBuilder::with_on_upgrade_needed_fut --- src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cea5db1..bb96106 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,9 +80,9 @@ //! ## Opening a database & making some schema changes //! //! ``` -//! use indexed_db_futures::database::Database; +//! use indexed_db_futures::database::{Database, VersionChangeEvent}; //! use indexed_db_futures::prelude::*; -//! use indexed_db_futures::transaction::TransactionMode; +//! use indexed_db_futures::transaction::{Transaction, TransactionMode}; //! //! # async fn example() -> indexed_db_futures::OpenDbResult<()> { //! # #[allow(dead_code)] @@ -92,7 +92,8 @@ //! log::debug!("DB upgrade blocked: {:?}", event); //! Ok(()) //! }) -//! .with_on_upgrade_needed_fut(|event, db| async move { +//! .with_on_upgrade_needed_fut(async |event: VersionChangeEvent, tx: &Transaction<'_>| { +//! let db = tx.db(); //! // Convert versions from floats to integers to allow using them in match expressions //! let old_version = event.old_version() as u64; //! let new_version = event.new_version().map(|v| v as u64); From 200d0b2f232d716ec28e1eddcf2334a051005909 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Sun, 7 Sep 2025 11:29:40 -0400 Subject: [PATCH 09/13] refactor: appease clippy lints For details on lifetime syntax lint, see https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/#mismatched-lifetime-syntaxes-lint. --- src/cursor.rs | 6 +++--- src/cursor/key_cursor.rs | 4 ++-- src/cursor/stream.rs | 2 +- src/database.rs | 6 +++--- src/future/open_db/listener.rs | 4 ++-- src/index/object_store_ext.rs | 6 +++--- src/key_path.rs | 2 +- src/object_store.rs | 6 +++--- src/query_source.rs | 28 ++++++++++++++-------------- src/transaction/base.rs | 4 ++-- src/typed_array.rs | 2 +- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/cursor.rs b/src/cursor.rs index 4787132..c096db3 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -64,7 +64,7 @@ impl<'a, Qs> Cursor<'a, Qs> { /// followed by [`value`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorWithValue/value) in JS. #[errdoc(Cursor(TransactionInactiveError, InvalidStateError))] #[inline] - pub fn next_record(&mut self) -> CursorNextRequest + pub fn next_record(&mut self) -> CursorNextRequest<'_, T> where T: TryFromJs, { @@ -73,7 +73,7 @@ impl<'a, Qs> Cursor<'a, Qs> { /// Mirror of [`Self::next_record`] for `serde`-deserialisable values. #[cfg(feature = "serde")] - pub fn next_record_ser(&mut self) -> CursorNextRequest + pub fn next_record_ser(&mut self) -> CursorNextRequest<'_, T> where T: crate::serde::DeserialiseFromJs, { @@ -106,7 +106,7 @@ impl<'a, Qs> Cursor<'a, Qs> { DataCloneError, ))] #[inline] - pub fn update(&self, value: V) -> Update { + pub fn update(&self, value: V) -> Update<'_, V> { Update::new(self, value) } diff --git a/src/cursor/key_cursor.rs b/src/cursor/key_cursor.rs index a4a57a1..310f843 100644 --- a/src/cursor/key_cursor.rs +++ b/src/cursor/key_cursor.rs @@ -34,7 +34,7 @@ impl<'a, Qs> KeyCursor<'a, Qs> { /// followed by [`key`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor/key) in JS. #[inline] #[errdoc(Cursor(TransactionInactiveError, InvalidStateError))] - pub fn next_key(&mut self) -> CursorNextRequest + pub fn next_key(&mut self) -> CursorNextRequest<'_, T> where T: TryFromJs, { @@ -44,7 +44,7 @@ impl<'a, Qs> KeyCursor<'a, Qs> { /// Mirror of [`Self::next_key`] for `serde`-deserialisable keys. #[inline] #[cfg(feature = "serde")] - pub fn next_key_ser(&mut self) -> CursorNextRequest + pub fn next_key_ser(&mut self) -> CursorNextRequest<'_, T> where T: crate::serde::DeserialiseFromJs, { diff --git a/src/cursor/stream.rs b/src/cursor/stream.rs index 54490ec..ed3e020 100644 --- a/src/cursor/stream.rs +++ b/src/cursor/stream.rs @@ -90,7 +90,7 @@ impl<'a, Qs, T> Stream, T> { DataErrorUpdate, DataCloneError ))] - pub fn update(&self, value: V) -> super::Update { + pub fn update(&self, value: V) -> super::Update<'_, V> { self.cursor.update(value) } diff --git a/src/database.rs b/src/database.rs index c0105e9..c9823f0 100644 --- a/src/database.rs +++ b/src/database.rs @@ -52,7 +52,7 @@ impl Database { /// Create an object store with the given name. #[generic_bounds(store_name(N))] #[inline] - pub fn create_object_store(&self, name: N) -> StoreBuilder { + pub fn create_object_store(&self, name: N) -> StoreBuilder<'_, N> { StoreBuilder::new(self, name) } @@ -99,7 +99,7 @@ impl Database { /// List the names of the object stores within this database. #[inline] - pub fn object_store_names(&self) -> DomStringIter { + pub fn object_store_names(&self) -> DomStringIter<'_> { DomStringIter::new(self.as_sys().object_store_names()) } @@ -107,7 +107,7 @@ impl Database { /// [`Build::build`](crate::Build::build). #[errdoc(Database(NotFoundErrorTx, InvalidAccessErrorTx))] #[inline] - pub fn transaction(&self, store_names: S) -> TransactionBuilder { + pub fn transaction(&self, store_names: S) -> TransactionBuilder<'_, S> { TransactionBuilder::new(self, store_names) } diff --git a/src/future/open_db/listener.rs b/src/future/open_db/listener.rs index 63567ce..f0de35a 100644 --- a/src/future/open_db/listener.rs +++ b/src/future/open_db/listener.rs @@ -59,7 +59,7 @@ impl OpenDbListener { listener: Closure::once(move |evt: web_sys::IdbVersionChangeEvent| { let res = Database::from_event(&evt).and_then(|db| { Transaction::from_raw_version_change_event(&db, &evt).and_then(|mut tx| { - callback(VersionChangeEvent::new(evt), &tx).inspect(|_| { + callback(VersionChangeEvent::new(evt), &tx).inspect(|()| { // If the callback succeeded, we want to ensure that // the transaction is committed when dropped and not // aborted. @@ -181,7 +181,7 @@ const _: () = { let result = match Transaction::from_raw_version_change_event(&db, &evt) { Ok(mut transaction) => { match callback(VersionChangeEvent::new(evt), &transaction).await { - Ok(_) => { + Ok(()) => { // If the callback succeeded, we want to ensure that // the transaction is committed when dropped and not // aborted. diff --git a/src/index/object_store_ext.rs b/src/index/object_store_ext.rs index 3735621..d197cef 100644 --- a/src/index/object_store_ext.rs +++ b/src/index/object_store_ext.rs @@ -19,7 +19,7 @@ impl ObjectStore<'_> { ))] #[generic_bounds(index_name(N), key_path(KP))] #[inline] - pub fn create_index(&self, name: N, key_path: KeyPath) -> IndexBuilder { + pub fn create_index(&self, name: N, key_path: KeyPath) -> IndexBuilder<'_, N, KP> { IndexBuilder::new(self, name, key_path) } @@ -38,7 +38,7 @@ impl ObjectStore<'_> { /// Open an index with the given name #[errdoc(Index(InvalidStateErrorIndex, NotFoundError))] #[allow(clippy::missing_errors_doc)] - pub fn index(&self, name: &str) -> crate::Result { + pub fn index(&self, name: &str) -> crate::Result> { match self.as_sys().index(name) { Ok(sys) => Ok(Index::new(self, sys)), Err(e) => Err(e.into()), @@ -46,7 +46,7 @@ impl ObjectStore<'_> { } /// Return the names of the indices on this object store. - pub fn index_names(&self) -> DomStringIter { + pub fn index_names(&self) -> DomStringIter<'_> { DomStringIter::new(self.as_sys().index_names()) } } diff --git a/src/key_path.rs b/src/key_path.rs index ec2ca97..50aa38f 100644 --- a/src/key_path.rs +++ b/src/key_path.rs @@ -22,7 +22,7 @@ pub enum KeyPath { #[generic_bounds(key_path(T))] impl KeyPath { - /// Convert the key path to a JsValue. + /// Convert the key path to a `JsValue`. pub fn to_js(&self) -> JsValue { match self { Self::One(v) => JsValue::from_str(v.as_ref()), diff --git a/src/object_store.rs b/src/object_store.rs index 4be2c87..626ee14 100644 --- a/src/object_store.rs +++ b/src/object_store.rs @@ -56,7 +56,7 @@ impl<'a> ObjectStore<'a> { ConstraintError, ))] #[inline] - pub fn add(&self, value: V) -> Add { + pub fn add(&self, value: V) -> Add<'_, V> { Add::new(self, value) } @@ -80,7 +80,7 @@ impl<'a> ObjectStore<'a> { ConstraintError, ))] #[inline] - pub fn put(&self, value: V) -> Put { + pub fn put(&self, value: V) -> Put<'_, V> { Put::new(self, value) } @@ -117,7 +117,7 @@ impl<'a> ObjectStore<'a> { InvalidStateError, DataErrorDelete, ))] - pub fn delete(&self, key_range: I) -> Delete + pub fn delete(&self, key_range: I) -> Delete<'_, K> where I: Into>, { diff --git a/src/query_source.rs b/src/query_source.rs index c128b1e..e78a8c4 100644 --- a/src/query_source.rs +++ b/src/query_source.rs @@ -30,34 +30,34 @@ mod get_key; pub trait QuerySource { /// Count the number of documents in the index/object store. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn count(&self) -> Count + fn count(&self) -> Count<'_, Self> where Self: Sized; /// Get one record from the object store or index. Returns the first match if a non-[only](KeyRange::Only) key is /// provided and multiple records match. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn get(&self, key: I) -> Get + fn get(&self, key: I) -> Get<'_, Self, K, V> where Self: Sized, I: Into>; /// Return the first matching key selected by the specified query. #[errdoc(QuerySource(TransactionInactiveError, InvalidStateError, DataError))] - fn get_key(&self, key_range: I) -> GetKey + fn get_key(&self, key_range: I) -> GetKey<'_, Self, K> where Self: Sized, I: Into>; /// Get all records in the object store or index. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn get_all(&self) -> GetAllRecords + fn get_all(&self) -> GetAllRecords<'_, Self, V> where Self: Sized; /// Get all keys in the object store or index. #[errdoc(QuerySource(InvalidStateError, TransactionInactiveError, DataError))] - fn get_all_keys(&self) -> GetAllKeys + fn get_all_keys(&self) -> GetAllKeys<'_, Self, K> where Self: Sized; @@ -77,12 +77,12 @@ pub trait QuerySource { /// Open a cursor that iterates over the records in the index or object store. /// Resolves to `None` if the cursor is empty. #[errdoc(Cursor(TransactionInactiveError, DataErrorOpen, InvalidStateErrorOpen))] - fn open_cursor(&self) -> CursorBuilder where Self: Sized; + fn open_cursor(&self) -> CursorBuilder<'_, Self> where Self: Sized; /// Open a cursor that iterates over the keys in the index or object store. /// Resolves to `None` if the cursor is empty. #[errdoc(Cursor(TransactionInactiveError, DataErrorOpen, InvalidStateErrorOpen))] - fn open_key_cursor(&self) -> KeyCursorBuilder where Self: Sized; + fn open_key_cursor(&self) -> KeyCursorBuilder<'_, Self> where Self: Sized; } } @@ -99,7 +99,7 @@ impl, R: QuerySourceInternal> QuerySource for T { } #[inline] - fn count(&self) -> Count { + fn count(&self) -> Count<'_, Self> { Count::new(self) } @@ -110,14 +110,14 @@ impl, R: QuerySourceInternal> QuerySource for T { } } - fn get(&self, key: I) -> Get + fn get(&self, key: I) -> Get<'_, Self, K, V> where I: Into>, { Get::new(self, key.into()) } - fn get_key(&self, key_range: I) -> GetKey + fn get_key(&self, key_range: I) -> GetKey<'_, Self, K> where I: Into>, { @@ -125,24 +125,24 @@ impl, R: QuerySourceInternal> QuerySource for T { } #[inline] - fn get_all(&self) -> GetAllRecords { + fn get_all(&self) -> GetAllRecords<'_, Self, V> { GetAllRecords::new(self) } #[inline] - fn get_all_keys(&self) -> GetAllKeys { + fn get_all_keys(&self) -> GetAllKeys<'_, Self, K> { GetAllKeys::new(self) } iffeat! { #[cfg(feature = "cursors")] #[inline] - fn open_cursor(&self) -> CursorBuilder { + fn open_cursor(&self) -> CursorBuilder<'_, Self> { CursorBuilder::new(self) } #[inline] - fn open_key_cursor(&self) -> KeyCursorBuilder { + fn open_key_cursor(&self) -> KeyCursorBuilder<'_, Self> { KeyCursorBuilder::new(self) } } diff --git a/src/transaction/base.rs b/src/transaction/base.rs index b6f0038..4f2c730 100644 --- a/src/transaction/base.rs +++ b/src/transaction/base.rs @@ -32,7 +32,7 @@ impl<'a> TransactionRef<'a> { /// Get an object store that's part of the transaction. #[errdoc(Transaction(NotFoundError, InvalidStateError))] #[allow(clippy::missing_errors_doc)] - pub fn object_store(&self, name: &str) -> crate::Result { + pub fn object_store(&self, name: &str) -> crate::Result> { match self.as_sys().object_store(name) { Ok(store) => Ok(ObjectStore::new(store, self)), Err(e) => Err(e.into()), @@ -41,7 +41,7 @@ impl<'a> TransactionRef<'a> { /// Get an iterator of the names of [`IdbObjectStore`](ObjectStore) objects /// associated with the transaction. - pub fn object_store_names(&self) -> DomStringIter { + pub fn object_store_names(&self) -> DomStringIter<'_> { DomStringIter::new(self.as_sys().object_store_names()) } diff --git a/src/typed_array.rs b/src/typed_array.rs index 499db0a..9a25100 100644 --- a/src/typed_array.rs +++ b/src/typed_array.rs @@ -36,7 +36,7 @@ pub struct TypedArraySlice<'a, T>(#[new(name(source))] &'a [T]); impl TypedArray { /// Convert this [`TypedArray`] into a [`TypedArraySlice`]. #[must_use] - pub fn as_slice(&self) -> TypedArraySlice { + pub fn as_slice(&self) -> TypedArraySlice<'_, T> { TypedArraySlice::new(&self.0) } } From 0a4b2e0aea2e7450ea8f422ef1839c73cdbd0c66 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Sun, 7 Sep 2025 12:08:52 -0400 Subject: [PATCH 10/13] fix: remove explicit transaction commit on drop For some reason, calling `TransactionSys::do_commit` in the `Drop` implementation of `Transaction` causes tests in a headless Chrome browser to hang, even though they pass in a non-headless context. Ultimately, the default behavior on the JavaScript end is for the transaction to be committed, so it is safe to exclude this action. --- src/transaction.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/transaction.rs b/src/transaction.rs index 5def5c1..e4d663f 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -162,11 +162,11 @@ impl Drop for Transaction<'_> { fn drop(&mut self) { self.listeners.free_listeners(); - if !self.done { - let _ = match self.on_drop { - OnTransactionDrop::Abort => self.as_sys().abort(), - OnTransactionDrop::Commit => self.as_sys().do_commit(), - }; + // Given that the default behavior in JavaScript is to commit the + // transaction when it is dropped, we only need to perform an action + // when we want to abort the transaction. + if !self.done & matches!(self.on_drop, OnTransactionDrop::Abort) { + let _ = self.as_sys().abort(); } } } From 2ac8f0bc1c53f8d8654efcdf9e32b6d359457398 Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Sun, 7 Sep 2025 14:51:09 -0400 Subject: [PATCH 11/13] docs: add relevant feature gates to doc tests --- src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bb96106..2ff81b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ //! //! # async fn example() -> indexed_db_futures::OpenDbResult<()> { //! # #[allow(dead_code)] +//! # #[cfg(all(feature = "async-upgrade", feature = "tx-done"))] //! let db = Database::open("my_db") //! .with_version(2u8) //! .with_on_blocked(|event| { @@ -142,6 +143,8 @@ //! ## Reading/writing with `serde` //! //! ``` +//! # #[cfg(feature = "serde")] +//! # mod wrapper { //! # use indexed_db_futures::object_store::ObjectStore; //! # use indexed_db_futures::prelude::*; //! # use serde::{Deserialize, Serialize}; @@ -158,6 +161,7 @@ //! let user: Option = object_store.get(1u32).serde()?.await?; //! # Ok(()) //! # } +//! # } //! ``` //! //! # Iterating a cursor @@ -167,6 +171,7 @@ //! # use indexed_db_futures::prelude::*; //! # //! # #[allow(dead_code)] +//! # #[cfg(feature = "cursors")] //! # async fn example(object_store: ObjectStore<'_>) -> indexed_db_futures::Result<()> { //! let Some(mut cursor) = object_store.open_cursor().await? else { //! log::debug!("Cursor empty"); @@ -182,6 +187,8 @@ //! # Iterating an index as a stream //! //! ``` +//! # #[cfg(all(feature = "serde", feature = "indices", feature = "cursors", feature = "streams"))] +//! # mod wrapper { //! # use indexed_db_futures::object_store::ObjectStore; //! # use indexed_db_futures::prelude::*; //! # use serde::{Deserialize, Serialize}; @@ -204,6 +211,7 @@ //! let records = stream.try_collect::>().await?; //! # Ok(()) //! # } +//! # } //! ``` //! From 77cd2ab361227c02b2949faaeb27ee9278b37e3b Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 30 Sep 2025 10:07:43 -0400 Subject: [PATCH 12/13] refactor: add and use default impl for OnTransactionDrop --- src/transaction.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/transaction.rs b/src/transaction.rs index e4d663f..c96cbc1 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -47,10 +47,11 @@ pub struct Transaction<'a> { /// committing - i.e., [`OnTransactionDrop::Commit`] - the transaction! /// /// [1]: https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Default)] pub enum OnTransactionDrop { /// Abort the [`Transaction`] when it is dropped. This is the default /// behavior of [`Transaction`]. + #[default] Abort, /// Commit the [`Transaction`] when it is dropped. This is the default /// behavior of an [`IDBTransaction`][1] in JavaScript. @@ -87,7 +88,7 @@ impl<'a> Transaction<'a> { Self { listeners: TxListeners::new(db, inner), done: false, - on_drop: OnTransactionDrop::Abort, + on_drop: OnTransactionDrop::default(), } } From 6ec78c3da741bfbbdb728316f16c916c3403df7f Mon Sep 17 00:00:00 2001 From: Michael Goldenberg Date: Tue, 30 Sep 2025 10:17:24 -0400 Subject: [PATCH 13/13] docs: add comment explaining implicit transaction commit on drop --- src/transaction.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/transaction.rs b/src/transaction.rs index c96cbc1..144a08a 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -166,6 +166,14 @@ impl Drop for Transaction<'_> { // Given that the default behavior in JavaScript is to commit the // transaction when it is dropped, we only need to perform an action // when we want to abort the transaction. + // + // Typically, it would make sense to explicitly commit the transaction + // with `TransactionSys::do_commit` when encountering `OnTransactionDrop::Commit`. + // However, for some reason, explicitly committing the transaction causes + // tests in a headless Chrome browser to hang, even though they pass in + // all other contexts, including a non-headless Chrome browser. So, until + // this is resolved, it is best to let `OnTransactionDrop::Commit` be + // handled implicitly by the JavaScript runtime. if !self.done & matches!(self.on_drop, OnTransactionDrop::Abort) { let _ = self.as_sys().abort(); }