ankurah_core/
transaction.rs1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::Arc;
3
4use ankurah_proto::{self as proto, EntityId};
5
6use crate::error::RetrievalError;
7use crate::policy::AccessDenied;
8use crate::{
9 context::TContext,
10 entity::Entity,
11 error::MutationError,
12 model::{Model, MutableBorrow},
13};
14
15use append_only_vec::AppendOnlyVec;
16
17#[cfg(feature = "wasm")]
18use wasm_bindgen::prelude::*;
19
20#[cfg_attr(feature = "wasm", wasm_bindgen)]
24#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
25pub struct Transaction {
26 pub(crate) dyncontext: Arc<dyn TContext + Send + Sync + 'static>,
27 pub(crate) id: proto::TransactionId,
28 pub(crate) entities: AppendOnlyVec<Entity>,
29 pub(crate) alive: Arc<AtomicBool>,
30 pub(crate) created_entity_ids: std::sync::RwLock<std::collections::HashSet<EntityId>>,
34}
35
36#[cfg(feature = "wasm")]
37#[wasm_bindgen]
38impl Transaction {
39 #[wasm_bindgen(js_name = "commit")]
40 pub async fn js_commit(self) -> Result<(), JsValue> {
41 self.dyncontext.commit_local_trx(&self).await?;
42 Ok(())
43 }
44}
45
46impl Transaction {
47 pub(crate) fn new(dyncontext: Arc<dyn TContext + Send + Sync + 'static>) -> Self {
48 Self {
49 dyncontext,
50 id: proto::TransactionId::new(),
51 entities: AppendOnlyVec::new(),
52 alive: Arc::new(AtomicBool::new(true)),
53 created_entity_ids: std::sync::RwLock::new(std::collections::HashSet::new()),
54 }
55 }
56
57 pub(crate) fn add_entity(&self, entity: Entity) -> &Entity {
58 let index = self.entities.push(entity);
59 &self.entities[index]
60 }
61
62 pub async fn create<'rec, 'trx: 'rec, M: Model>(&'trx self, model: &M) -> Result<MutableBorrow<'rec, M::Mutable>, MutationError> {
63 let entity = self.dyncontext.create_entity(M::collection(), self.alive.clone());
64 model.initialize_new_entity(&entity);
65 self.dyncontext.check_write(&entity)?;
66
67 self.created_entity_ids.write().unwrap().insert(entity.id);
69
70 let entity_ref = self.add_entity(entity);
71 Ok(MutableBorrow::new(entity_ref))
72 }
73 fn get_trx_entity(&self, id: &EntityId) -> Option<&Entity> { self.entities.iter().find(|e| e.id == *id) }
74 pub async fn get<'rec, 'trx: 'rec, M: Model>(&'trx self, id: &EntityId) -> Result<MutableBorrow<'rec, M::Mutable>, RetrievalError> {
75 match self.get_trx_entity(id) {
76 Some(entity) => Ok(MutableBorrow::new(entity)),
77 None => {
78 let retrieved_entity = self.dyncontext.get_entity(*id, &M::collection(), false).await?;
80 if let Some(entity) = self.get_trx_entity(&retrieved_entity.id) {
83 Ok(MutableBorrow::new(entity))
86 } else {
87 Ok(MutableBorrow::new(self.add_entity(retrieved_entity.snapshot(self.alive.clone()))))
88 }
89 }
90 }
91 }
92 pub fn edit<'rec, 'trx: 'rec, M: Model>(&'trx self, entity: &Entity) -> Result<MutableBorrow<'rec, M::Mutable>, AccessDenied> {
93 if let Some(entity) = self.get_trx_entity(&entity.id) {
94 return Ok(MutableBorrow::new(entity));
95 }
96 self.dyncontext.check_write(entity)?;
97
98 Ok(MutableBorrow::new(self.add_entity(entity.snapshot(self.alive.clone()))))
99 }
100
101 #[must_use]
102 pub async fn commit(self) -> Result<(), MutationError> { self.dyncontext.commit_local_trx(&self).await }
103
104 pub fn rollback(self) {
105 self.alive.store(false, Ordering::Release);
107 }
109
110 }
125
126impl Drop for Transaction {
127 fn drop(&mut self) {
128 self.alive.store(false, Ordering::Release);
130 }
132}
133
134#[cfg(feature = "uniffi")]
135#[uniffi::export]
136impl Transaction {
137 #[uniffi::method(name = "commit")]
140 pub async fn uniffi_commit(self: Arc<Self>) -> Result<(), MutationError> { self.dyncontext.commit_local_trx(&self).await }
141
142 #[uniffi::method(name = "rollback")]
144 pub fn uniffi_rollback(&self) { self.alive.store(false, Ordering::Release); }
145}