Skip to main content

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    /// Event metadata for logging and diagnostics
43    pub event_context: Option<EventContext>,
44}
45
46#[derive(Debug, Clone)]
47pub struct EventContext {
48    pub program: String,
49    pub event_kind: String,
50    pub event_type: String,
51    pub account: Option<String>,
52    pub accounts_count: Option<usize>,
53}
54
55impl MutationBatch {
56    pub fn new(mutations: SmallVec<[Mutation; 6]>) -> Self {
57        Self {
58            span: Span::current(),
59            mutations,
60            slot_context: None,
61            event_context: None,
62        }
63    }
64
65    pub fn with_span(span: Span, mutations: SmallVec<[Mutation; 6]>) -> Self {
66        Self {
67            span,
68            mutations,
69            slot_context: None,
70            event_context: None,
71        }
72    }
73
74    pub fn with_slot_context(
75        mutations: SmallVec<[Mutation; 6]>,
76        slot_context: SlotContext,
77    ) -> Self {
78        Self {
79            span: Span::current(),
80            mutations,
81            slot_context: Some(slot_context),
82            event_context: None,
83        }
84    }
85
86    pub fn with_event_context(mut self, event_context: EventContext) -> Self {
87        self.event_context = Some(event_context);
88        self
89    }
90
91    pub fn len(&self) -> usize {
92        self.mutations.len()
93    }
94
95    pub fn is_empty(&self) -> bool {
96        self.mutations.is_empty()
97    }
98}