diff --git a/patch-db/src/lib.rs b/patch-db/src/lib.rs index 1bf595a..0e4b0eb 100644 --- a/patch-db/src/lib.rs +++ b/patch-db/src/lib.rs @@ -18,7 +18,7 @@ pub use imbl_value::Value; pub use model::{HasModel, Model, ModelExt}; pub use patch::{DiffPatch, Dump, Revision}; pub use patch_db_macro::HasModel; -pub use store::{PatchDb, Store}; +pub use store::{PatchDb, Store, TypedPatchDb}; use tokio::sync::TryLockError; pub use {imbl_value as value, json_patch, json_ptr}; diff --git a/patch-db/src/store.rs b/patch-db/src/store.rs index 9a12c91..8c97117 100644 --- a/patch-db/src/store.rs +++ b/patch-db/src/store.rs @@ -1,16 +1,18 @@ use std::collections::{BTreeSet, HashMap}; use std::fs::OpenOptions; use std::io::{SeekFrom, Write}; +use std::marker::PhantomData; use std::panic::UnwindSafe; use std::path::{Path, PathBuf}; use std::sync::Arc; use fd_lock_rs::FdLock; -use futures::FutureExt; +use futures::{Future, FutureExt}; use imbl_value::{InternedString, Value}; use json_patch::PatchError; -use json_ptr::{JsonPointer, SegList}; +use json_ptr::{JsonPointer, SegList, ROOT}; use lazy_static::lazy_static; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use tokio::fs::File; use tokio::io::AsyncSeekExt; @@ -18,7 +20,7 @@ use tokio::sync::{Mutex, OwnedMutexGuard, RwLock}; use crate::patch::{diff, DiffPatch, Dump, Revision}; use crate::subscriber::Broadcast; -use crate::{Error, Subscriber}; +use crate::{Error, HasModel, Subscriber}; lazy_static! { static ref OPEN_STORES: Mutex>>> = Mutex::new(HashMap::new()); @@ -357,3 +359,89 @@ impl PatchDb { } } } + +pub struct TypedPatchDb = Error> { + db: PatchDb, + _phantom: PhantomData<(T, E)>, +} +impl> Clone for TypedPatchDb { + fn clone(&self) -> Self { + Self { + db: self.db.clone(), + _phantom: PhantomData, + } + } +} +impl> std::ops::Deref for TypedPatchDb { + type Target = PatchDb; + fn deref(&self) -> &Self::Target { + &self.db + } +} +impl> TypedPatchDb { + pub fn load_unchecked(db: PatchDb) -> Self { + Self { + db, + _phantom: PhantomData, + } + } + pub async fn peek(&self) -> T::Model { + use crate::ModelExt; + T::Model::from_value(self.db.dump(&ROOT).await.value) + } + pub async fn mutate( + &self, + f: impl FnOnce(&mut T::Model) -> Result + UnwindSafe + Send, + ) -> Result { + use crate::ModelExt; + Ok(self + .apply_function(|mut v| { + let model = T::Model::value_as_mut(&mut v); + let res = f(model)?; + Ok::<_, E>((v, res)) + }) + .await? + .1) + } + pub async fn map_mutate( + &self, + f: impl FnOnce(T::Model) -> Result + UnwindSafe + Send, + ) -> Result { + use crate::ModelExt; + Ok(T::Model::from_value( + self.apply_function(|v| { + f(T::Model::from_value(v)).map(|a| (T::Model::into_value(a), ())) + }) + .await? + .0, + )) + } +} + +impl> TypedPatchDb { + pub async fn load(db: PatchDb) -> Result { + use crate::ModelExt; + let res = Self::load_unchecked(db); + res.map_mutate(|db| { + Ok(T::Model::from_value( + imbl_value::to_value( + &imbl_value::from_value::(db.into_value()).map_err(Error::from)?, + ) + .map_err(Error::from)?, + )) + }) + .await?; + Ok(res) + } + pub async fn load_or_init Fut, Fut: Future>>( + db: PatchDb, + init: F, + ) -> Result { + if db.dump(&ROOT).await.value.is_null() { + db.put(&ROOT, &init().await?).await?; + Ok(Self::load_unchecked(db)) + } else { + Self::load(db).await + } + } +}