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