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:
- Shield you from having to interact with
web_sys
orjs_sys
APIs - this should feel like a native Rust API. - Integrate with
serde
, but don't require it - as a rule of thumb, you'll useserde
-serialisable types when working with JS objects & bypassserde
for Javascript primitives. - Implement
Stream
where applicable - cursors and key cursors have this at the time of writing. - Implement a more Rust-oriented API - for example, transactions will roll back by default unless explicitly committed to allow you to use
?
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!