Skip to content

Alorel/rust-indexed-db

Folders and files

NameName
Last commit message
Last commit date

Latest commit

abf8ddc · May 11, 2025

History

85 Commits
May 11, 2025
Oct 27, 2024
Oct 27, 2024
Oct 27, 2024
May 11, 2025
May 11, 2025
Jun 9, 2021
Oct 27, 2024
May 11, 2025
May 11, 2025
Apr 19, 2025
May 11, 2025
Jul 25, 2021

Repository files navigation

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.

master CI badge crates.io badge docs.rs badge dependencies badge

Goals & features:

  • Shield you from having to interact with web_sys or js_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 use serde-serialisable types when working with JS objects & bypass serde 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!