use std::collections::HashSet;
use js_sys::Reflect;
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{Event, IdbDatabase, IdbFactory, IdbOpenDbRequest};
use crate::{request::wait_request, utils::set_panic_hook, Error, ObjectStore, Result, Rexie};
pub struct RexieBuilder {
name: String,
version: Option<u32>,
object_stores: Vec<ObjectStore>,
}
impl RexieBuilder {
pub fn new(name: &str) -> Self {
set_panic_hook();
Self {
name: name.to_owned(),
version: None,
object_stores: Vec::new(),
}
}
pub fn version(mut self, version: u32) -> Self {
self.version = Some(version);
self
}
pub fn add_object_store(mut self, object_store: ObjectStore) -> Self {
self.object_stores.push(object_store);
self
}
pub async fn build(self) -> Result<Rexie> {
let idb_open_request = get_idb_open_request(&self.name, self.version)?;
let _upgrade_handler = set_upgrade_handler(&idb_open_request, self.object_stores);
let db = wait_request(idb_open_request, Error::IndexedDbOpenFailed)
.await?
.unchecked_into();
Ok(Rexie { db })
}
pub async fn delete(self) -> Result<()> {
let factory = get_idb_factory()?;
let idb_open_request = factory
.delete_database(&self.name)
.map_err(Error::IndexedDbOpenFailed)?;
wait_request(idb_open_request, Error::IndexedDbDeleteFailed)
.await
.map(|_| ())
}
}
fn get_idb_factory() -> Result<IdbFactory> {
Reflect::get(&js_sys::global(), &JsValue::from("indexedDB"))
.map_err(Error::IndexedDbNotFound)?
.dyn_into()
.map_err(Error::IndexedDbNotFound)
}
fn get_idb_open_request(name: &str, version: Option<u32>) -> Result<IdbOpenDbRequest> {
let idb_factory = get_idb_factory()?;
match version {
Some(version) => idb_factory.open_with_u32(name, version),
None => idb_factory.open(name),
}
.map_err(Error::IndexedDbOpenFailed)
}
fn set_upgrade_handler(
idb_open_request: &IdbOpenDbRequest,
object_stores: Vec<ObjectStore>,
) -> Closure<dyn FnMut(Event) -> Result<()>> {
let upgrade_handler =
Closure::once(move |event: Event| -> Result<()> { upgrade_handler(event, object_stores) });
idb_open_request.set_onupgradeneeded(Some(upgrade_handler.as_ref().unchecked_ref()));
upgrade_handler
}
fn upgrade_handler(event: Event, object_stores: Vec<ObjectStore>) -> Result<()> {
let mut store_names: HashSet<String> = object_stores.iter().map(|os| os.name.clone()).collect();
let idb_open_request: IdbOpenDbRequest = event
.target()
.ok_or(Error::EventTargetNotFound)?
.unchecked_into();
let idb: IdbDatabase = idb_open_request
.result()
.map_err(Error::IndexedDbNotSupported)?
.unchecked_into();
for object_store in object_stores {
object_store.create(&idb_open_request, &idb)?;
}
let db_store_names = idb.object_store_names();
let mut stores_to_remove = Vec::new();
for index in 0..db_store_names.length() {
let db_store_name = db_store_names
.get(index)
.ok_or_else(|| Error::IndexedDbUpgradeFailed("unable to get store name".into()))?;
if store_names.contains(&db_store_name) {
store_names.remove(&db_store_name);
} else {
stores_to_remove.push(db_store_name);
}
}
for store_name in stores_to_remove {
idb.delete_object_store(&store_name)
.map_err(Error::IndexedDbUpgradeFailed)?;
}
Ok(())
}