alorel / rust-indexed-db Goto Github PK
View Code? Open in Web Editor NEWFuture bindings for IndexedDB via web_sys
Future bindings for IndexedDB via web_sys
web_sys
defines IdbObjectStore::get_all_with_key_and_limit
and IdbIndex::get_all_with_key_and_limit
, but as far as I can tell they are not exposed by rust-indexed-db.
This event is fired during a transaction with mode versionchange
, which should be available in event.target.transaction
.
Although accessing the IdbDatabase
allows us to create/delete object stores and set their indexes, it doesn't allow to access current stores. We need to access them via the IdbTransaction
.
It would allow to change the indexes of the existing object stores, and to fix data in the stores or even migrate data from one store to the other.
Hey!
Here's an idea for indexed-db-futures v0.5: what if transactions aborted on drop, instead of committing on drop?
This'd make them farther away from the indexed-db standard, for sure, but the indexed-db standard is based on callbacks, which are pretty different from futures anyway. And, in Rust, it's very easy to miss one early-return point, to make sure that returning Err
from a function aborts the transaction.
TL;DR:
fn foo() {
let transaction = [...];
transaction.add_key_val(...).unwrap().await.unwrap();
do_some_check(&transaction).await?;
Ok(())
}
This will (AFAICT) commit the transaction if do_some_check
were to return an Err
. The behavior I'd expect from the code if just reading it intuitively, would be for the transaction to be aborted.
In order to get the behavior I'd instinctively expect, I need to use the following code, which is quite a bit less pleasant to both write and read:
fn foo() {
let transaction = [...];
transaction.add_key_val(...).unwrap().await.unwrap();
if let Err(e) = do_some_check(&transaction).await {
transaction.abort().unwrap();
return Err(e);
}
Ok(())
}
WDYT about adding a commit(self)
function to transaction
, that'd commit it (ie. just drop it), and to have IdbTransaction
's Drop
implementation abort the transaction if it has not been explicitly committed?
Anyway, I'm just starting using indexed-db-futures, but it already seems much, much more usable than the web-sys version. So, thank you! :D
Hi,
first of all, thanks for this nice crate.
I want to work with indexes, and I wonder how I get an index in a transaction. Did I miss something, or is that not yet implemented? Basically this: https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/index
I can give it a try if this is indeed missing.
In the callback we might need to await
a transaction to make sure everything happens in the correct order.
For example porting the example of the "Structuring the database" section of MDN's guide is currently not possible.
They recommend to wait for the createObjectStore
transaction to be completed before adding data in it. In Rust code that requires turning the transaction into a future.
Need help, I am new to rust. I am not sure what is wrong in below code
use indexed_db_futures::prelude::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use wee_alloc::WeeAlloc;
use web_sys::DomException;
// use wasm_bindgen_futures::{spawn_local, JsFuture};
// Use `wee_alloc` as the global allocator.
#[global_allocator]
static ALLOC: WeeAlloc = WeeAlloc::INIT;
#[wasm_bindgen]
pub struct AppIDB {
pub db_name: String,
pub store_name: String,
pub version: u32,
pub db: IdbDatabase
}
#[wasm_bindgen]
impl AppIDB {
pub async fn open_db(db_name: String, store_name: String, version: u32) -> Result<Option<AppIDB>, JsValue> {
let db_name_clone = db_name.clone();
let store_name_clone = store_name.clone();
let version_clone = version.clone();
let sn1 = store_name_clone.clone();
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(&db_name_clone, version_clone)?;
db_req.set_on_upgrade_needed(Some(move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Check if the object store exists; create it if it doesn't
if let None = evt.db().object_store_names().find(|n| n == sn1.as_str()) {
evt.db().create_object_store(sn1.as_str())?;
}
Ok(())
}));
let db = db_req.into_future().await?;
let app_idb = AppIDB {
db,
db_name: db_name_clone,
store_name: store_name_clone,
version: version_clone
};
Ok(Some(app_idb))
}
pub async fn get(self: AppIDB, key: String) -> Result<Option<JsValue>, DomException> {
let key_clone = key.clone();
// Get a record
let tx = self.db.transaction_on_one(&self.store_name)?;
let store = tx.object_store(&self.store_name)?;
let value: Option<JsValue> = store.get_owned(key_clone)?.await?;
//use_value(value);
// All of the requests in the transaction have already finished so we can just drop it to
// avoid the unused future warning, or assign it to _.
let _ = tx;
Ok(value)
}
pub async fn insert(self: AppIDB, key: String, value: String) -> Result<(), DomException> {
let key_clone = key.clone();
let value_clone = value.clone();
// Insert/overwrite a record
let tx: IdbTransaction = self.db
.transaction_on_one_with_mode(&self.store_name, IdbTransactionMode::Readwrite)?;
let store: IdbObjectStore = tx.object_store(&self.store_name)?;
//let value_to_put: String = value.to_string();
store.put_key_val_owned(key_clone, &JsValue::from(value_clone))?;
// IDBTransactions can have an Error or an Abort event; into_result() turns both into a
// DOMException
tx.await.into_result()?;
Ok(())
}
pub async fn delete(self: AppIDB, key: String) -> Result<(), DomException> {
let key_clone = key.clone();
// Delete a record
let tx = self.db.transaction_on_one_with_mode(&self.store_name, IdbTransactionMode::Readwrite)?;
let store = tx.object_store(&self.store_name)?;
store.delete_owned(key_clone)?;
tx.await.into_result()?;
Ok(())
}
}
My testcase is failing. below is the error message:
12 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `IntoWasmAbi` is not implemented for `indexed_db_futures::IdbDatabase`
|
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
Hi, thanks for the library! I am trying to get a basic string-storage working as a first step, but ran into a problem while following the sample code in the lib.rs file. When attempting to use the exact code from the sample, it produces an error. I am using rustc v1.24.3
.
db_req.set_on_upgrade_needed(Some(|evt: IdbVersionChangeEvent| -> Result<(), JsValue> {
// Check if the object store exists; create it if it doesn't
if let None = evt.db().object_store_names().find(|n| n == "my_store") {
evt.db().create_object_store("my_store")?;
}
Ok(())
}));
type mismatch in closure arguments
expected signature of `for<'r> fn(&'r indexed_db_futures::IdbVersionChangeEvent) -> _`rustcE0631
indexeddb.rs(9, 36): found signature of `fn(indexed_db_futures::IdbVersionChangeEvent) -> _`
Do you have any suggestions? Thanks a lot!
Took good care to make sure the IDB transaction's result
RefCell
is only borrowed once at a time, but clearly missed a case somewhere. Doing
store.add_keyval(key, value);
store.add_keyval(key, value); // same key as before to trigger an error
tx.await; // this will panic
crashes as the code tries to mutably borrow the RefCell.
in trying to retrieve all the keys and values from my indexeddb, however, i have no idea how to get the Array type from the JsCastRequestFuture<Array>
return type of
Could you please contact me?
Currently, indexed_db_futures
relies on behavior of the futures executor that is not actually specified: it assumes that waking a task from an IndexedDB callback, and then returning from the IndexedDB callback, will result in the awoken task being executed before returning to the browser event loop.
This is not necessarily true, and in particular breaks with the multi-threaded wasm-bindgen executor. I opened an issue upstream, but unfortunately it seems hard to fix, and was closed as wontfix, because that specific behavior of the singlethread executor was never documented in the first place:
I'm opening this issue to let you know about this current limitation of indexed_db_futures. Please don't just comment on the upstream issue without carefully considering whether you're bringing something new to the table, as that would only be bad vibes for the wasm-bindgen maintainers, and they're doing an awesome job.
On my end I'm planning to fix this in my IndexedDB crate via this change that makes the transaction only ever execute from the callbacks. I verified and it works with the multi-threaded executor, but it also requires a slightly less convenient API. I'm hoping one of the places where I'm opening this issue will have an idea for how to handle this differently :)
Hope that helps!
Based on https://github.com/leptos-rs/leptos/tree/main/examples/tailwind_csr_trunk
use indexed_db_futures::{
prelude::{IdbObjectStore, IdbTransaction},
request::IdbOpenDbRequestLike,
web_sys::IdbTransactionMode,
IdbDatabase, IdbQuerySource, IdbVersionChangeEvent,
};
use leptos::*;
use wasm_bindgen::JsValue;
use crate::app::navbar::Navbar;
async fn prepare_db() -> String {
println!("prepare db");
let mut db_req = indexed_db_futures::IdbDatabase::open_u32("my_db", 1).unwrap();
db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Check if the object store exists; create it if it doesn't
println!("upgrade needed");
if let None = evt.db().object_store_names().find(|n| n == "my_store") {
evt.db().create_object_store("my_store")?;
}
Ok(())
}));
Ok("test".to_string())
}
#[component]
pub fn Settings(cx: Scope) -> impl IntoView {
let settings = create_local_resource(cx, || (), |_| async { prepare_db().await });
view! { cx,
<Navbar></Navbar>
<div class="flex flex-col w-full border-opacity-50">
<div class="grid h-20 card rounded-box place-items-center">
<div class="card w-9/12 bg-secondary-content shadow-xl">
<div class="card-body">
<Await
// `future` provides the `Future` to be resolved
future=|cx| prepare_db()
// the data is bound to whatever variable name you provide
bind:data
>
<p>{data} " example..."</p>
</Await>
</div>
</div>
</div>
}
}
the console prints
tailwind-csr-trunk-b03fafd26d188d41.js:353 panicked at src/app/settings.rs:29:10:
called `Result::unwrap()` on an `Err` value: DomException { obj: Object { obj: JsValue(NotFoundError: Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found.
Error: Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found.
at http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41.js:640:33
at handleError (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41.js:285:18)
at imports.wbg.__wbg_transaction_d6f1ef0b34b58a31 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41.js:638:70)
at web_sys::features::gen_IdbDatabase::IdbDatabase::transaction_with_str_and_mode::h9680dab48f88a29d (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[2074]:0x321e85)
at indexed_db_futures::idb_database::IdbDatabase::transaction_on_one_with_mode::h6d88b6cf300c7fe8 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[1260]:0x2cd142)
at tailwind_csr_trunk::app::settings::prepare_db::{{closure}}::h6768f907d6776f11 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[494]:0x2184ae)
at tailwind_csr_trunk::app::settings::Settings::__Settings::{{closure}}::{{closure}}::h137b5fabeaf8260e (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[1226]:0x2c8ad7)
at <core::pin::Pin<P> as core::future::future::Future>::poll::h5f631c18c6c04a0b (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[4431]:0x3ae561)
at leptos_reactive::resource::ResourceState<S,T>::load::{{closure}}::{{closure}}::hf79dd7b6b235cccc (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[164]:0x41b99)
at wasm_bindgen_futures::task::singlethread::Task::run::h9d4a0df6390fda94 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[1257]:0x2ccbca)) } }
Stack:
Error
at imports.wbg.__wbg_new_abda76e883ba8a5f (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41.js:356:17)
at console_error_panic_hook::Error::new::hf9c38ed484c370cc (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[7904]:0x40051c)
at console_error_panic_hook::hook_impl::hf60de235cbe0d0f8 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[1679]:0x2fd107)
at console_error_panic_hook::hook::h56198eb557ac7d98 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[8471]:0x4083af)
at core::ops::function::Fn::call::ha69410e5d033974e (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[7598]:0x3fb91b)
at std::panicking::rust_panic_with_hook::he756764f61c69795 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[3570]:0x387195)
at std::panicking::begin_panic_handler::{{closure}}::hc32c236557ee2684 (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[4445]:0x3aee18)
at std::sys_common::backtrace::__rust_end_short_backtrace::hb038a7c53aa47b5e (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[10033]:0x417335)
at rust_begin_unwind (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[6207]:0x3e1634)
at core::panicking::panic_fmt::h4f7b0621cfc33d8f (http://127.0.0.1:8080/tailwind-csr-trunk-b03fafd26d188d41_bg.wasm:wasm-function[8347]:0x406a4e)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.