chronon_runtime/
builder.rs1use std::sync::Arc;
4
5use chronon_core::context::{ContextFactory, NoOpContextFactory};
6use chronon_core::error::{ChrononError, Result};
7use chronon_core::store::SchedulerStore;
8use chronon_executor::{Executor, ScriptRegistry};
9use chronon_scheduler::{Scheduler, SchedulerConfig};
10use chronon_telemetry::{NoOpSink, TelemetrySink};
11use tokio::sync::{mpsc, Notify};
12
13#[derive(Debug, Clone, PartialEq, Eq, Default)]
17pub enum DeploymentShape {
18 #[default]
20 Embedded,
21 CoordinatorOnly,
23 Worker(String),
25 RemoteClient(String),
27}
28
29pub struct ChrononBuilder {
50 store: Option<Arc<dyn SchedulerStore>>,
51 context_factory: Option<Arc<dyn ContextFactory>>,
52 telemetry: Option<Arc<dyn TelemetrySink>>,
53 registry: Option<Arc<ScriptRegistry>>,
54 deployment: DeploymentShape,
55 auto_registry: bool,
56 tick_interval_ms: u64,
57 instance_id: Option<String>,
58}
59
60impl ChrononBuilder {
61 pub fn new() -> Self {
63 Self {
64 store: None,
65 context_factory: None,
66 telemetry: None,
67 registry: None,
68 deployment: DeploymentShape::Embedded,
69 auto_registry: false,
70 tick_interval_ms: chronon_scheduler::tick_interval_ms_from_env(),
71 instance_id: None,
72 }
73 }
74
75 pub fn scheduler_store(mut self, store: Arc<dyn SchedulerStore>) -> Self {
77 self.store = Some(store);
78 self
79 }
80
81 pub fn scheduler_store_from_global(mut self) -> Result<Self> {
83 self.store = Some(chronon_core::default_store_from_global()?);
84 Ok(self)
85 }
86
87 pub fn context_factory(mut self, factory: Arc<dyn ContextFactory>) -> Self {
89 self.context_factory = Some(factory);
90 self
91 }
92
93 pub fn telemetry_sink(mut self, sink: Arc<dyn TelemetrySink>) -> Self {
95 self.telemetry = Some(sink);
96 self
97 }
98
99 pub fn script_registry(mut self, registry: Arc<ScriptRegistry>) -> Self {
101 self.registry = Some(registry);
102 self
103 }
104
105 pub fn instance_id(mut self, id: impl Into<String>) -> Self {
107 self.instance_id = Some(id.into());
108 self
109 }
110
111 pub fn embedded(mut self) -> Self {
113 self.deployment = DeploymentShape::Embedded;
114 self
115 }
116
117 pub fn coordinator_only(mut self) -> Self {
119 self.deployment = DeploymentShape::CoordinatorOnly;
120 self
121 }
122
123 pub fn worker(mut self, pool_id: impl Into<String>) -> Self {
125 self.deployment = DeploymentShape::Worker(pool_id.into());
126 self
127 }
128
129 pub fn remote_coordinator(mut self, base_url: impl Into<String>) -> Self {
131 self.deployment = DeploymentShape::RemoteClient(base_url.into());
132 self
133 }
134
135 pub fn auto_registry(mut self) -> Self {
137 self.auto_registry = true;
138 self
139 }
140
141 pub fn tick_interval_ms(mut self, ms: u64) -> Self {
143 self.tick_interval_ms = ms;
144 self
145 }
146
147 pub fn build(self) -> Result<super::Chronon> {
149 let store = self
150 .store
151 .ok_or_else(|| ChrononError::Internal("scheduler_store is required".into()))?;
152 let context_factory = self
153 .context_factory
154 .unwrap_or_else(|| Arc::new(NoOpContextFactory));
155 let telemetry = self
156 .telemetry
157 .unwrap_or_else(|| Arc::new(NoOpSink) as Arc<dyn TelemetrySink>);
158 let registry = match self.registry {
159 Some(registry) => registry,
160 None if self.auto_registry => Arc::new(ScriptRegistry::from_inventory()),
161 None => Arc::new(ScriptRegistry::new()),
162 };
163
164 let embedded_partitions = matches!(self.deployment, DeploymentShape::Embedded);
165 let instance_id = self
166 .instance_id
167 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
168
169 let scheduler = Arc::new(Scheduler::new(
170 SchedulerConfig {
171 tick_interval_ms: self.tick_interval_ms,
172 instance_id,
173 num_partitions: chronon_scheduler::num_partitions_from_env(),
174 embedded: embedded_partitions,
175 },
176 store.clone(),
177 telemetry.clone(),
178 ));
179
180 let (event_tx, event_rx) = mpsc::unbounded_channel();
181 let executor = Arc::new(Executor::new(
182 registry,
183 context_factory,
184 telemetry,
185 event_tx,
186 ));
187
188 Ok(super::Chronon::new(
189 store,
190 scheduler,
191 executor,
192 self.deployment,
193 Arc::new(Notify::new()),
194 event_rx,
195 ))
196 }
197}
198
199impl Default for ChrononBuilder {
200 fn default() -> Self {
201 Self::new()
202 }
203}
204
205pub fn builder() -> ChrononBuilder {
207 ChrononBuilder::new()
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use chronon_backend_mem::InMemorySchedulerStore;
214 use chronon_telemetry::ConsoleSink;
215
216 #[tokio::test]
217 async fn builder_embedded_compiles() {
218 let store = Arc::new(InMemorySchedulerStore::new());
219 let chronon = ChrononBuilder::new()
220 .scheduler_store(store)
221 .telemetry_sink(Arc::new(ConsoleSink))
222 .embedded()
223 .build()
224 .expect("build");
225 assert_eq!(chronon.deployment, DeploymentShape::Embedded);
226 }
227
228 #[tokio::test]
229 async fn builder_auto_registry_from_inventory() {
230 let store = Arc::new(InMemorySchedulerStore::new());
231 let chronon = ChrononBuilder::new()
232 .scheduler_store(store)
233 .embedded()
234 .auto_registry()
235 .build()
236 .expect("build");
237 let _ = chronon.executor().script_count();
238 }
239
240 #[tokio::test]
241 async fn builder_scheduler_store_from_global() {
242 let _installed = chronon_backend_mem::install_default_mem_store();
243 let chronon = ChrononBuilder::new()
244 .scheduler_store_from_global()
245 .expect("global store")
246 .embedded()
247 .build()
248 .expect("build");
249 assert_eq!(chronon.deployment, DeploymentShape::Embedded);
250 }
251}