Skip to main content

valence_core/runtime/
builder.rs

1//! [`Valence`] builder — host wiring for storage, injectable ports, and actor context.
2
3use super::Valence;
4use crate::actor::Actor;
5use crate::backend::DatabaseBackend;
6use crate::error::{Error, Result};
7use crate::ports::actor::{ActorFactory, JsonActorFactory};
8use crate::ports::endpoints::{DatabaseEndpointResolver, NoopEndpointResolver};
9use crate::ports::secrets::{NoOpSecretProvider, SecretProvider};
10use crate::router::DatabaseRouter;
11use crate::router_key::router_key;
12use std::sync::Arc;
13use valence_telemetry::{install_telemetry_sink, NoOpSink, TelemetrySink};
14
15/// Builder for constructing a [`Valence`] runtime.
16///
17/// ## Storage
18///
19/// | Method | Use |
20/// |--------|-----|
21/// | [`Self::add_backend`] | Primary API; key from `backend.engine_id()` |
22/// | [`Self::add_backend_key`] | Explicit compound key |
23/// | [`Self::database_router`] | Inject a pre-built [`DatabaseRouter`] |
24/// | [`Self::default_backend_key`] | Active backend when models omit per-table routing |
25/// | [`Self::build`] | Requires ≥1 backend — **no** silent mem fallback |
26///
27/// ## Host ports
28///
29/// Optional: [`Self::telemetry_sink`], [`Self::secret_provider`], [`Self::actor_factory`],
30/// [`Self::endpoint_resolver`]. See [`crate::ports`] for the port table and reference impls.
31///
32/// Privacy policies are schema-attached ([`crate::PolicyEvaluator`]), not registered here.
33///
34/// # Examples
35///
36/// ```
37/// use std::sync::Arc;
38/// use valence_backend_mem::InMemoryBackend;
39/// use valence_core::Valence;
40///
41/// let valence = Valence::builder()
42///     .add_backend("default", Arc::new(InMemoryBackend::new()))
43///     .build()
44///     .expect("build");
45/// assert!(valence.active_backend().is_ok());
46/// ```
47#[derive(Default)]
48pub struct ValenceBuilder {
49    router: DatabaseRouter,
50    injected_router: Option<Arc<DatabaseRouter>>,
51    registered_keys: Vec<String>,
52    default_backend_key: Option<String>,
53    telemetry_sink: Option<Arc<dyn TelemetrySink>>,
54    secret_provider: Option<Arc<dyn SecretProvider>>,
55    actor_factory: Option<Arc<dyn ActorFactory>>,
56    endpoint_resolver: Option<Arc<dyn DatabaseEndpointResolver>>,
57    actor: Option<Actor>,
58}
59
60impl ValenceBuilder {
61    /// Start with an empty router; call [`Self::add_backend`] before [`Self::build`].
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Register a backend under `router_key(logical_name, backend.engine_id())`.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use std::sync::Arc;
72    /// use valence_backend_mem::InMemoryBackend;
73    /// use valence_core::ValenceBuilder;
74    ///
75    /// let builder = ValenceBuilder::new()
76    ///     .add_backend("default", Arc::new(InMemoryBackend::new()));
77    /// let _ = builder.build().expect("build");
78    /// ```
79    pub fn add_backend(
80        mut self,
81        logical_name: impl AsRef<str>,
82        backend: Arc<dyn DatabaseBackend>,
83    ) -> Self {
84        let key = router_key(logical_name.as_ref(), backend.engine_id());
85        self.registered_keys.push(key.clone());
86        self.router.register(key, backend);
87        self
88    }
89
90    /// Register a backend under an explicit router key (for pre-built heterogeneous routers).
91    pub fn add_backend_key(
92        mut self,
93        key: impl Into<String>,
94        backend: Arc<dyn DatabaseBackend>,
95    ) -> Self {
96        let key = key.into();
97        self.registered_keys.push(key.clone());
98        self.router.register(key, backend);
99        self
100    }
101
102    /// Inject a fully built router instead of registering backends on this builder.
103    ///
104    /// **Contract:** mutually exclusive with [`Self::add_backend`] / [`Self::add_backend_key`].
105    pub fn database_router(mut self, router: Arc<DatabaseRouter>) -> Self {
106        self.injected_router = Some(router);
107        self
108    }
109
110    /// Set the active backend key used by generated models without per-table routing.
111    ///
112    /// **Contract:** required when more than one backend is registered; optional when exactly one.
113    pub fn default_backend_key(mut self, key: impl Into<String>) -> Self {
114        self.default_backend_key = Some(key.into());
115        self
116    }
117
118    /// Install a process-global telemetry sink (also stored on the built [`Valence`]).
119    pub fn telemetry_sink(mut self, sink: Arc<dyn TelemetrySink>) -> Self {
120        self.telemetry_sink = Some(sink);
121        self
122    }
123
124    /// Provide secret lookup for host-owned credential resolution.
125    pub fn secret_provider(mut self, provider: Arc<dyn SecretProvider>) -> Self {
126        self.secret_provider = Some(provider);
127        self
128    }
129
130    /// Build request-scoped [`crate::ActorContext`] values from JSON actor payloads.
131    pub fn actor_factory(mut self, factory: Arc<dyn ActorFactory>) -> Self {
132        self.actor_factory = Some(factory);
133        self
134    }
135
136    /// Resolve physical database URLs from logical names at bootstrap.
137    pub fn endpoint_resolver(mut self, resolver: Arc<dyn DatabaseEndpointResolver>) -> Self {
138        self.endpoint_resolver = Some(resolver);
139        self
140    }
141
142    /// Set the default actor for this runtime (defaults to anonymous).
143    pub fn with_actor(mut self, actor: Actor) -> Self {
144        self.actor = Some(actor);
145        self
146    }
147
148    /// Construct a [`Valence`] runtime from the configured router and ports.
149    ///
150    /// **Errors:** when no backends are registered, when both injected and local routers are used,
151    /// or when multiple backends are registered without [`Self::default_backend_key`].
152    pub fn build(self) -> Result<Valence> {
153        let router = if let Some(injected) = self.injected_router {
154            if self.registered_keys.is_empty() {
155                injected
156            } else {
157                return Err(Error::Internal(
158                    "ValenceBuilder: use database_router() or add_backend(), not both".into(),
159                ));
160            }
161        } else {
162            if self.router.len()? == 0 {
163                return Err(Error::Internal(
164                    "ValenceBuilder requires at least one backend — call add_backend() or add_backend_key()".into(),
165                ));
166            }
167            Arc::new(self.router)
168        };
169
170        let active_backend_key = match self.default_backend_key {
171            Some(key) => key,
172            None if self.registered_keys.len() == 1 => self.registered_keys[0].clone(),
173            None => {
174                return Err(Error::Internal(
175                    "ValenceBuilder requires default_backend_key() when multiple backends are registered".into(),
176                ));
177            }
178        };
179
180        let telemetry_sink = match self.telemetry_sink {
181            Some(sink) => {
182                install_telemetry_sink(Arc::clone(&sink));
183                sink
184            }
185            None => Arc::new(NoOpSink),
186        };
187
188        let actor = self.actor.unwrap_or(Actor::Anonymous);
189
190        Ok(Valence {
191            router,
192            active_backend_key,
193            telemetry_sink,
194            secret_provider: self
195                .secret_provider
196                .unwrap_or_else(|| Arc::new(NoOpSecretProvider)),
197            actor_factory: self
198                .actor_factory
199                .unwrap_or_else(|| Arc::new(JsonActorFactory)),
200            endpoint_resolver: self
201                .endpoint_resolver
202                .unwrap_or_else(|| Arc::new(NoopEndpointResolver)),
203            actor,
204            owner_override: None,
205        })
206    }
207}