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