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}