| Crates.io | indexed_db_futures |
| lib.rs | indexed_db_futures |
| version | 0.6.4 |
| created_at | 2021-07-25 14:45:45.084954+00 |
| updated_at | 2025-05-11 14:05:04.271305+00 |
| description | Future bindings for IndexedDB via web_sys |
| homepage | |
| repository | https://github.com/Alorel/rust-indexed-db |
| max_upload_size | |
| id | 427090 |
| size | 285,150 |
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.
Goals & features:
web_sys or
js_sys APIs - this should feel like a native Rust API.serde, but don't require it - as a rule of thumb, you'll use
serde-serialisable types when working with JS objects & bypass serde for Javascript primitives.Stream where applicable - cursors
and key cursors have this at the time of writing.?s.use indexed_db_futures::database::Database;
use indexed_db_futures::prelude::*;
use indexed_db_futures::transaction::TransactionMode;
#[derive(Serialize, Deserialize)]
struct MySerdeType(u8, String);
#[derive(Deserialize)]
#[serde(untagged)]
enum ObjectOrString {
Object(MySerdeType),
String(String),
}
async fn main() -> indexed_db_futures::OpenDbResult<()> {
let db = Database::open("my_db")
.with_version(2u8)
.with_on_upgrade_needed(|event, 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 (event.old_version(), event.new_version()) {
(0, Some(1)) => {
db.create_object_store("my_store")
.with_auto_increment(true)
.build()?;
}
(prev, Some(2)) => {
if prev == 1 {
let _ = db.delete_object_store("my_store");
}
db.create_object_store("my_other_store").build()?;
}
_ => {}
}
Ok(())
})
.await?;
// Populate some data
let transaction = db
.transaction("my_other_store")
.with_mode(TransactionMode::Readwrite)
.build()?;
let store = transaction.object_store("my_other_store")?;
store
.put("a primitive value that doesn't need serde")
.with_key("my_key")
.await?;
// Awaiting individual requests is optional - they still go out
store
.put(MySerdeType(10, "foos".into()))
.with_key("my_serde_key")
.with_key_type::<String>() // `serde` keys must be deserialisable; String is, but the &str above isn't
.serde()?;
// Unlike JS, transactions ROLL BACK INSTEAD OF COMMITTING BY DEFAULT
transaction.commit().await?;
// Read some data
let transaction = db.transaction("my_other_store").build()?;
let store = transaction.object_store("my_other_store")?;
// `None` is returned if the cursor is empty
if let Some(mut cursor) = store.open_cursor().await? {
// Use a limited loop in case we made a mistake and result in an infinite loop
for _ in 0..5 {
// We inserted a serde record and a primitive one so we need to deserialise as an enum that supports both
match cursor.next_record_ser::<ObjectOrString>().await {
Ok(Some(record)) => match record {
ObjectOrString::Object(serde_record) => {
assert_eq!(serde_record.0, 10);
assert_eq!(serde_record.1, "foos");
}
ObjectOrString::String(string_record) => {
assert_eq!(
string_record.as_str(),
"a primitive value that doesn't need serde"
);
}
},
Err(e) => return Err(e.into()),
Ok(None) => return Ok(()), // reached cursor end
}
}
panic!("Got an infinite loop!");
}
Ok(())
}
Head over to the docs for a proper introduction!