Skip to main content

engram/realtime/
events.rs

1//! Real-time event types
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::types::MemoryId;
7
8/// Types of real-time events
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum EventType {
12    MemoryCreated,
13    MemoryUpdated,
14    MemoryDeleted,
15    CrossrefCreated,
16    CrossrefDeleted,
17    SyncStarted,
18    SyncCompleted,
19    SyncFailed,
20}
21
22/// A real-time event
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct RealtimeEvent {
25    /// Sequential event ID, stamped by `RealtimeManager::broadcast`.
26    /// `None` for events that have not yet been processed by the manager.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub seq_id: Option<u64>,
29    /// Event type
30    #[serde(rename = "type")]
31    pub event_type: EventType,
32    /// Timestamp
33    pub timestamp: DateTime<Utc>,
34    /// Related memory ID (if applicable)
35    pub memory_id: Option<MemoryId>,
36    /// Preview of content (for created/updated)
37    pub preview: Option<String>,
38    /// List of changed fields (for updates)
39    pub changes: Option<Vec<String>>,
40    /// Additional data
41    pub data: Option<serde_json::Value>,
42}
43
44impl RealtimeEvent {
45    /// Create a memory created event
46    pub fn memory_created(id: MemoryId, preview: String) -> Self {
47        Self {
48            seq_id: None,
49            event_type: EventType::MemoryCreated,
50            timestamp: Utc::now(),
51            memory_id: Some(id),
52            preview: Some(truncate(&preview, 100)),
53            changes: None,
54            data: None,
55        }
56    }
57
58    /// Create a memory updated event
59    pub fn memory_updated(id: MemoryId, changes: Vec<String>) -> Self {
60        Self {
61            seq_id: None,
62            event_type: EventType::MemoryUpdated,
63            timestamp: Utc::now(),
64            memory_id: Some(id),
65            preview: None,
66            changes: Some(changes),
67            data: None,
68        }
69    }
70
71    /// Create a memory deleted event
72    pub fn memory_deleted(id: MemoryId) -> Self {
73        Self {
74            seq_id: None,
75            event_type: EventType::MemoryDeleted,
76            timestamp: Utc::now(),
77            memory_id: Some(id),
78            preview: None,
79            changes: None,
80            data: None,
81        }
82    }
83
84    /// Create a sync completed event
85    pub fn sync_completed(direction: &str, changes: i64) -> Self {
86        Self {
87            seq_id: None,
88            event_type: EventType::SyncCompleted,
89            timestamp: Utc::now(),
90            memory_id: None,
91            preview: None,
92            changes: None,
93            data: Some(serde_json::json!({
94                "direction": direction,
95                "changes": changes,
96            })),
97        }
98    }
99
100    /// Create a sync failed event
101    pub fn sync_failed(error: &str) -> Self {
102        Self {
103            seq_id: None,
104            event_type: EventType::SyncFailed,
105            timestamp: Utc::now(),
106            memory_id: None,
107            preview: None,
108            changes: None,
109            data: Some(serde_json::json!({
110                "error": error,
111            })),
112        }
113    }
114}
115
116/// Truncate string for preview (UTF-8 safe)
117fn truncate(s: &str, max: usize) -> String {
118    if s.chars().count() <= max {
119        s.to_string()
120    } else {
121        // Take max - 3 chars safely, then append "..."
122        let truncated: String = s.chars().take(max.saturating_sub(3)).collect();
123        format!("{}...", truncated)
124    }
125}
126
127/// Subscription filter for events
128#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129pub struct SubscriptionFilter {
130    /// Only events for specific memory IDs
131    pub memory_ids: Option<Vec<MemoryId>>,
132    /// Only events with specific tags
133    pub tags: Option<Vec<String>>,
134    /// Only specific event types
135    pub event_types: Option<Vec<EventType>>,
136}
137
138impl SubscriptionFilter {
139    /// Check if an event matches this filter
140    pub fn matches(&self, event: &RealtimeEvent) -> bool {
141        // Check event type filter
142        if let Some(ref types) = self.event_types {
143            if !types.contains(&event.event_type) {
144                return false;
145            }
146        }
147
148        // Check memory ID filter
149        if let Some(ref ids) = self.memory_ids {
150            if let Some(event_id) = event.memory_id {
151                if !ids.contains(&event_id) {
152                    return false;
153                }
154            }
155        }
156
157        // Tags filter would require additional context
158        // (memory tags aren't included in events by default)
159
160        true
161    }
162}