indexed-db 0.4.2

Bindings to IndexedDB that default the transactions to aborting and can work multi-threaded
Documentation
use futures_channel::oneshot;
use futures_util::future::{self, Either};
use std::ops::{Bound, RangeBounds};
use web_sys::{
    js_sys::{Array, Function, JsString, Number, TypeError},
    wasm_bindgen::{closure::Closure, JsCast, JsValue},
    DomException, IdbKeyRange, IdbRequest,
};

pub(crate) async fn generic_request(req: IdbRequest) -> Result<web_sys::Event, web_sys::Event> {
    let (success_tx, success_rx) = oneshot::channel();
    let (error_tx, error_rx) = oneshot::channel();

    let on_success = Closure::once(move |v| success_tx.send(v));
    let on_error = Closure::once(move |v| error_tx.send(v));

    req.set_onsuccess(Some(on_success.as_ref().dyn_ref::<Function>().unwrap()));
    req.set_onerror(Some(on_error.as_ref().dyn_ref::<Function>().unwrap()));

    match future::select(success_rx, error_rx).await {
        Either::Left((res, _)) => Ok(res.unwrap()),
        Either::Right((res, _)) => Err(res.unwrap()),
    }
}

pub(crate) fn none_if_undefined(v: JsValue) -> Option<JsValue> {
    if v.is_undefined() {
        None
    } else {
        Some(v)
    }
}

pub(crate) fn array_to_vec(v: JsValue) -> Vec<JsValue> {
    let array = v
        .dyn_into::<Array>()
        .expect("Value was not of the expected Array type");
    let len = array.length();
    let mut res = Vec::with_capacity(usize::try_from(len).unwrap());
    for i in 0..len {
        res.push(array.get(i));
    }
    res
}

pub(crate) fn str_slice_to_array(s: &[&str]) -> Array {
    let res = Array::new_with_length(u32::try_from(s.len()).unwrap());
    for (i, v) in s.iter().enumerate() {
        res.set(u32::try_from(i).unwrap(), JsString::from(*v).into());
    }
    res
}

pub(crate) fn err_from_event(evt: web_sys::Event) -> DomException {
    evt.prevent_default(); // Avoid the transaction aborting upon an error
    let idb_request = evt
        .target()
        .expect("Trying to parse indexed_db::Error from an event that has no target")
        .dyn_into::<web_sys::IdbRequest>()
        .expect("Trying to parse indexed_db::Error from an event that is not from an IDBRequest");
    idb_request
        .error()
        .expect("Failed to retrieve the error from the IDBRequest that called on_error")
        .expect("IDBRequest::error did not return a DOMException")
}

pub(crate) fn map_add_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("ReadOnlyError") => crate::Error::ReadOnly,
        Some("TransactionInactiveError") => {
            panic!("Tried adding to an ObjectStore while the transaction was inactive")
        }
        Some("DataError") => crate::Error::InvalidKey,
        Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
        Some("DataCloneError") => crate::Error::FailedClone,
        Some("ConstraintError") => crate::Error::AlreadyExists,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_clear_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("ReadOnlyError") => crate::Error::ReadOnly,
        Some("TransactionInactiveError") => {
            panic!("Tried clearing an ObjectStore while the transaction was inactive")
        }
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_count_res(res: JsValue) -> usize {
    let num = res
        .dyn_into::<Number>()
        .expect("IDBObjectStore::count did not return a Number");
    assert!(
        Number::is_integer(&num),
        "Number of elements in object store is not an integer"
    );
    num.value_of() as usize
}

pub(crate) fn map_count_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
        Some("TransactionInactiveError") => {
            panic!("Tried counting in an ObjectStore while the transaction was inactive")
        }
        Some("DataError") => crate::Error::InvalidKey,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_delete_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("ReadOnlyError") => crate::Error::ReadOnly,
        Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
        Some("TransactionInactiveError") => {
            panic!("Tried deleting from an ObjectStore while the transaction was inactive")
        }
        Some("DataError") => crate::Error::InvalidKey,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_get_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
        Some("TransactionInactiveError") => {
            panic!("Tried getting from an ObjectStore while the transaction was inactive")
        }
        Some("DataError") => crate::Error::InvalidKey,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_open_cursor_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::ObjectStoreWasRemoved,
        Some("TransactionInactiveError") => {
            panic!("Tried opening a Cursor on an ObjectStore while the transaction was inactive")
        }
        Some("DataError") => crate::Error::InvalidKey,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_cursor_advance_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::CursorCompleted,
        Some("TransactionInactiveError") => {
            panic!("Tried advancing a Cursor on an ObjectStore while the transaction was inactive")
        }
        None if err.has_type::<TypeError>() => crate::Error::InvalidArgument,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_cursor_advance_until_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::CursorCompleted,
        Some("TransactionInactiveError") => {
            panic!("Tried advancing a Cursor on an ObjectStore while the transaction was inactive")
        }
        Some("DataError") => crate::Error::InvalidKey,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_cursor_advance_until_primary_key_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::CursorCompleted,
        Some("TransactionInactiveError") => {
            panic!("Tried advancing a Cursor on an ObjectStore while the transaction was inactive")
        }
        Some("DataError") => crate::Error::InvalidKey,
        Some("InvalidAccessError") => crate::Error::InvalidArgument,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_cursor_delete_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::CursorCompleted,
        Some("TransactionInactiveError") => {
            panic!("Tried advancing a Cursor on an ObjectStore while the transaction was inactive")
        }
        Some("ReadOnlyError") => crate::Error::ReadOnly,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn map_cursor_update_err<Err>(err: JsValue) -> crate::Error<Err> {
    match error_name!(&err) {
        Some("InvalidStateError") => crate::Error::CursorCompleted,
        Some("TransactionInactiveError") => {
            panic!("Tried advancing a Cursor on an ObjectStore while the transaction was inactive")
        }
        Some("ReadOnlyError") => crate::Error::ReadOnly,
        Some("DataError") => crate::Error::InvalidKey,
        Some("DataCloneError") => crate::Error::FailedClone,
        _ => crate::Error::from_js_value(err),
    }
}

pub(crate) fn make_key_range<Err>(range: impl RangeBounds<JsValue>) -> crate::Result<JsValue, Err> {
    match (range.start_bound(), range.end_bound()) {
        (Bound::Unbounded, Bound::Unbounded) => return Err(crate::Error::InvalidRange),
        (Bound::Unbounded, Bound::Included(b)) => IdbKeyRange::upper_bound_with_open(b, false),
        (Bound::Unbounded, Bound::Excluded(b)) => IdbKeyRange::upper_bound_with_open(b, true),
        (Bound::Included(b), Bound::Unbounded) => IdbKeyRange::lower_bound_with_open(b, false),
        (Bound::Excluded(b), Bound::Unbounded) => IdbKeyRange::lower_bound_with_open(b, true),
        (Bound::Included(l), Bound::Included(u)) => {
            IdbKeyRange::bound_with_lower_open_and_upper_open(l, u, false, false)
        }
        (Bound::Included(l), Bound::Excluded(u)) => {
            IdbKeyRange::bound_with_lower_open_and_upper_open(l, u, false, true)
        }
        (Bound::Excluded(l), Bound::Included(u)) => {
            IdbKeyRange::bound_with_lower_open_and_upper_open(l, u, true, false)
        }
        (Bound::Excluded(l), Bound::Excluded(u)) => {
            IdbKeyRange::bound_with_lower_open_and_upper_open(l, u, true, true)
        }
    }
    .map(|k| k.into())
    .map_err(|err| match error_name!(&err) {
        Some("DataError") => crate::Error::InvalidKey,
        _ => crate::Error::from_js_value(err),
    })
}