use indexed_db_futures::{
database::Database,
error::{DomException, Error, OpenDbError},
transaction::Transaction,
};
use thiserror::Error;
pub mod current {
use super::{Version, v4};
pub const VERSION: Version = Version::V4;
pub use v4::keys;
}
#[allow(unused)]
pub async fn open_and_upgrade_db(name: &str) -> Result<Database, OpenDbError> {
Database::open(name)
.with_version(current::VERSION as u32)
.with_on_upgrade_needed(|event, transaction| {
let mut version = Version::try_from(event.old_version() as u32)?;
while version < current::VERSION {
version = match version.upgrade(transaction)? {
Some(next) => next,
None => current::VERSION,
};
}
Ok(())
})
.await
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Version {
V0 = 0,
V1 = 1,
V2 = 2,
V3 = 3,
V4 = 4,
}
impl Version {
pub fn upgrade(self, transaction: &Transaction<'_>) -> Result<Option<Self>, Error> {
match self {
Self::V0 => v0::upgrade(transaction).map(Some),
Self::V1 => v1::upgrade(transaction).map(Some),
Self::V2 => v2::upgrade(transaction).map(Some),
Self::V3 => v3::upgrade(transaction).map(Some),
Self::V4 => Ok(None),
}
}
}
#[derive(Debug, Error)]
#[error("unknown version: {0}")]
pub struct UnknownVersionError(u32);
impl TryFrom<u32> for Version {
type Error = UnknownVersionError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Version::V0),
1 => Ok(Version::V1),
2 => Ok(Version::V2),
3 => Ok(Version::V3),
4 => Ok(Version::V4),
v => Err(UnknownVersionError(v)),
}
}
}
impl From<UnknownVersionError> for Error {
fn from(value: UnknownVersionError) -> Self {
let message = format!("unknown version: {}", value.0);
let name = "UnknownVersionError";
match web_sys::DomException::new_with_message_and_name(&message, name) {
Ok(inner) => Self::DomException(DomException::DataError(inner)),
Err(err) => err.into(),
}
}
}
pub mod v0 {
use super::*;
pub fn upgrade(transaction: &Transaction<'_>) -> Result<Version, Error> {
v1::create_object_stores(transaction.db())?;
Ok(Version::V1)
}
}
pub mod v1 {
use indexed_db_futures::Build;
use super::*;
pub mod keys {
pub const LEASES: &str = "leases";
pub const LEASES_KEY_PATH: &str = "id";
pub const ROOMS: &str = "rooms";
pub const LINKED_CHUNK_IDS: &str = "linked_chunk_ids";
pub const LINKED_CHUNKS: &str = "linked_chunks";
pub const LINKED_CHUNKS_KEY_PATH: &str = "id";
pub const LINKED_CHUNKS_NEXT: &str = "linked_chunks_next";
pub const LINKED_CHUNKS_NEXT_KEY_PATH: &str = "next";
pub const EVENTS: &str = "events";
pub const EVENTS_KEY_PATH: &str = "id";
pub const EVENTS_ROOM: &str = "events_room";
pub const EVENTS_ROOM_KEY_PATH: &str = "room";
pub const EVENTS_POSITION: &str = "events_position";
pub const EVENTS_POSITION_KEY_PATH: &str = "position";
pub const EVENTS_RELATION: &str = "events_relation";
pub const EVENTS_RELATION_KEY_PATH: &str = "relation";
pub const EVENTS_RELATION_RELATED_EVENTS: &str = "events_relation_related_event";
pub const EVENTS_RELATION_RELATION_TYPES: &str = "events_relation_relation_type";
pub const GAPS: &str = "gaps";
pub const GAPS_KEY_PATH: &str = "id";
}
pub fn create_object_stores(db: &Database) -> Result<(), Error> {
create_lease_object_store(db)?;
create_linked_chunks_object_store(db)?;
create_events_object_store(db)?;
create_gaps_object_store(db)?;
Ok(())
}
fn create_lease_object_store(db: &Database) -> Result<(), Error> {
let _ = db
.create_object_store(keys::LEASES)
.with_key_path(keys::LEASES_KEY_PATH.into())
.build()?;
Ok(())
}
fn create_linked_chunks_object_store(db: &Database) -> Result<(), Error> {
let _ = db
.create_object_store(keys::LINKED_CHUNKS)
.with_key_path(keys::LINKED_CHUNKS_KEY_PATH.into())
.build()?
.create_index(keys::LINKED_CHUNKS_NEXT, keys::LINKED_CHUNKS_NEXT_KEY_PATH.into())
.build()?;
Ok(())
}
fn create_events_object_store(db: &Database) -> Result<(), Error> {
let events = db
.create_object_store(keys::EVENTS)
.with_key_path(keys::EVENTS_KEY_PATH.into())
.build()?;
let _ = events
.create_index(keys::EVENTS_ROOM, keys::EVENTS_ROOM_KEY_PATH.into())
.with_unique(true)
.build()?;
let _ = events
.create_index(keys::EVENTS_POSITION, keys::EVENTS_POSITION_KEY_PATH.into())
.with_unique(true)
.build()?;
let _ = events
.create_index(keys::EVENTS_RELATION, keys::EVENTS_RELATION_KEY_PATH.into())
.build()?;
Ok(())
}
fn create_gaps_object_store(db: &Database) -> Result<(), Error> {
let _ =
db.create_object_store(keys::GAPS).with_key_path(keys::GAPS_KEY_PATH.into()).build()?;
Ok(())
}
pub fn upgrade(transaction: &Transaction<'_>) -> Result<Version, Error> {
v2::empty_leases(transaction)?;
Ok(Version::V2)
}
}
mod v2 {
pub use super::v1::keys;
use super::*;
pub fn empty_leases(transaction: &Transaction<'_>) -> Result<(), Error> {
let object_store = transaction.object_store(keys::LEASES)?;
object_store.clear()?;
Ok(())
}
pub fn upgrade(transaction: &Transaction<'_>) -> Result<Version, Error> {
v3::update_events_object_store(transaction)?;
Ok(Version::V3)
}
}
mod v3 {
use indexed_db_futures::Build;
pub use super::v2::keys;
use super::*;
pub fn update_events_object_store(transaction: &Transaction<'_>) -> Result<(), Error> {
remove_events_object_store(transaction)?;
create_events_object_store(transaction.db())?;
Ok(())
}
pub fn remove_events_object_store(transaction: &Transaction<'_>) -> Result<(), Error> {
let object_store = transaction.object_store(keys::EVENTS)?;
object_store.clear()?;
transaction.db().delete_object_store(keys::EVENTS)?;
Ok(())
}
pub fn create_events_object_store(db: &Database) -> Result<(), Error> {
let events = db
.create_object_store(keys::EVENTS)
.with_key_path(keys::EVENTS_KEY_PATH.into())
.build()?;
let _ =
events.create_index(keys::EVENTS_ROOM, keys::EVENTS_ROOM_KEY_PATH.into()).build()?;
let _ = events
.create_index(keys::EVENTS_POSITION, keys::EVENTS_POSITION_KEY_PATH.into())
.with_unique(true)
.build()?;
let _ = events
.create_index(keys::EVENTS_RELATION, keys::EVENTS_RELATION_KEY_PATH.into())
.build()?;
Ok(())
}
pub fn upgrade(transaction: &Transaction<'_>) -> Result<Version, Error> {
v4::empty_event_cache(transaction)?;
Ok(Version::V4)
}
}
mod v4 {
pub use super::v3::keys;
use super::*;
pub fn empty_event_cache(transaction: &Transaction<'_>) -> Result<(), Error> {
let linked_chunks = transaction.object_store(keys::LINKED_CHUNKS)?;
linked_chunks.clear()?;
let gaps = transaction.object_store(keys::GAPS)?;
gaps.clear()?;
let events = transaction.object_store(keys::EVENTS)?;
events.clear()?;
Ok(())
}
}