Skip to main content

chronon_core/
router.rs

1//! Named [`SchedulerStore`] registration at host boot.
2//!
3//! Register one or more backends under logical names before constructing
4//! `Chronon` in `chronon-runtime`. Use [`StoreRouter::register_global`] with
5//! [`DEFAULT_STORE_NAME`] for single-store setups.
6
7use std::collections::HashMap;
8use std::sync::{Arc, OnceLock, RwLock};
9
10use crate::error::{ChrononError, Result};
11use crate::store::SchedulerStore;
12
13/// Default logical store name when hosts register a single backend.
14pub const DEFAULT_STORE_NAME: &str = "default";
15
16static GLOBAL_ROUTER: OnceLock<RwLock<StoreRouter>> = OnceLock::new();
17
18fn global_router() -> &'static RwLock<StoreRouter> {
19    GLOBAL_ROUTER.get_or_init(|| RwLock::new(StoreRouter::new()))
20}
21
22/// Registers named [`SchedulerStore`] backends at host boot.
23///
24/// Use for multi-store hosts or a single default via [`DEFAULT_STORE_NAME`]. Typical Mode 1
25/// flow:
26///
27/// 1. [`Self::register_global`] (or `install_default_mem_store` from the mem backend).
28/// 2. `ChrononBuilder::scheduler_store_from_global()`.
29///
30/// Prefer passing a store directly to `ChrononBuilder::scheduler_store` in Mode 2/3 when each
31/// binary already shares connection URLs — the global router is optional convenience for
32/// single-process boots, not a substitute for a shared durable database.
33///
34/// Thread-safe when accessed through [`Self::register_global`] / [`default_store_from_global`];
35/// direct mutation requires exclusive access to the router instance.
36///
37/// # Examples
38///
39/// ```
40/// # use std::sync::Arc;
41/// # use chronon_core::{SchedulerStore, StoreRouter, DEFAULT_STORE_NAME};
42/// # fn demo(store: Arc<dyn SchedulerStore>) {
43/// StoreRouter::register_global(DEFAULT_STORE_NAME, store);
44/// # }
45/// ```
46///
47/// Runnable end-to-end: `cargo run -p uf-chronon --example store_router_boot --features mem`.
48#[derive(Default)]
49pub struct StoreRouter {
50    stores: HashMap<String, Arc<dyn SchedulerStore>>,
51}
52
53impl StoreRouter {
54    /// Create an empty router (no stores registered).
55    pub fn new() -> Self {
56        Self {
57            stores: HashMap::new(),
58        }
59    }
60
61    /// Register a store under a logical name (overwrites any previous entry).
62    pub fn register(&mut self, name: impl Into<String>, store: Arc<dyn SchedulerStore>) {
63        self.stores.insert(name.into(), store);
64    }
65
66    /// Resolve a registered store by name.
67    pub fn get(&self, name: &str) -> Option<Arc<dyn SchedulerStore>> {
68        self.stores.get(name).cloned()
69    }
70
71    /// Replace the process-global router (typically once at startup).
72    ///
73    /// Subsequent calls are ignored; the first successful install wins.
74    pub fn install_global(router: Self) {
75        let _ = GLOBAL_ROUTER.set(RwLock::new(router));
76    }
77
78    /// Register a store on the process-global router.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// # use std::sync::Arc;
84    /// # use chronon_core::{SchedulerStore, StoreRouter, DEFAULT_STORE_NAME};
85    /// # fn demo(store: Arc<dyn SchedulerStore>) {
86    /// StoreRouter::register_global(DEFAULT_STORE_NAME, store);
87    /// # }
88    /// ```
89    ///
90    /// Panics if the global router lock is poisoned.
91    pub fn register_global(name: impl Into<String>, store: Arc<dyn SchedulerStore>) {
92        global_router()
93            .write()
94            .expect("StoreRouter lock poisoned")
95            .register(name, store);
96    }
97}
98
99/// Resolves the default store from the global router.
100///
101/// Returns [`ChrononError::StorageError`] when no store is registered under
102/// [`DEFAULT_STORE_NAME`].
103pub fn default_store_from_global() -> Result<Arc<dyn SchedulerStore>> {
104    global_router()
105        .read()
106        .expect("StoreRouter lock poisoned")
107        .get(DEFAULT_STORE_NAME)
108        .ok_or_else(|| ChrononError::StorageError("no default SchedulerStore registered".into()))
109}