Skip to main content

fraiseql_cli/schema/intermediate/
subscriptions.rs

1//! Subscription/observer structs: `IntermediateSubscription`,
2//! `IntermediateSubscriptionFilter`, `IntermediateFilterCondition`,
3//! `IntermediateObserver`, `IntermediateRetryConfig`.
4
5use serde::{Deserialize, Serialize};
6
7use super::{operations::IntermediateArgument, types::IntermediateDeprecation};
8
9// =============================================================================
10// Subscription Definitions
11// =============================================================================
12
13/// Subscription definition in intermediate format.
14///
15/// Subscriptions provide real-time event streams for GraphQL clients.
16///
17/// # Example JSON
18///
19/// ```json
20/// {
21///   "name": "orderUpdated",
22///   "return_type": "Order",
23///   "arguments": [
24///     {"name": "orderId", "type": "ID", "nullable": true}
25///   ],
26///   "topic": "order_events",
27///   "filter": {
28///     "conditions": [
29///       {"argument": "orderId", "path": "$.id"}
30///     ]
31///   },
32///   "description": "Stream of order update events"
33/// }
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(deny_unknown_fields)]
37pub struct IntermediateSubscription {
38    /// Subscription name (e.g., "orderUpdated")
39    pub name: String,
40
41    /// Return type name (e.g., "Order")
42    pub return_type: String,
43
44    /// Subscription arguments (for filtering events)
45    #[serde(default)]
46    pub arguments: Vec<IntermediateArgument>,
47
48    /// Subscription description (from docstring)
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub description: Option<String>,
51
52    /// Event topic to subscribe to (e.g., "order_events")
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub topic: Option<String>,
55
56    /// Filter configuration for event matching
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub filter: Option<IntermediateSubscriptionFilter>,
59
60    /// Fields to project from event data
61    #[serde(default, skip_serializing_if = "Vec::is_empty")]
62    pub fields: Vec<String>,
63
64    /// Deprecation info (from @deprecated directive)
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub deprecated: Option<IntermediateDeprecation>,
67}
68
69/// Subscription filter definition for event matching.
70///
71/// Maps subscription arguments to JSONB paths in event data.
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74pub struct IntermediateSubscriptionFilter {
75    /// Filter conditions mapping arguments to event data paths
76    pub conditions: Vec<IntermediateFilterCondition>,
77}
78
79/// A single filter condition for subscription event matching.
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(deny_unknown_fields)]
82pub struct IntermediateFilterCondition {
83    /// Argument name from subscription arguments
84    pub argument: String,
85
86    /// JSON path to the value in event data (e.g., "$.id", "$.order_status")
87    pub path: String,
88}
89
90// =============================================================================
91// Observer Definitions
92// =============================================================================
93
94/// Observer definition in intermediate format.
95///
96/// Observers listen to database change events (INSERT/UPDATE/DELETE) and execute
97/// actions (webhooks, Slack, email) when conditions are met.
98///
99/// # Example JSON
100///
101/// ```json
102/// {
103///   "name": "onHighValueOrder",
104///   "entity": "Order",
105///   "event": "INSERT",
106///   "condition": "total > 1000",
107///   "actions": [
108///     {
109///       "type": "webhook",
110///       "url": "https://api.example.com/orders",
111///       "headers": {"Content-Type": "application/json"}
112///     },
113///     {
114///       "type": "slack",
115///       "channel": "#sales",
116///       "message": "New order: {id}",
117///       "webhook_url_env": "SLACK_WEBHOOK_URL"
118///     }
119///   ],
120///   "retry": {
121///     "max_attempts": 3,
122///     "backoff_strategy": "exponential",
123///     "initial_delay_ms": 100,
124///     "max_delay_ms": 60000
125///   }
126/// }
127/// ```
128#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
129#[serde(deny_unknown_fields)]
130pub struct IntermediateObserver {
131    /// Observer name (unique identifier)
132    pub name: String,
133
134    /// Entity type to observe (e.g., "Order", "User")
135    pub entity: String,
136
137    /// Event type: INSERT, UPDATE, or DELETE
138    pub event: String,
139
140    /// Actions to execute when observer triggers
141    pub actions: Vec<IntermediateObserverAction>,
142
143    /// Optional condition expression in FraiseQL DSL
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub condition: Option<String>,
146
147    /// Retry configuration for action execution
148    pub retry: IntermediateRetryConfig,
149}
150
151/// Observer action (webhook, Slack, email, etc.).
152///
153/// Actions are stored as flexible JSON objects since they have different
154/// structures based on action type.
155pub type IntermediateObserverAction = serde_json::Value;
156
157/// Retry configuration for observer actions.
158///
159/// # Example JSON
160///
161/// ```json
162/// {
163///   "max_attempts": 5,
164///   "backoff_strategy": "exponential",
165///   "initial_delay_ms": 100,
166///   "max_delay_ms": 60000
167/// }
168/// ```
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170#[serde(deny_unknown_fields)]
171pub struct IntermediateRetryConfig {
172    /// Maximum number of retry attempts
173    pub max_attempts: u32,
174
175    /// Backoff strategy: exponential, linear, or fixed
176    pub backoff_strategy: String,
177
178    /// Initial delay in milliseconds
179    pub initial_delay_ms: u32,
180
181    /// Maximum delay in milliseconds
182    pub max_delay_ms: u32,
183}