1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Transaction-based API for ergonomic CRDT mutations.
//!
//! This module provides a transaction-based API for making changes to DSON stores.
//! Unlike the callback-based `api` module, transactions provide:
//!
//! - **Method chaining** - No nested callbacks
//! - **Explicit conflict handling** - Enums force handling of type conflicts
//! - **Automatic rollback** - Changes drop unless you call `commit()`
//! - **Automatic delta management** - Deltas accumulate and return on commit
//!
//! # Example
//!
//! ```
//! use dson::{CausalDotStore, Identifier, OrMap, crdts::mvreg::MvRegValue, transaction::CrdtValue};
//! use dson::crdts::snapshot::ToValue;
//!
//! let mut store = CausalDotStore::<OrMap<String>>::default();
//! let id = Identifier::new(0, 0);
//!
//! // Create a transaction
//! let mut tx = store.transact(id);
//!
//! // Write values
//! tx.write_register("name", MvRegValue::String("Alice".to_string()));
//! tx.write_register("age", MvRegValue::U64(30));
//!
//! // IMPORTANT: You must call commit() or changes are lost
//! let delta = tx.commit();
//!
//! // Read with explicit type handling
//! let tx = store.transact(id);
//! match tx.get(&"name".to_string()) {
//! Some(CrdtValue::Register(reg)) => {
//! println!("Name: {:?}", reg.value().unwrap());
//! }
//! Some(CrdtValue::Conflicted(conflicts)) => {
//! println!("Type conflict!");
//! }
//! None => {
//! println!("Key not found");
//! }
//! _ => {}
//! }
//! ```
//!
//! # Transaction Semantics
//!
//! Both [`MapTransaction`] and [`ArrayTransaction`] clone the store and work on the copy.
//! Changes apply immediately to the clone, enabling reads within the transaction to see
//! uncommitted changes. Call `commit()` to apply changes permanently. Drop the transaction
//! without committing to discard all changes (automatic rollback).
//!
//! ## How Transactions Work
//!
//! - **On creation**: The store is cloned
//! - **During operations**: Changes apply to the cloned store
//! - **On commit**: The clone swaps back into the original store
//! - **On drop**: Changes discard automatically if not committed
//!
//! ## Why This Design
//!
//! This provides:
//! - **Automatic rollback**: Drop the transaction to undo changes
//! - **Isolation**: Reads see uncommitted changes within the same transaction
//! - **Simplicity**: What you write is what you read
//!
//! ## Performance Tradeoff
//!
//! The transaction API trades performance for ergonomics. Top-level transactions clone the store
//! on creation and apply each operation eagerly to the clone. This enables rollback support
//! and ensures reads within the transaction see uncommitted changes.
//!
//! Benchmarks on an empty map show **2-2.5x overhead** compared to the raw API:
//!
//! | Operation | Raw API | Transaction | Overhead |
//! |-----------|---------|-------------|----------|
//! | Insert | 156 ns | 347 ns | 2.2x |
//! | Update | 159 ns | 344 ns | 2.2x |
//! | Remove | 50 ns | 69 ns | 1.4x |
//!
//! The overhead stems from the clone-and-swap implementation. Top-level transactions clone the
//! store on creation and apply each operation eagerly to the clone. This ensures reads within
//! the transaction see uncommitted changes and enables automatic rollback on drop.
//!
//! ### Nested Transaction Optimization
//!
//! Nested transactions (`in_map`, `in_array`, `insert_map`, `insert_array`) use `mem::take`
//! instead of cloning the parent store. This moves nested structures without copying:
//!
//! - **Shallow nesting (1-2 levels)**: Minimal impact
//! - **Deep nesting (3+ levels)**: Savings from avoided parent store clones
//! - **Large nested collections**: Saves proportional to parent store size
//!
//! The ~200-300ns overhead per operation is acceptable for most applications. For
//! latency-critical single-field updates, use [`api`](crate::api). For complex mutations
//! where clarity and safety outweigh microseconds, use transactions
//!
//! # Type Conflict Handling
//!
//! DSON's unique feature is preserving type conflicts. When different replicas
//! concurrently write different types to the same key, DSON preserves both.
//! The transaction API exposes this through the [`CrdtValue`] enum:
//!
//! ```no_run
//! # use dson::transaction::{MapTransaction, CrdtValue};
//! # let tx: MapTransaction<String> = todo!();
//! match tx.get(&"field".to_string()) {
//! Some(CrdtValue::Map(map)) => { /* single type: map */ }
//! Some(CrdtValue::Array(array)) => { /* single type: array */ }
//! Some(CrdtValue::Register(reg)) => { /* single type: register */ }
//! Some(CrdtValue::Conflicted(c)) => {
//! // Type conflict!
//! if c.has_map() && c.has_array() {
//! // Application must resolve
//! }
//! }
//! None => { /* key doesn't exist */ }
//! Some(CrdtValue::Empty) => { /* key exists but is empty */ }
//! }
//! ```
//!
//! # Nested Operations
//!
//! The transaction API provides uniform ergonomics at all nesting levels:
//!
//! ```
//! # use dson::{CausalDotStore, Identifier, OrMap};
//! # use dson::crdts::mvreg::MvRegValue;
//! # let mut store = CausalDotStore::<OrMap<String>>::default();
//! # let id = Identifier::new(0, 0);
//! let mut tx = store.transact(id);
//!
//! tx.in_map("user", |user_tx| {
//! user_tx.write_register("email", MvRegValue::String("alice@example.com".to_string()));
//! user_tx.write_register("age", MvRegValue::U64(30));
//!
//! user_tx.in_array("tags", |tags_tx| {
//! tags_tx.insert_register(0, MvRegValue::String("admin".to_string()));
//! // Nested transaction commits automatically when closure returns
//! });
//! // Nested transaction commits automatically when closure returns
//! });
//!
//! // Top-level transaction requires explicit commit
//! let delta = tx.commit();
//! ```
//!
//! **Important**: Nested transactions (`in_map`, `in_array`, `insert_map`, `insert_array`)
//! commit automatically when their closure returns. Only the top-level transaction requires
//! an explicit `commit()` call.
//!
//! Use [`MapTransaction::in_map`] and [`MapTransaction::in_array`] for nesting.
//! Use [`ArrayTransaction::insert_map`] and [`ArrayTransaction::insert_array`]
//! for arrays containing collections.
pub use ArrayTransaction;
pub use ConflictedValue;
pub use CrdtValue;
pub use Delta;
pub use MapTransaction;