Skip to main content

valence_core/runtime/
mod.rs

1//! Valence runtime handle and builder.
2
3mod builder;
4mod factory;
5
6pub use builder::ValenceBuilder;
7pub use factory::{RouterValenceFactory, RouterValenceFactoryConfig, ValenceFactory};
8
9use crate::actor::Actor;
10use crate::backend::DatabaseBackend;
11use crate::error::Result;
12use crate::owner_ref::OwnerRef;
13use crate::ports::actor::ActorFactory;
14use crate::ports::endpoints::DatabaseEndpointResolver;
15use crate::ports::secrets::SecretProvider;
16use crate::router::DatabaseRouter;
17use crate::router_key::router_key;
18use crate::schema::SchemaRegistry;
19use std::sync::Arc;
20use valence_telemetry::TelemetrySink;
21
22/// Host-assembled Valence handle.
23#[derive(Clone)]
24pub struct Valence {
25    pub(crate) router: Arc<DatabaseRouter>,
26    pub(crate) active_backend_key: String,
27    telemetry_sink: Arc<dyn TelemetrySink>,
28    secret_provider: Arc<dyn SecretProvider>,
29    actor_factory: Arc<dyn ActorFactory>,
30    endpoint_resolver: Arc<dyn DatabaseEndpointResolver>,
31    actor: Actor,
32    owner_override: Option<OwnerRef>,
33}
34
35impl Valence {
36    pub fn builder() -> ValenceBuilder {
37        ValenceBuilder::new()
38    }
39
40    pub fn database_router(&self) -> &Arc<DatabaseRouter> {
41        &self.router
42    }
43
44    pub fn active_backend(&self) -> Result<Arc<dyn DatabaseBackend>> {
45        self.router.resolve(&self.active_backend_key)
46    }
47
48    pub fn backend_for_table(&self, table: &str) -> Result<Arc<dyn DatabaseBackend>> {
49        let Some(meta) = SchemaRegistry::global().get_schema(table) else {
50            return self.active_backend();
51        };
52        let eval = meta.schema.database_evaluator;
53        let key = router_key(eval.logical_name(), eval.engine_id());
54        self.router
55            .resolve(&key)
56            .or_else(|_| self.active_backend())
57            .inspect_err(|_e| {
58                crate::instrumentation::metrics::record_router_resolve_error(table);
59            })
60    }
61
62    pub fn telemetry_sink(&self) -> &Arc<dyn TelemetrySink> {
63        &self.telemetry_sink
64    }
65
66    pub fn secret_provider(&self) -> &Arc<dyn SecretProvider> {
67        &self.secret_provider
68    }
69
70    pub fn actor_factory(&self) -> &Arc<dyn ActorFactory> {
71        &self.actor_factory
72    }
73
74    pub fn endpoint_resolver(&self) -> &Arc<dyn DatabaseEndpointResolver> {
75        &self.endpoint_resolver
76    }
77
78    pub fn actor(&self) -> &Actor {
79        &self.actor
80    }
81
82    pub fn with_actor(&self, actor: Actor) -> Self {
83        Self {
84            actor,
85            ..self.clone()
86        }
87    }
88
89    pub fn with_owner_override(&self, owner: OwnerRef) -> Self {
90        Self {
91            owner_override: Some(owner),
92            ..self.clone()
93        }
94    }
95
96    pub fn owner_override(&self) -> Option<&OwnerRef> {
97        self.owner_override.as_ref()
98    }
99
100    pub async fn ensure_unique_field_index(&self, table: &str, field: &str) -> Result<()> {
101        let backend = self.backend_for_table(table)?;
102        backend.define_unique_index(table, field).await
103    }
104
105    pub fn is_system(&self) -> bool {
106        self.actor.is_system()
107    }
108}
109
110impl std::fmt::Debug for Valence {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("Valence")
113            .field("active_backend_key", &self.active_backend_key)
114            .finish_non_exhaustive()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::ports::actor::JsonActorFactory;
122
123    #[test]
124    fn builder_requires_backend() {
125        let err = Valence::builder()
126            .actor_factory(Arc::new(JsonActorFactory))
127            .build()
128            .unwrap_err();
129        assert!(err.to_string().contains("at least one backend"));
130    }
131
132    #[test]
133    fn builder_accepts_actor_with_backend() {
134        use crate::backend::DatabaseBackend;
135        use async_trait::async_trait;
136        use std::sync::atomic::{AtomicUsize, Ordering};
137
138        #[derive(Debug)]
139        struct MockBackend;
140
141        #[async_trait]
142        impl DatabaseBackend for MockBackend {
143            fn engine_id(&self) -> &'static str {
144                "mem"
145            }
146
147            fn capabilities(&self) -> crate::backend::BackendCapabilities {
148                crate::backend::BackendCapabilities::mem()
149            }
150
151            async fn execute_compiled_query(
152                &self,
153                _compiled: &crate::compiled_query::CompiledQuery,
154            ) -> crate::error::Result<Vec<serde_json::Value>> {
155                Ok(vec![])
156            }
157
158            async fn get_record(
159                &self,
160                _table: &str,
161                _id: &str,
162            ) -> crate::error::Result<Option<serde_json::Value>> {
163                static GETS: AtomicUsize = AtomicUsize::new(0);
164                GETS.fetch_add(1, Ordering::SeqCst);
165                Ok(None)
166            }
167
168            async fn create_record(
169                &self,
170                _table: &str,
171                _content: serde_json::Value,
172            ) -> crate::error::Result<serde_json::Value> {
173                Ok(serde_json::json!({}))
174            }
175
176            async fn update_record(
177                &self,
178                _table: &str,
179                _id: &str,
180                _content: serde_json::Value,
181            ) -> crate::error::Result<serde_json::Value> {
182                Ok(serde_json::json!({}))
183            }
184
185            async fn upsert_record(
186                &self,
187                _table: &str,
188                _id: &str,
189                _content: serde_json::Value,
190            ) -> crate::error::Result<serde_json::Value> {
191                Ok(serde_json::json!({}))
192            }
193
194            async fn delete_record(&self, _table: &str, _id: &str) -> crate::error::Result<()> {
195                Ok(())
196            }
197
198            async fn relate_edge(
199                &self,
200                _from: &crate::record_id::RecordId,
201                _edge_table: &str,
202                _to: &crate::record_id::RecordId,
203            ) -> crate::error::Result<()> {
204                Ok(())
205            }
206
207            async fn unrelate_edge(
208                &self,
209                _from: &crate::record_id::RecordId,
210                _edge_table: &str,
211                _to: &crate::record_id::RecordId,
212            ) -> crate::error::Result<()> {
213                Ok(())
214            }
215
216            async fn get_edge_targets(
217                &self,
218                _from: &crate::record_id::RecordId,
219                _edge_table: &str,
220            ) -> crate::error::Result<Vec<crate::record_id::RecordId>> {
221                Ok(vec![])
222            }
223        }
224
225        let v = Valence::builder()
226            .add_backend("default", Arc::new(MockBackend))
227            .with_actor(Actor::System {
228                operation: "test".into(),
229            })
230            .build()
231            .expect("build");
232        assert!(v.is_system());
233        let _ = v.active_backend().expect("backend");
234    }
235}