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/// Thread-safe when accessed through [`Self::register_global`] / [`default_store_from_global`];
25/// direct mutation requires exclusive access to the router instance.
26#[derive(Default)]
27pub struct StoreRouter {
28 stores: HashMap<String, Arc<dyn SchedulerStore>>,
29}
30
31impl StoreRouter {
32 /// Create an empty router (no stores registered).
33 pub fn new() -> Self {
34 Self {
35 stores: HashMap::new(),
36 }
37 }
38
39 /// Register a store under a logical name (overwrites any previous entry).
40 pub fn register(&mut self, name: impl Into<String>, store: Arc<dyn SchedulerStore>) {
41 self.stores.insert(name.into(), store);
42 }
43
44 /// Resolve a registered store by name.
45 pub fn get(&self, name: &str) -> Option<Arc<dyn SchedulerStore>> {
46 self.stores.get(name).cloned()
47 }
48
49 /// Replace the process-global router (typically once at startup).
50 ///
51 /// Subsequent calls are ignored; the first successful install wins.
52 pub fn install_global(router: Self) {
53 let _ = GLOBAL_ROUTER.set(RwLock::new(router));
54 }
55
56 /// Register a store on the process-global router.
57 ///
58 /// # Examples
59 ///
60 /// ```
61 /// # use std::sync::Arc;
62 /// # use chronon_core::{SchedulerStore, StoreRouter, DEFAULT_STORE_NAME};
63 /// # fn demo(store: Arc<dyn SchedulerStore>) {
64 /// StoreRouter::register_global(DEFAULT_STORE_NAME, store);
65 /// # }
66 /// ```
67 ///
68 /// Panics if the global router lock is poisoned.
69 pub fn register_global(name: impl Into<String>, store: Arc<dyn SchedulerStore>) {
70 global_router()
71 .write()
72 .expect("StoreRouter lock poisoned")
73 .register(name, store);
74 }
75}
76
77/// Resolves the default store from the global router.
78///
79/// Returns [`ChrononError::StorageError`] when no store is registered under
80/// [`DEFAULT_STORE_NAME`].
81pub fn default_store_from_global() -> Result<Arc<dyn SchedulerStore>> {
82 global_router()
83 .read()
84 .expect("StoreRouter lock poisoned")
85 .get(DEFAULT_STORE_NAME)
86 .ok_or_else(|| ChrononError::StorageError("no default SchedulerStore registered".into()))
87}