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}