Skip to main content

jellyflow_core/ops/transaction/
batch.rs

1use serde::{Deserialize, Serialize};
2
3use crate::core::Graph;
4use crate::ops::apply::ApplyError;
5use crate::ops::transaction::GraphOp;
6
7/// A batch of edit operations that should be applied and undone as one unit.
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct GraphTransaction {
10    /// Optional human-readable label for history UI.
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub label: Option<String>,
13    /// Operations in order.
14    #[serde(default, skip_serializing_if = "Vec::is_empty")]
15    pub ops: Vec<GraphOp>,
16}
17
18impl GraphTransaction {
19    /// Creates an empty transaction.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Creates an unlabeled transaction from ops.
25    pub fn from_ops(ops: impl IntoIterator<Item = GraphOp>) -> Self {
26        Self::new().with_ops(ops)
27    }
28
29    /// Creates a transaction from metadata and ops.
30    pub fn from_parts(label: Option<String>, ops: impl IntoIterator<Item = GraphOp>) -> Self {
31        Self {
32            label,
33            ops: ops.into_iter().collect(),
34        }
35    }
36
37    /// Builds a deterministic transaction that transforms `from` into `to`.
38    pub fn diff(from: &Graph, to: &Graph) -> Self {
39        crate::ops::diff::graph_diff(from, to)
40    }
41
42    /// Sets a label.
43    pub fn with_label(mut self, label: impl Into<String>) -> Self {
44        self.label = Some(label.into());
45        self
46    }
47
48    /// Sets or clears the optional label.
49    pub fn with_optional_label(mut self, label: Option<String>) -> Self {
50        self.label = label;
51        self
52    }
53
54    /// Adds ops in order and returns this transaction.
55    pub fn with_ops(mut self, ops: impl IntoIterator<Item = GraphOp>) -> Self {
56        self.extend(ops);
57        self
58    }
59
60    /// Transforms this transaction's ops while preserving metadata.
61    pub fn map_ops(mut self, f: impl FnOnce(Vec<GraphOp>) -> Vec<GraphOp>) -> Self {
62        self.ops = f(self.ops);
63        self
64    }
65
66    /// Pushes an op.
67    pub fn push(&mut self, op: GraphOp) {
68        self.ops.push(op);
69    }
70
71    /// Extends this transaction with ops in order.
72    pub fn extend(&mut self, ops: impl IntoIterator<Item = GraphOp>) {
73        self.ops.extend(ops);
74    }
75
76    /// Retains ops that match `f`.
77    pub fn retain_ops(&mut self, f: impl FnMut(&GraphOp) -> bool) {
78        self.ops.retain(f);
79    }
80
81    /// Removes all ops while preserving transaction metadata.
82    pub fn clear_ops(&mut self) {
83        self.ops.clear();
84    }
85
86    pub fn is_empty(&self) -> bool {
87        self.ops.is_empty()
88    }
89
90    /// Returns the optional human-readable label.
91    pub fn label(&self) -> Option<&str> {
92        self.label.as_deref()
93    }
94
95    /// Returns ops in order.
96    pub fn ops(&self) -> &[GraphOp] {
97        &self.ops
98    }
99
100    /// Consumes this transaction and returns its ops in order.
101    pub fn into_ops(self) -> Vec<GraphOp> {
102        self.ops
103    }
104
105    /// Consumes this transaction and returns its metadata and ops.
106    pub fn into_parts(self) -> (Option<String>, Vec<GraphOp>) {
107        let Self { label, ops } = self;
108        (label, ops)
109    }
110
111    /// Returns the number of ops.
112    pub fn len(&self) -> usize {
113        self.ops.len()
114    }
115
116    /// Applies this transaction atomically to `graph`.
117    pub fn apply_to(&self, graph: &mut Graph) -> Result<(), ApplyError> {
118        crate::ops::apply::apply_transaction(graph, self)
119    }
120
121    /// Builds the inverse transaction for undo/redo.
122    pub fn inverse(&self) -> Self {
123        crate::ops::history::invert_transaction(self)
124    }
125}