Skip to main content

fraiseql_server/subsystems/
mod.rs

1//! Server subsystem assembly and lifecycle management.
2//!
3//! [`ServerSubsystems`] bundles the three optional platform extensions —
4//! object storage, serverless functions, and realtime entity streams — into a
5//! single coherent struct that the server can query, route-mount, and shut down
6//! in a controlled order.
7//!
8//! # Assembly
9//!
10//! Use [`builder::ServerSubsystemsBuilder`] to assemble subsystems from their
11//! pre-built parts. The builder validates cross-subsystem dependencies before
12//! returning the final [`ServerSubsystems`]:
13//!
14//! ```rust,ignore
15//! let subsystems = ServerSubsystemsBuilder::new()
16//!     .with_storage(storage_subsystem)
17//!     .with_functions(functions_subsystem)
18//!     .with_realtime(realtime_subsystem)
19//!     .build()?;
20//! ```
21//!
22//! # Shutdown order
23//!
24//! Shutdown proceeds in reverse initialization order:
25//! 1. Stop the cron scheduler (functions)
26//! 2. Drain the realtime event channel
27//! 3. Drop the realtime server (closes all `WebSocket` connections)
28//! 4. Drop the functions observer (stops dispatching events)
29//! 5. Drop the storage backend (flushes any pending writes)
30
31pub mod builder;
32pub mod validator;
33
34#[cfg(test)]
35mod tests;
36
37use std::sync::Arc;
38
39pub use builder::{ServerSubsystemsBuilder, SubsystemBuildError};
40use fraiseql_functions::{FunctionObserver, triggers::TriggerRegistry};
41pub use validator::{SubsystemConfigWarning, validate_subsystems_config};
42
43use crate::{
44    realtime::{
45        observer::RealtimeBroadcastObserver, routes::RealtimeSchemaConfig, server::RealtimeServer,
46    },
47    schema::loader::{FunctionsConfig, SchemaStorageConfig},
48};
49
50// ── Subsystem structs ─────────────────────────────────────────────────────────
51
52/// Storage subsystem: backend, metadata repository, RLS evaluator, and bucket config.
53///
54/// Assembled at server startup from the `[storage]` section of the compiled schema
55/// and the server's `PgPool`. Use the [`fraiseql_storage::storage_router`] with the
56/// contained `state` to mount the `/storage/v1` route tree.
57pub struct StorageSubsystem {
58    /// Runtime storage state (backend + metadata repo + RLS + bucket config map).
59    ///
60    /// Pass this to [`fraiseql_storage::storage_router`] to mount the HTTP routes.
61    pub state: fraiseql_storage::StorageState,
62
63    /// Schema-level bucket definitions from the compiled schema.
64    pub schema_config: SchemaStorageConfig,
65}
66
67/// Functions subsystem: observer and trigger registry.
68///
69/// Assembled at server startup from the `[functions]` section of the compiled schema.
70/// The observer dispatches events to function runtimes; the registry maps triggers to
71/// function definitions and provides HTTP route matchers.
72pub struct FunctionsSubsystem {
73    /// Observer that dispatches trigger events to the appropriate function runtime.
74    pub observer: Arc<FunctionObserver>,
75
76    /// Registry mapping trigger types to function definitions.
77    pub trigger_registry: TriggerRegistry,
78
79    /// Loaded function modules keyed by function name.
80    ///
81    /// Populated at server startup by reading source files from `config.module_dir`.
82    /// Used by the before-mutation chain and the after-mutation dispatcher.
83    pub module_registry: std::collections::HashMap<String, fraiseql_functions::FunctionModule>,
84
85    /// Schema-level functions configuration (definitions + module directory).
86    pub config: FunctionsConfig,
87}
88
89/// Realtime subsystem: `WebSocket` broadcast server and event observer.
90///
91/// Assembled at server startup from the `[realtime]` section of the compiled schema.
92/// The server handles `WebSocket` connections; the observer receives mutation events
93/// from the observer pipeline and forwards them to connected clients.
94pub struct RealtimeSubsystem {
95    /// The `WebSocket` broadcast server.
96    ///
97    /// Pass this to [`crate::realtime::routes::realtime_router`] to mount `/realtime/v1`.
98    pub server: Arc<RealtimeServer>,
99
100    /// Observer that forwards mutation events into the realtime delivery pipeline.
101    pub observer: RealtimeBroadcastObserver,
102
103    /// Schema-level realtime configuration (enabled flag, entity list, capacity overrides).
104    pub schema_config: RealtimeSchemaConfig,
105}
106
107// ── Aggregated container ──────────────────────────────────────────────────────
108
109/// All optional platform subsystems assembled from the compiled schema.
110///
111/// Each field is `None` when the corresponding section is absent from or disabled
112/// in the compiled schema. Callers can use [`is_storage_enabled`][Self::is_storage_enabled]
113/// etc. to check at a glance, or match directly on the `Option` fields.
114#[allow(missing_debug_implementations)] // Reason: inner types (RealtimeBroadcastObserver) don't implement Debug
115pub struct ServerSubsystems {
116    /// Object storage subsystem, present when the schema's `"storage"` key is set.
117    pub storage: Option<StorageSubsystem>,
118
119    /// Serverless functions subsystem, present when the schema's `"functions"` key is set.
120    pub functions: Option<FunctionsSubsystem>,
121
122    /// Realtime broadcast subsystem, present when the schema's `"realtime"` key is set
123    /// and `enabled` is `true`.
124    pub realtime: Option<RealtimeSubsystem>,
125}
126
127impl ServerSubsystems {
128    /// Create an empty `ServerSubsystems` with all subsystems disabled.
129    ///
130    /// Equivalent to `ServerSubsystemsBuilder::new().build().unwrap()`.
131    #[must_use]
132    pub const fn none() -> Self {
133        Self {
134            storage:   None,
135            functions: None,
136            realtime:  None,
137        }
138    }
139
140    /// Returns `true` if the storage subsystem is present.
141    #[must_use]
142    pub const fn is_storage_enabled(&self) -> bool {
143        self.storage.is_some()
144    }
145
146    /// Returns `true` if the functions subsystem is present.
147    #[must_use]
148    pub const fn is_functions_enabled(&self) -> bool {
149        self.functions.is_some()
150    }
151
152    /// Returns `true` if the realtime subsystem is present.
153    #[must_use]
154    pub const fn is_realtime_enabled(&self) -> bool {
155        self.realtime.is_some()
156    }
157}
158
159// ── Before-mutation hook bundle ───────────────────────────────────────────────
160
161/// Shared bundle of before-mutation state passed into `AppState` for handler access.
162///
163/// This is a lightweight, cloneable snapshot of the parts of [`FunctionsSubsystem`]
164/// that are needed on the hot path for before-mutation checks. It is extracted once
165/// at server startup and stored in `AppState` via an `Arc`.
166pub struct BeforeMutationHooks {
167    /// Registry of all loaded triggers, keyed by trigger type and mutation name.
168    pub trigger_registry: TriggerRegistry,
169    /// Loaded function modules keyed by function name.
170    pub module_registry:  std::collections::HashMap<String, fraiseql_functions::FunctionModule>,
171    /// Observer that dispatches events to the appropriate function runtime.
172    pub observer:         std::sync::Arc<FunctionObserver>,
173}