Skip to main content

uni_db/api/
template.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2026 Dragonscale Team
3
4//! Session templates — pre-configured session factories.
5//!
6//! Templates allow pre-compiling rules, binding parameters, and attaching hooks
7//! once at startup, then cheaply stamping out sessions per-request.
8
9use std::collections::HashMap;
10use std::sync::Arc;
11use std::time::Duration;
12
13use uni_common::{Result, Value};
14
15use crate::api::UniInner;
16use crate::api::hooks::SessionHook;
17use crate::api::impl_locy::{self, LocyRuleRegistry};
18use crate::api::session::Session;
19
20/// A pre-configured session factory.
21///
22/// Templates pre-compile Locy rules and store parameters and hooks so that
23/// `create()` is a cheap, synchronous clone operation.
24///
25/// # Examples
26///
27/// ```no_run
28/// # use uni_db::Uni;
29/// # async fn example(db: &Uni) -> uni_db::Result<()> {
30/// let template = db.session_template()
31///     .param("tenant", 42)
32///     .rules("edge_rule(X,Y) :- knows(X,Y).")?
33///     .query_timeout(std::time::Duration::from_secs(30))
34///     .build()?;
35///
36/// // Cheap per-request session creation:
37/// let session = template.create();
38/// # Ok(())
39/// # }
40/// ```
41pub struct SessionTemplate {
42    db: Arc<UniInner>,
43    params: HashMap<String, Value>,
44    rule_registry: LocyRuleRegistry,
45    hooks: HashMap<String, Arc<dyn SessionHook>>,
46    pub(crate) query_timeout: Option<Duration>,
47    pub(crate) transaction_timeout: Option<Duration>,
48}
49
50impl SessionTemplate {
51    /// Create a new session from this template.
52    ///
53    /// This is cheap and synchronous — rules are cloned (not recompiled),
54    /// parameters are cloned, and hooks are Arc-cloned.
55    pub fn create(&self) -> Session {
56        Session::new_from_template(
57            self.db.clone(),
58            self.params.clone(),
59            self.rule_registry.clone(),
60            self.hooks.clone(),
61            self.query_timeout,
62            self.transaction_timeout,
63        )
64    }
65}
66
67/// Builder for constructing a [`SessionTemplate`].
68pub struct SessionTemplateBuilder {
69    db: Arc<UniInner>,
70    params: HashMap<String, Value>,
71    rule_registry: LocyRuleRegistry,
72    hooks: HashMap<String, Arc<dyn SessionHook>>,
73    query_timeout: Option<Duration>,
74    transaction_timeout: Option<Duration>,
75}
76
77impl SessionTemplateBuilder {
78    pub(crate) fn new(db: Arc<UniInner>) -> Self {
79        // Start from the global rule registry
80        let global_registry = db.locy_rule_registry.read().unwrap();
81        let registry = global_registry.clone();
82        drop(global_registry);
83
84        Self {
85            db,
86            params: HashMap::new(),
87            rule_registry: registry,
88            hooks: HashMap::new(),
89            query_timeout: None,
90            transaction_timeout: None,
91        }
92    }
93
94    /// Bind a parameter that all sessions created from this template will inherit.
95    pub fn param<K: Into<String>, V: Into<Value>>(mut self, key: K, value: V) -> Self {
96        self.params.insert(key.into(), value.into());
97        self
98    }
99
100    /// Pre-compile Locy rules into the template.
101    ///
102    /// Rules are compiled eagerly so that `create()` only needs a cheap clone.
103    pub fn rules(mut self, program: &str) -> Result<Self> {
104        let temp_registry = Arc::new(std::sync::RwLock::new(self.rule_registry.clone()));
105        impl_locy::register_rules_on_registry(&temp_registry, program)?;
106        self.rule_registry = temp_registry.read().unwrap().clone();
107        Ok(self)
108    }
109
110    /// Attach a named hook that all sessions created from this template will inherit.
111    pub fn hook(mut self, name: impl Into<String>, hook: impl SessionHook + 'static) -> Self {
112        self.hooks.insert(name.into(), Arc::new(hook));
113        self
114    }
115
116    /// Set the default query timeout for sessions created from this template.
117    pub fn query_timeout(mut self, duration: Duration) -> Self {
118        self.query_timeout = Some(duration);
119        self
120    }
121
122    /// Set the default transaction timeout for sessions created from this template.
123    pub fn transaction_timeout(mut self, duration: Duration) -> Self {
124        self.transaction_timeout = Some(duration);
125        self
126    }
127
128    /// Build the session template.
129    pub fn build(self) -> Result<SessionTemplate> {
130        Ok(SessionTemplate {
131            db: self.db,
132            params: self.params,
133            rule_registry: self.rule_registry,
134            hooks: self.hooks,
135            query_timeout: self.query_timeout,
136            transaction_timeout: self.transaction_timeout,
137        })
138    }
139}