Skip to main content

teaql_runtime/
registry.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use teaql_core::{
5    CompactRow, DeleteCommand, Entity, EntityDescriptor, EntityDescriptorStore, EntityError,
6    IdentifiableEntity, InsertCommand, RecoverCommand, SelectQuery, UpdateCommand,
7};
8
9use crate::{
10    Checker, EntityGraphBuilder, EntityRuntimeState, GraphNode, InMemoryCheckerRegistry,
11    InMemoryRawAuditEventSink, Language, RawAuditEventSink, RuntimeError, UserContext,
12};
13
14type CompactEntityGraphDecoder =
15    fn(CompactRow, &EntityRuntimeState, &mut EntityGraphBuilder) -> Result<(), EntityError>;
16type CompactEntityGraphBatchDecoder =
17    fn(Vec<CompactRow>, &EntityRuntimeState, &mut EntityGraphBuilder) -> Result<(), EntityError>;
18type CompactEntityGraphListDecoder = fn(
19    Vec<CompactRow>,
20    &EntityRuntimeState,
21    &mut EntityGraphBuilder,
22    &str,
23    u64,
24    &str,
25) -> Result<(), EntityError>;
26
27#[derive(Default, Clone)]
28pub struct InMemoryEntityGraphDecoderRegistry {
29    compact_decoders: BTreeMap<String, CompactEntityGraphDecoder>,
30    compact_batch_decoders: BTreeMap<String, CompactEntityGraphBatchDecoder>,
31    compact_list_decoders: BTreeMap<String, CompactEntityGraphListDecoder>,
32    compact_option_decoders: BTreeMap<String, CompactEntityGraphListDecoder>,
33}
34
35impl InMemoryEntityGraphDecoderRegistry {
36    pub fn contains(&self, entity: &str) -> bool {
37        self.compact_decoders.contains_key(entity)
38    }
39
40    pub fn register<T>(&mut self)
41    where
42        T: Entity + IdentifiableEntity + Send + Sync + 'static,
43    {
44        fn decode_compact<T>(
45            row: CompactRow,
46            root: &EntityRuntimeState,
47            graph: &mut EntityGraphBuilder,
48        ) -> Result<(), EntityError>
49        where
50            T: Entity + IdentifiableEntity + Send + Sync + 'static,
51        {
52            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
53            let entity = T::from_compact_row_with_context(row, &graph_root as &dyn std::any::Any)?;
54            let id = entity.id_value().try_u64().ok_or_else(|| {
55                EntityError::new(T::ENTITY_NAME, "identity graph requires a u64 entity id")
56            })?;
57            graph.install(id, entity);
58            Ok(())
59        }
60
61        fn decode_compact_list<T>(
62            rows: Vec<CompactRow>,
63            root: &EntityRuntimeState,
64            graph: &mut EntityGraphBuilder,
65            owner_entity: &str,
66            owner_id: u64,
67            relation: &str,
68        ) -> Result<(), EntityError>
69        where
70            T: Entity + IdentifiableEntity + Send + Sync + 'static,
71        {
72            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
73            // `collect::<Result<Vec<_>, _>>()` cannot retain the exact size hint
74            // through the fallible adapter. For large generated entities that
75            // grows 4 -> 8 -> 16 even when the relation cardinality is already
76            // known. Reserve the exact row count and decode directly into the
77            // final SmartList allocation.
78            let mut entities = Vec::with_capacity(rows.len());
79            for row in rows {
80                entities.push(T::from_compact_row_with_context(
81                    row,
82                    &graph_root as &dyn std::any::Any,
83                )?);
84            }
85            graph.install_relation_list(
86                owner_entity,
87                owner_id,
88                relation,
89                teaql_core::SmartList::new(entities),
90            );
91            Ok(())
92        }
93
94        fn decode_compact_batch<T>(
95            rows: Vec<CompactRow>,
96            root: &EntityRuntimeState,
97            graph: &mut EntityGraphBuilder,
98        ) -> Result<(), EntityError>
99        where
100            T: Entity + IdentifiableEntity + Send + Sync + 'static,
101        {
102            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
103            for row in rows {
104                let entity =
105                    T::from_compact_row_with_context(row, &graph_root as &dyn std::any::Any)?;
106                let id = entity.id_value().try_u64().ok_or_else(|| {
107                    EntityError::new(T::ENTITY_NAME, "identity graph requires a u64 entity id")
108                })?;
109                graph.install(id, entity);
110            }
111            Ok(())
112        }
113
114        fn decode_compact_option<T>(
115            rows: Vec<CompactRow>,
116            root: &EntityRuntimeState,
117            graph: &mut EntityGraphBuilder,
118            owner_entity: &str,
119            owner_id: u64,
120            relation: &str,
121        ) -> Result<(), EntityError>
122        where
123            T: Entity + IdentifiableEntity + Send + Sync + 'static,
124        {
125            let graph_root = EntityRuntimeState::fresh_with_weak_graph(root);
126            let value = rows
127                .into_iter()
128                .next()
129                .map(|row| T::from_compact_row_with_context(row, &graph_root as &dyn std::any::Any))
130                .transpose()?;
131            graph.install_relation_option(owner_entity, owner_id, relation, value);
132            Ok(())
133        }
134
135        self.compact_decoders
136            .insert(T::ENTITY_NAME.to_owned(), decode_compact::<T>);
137        self.compact_batch_decoders
138            .insert(T::ENTITY_NAME.to_owned(), decode_compact_batch::<T>);
139        self.compact_list_decoders
140            .insert(T::ENTITY_NAME.to_owned(), decode_compact_list::<T>);
141        self.compact_option_decoders
142            .insert(T::ENTITY_NAME.to_owned(), decode_compact_option::<T>);
143    }
144
145    pub fn decode_compact(
146        &self,
147        entity: &str,
148        row: CompactRow,
149        root: &EntityRuntimeState,
150        graph: &mut EntityGraphBuilder,
151    ) -> Result<(), EntityError> {
152        self.compact_decoders.get(entity).ok_or_else(|| {
153            EntityError::new(
154                entity,
155                "entity has no compact identity graph decoder in RuntimeModule",
156            )
157        })?(row, root, graph)
158    }
159
160    #[allow(clippy::too_many_arguments)] // Stable generated decoder boundary.
161    pub fn decode_compact_list(
162        &self,
163        entity: &str,
164        rows: Vec<CompactRow>,
165        root: &EntityRuntimeState,
166        graph: &mut EntityGraphBuilder,
167        owner_entity: &str,
168        owner_id: u64,
169        relation: &str,
170    ) -> Result<(), EntityError> {
171        self.compact_list_decoders.get(entity).ok_or_else(|| {
172            EntityError::new(
173                entity,
174                "entity has no compact identity graph list decoder in RuntimeModule",
175            )
176        })?(rows, root, graph, owner_entity, owner_id, relation)
177    }
178
179    pub fn decode_compact_batch(
180        &self,
181        entity: &str,
182        rows: Vec<CompactRow>,
183        root: &EntityRuntimeState,
184        graph: &mut EntityGraphBuilder,
185    ) -> Result<(), EntityError> {
186        self.compact_batch_decoders.get(entity).ok_or_else(|| {
187            EntityError::new(
188                entity,
189                "entity has no compact identity graph batch decoder in RuntimeModule",
190            )
191        })?(rows, root, graph)
192    }
193
194    #[allow(clippy::too_many_arguments)] // Stable generated decoder boundary.
195    pub fn decode_compact_option(
196        &self,
197        entity: &str,
198        rows: Vec<CompactRow>,
199        root: &EntityRuntimeState,
200        graph: &mut EntityGraphBuilder,
201        owner_entity: &str,
202        owner_id: u64,
203        relation: &str,
204    ) -> Result<(), EntityError> {
205        self.compact_option_decoders.get(entity).ok_or_else(|| {
206            EntityError::new(
207                entity,
208                "entity has no compact identity graph option decoder in RuntimeModule",
209            )
210        })?(rows, root, graph, owner_entity, owner_id, relation)
211    }
212}
213
214pub trait MetadataStore: Send + Sync {
215    fn entity(&self, name: &str) -> Option<&EntityDescriptor>;
216    fn all_entities(&self) -> Vec<&EntityDescriptor>;
217    fn record_metadata_log(&self, _metadata: &teaql_data_service::ExecutionMetadata) {}
218    fn capture_query_debug(&self) -> bool {
219        true
220    }
221    fn capture_execution_metadata(&self) -> bool {
222        true
223    }
224}
225
226pub trait EntityRegistry: Send + Sync {
227    fn contains(&self, entity: &str) -> bool;
228}
229
230pub trait RequestPolicy: Send + Sync {
231    fn enforce_select(
232        &self,
233        _ctx: &UserContext,
234        _query: &mut SelectQuery,
235    ) -> Result<(), RuntimeError> {
236        Ok(())
237    }
238
239    fn enforce_insert(
240        &self,
241        _ctx: &UserContext,
242        _command: &mut InsertCommand,
243    ) -> Result<(), RuntimeError> {
244        Ok(())
245    }
246
247    fn enforce_update(
248        &self,
249        _ctx: &UserContext,
250        _command: &mut UpdateCommand,
251    ) -> Result<(), RuntimeError> {
252        Ok(())
253    }
254
255    fn enforce_delete(
256        &self,
257        _ctx: &UserContext,
258        _command: &mut DeleteCommand,
259    ) -> Result<(), RuntimeError> {
260        Ok(())
261    }
262
263    fn enforce_recover(
264        &self,
265        _ctx: &UserContext,
266        _command: &mut RecoverCommand,
267    ) -> Result<(), RuntimeError> {
268        Ok(())
269    }
270}
271
272pub trait EntityDataServiceBehavior: Send + Sync {
273    fn before_select(
274        &self,
275        _ctx: &UserContext,
276        _query: &mut SelectQuery,
277    ) -> Result<(), RuntimeError> {
278        Ok(())
279    }
280
281    fn before_insert(
282        &self,
283        _ctx: &UserContext,
284        _command: &mut InsertCommand,
285    ) -> Result<(), RuntimeError> {
286        Ok(())
287    }
288
289    fn before_update(
290        &self,
291        _ctx: &UserContext,
292        _command: &mut UpdateCommand,
293    ) -> Result<(), RuntimeError> {
294        Ok(())
295    }
296
297    fn before_delete(
298        &self,
299        _ctx: &UserContext,
300        _command: &mut DeleteCommand,
301    ) -> Result<(), RuntimeError> {
302        Ok(())
303    }
304
305    fn before_recover(
306        &self,
307        _ctx: &UserContext,
308        _command: &mut RecoverCommand,
309    ) -> Result<(), RuntimeError> {
310        Ok(())
311    }
312
313    fn relation_loads(&self, _ctx: &UserContext) -> Vec<String> {
314        Vec::new()
315    }
316}
317
318pub trait EntityDataServiceBehaviorRegistry: Send + Sync {
319    fn behavior(&self, entity: &str) -> Option<Arc<dyn EntityDataServiceBehavior>>;
320}
321
322#[derive(Debug, Default, Clone)]
323pub struct InMemoryMetadataStore {
324    entities: BTreeMap<String, EntityDescriptor>,
325}
326
327impl InMemoryMetadataStore {
328    pub fn new() -> Self {
329        Self::default()
330    }
331
332    pub fn register(&mut self, entity: EntityDescriptor) {
333        self.entities.insert(entity.name.clone(), entity);
334    }
335
336    pub fn with_entity(mut self, entity: EntityDescriptor) -> Self {
337        self.register(entity);
338        self
339    }
340}
341
342impl MetadataStore for InMemoryMetadataStore {
343    fn entity(&self, name: &str) -> Option<&EntityDescriptor> {
344        self.entities.get(name)
345    }
346
347    fn all_entities(&self) -> Vec<&EntityDescriptor> {
348        self.entities.values().collect()
349    }
350}
351
352impl teaql_data_service::SchemaProvider for InMemoryMetadataStore {
353    fn get_entity(&self, name: &str) -> Option<std::sync::Arc<teaql_core::EntityDescriptor>> {
354        self.entities
355            .get(name)
356            .map(|e| std::sync::Arc::new(e.clone()))
357    }
358}
359
360impl EntityDescriptorStore for InMemoryMetadataStore {
361    fn register_descriptor(&mut self, descriptor: EntityDescriptor) {
362        self.register(descriptor);
363    }
364}
365
366#[derive(Debug, Default, Clone)]
367pub struct InMemoryEntityRegistry {
368    entities: BTreeMap<String, String>,
369}
370
371impl InMemoryEntityRegistry {
372    pub fn new() -> Self {
373        Self::default()
374    }
375
376    pub fn register(&mut self, entity: impl Into<String>) {
377        let entity = entity.into();
378        self.entities.insert(entity.clone(), entity);
379    }
380
381    pub fn with_entity(mut self, entity: impl Into<String>) -> Self {
382        self.register(entity);
383        self
384    }
385}
386
387impl EntityRegistry for InMemoryEntityRegistry {
388    fn contains(&self, entity: &str) -> bool {
389        self.entities.contains_key(entity)
390    }
391}
392
393#[derive(Default, Clone)]
394pub struct InMemoryEntityDataServiceBehaviorRegistry {
395    behaviors: BTreeMap<String, Arc<dyn EntityDataServiceBehavior>>,
396}
397
398impl InMemoryEntityDataServiceBehaviorRegistry {
399    pub fn new() -> Self {
400        Self::default()
401    }
402
403    pub fn register(
404        &mut self,
405        entity: impl Into<String>,
406        behavior: impl EntityDataServiceBehavior + 'static,
407    ) {
408        self.behaviors.insert(entity.into(), Arc::new(behavior));
409    }
410
411    pub fn with_behavior(
412        mut self,
413        entity: impl Into<String>,
414        behavior: impl EntityDataServiceBehavior + 'static,
415    ) -> Self {
416        self.register(entity, behavior);
417        self
418    }
419}
420
421impl EntityDataServiceBehaviorRegistry for InMemoryEntityDataServiceBehaviorRegistry {
422    fn behavior(&self, entity: &str) -> Option<Arc<dyn EntityDataServiceBehavior>> {
423        self.behaviors.get(entity).cloned()
424    }
425}
426
427#[derive(Default, Clone)]
428pub struct RuntimeModule {
429    pub metadata: InMemoryMetadataStore,
430    entity_registry: InMemoryEntityRegistry,
431    behaviors: InMemoryEntityDataServiceBehaviorRegistry,
432    checkers: InMemoryCheckerRegistry,
433    event_sinks: InMemoryRawAuditEventSink,
434    language: Option<Language>,
435    initial_graphs: Vec<GraphNode>,
436    root_graphs: Vec<GraphNode>,
437    generated_schema_bootstraps: Vec<crate::GeneratedSchemaBootstrap>,
438    graph_decoders: InMemoryEntityGraphDecoderRegistry,
439}
440
441impl RuntimeModule {
442    pub fn new() -> Self {
443        Self::default()
444    }
445
446    pub fn entity<T>(mut self) -> Self
447    where
448        T: Entity + IdentifiableEntity + Send + Sync + 'static,
449    {
450        let descriptor = T::entity_descriptor();
451        self.entity_registry.register(descriptor.name.clone());
452        self.metadata.register(descriptor);
453        self.graph_decoders.register::<T>();
454        self
455    }
456
457    pub fn entity_with_behavior<T, B>(mut self, behavior: B) -> Self
458    where
459        T: Entity + IdentifiableEntity + Send + Sync + 'static,
460        B: EntityDataServiceBehavior + 'static,
461    {
462        let descriptor = T::entity_descriptor();
463        let entity_name = descriptor.name.clone();
464        self.entity_registry.register(entity_name.clone());
465        self.metadata.register(descriptor);
466        self.behaviors.register(entity_name, behavior);
467        self.graph_decoders.register::<T>();
468        self
469    }
470
471    pub fn descriptor(mut self, descriptor: EntityDescriptor) -> Self {
472        self.entity_registry.register(descriptor.name.clone());
473        self.metadata.register(descriptor);
474        self
475    }
476
477    pub fn behavior(
478        mut self,
479        entity: impl Into<String>,
480        behavior: impl EntityDataServiceBehavior + 'static,
481    ) -> Self {
482        self.behaviors.register(entity, behavior);
483        self
484    }
485
486    pub fn checker(mut self, checker: impl Checker + 'static) -> Self {
487        self.checkers.register(checker);
488        self
489    }
490
491    pub fn event_sink(mut self, sink: impl RawAuditEventSink + 'static) -> Self {
492        self.event_sinks.register(sink);
493        self
494    }
495
496    pub fn language(mut self, language: Language) -> Self {
497        self.language = Some(language);
498        self
499    }
500
501    pub fn initial_graph(mut self, graph: GraphNode) -> Self {
502        self.initial_graphs.push(graph);
503        self
504    }
505
506    pub fn initial_graphs(mut self, graphs: impl IntoIterator<Item = GraphNode>) -> Self {
507        self.initial_graphs.extend(graphs);
508        self
509    }
510
511    /// Register create-if-absent root data. Unlike constant initial graphs,
512    /// existing root rows are never reconciled from module defaults.
513    pub fn root_graph(mut self, graph: GraphNode) -> Self {
514        self.root_graphs.push(graph);
515        self
516    }
517
518    pub fn root_graphs(mut self, graphs: impl IntoIterator<Item = GraphNode>) -> Self {
519        self.root_graphs.extend(graphs);
520        self
521    }
522
523    pub fn generated_schema_bootstrap(
524        mut self,
525        bootstrap: crate::GeneratedSchemaBootstrap,
526    ) -> Self {
527        self.generated_schema_bootstraps.push(bootstrap);
528        self
529    }
530
531    pub fn apply_to(self, context: &mut UserContext) {
532        context.set_metadata(self.metadata);
533        context.set_entity_registry(self.entity_registry);
534        context.set_entity_data_service_behavior_registry(self.behaviors);
535        context.set_checker_registry(self.checkers);
536        context.set_event_sink(self.event_sinks);
537        context.set_initial_graphs(self.initial_graphs);
538        context.set_root_graphs(self.root_graphs);
539        context.set_generated_schema_bootstraps(self.generated_schema_bootstraps);
540        context.set_entity_graph_decoder_registry(self.graph_decoders);
541        if let Some(language) = self.language {
542            context.set_language(language);
543        }
544    }
545
546    pub fn into_context(self) -> UserContext {
547        let mut context = UserContext::new();
548        self.apply_to(&mut context);
549        context
550    }
551}
552
553#[macro_export]
554macro_rules! module {
555    ($($entity:ty $(=> $behavior:expr)?),+ $(,)?) => {{
556        let module = $crate::RuntimeModule::new();
557        $crate::module!(@build module; $($entity $(=> $behavior)?),+)
558    }};
559
560    (@build $module:expr; $entity:ty => $behavior:expr, $($rest:tt)*) => {{
561        let module = $module.entity_with_behavior::<$entity, _>($behavior);
562        $crate::module!(@build module; $($rest)*)
563    }};
564
565    (@build $module:expr; $entity:ty, $($rest:tt)*) => {{
566        let module = $module.entity::<$entity>();
567        $crate::module!(@build module; $($rest)*)
568    }};
569
570    (@build $module:expr; $entity:ty => $behavior:expr) => {
571        $module.entity_with_behavior::<$entity, _>($behavior)
572    };
573
574    (@build $module:expr; $entity:ty) => {
575        $module.entity::<$entity>()
576    };
577}