Expand description
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 indexed_db_futures::database::Database;
use indexed_db_futures::prelude::*;
use indexed_db_futures::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(|event, db| async move {
// 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. |
Modules§
- cursor
cursors IDBCursor&IDBCursorWithValueimplementations.- database
- An
IDBDatabaseimplementation. - date
dates - Date handling. Re-exports the
web_timecrate. - error
- Crate errors.
- factory
- An
IDBFactoryimplementation. - future
- Futures in use by the crate.
- index
indices - An
IDBIndeximplementation. - internals
- Internal type representations
- iter
- Iterators used by the crate.
- object_
store - An
IDBObjectStoreimplementation. - prelude
- Things to
use::*. - primitive
- Types for working with
wasm-bindgenandjs-sysprimitive that don’t requireserdefor converting between Rust & JS. - query_
source - Common functionality for making queries.
- transaction
- An
IDBTransactionimplementation. - typed_
array typed-arrays - Partial
TypedArrayimplementations. Currently only aims to offer convenient conversion to/fromVec.
Enums§
- KeyPath
- A key path representation.
- KeyRange
- An
IDBKeyRangeimplementation.
Traits§
- Build
- Finalise the builder.
- Build
Primitive - Finalise the builder for returning a primitive result.
- Build
Serde - Finalise the builder for returning a deserialisable result.
- Deserialise
From Js serde - A type that’s convertible from
JsValueusingserde, but not necessarilydeserialisableas a whole. - Serialise
ToJs serde - A type that’s convertible to
JsValueusingserde, but not necessarilyserialisableas a whole.
Type Aliases§
- KeyPath
Seq - Alias for the
SmallVecs used forSequencekey paths. - Open
DbResult - A
Resultwith anOpenDbErroras the error type. - Result
- A
Resultwith anErroras the error type.