Skip to main content

fraiseql_core/runtime/subscription/
mod.rs

1//! Subscription runtime for event-driven GraphQL subscriptions.
2//!
3//! FraiseQL subscriptions are **compiled projections of database events**, not
4//! traditional resolver-based subscriptions. Events originate from database
5//! transactions (via LISTEN/NOTIFY or CDC) and are delivered through transport
6//! adapters.
7//!
8//! # Architecture
9//!
10//! ```text
11//! Database Transaction (INSERT/UPDATE/DELETE)
12//!     ↓ (commits)
13//! LISTEN/NOTIFY (PostgreSQL)
14//!     ↓
15//! SubscriptionManager (event routing)
16//!     ↓
17//! SubscriptionMatcher (filter evaluation)
18//!     ↓ (parallel delivery)
19//! ├─ graphql-ws Adapter (WebSocket)
20//! ├─ Webhook Adapter (HTTP POST)
21//! └─ Kafka Adapter (event streaming)
22//! ```
23//!
24//! # Example
25//!
26//! ```ignore
27//! use fraiseql_core::runtime::subscription::{
28//!     SubscriptionManager, SubscriptionEvent, SubscriptionId,
29//! };
30//! use tokio::sync::broadcast;
31//!
32//! // Create subscription manager
33//! let manager = SubscriptionManager::new(schema);
34//!
35//! // Subscribe to events
36//! let subscription_id = manager.subscribe(
37//!     "OrderCreated",
38//!     user_context,
39//!     variables,
40//! ).await?;
41//!
42//! // Receive events
43//! let mut receiver = manager.receiver();
44//! while let Ok(event) = receiver.recv().await {
45//!     if event.matches_subscription(subscription_id) {
46//!         // Deliver to client
47//!     }
48//! }
49//!
50//! // Unsubscribe
51//! manager.unsubscribe(subscription_id).await?;
52//! ```
53
54use thiserror::Error;
55
56mod kafka;
57mod manager;
58pub mod protocol;
59#[cfg(test)]
60mod tests;
61mod transport;
62mod types;
63mod webhook;
64
65pub use kafka::{KafkaAdapter, KafkaConfig, KafkaMessage};
66pub use manager::SubscriptionManager;
67pub use transport::{DeliveryResult, TransportAdapter, TransportManager};
68pub use types::{
69    ActiveSubscription, SubscriptionEvent, SubscriptionId, SubscriptionOperation,
70    SubscriptionPayload,
71};
72pub use webhook::{WebhookAdapter, WebhookConfig, WebhookPayload};
73
74// =============================================================================
75// Error Types
76// =============================================================================
77
78/// Errors that can occur during subscription operations.
79#[derive(Debug, Error)]
80pub enum SubscriptionError {
81    /// Subscription type not found in schema.
82    #[error("Subscription not found: {0}")]
83    SubscriptionNotFound(String),
84
85    /// Authentication required for subscription.
86    #[error("Authentication required for subscription: {0}")]
87    AuthenticationRequired(String),
88
89    /// User not authorized for subscription.
90    #[error("Not authorized for subscription: {0}")]
91    Forbidden(String),
92
93    /// Invalid subscription variables.
94    #[error("Invalid subscription variables: {0}")]
95    InvalidVariables(String),
96
97    /// Subscription already exists.
98    #[error("Subscription already exists: {0}")]
99    AlreadyExists(String),
100
101    /// Subscription not active.
102    #[error("Subscription not active: {0}")]
103    NotActive(String),
104
105    /// Internal subscription error.
106    #[error("Subscription error: {0}")]
107    Internal(String),
108
109    /// Channel send error.
110    #[error("Failed to send event: {0}")]
111    SendError(String),
112
113    /// Database connection error.
114    #[error("Database connection error: {0}")]
115    DatabaseConnection(String),
116
117    /// Listener already running.
118    #[error("Listener already running")]
119    ListenerAlreadyRunning,
120
121    /// Listener not running.
122    #[error("Listener not running")]
123    ListenerNotRunning,
124
125    /// Failed to parse notification payload.
126    #[error("Failed to parse notification: {0}")]
127    InvalidNotification(String),
128
129    /// Failed to deliver event to transport.
130    #[error("Failed to deliver to {transport}: {reason}")]
131    DeliveryFailed {
132        /// Transport that failed.
133        transport: String,
134        /// Reason for failure.
135        reason:    String,
136    },
137}