hyperstack_server/
mutation_batch.rs

1//! MutationBatch - Envelope type for propagating trace context across async boundaries.
2
3use hyperstack_interpreter::Mutation;
4use smallvec::SmallVec;
5use tracing::Span;
6
7/// Slot context for ordering mutations by blockchain position.
8/// Used to derive `_seq` field for default recency sorting.
9#[derive(Debug, Clone, Copy, Default)]
10pub struct SlotContext {
11    /// Solana slot number
12    pub slot: u64,
13    /// Index within the slot (write_version for accounts, txn_index for instructions)
14    pub slot_index: u64,
15}
16
17impl SlotContext {
18    pub fn new(slot: u64, slot_index: u64) -> Self {
19        Self { slot, slot_index }
20    }
21
22    /// Compute a monotonic sequence number for sorting.
23    /// Encodes as string to preserve precision in JSON: "{slot}:{slot_index:012}"
24    /// This gives lexicographic ordering that matches (slot, slot_index) tuple ordering.
25    pub fn to_seq_string(&self) -> String {
26        format!("{}:{:012}", self.slot, self.slot_index)
27    }
28}
29
30/// Envelope type that carries mutations along with their originating span context.
31///
32/// This enables trace context propagation across the mpsc channel boundary
33/// from the Vixen parser to the Projector.
34#[derive(Debug)]
35pub struct MutationBatch {
36    /// The span from which these mutations originated
37    pub span: Span,
38    /// The mutations to process
39    pub mutations: SmallVec<[Mutation; 6]>,
40    /// Slot context for ordering (optional for backward compatibility)
41    pub slot_context: Option<SlotContext>,
42}
43
44impl MutationBatch {
45    pub fn new(mutations: SmallVec<[Mutation; 6]>) -> Self {
46        Self {
47            span: Span::current(),
48            mutations,
49            slot_context: None,
50        }
51    }
52
53    pub fn with_span(span: Span, mutations: SmallVec<[Mutation; 6]>) -> Self {
54        Self {
55            span,
56            mutations,
57            slot_context: None,
58        }
59    }
60
61    pub fn with_slot_context(
62        mutations: SmallVec<[Mutation; 6]>,
63        slot_context: SlotContext,
64    ) -> Self {
65        Self {
66            span: Span::current(),
67            mutations,
68            slot_context: Some(slot_context),
69        }
70    }
71
72    pub fn len(&self) -> usize {
73        self.mutations.len()
74    }
75
76    pub fn is_empty(&self) -> bool {
77        self.mutations.is_empty()
78    }
79}