use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use ankurah_proto::{self as proto, EntityId};
use crate::error::RetrievalError;
use crate::policy::AccessDenied;
use crate::{
context::TContext,
entity::Entity,
error::MutationError,
model::{Model, MutableBorrow},
};
use append_only_vec::AppendOnlyVec;
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
pub struct Transaction {
pub(crate) dyncontext: Arc<dyn TContext + Send + Sync + 'static>,
pub(crate) id: proto::TransactionId,
pub(crate) entities: AppendOnlyVec<Entity>,
pub(crate) alive: Arc<AtomicBool>,
pub(crate) created_entity_ids: std::sync::RwLock<std::collections::HashSet<EntityId>>,
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl Transaction {
#[wasm_bindgen(js_name = "commit")]
pub async fn js_commit(self) -> Result<(), JsValue> {
self.dyncontext.commit_local_trx(&self).await?;
Ok(())
}
}
impl Transaction {
pub(crate) fn new(dyncontext: Arc<dyn TContext + Send + Sync + 'static>) -> Self {
Self {
dyncontext,
id: proto::TransactionId::new(),
entities: AppendOnlyVec::new(),
alive: Arc::new(AtomicBool::new(true)),
created_entity_ids: std::sync::RwLock::new(std::collections::HashSet::new()),
}
}
pub(crate) fn add_entity(&self, entity: Entity) -> &Entity {
let index = self.entities.push(entity);
&self.entities[index]
}
pub async fn create<'rec, 'trx: 'rec, M: Model>(&'trx self, model: &M) -> Result<MutableBorrow<'rec, M::Mutable>, MutationError> {
let entity = self.dyncontext.create_entity(M::collection(), self.alive.clone());
model.initialize_new_entity(&entity);
self.dyncontext.check_write(&entity)?;
self.created_entity_ids.write().unwrap().insert(entity.id);
let entity_ref = self.add_entity(entity);
Ok(MutableBorrow::new(entity_ref))
}
fn get_trx_entity(&self, id: &EntityId) -> Option<&Entity> { self.entities.iter().find(|e| e.id == *id) }
pub async fn get<'rec, 'trx: 'rec, M: Model>(&'trx self, id: &EntityId) -> Result<MutableBorrow<'rec, M::Mutable>, RetrievalError> {
match self.get_trx_entity(id) {
Some(entity) => Ok(MutableBorrow::new(entity)),
None => {
let retrieved_entity = self.dyncontext.get_entity(*id, &M::collection(), false).await?;
if let Some(entity) = self.get_trx_entity(&retrieved_entity.id) {
Ok(MutableBorrow::new(entity))
} else {
Ok(MutableBorrow::new(self.add_entity(retrieved_entity.snapshot(self.alive.clone()))))
}
}
}
}
pub fn edit<'rec, 'trx: 'rec, M: Model>(&'trx self, entity: &Entity) -> Result<MutableBorrow<'rec, M::Mutable>, AccessDenied> {
if let Some(entity) = self.get_trx_entity(&entity.id) {
return Ok(MutableBorrow::new(entity));
}
self.dyncontext.check_write(entity)?;
Ok(MutableBorrow::new(self.add_entity(entity.snapshot(self.alive.clone()))))
}
#[must_use]
pub async fn commit(self) -> Result<(), MutationError> { self.dyncontext.commit_local_trx(&self).await }
pub fn rollback(self) {
self.alive.store(false, Ordering::Release);
}
}
impl Drop for Transaction {
fn drop(&mut self) {
self.alive.store(false, Ordering::Release);
}
}
#[cfg(feature = "uniffi")]
#[uniffi::export]
impl Transaction {
#[uniffi::method(name = "commit")]
pub async fn uniffi_commit(self: Arc<Self>) -> Result<(), MutationError> { self.dyncontext.commit_local_trx(&self).await }
#[uniffi::method(name = "rollback")]
pub fn uniffi_rollback(&self) { self.alive.store(false, Ordering::Release); }
}