Skip to main content

twine_core/
event.rs

1use std::collections::VecDeque;
2
3use thiserror::Error;
4
5use crate::application::RequestId;
6use crate::folder::FolderState;
7use crate::terminal::{TerminalExit, TerminalId};
8use crate::workflow::{SessionId, Workflow, WorkflowId, WorkflowState};
9
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum StateEvent {
12    ApplicationReady,
13    ConfigChanged(Box<crate::config::Config>),
14    WorkflowTypesChanged(Vec<crate::WorkflowType>),
15    TraceChanged(crate::TraceSummary),
16    WorkflowsChanged(WorkflowState),
17    WorkflowChanged(Workflow),
18    /// The open folder or the recent folders changed. Carries the complete new folder state.
19    FoldersChanged(FolderState),
20    TerminalClosed {
21        terminal_id: TerminalId,
22    },
23    TerminalExited {
24        terminal_id: TerminalId,
25        exit: TerminalExit,
26    },
27    TerminalFailed {
28        terminal_id: TerminalId,
29        message: String,
30    },
31}
32
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub enum CommandResult {
35    WorkflowTypeValidated { issues: Vec<crate::ValidationIssue> },
36    WorkflowTypeSaved { reference: crate::WorkflowTypeRef },
37    WorkflowTypeDeleted { reference: crate::WorkflowTypeRef },
38    SessionCreated { session_id: SessionId },
39    SessionRenamed { session_id: SessionId },
40    SessionSelected { session_id: SessionId },
41    SessionDeleted { session_id: SessionId },
42    Pong,
43    WorkflowCreated { workflow_id: WorkflowId },
44    WorkflowActivated { workflow_id: WorkflowId },
45    WorkflowClosed { workflow_id: WorkflowId },
46    AgentStarted { workflow_id: WorkflowId },
47    AgentCancelled { workflow_id: WorkflowId },
48    TerminalStarted { terminal_id: TerminalId },
49    TerminalClosed { terminal_id: TerminalId },
50}
51
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub enum EventKind {
54    State(StateEvent),
55    CommandCompleted {
56        request_id: RequestId,
57        result: CommandResult,
58    },
59}
60
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct Event {
63    pub sequence: u64,
64    pub kind: EventKind,
65}
66
67#[derive(Debug)]
68pub(crate) struct EventJournal {
69    capacity: usize,
70    events: VecDeque<Event>,
71    latest_sequence: u64,
72}
73
74impl EventJournal {
75    pub(crate) fn new(capacity: usize) -> Result<Self, EventError> {
76        if capacity == 0 {
77            return Err(EventError::ZeroCapacity);
78        }
79
80        Ok(Self {
81            capacity,
82            events: VecDeque::with_capacity(capacity),
83            latest_sequence: 0,
84        })
85    }
86
87    pub(crate) fn append(&mut self, kind: EventKind) -> Result<Event, EventError> {
88        let sequence = self
89            .latest_sequence
90            .checked_add(1)
91            .ok_or(EventError::SequenceOverflow)?;
92        let event = Event { sequence, kind };
93        self.events.push_back(event.clone());
94        self.latest_sequence = sequence;
95
96        if self.events.len() > self.capacity {
97            self.events.pop_front();
98        }
99
100        Ok(event)
101    }
102
103    pub(crate) const fn latest_sequence(&self) -> u64 {
104        self.latest_sequence
105    }
106
107    pub(crate) fn after(&self, sequence: u64, limit: usize) -> Result<Vec<Event>, EventError> {
108        if limit == 0 {
109            return Err(EventError::ZeroLimit);
110        }
111
112        if let Some(oldest) = self.events.front() {
113            let oldest_cursor = oldest.sequence.saturating_sub(1);
114            if sequence < oldest_cursor {
115                return Err(EventError::CursorExpired {
116                    requested: sequence,
117                    oldest_available: oldest.sequence,
118                });
119            }
120        }
121
122        Ok(self
123            .events
124            .iter()
125            .filter(|event| event.sequence > sequence)
126            .take(limit)
127            .cloned()
128            .collect())
129    }
130}
131
132#[derive(Debug, Error)]
133pub enum EventError {
134    #[error(
135        "event cursor {requested} has expired; the oldest available event is {oldest_available}"
136    )]
137    CursorExpired {
138        requested: u64,
139        oldest_available: u64,
140    },
141    #[error("event sequence overflowed")]
142    SequenceOverflow,
143    #[error("event journal capacity must be greater than zero")]
144    ZeroCapacity,
145    #[error("event batch limit must be greater than zero")]
146    ZeroLimit,
147}