1 unstable release
| 0.7.0 | Dec 3, 2025 |
|---|
#198 in WebAssembly
10,454 downloads per month
Used in 5 crates
(via matrix-sdk-indexeddb)
265KB
6.5K
SLoC
A fork of indexed_db_futures
This is a fork of indexed_db_futures while we are waiting on some patches to
be merged.
lib.rs:
Wraps the web_sys Indexed DB API in a Future-based API and
removes the pain of dealing with JS callbacks or JSValue in Rust.
Overall API design
This library implements the same structs and methods as the JavaScript API - there should be no learning curve involved if you're familiar with it.
Primitives
In the context of this library, primitives refer to types that would be considered scalar primitives in JavaScript
(bar some feature-flagged exceptions) and are converted using the TryToJs &
TryFromJs traits. They are meant to be quickly derivable from
JsValue, e.g. String is easily derivable via
JsValue::as_string.
Builders
Most API calls are constructed using builders which, in turn, get built using one of the following traits:
BuildPrimitive- implemented for requests use primitive serialisation.BuildSerde- implemented for requests that useserdeserialisation.Build- implemented for requests that aren'tserdeor primitive-serialisable (e.g. creating an index). Implemented automatically for any type that implementsBuildPrimitive. As a convenience method, types that implementBuildorBuildPrimitivealso implementIntoFuture.
Note that API requests go out immediately after being built, not after being awaited.
Transactions default to rolling back
❗ Unlike Javascript, transactions will roll back by default instead of committing - this design choice was made to
allow code to use ?s. There is one browser compatibility-related caveat, however - see comment on
Transaction::abort for more details.
Multi-threaded executor
You will likely run into issues if your app is compiled with #[cfg(target_feature = "atomics")] as reported in
#33.
Transactions auto-commit on JavasScript's end on the next tick of the event loop if there are no outstanding
requests active; this isn't a problem in the default single-threaded executor, but, in a multi-threaded environment,
wasm-bindgen-futures needs to schedule our closures on the next tick as well which causes transactions to
prematurely auto-commit.
As a workaround, you can try only awaiting individual requests after committing your transaction (requests go out
after being built, not after being polled).
#
let transaction = db.transaction("my_store").with_mode(TransactionMode::Readwrite).build()?;
let object_store = transaction.object_store("my_store")?;
let req1 = object_store.add("foo").primitive()?;
let req2 = object_store.add("bar").primitive()?;
transaction.commit().await?;
req1.await?;
req2.await?;
Alternatively, you can check out the indexed_db crate which explicitly
focuses on multi-threaded support at the cost of ergonomics.
Examples
Opening a database & making some schema changes
use matrix_indexed_db_futures::database::{Database, VersionChangeEvent};
use matrix_indexed_db_futures::prelude::*;
use matrix_indexed_db_futures::transaction::{Transaction, TransactionMode};
let db = Database::open("my_db")
.with_version(2u8)
.with_on_blocked(|event| {
log::debug!("DB upgrade blocked: {:?}", event);
Ok(())
})
.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);
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 {
if let Err(e) = db.delete_object_store("my_store") {
log::error!("Error deleting v1 object store: {}", e);
}
}
// 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(())
})
.await?;
Reading/writing with serde
#
#[derive(Serialize, Deserialize)]
struct UserRef {
id: u32,
name: String,
}
object_store.put(UserRef { id: 1, name: "Bobby Tables".into() }).serde()?.await?;
let user: Option<UserRef> = object_store.get(1u32).serde()?.await?;
Iterating a cursor
#
let Some(mut cursor) = object_store.open_cursor().await? else {
log::debug!("Cursor empty");
return Ok(());
};
// Retrieve the next record in the stream, expecting a String
let next: Option<String> = cursor.next_record().await?;
Iterating an index as a stream
use futures::TryStreamExt;
#
let index = object_store.index("my_index")?;
let Some(cursor) = index.open_cursor().with_query(10u32..=100u32).serde()?.await? else {
log::debug!("Cursor empty");
return Ok(());
};
let stream = cursor.stream_ser::<UserRef>();
let records = stream.try_collect::<Vec<_>>().await?;
Environment support
The following table is populated as a best effort attempt based on the crate's unit tests succeeding/failing under different configurations.
| Environment | Chrome | Firefox | Safari |
|---|---|---|---|
| Browser | ✅ | ✅ | ✅ |
| Dedicated Worker | ✅ | ✅ | ✅ |
| Shared Worker | ✅ | ✅ | ✅ |
| Worker | ✅ | ✅ | ✅ |
| Service worker | ✅ | ❌ | ✅ |
Feature table
| Feature | Description |
|---|---|
async-upgrade |
Enable async closures in upgradeneeded event listeners. |
cursors |
Enable opening IndexedDB cursors. |
dates |
Enable SystemTime & Date handling. |
indices |
Enable IndexedDB indices. |
list-databases |
Enable getting a list of defined databases. |
serde |
Enable serde integration. |
streams |
Implement Stream where applicable. |
switch |
Enable switches. |
tx-done |
Enable waiting for transactions to complete without consuming them. |
typed-arrays |
Enable typed array handling. |
version-change |
Enable listening for versionchange events. |
Dependencies
~9–12MB
~218K SLoC