Skip to main content

photon_runtime/
builder.rs

1//! [`Photon`] builder — storage port + backend assembly (**Integrating the host**).
2
3use std::sync::Arc;
4
5use photon_telemetry::{install_ops_log, OpsLog};
6
7use photon_backend::{
8    instrumentation, BackendContext, EmbeddedBackend, ExecutorServices, GenericPhotonBackend,
9    InProcStoragePort, PhotonBackend, PhotonError, Result, RetentionHook, RetentionPolicy,
10    StoragePort, TopicRegistry, TransportCrypto,
11};
12
13use crate::executor::ExecutorController;
14use crate::{Photon, PhotonRuntimeState};
15
16type BackendInstallFn =
17    Box<dyn FnOnce(BackendContext) -> Result<Arc<dyn PhotonBackend>> + Send>;
18
19/// Builder for constructing [`Photon`] runtimes — **Integrating the host**.
20///
21/// # Example
22///
23/// ```rust,no_run
24/// use photon_runtime::Photon;
25///
26/// # fn main() -> photon_backend::Result<()> {
27/// let _photon = Photon::builder().auto_registry().build()?;
28/// # Ok(())
29/// # }
30/// ```
31#[derive(Default)]
32pub struct PhotonBuilder {
33    storage_port: Option<Arc<dyn StoragePort>>,
34    backend: Option<Arc<dyn PhotonBackend>>,
35    backend_install: Option<BackendInstallFn>,
36    use_auto_registry: bool,
37    ops_log: Option<Arc<dyn OpsLog>>,
38    retention_policy: Option<RetentionPolicy>,
39    retention_hook: Option<Arc<dyn RetentionHook>>,
40}
41
42impl PhotonBuilder {
43    /// Explicit storage port (defaults to in-process `mem` tier).
44    ///
45    /// # Example
46    ///
47    /// ```rust,no_run
48    /// use std::sync::Arc;
49    ///
50    /// use photon_backend::{InProcStoragePort, StoragePort, TransportCrypto};
51    /// use photon_runtime::Photon;
52    ///
53    /// # fn main() -> photon_backend::Result<()> {
54    /// let port: Arc<dyn StoragePort> = Arc::new(InProcStoragePort::new(
55    ///     TransportCrypto::from_env()?,
56    /// ));
57    /// let _photon = Photon::builder().storage_port(port).auto_registry().build()?;
58    /// # Ok(())
59    /// # }
60    /// ```
61    #[must_use]
62    pub fn storage_port(mut self, port: Arc<dyn StoragePort>) -> Self {
63        self.storage_port = Some(port);
64        self
65    }
66
67    /// Pre-built backend instance.
68    #[must_use]
69    pub fn backend(mut self, backend: Arc<dyn PhotonBackend>) -> Self {
70        self.backend = Some(backend);
71        self.backend_install = None;
72        self
73    }
74
75    /// Build backend from shared [`BackendContext`] (typical for custom install fns).
76    #[must_use]
77    pub fn backend_with_context<F>(mut self, install: F) -> Self
78    where
79        F: FnOnce(BackendContext) -> Result<Arc<dyn PhotonBackend>> + Send + 'static,
80    {
81        self.backend = None;
82        self.backend_install = Some(Box::new(install));
83        self
84    }
85
86    /// Shorthand for [`Self::backend_with_context`](GenericPhotonBackend::install_mem).
87    #[must_use]
88    pub fn mem_backend(mut self) -> Self {
89        self.backend = None;
90        self.backend_install = Some(Box::new(EmbeddedBackend::install_mem));
91        self
92    }
93
94    /// Install a concrete [`OpsLog`] adapter before build.
95    #[must_use]
96    pub fn ops_log(mut self, log: impl OpsLog + 'static) -> Self {
97        self.ops_log = Some(Arc::new(log));
98        self
99    }
100
101    /// Install a shared [`OpsLog`] trait object before build.
102    #[must_use]
103    pub fn ops_log_arc(mut self, log: Arc<dyn OpsLog>) -> Self {
104        self.ops_log = Some(log);
105        self
106    }
107
108    /// Discover `#[photon::topic]` descriptors via Quark inventory instead of an empty registry.
109    ///
110    /// Required when using `#[topic]` / `#[subscribe]` in the same crate graph as the host.
111    /// Runnable: `cargo run -p uf-photon --example embedded_mem --features runtime,mem`.
112    #[must_use]
113    pub const fn auto_registry(mut self) -> Self {
114        self.use_auto_registry = true;
115        self
116    }
117
118    /// Override default retention policy (env fallbacks apply for unset fields).
119    #[must_use]
120    pub fn retention_policy(mut self, policy: RetentionPolicy) -> Self {
121        self.retention_policy = Some(policy);
122        self
123    }
124
125    /// Host hook for extra subscriptions and legal-hold floors.
126    #[must_use]
127    pub fn retention_hook(mut self, hook: Arc<dyn RetentionHook>) -> Self {
128        self.retention_hook = Some(hook);
129        self
130    }
131
132    /// Assemble the [`Photon`] runtime.
133    ///
134    /// # Errors
135    ///
136    /// Returns an error if the operation fails.
137    pub fn build(self) -> Result<Photon> {
138        if let Some(log) = self.ops_log {
139            install_ops_log(log);
140        }
141
142        let registry = if self.use_auto_registry {
143            TopicRegistry::auto_discover()
144        } else {
145            TopicRegistry::new()
146        };
147
148        let port = match self.storage_port {
149            Some(port) => port,
150            None => Arc::new(InProcStoragePort::new(TransportCrypto::from_env()?)),
151        };
152
153        let ctx = BackendContext {
154            registry: registry.clone(),
155        };
156
157        let backend = match (self.backend, self.backend_install) {
158            (Some(b), None) => b,
159            (None, Some(install)) => install(ctx)?,
160            (None, None) => GenericPhotonBackend::install_with_port(
161                BackendContext { registry },
162                Arc::clone(&port),
163            )?,
164            (Some(_), Some(_)) => {
165                return Err(PhotonError::Internal(
166                    "PhotonBuilder: set backend() or backend_with_context(), not both".into(),
167                ));
168            }
169        };
170
171        let retention_policy = self.retention_policy.unwrap_or_default();
172        let runtime = PhotonRuntimeState {
173            storage_port: Arc::clone(&port),
174            executor_services: Arc::new(ExecutorServices::new(
175                port,
176                retention_policy,
177                self.retention_hook,
178            )),
179            executor: Arc::new(ExecutorController::default()),
180        };
181
182        let backend = instrumentation::wrap_backend(backend);
183        Ok(Photon::new(backend, runtime))
184    }
185}