Skip to main content

zeph_scheduler/
lib.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Cron-based periodic task scheduler with `SQLite` persistence.
5//!
6//! `zeph-scheduler` drives time-based work inside the Zeph agent. It supports two
7//! task execution modes:
8//!
9//! - **Periodic** — tasks defined by a cron expression and re-scheduled after each run.
10//! - **One-shot** — tasks that run once at a specific `DateTime<Utc>` and are removed
11//!   on completion.
12//!
13//! # Architecture
14//!
15//! ```text
16//! ┌──────────────────────────────────────┐
17//! │              Scheduler               │
18//! │  tasks: Vec<ScheduledTask>           │
19//! │  handlers: HashMap<kind, TaskHandler>│
20//! │  store: JobStore (SQLite)            │
21//! │  shutdown_rx: watch::Receiver<bool>  │
22//! │  task_rx: mpsc::Receiver<Msg>        │
23//! └───────────┬──────────────────────────┘
24//!             │ tick() every N seconds
25//!             ▼
26//!     for each due task → handler.execute()
27//!             │
28//!             ▼
29//!     store.record_run() / store.mark_done()
30//! ```
31//!
32//! # Quick Start
33//!
34//! ```rust,no_run
35//! use tokio::sync::watch;
36//! use zeph_scheduler::{JobStore, Scheduler, ScheduledTask, TaskKind};
37//!
38//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
39//! // 1. Open the job store.
40//! let store = JobStore::open("sqlite:scheduler.db").await?;
41//!
42//! // 2. Create the scheduler.
43//! let (_shutdown_tx, shutdown_rx) = watch::channel(false);
44//! let (mut scheduler, _msg_tx) = Scheduler::new(store, shutdown_rx);
45//!
46//! // 3. Register a periodic task (every minute).
47//! let task = ScheduledTask::new(
48//!     "health-check",
49//!     "*/1 * * * *",
50//!     TaskKind::HealthCheck,
51//!     serde_json::Value::Null,
52//! )?;
53//! scheduler.add_task(task);
54//!
55//! // 4. Initialise persistence and run.
56//! scheduler.init().await?;
57//! scheduler.run().await;
58//! # Ok(())
59//! # }
60//! ```
61//!
62//! # Cron Expression Format
63//!
64//! The scheduler accepts both **5-field** (`min hour day month weekday`) and
65//! **6-field** (`sec min hour day month weekday`) cron expressions. Five-field
66//! expressions are automatically normalised by prepending `"0 "` (seconds = 0).
67//! Use [`normalize_cron_expr`] directly if you need the canonical form.
68//!
69//! # Shutdown
70//!
71//! Send `true` on the `watch::Sender<bool>` that was passed to [`Scheduler::new`]
72//! to trigger a graceful shutdown. The scheduler loop exits on the next tick.
73//!
74//! # `SQLite` Persistence
75//!
76//! All job definitions and run history are stored in a `SQLite` database managed by
77//! `zeph-db` migrations. Use [`JobStore::open`] to connect or [`JobStore::new`] when
78//! you already hold a [`zeph_db::DbPool`].
79
80#[allow(unused_imports)]
81pub(crate) use zeph_db::sql;
82
83mod error;
84mod handlers;
85mod sanitize;
86mod scheduler;
87mod store;
88mod task;
89pub mod update_check;
90
91#[cfg(all(unix, feature = "daemon"))]
92pub mod daemon;
93#[cfg(all(unix, feature = "daemon"))]
94pub mod pidfile;
95
96pub use error::SchedulerError;
97pub use handlers::CustomTaskHandler;
98pub use sanitize::sanitize_task_prompt;
99pub use scheduler::{Scheduler, SchedulerMessage};
100pub use store::{JobStore, ScheduledTaskInfo, ScheduledTaskRecord};
101pub use task::{
102    ScheduledTask, TaskDescriptor, TaskHandler, TaskKind, TaskMode, normalize_cron_expr,
103};
104
105#[cfg(all(unix, feature = "daemon"))]
106pub use daemon::{
107    DaemonConfig, DaemonStatus, TaskRunSummary, daemon_status, detach_and_run, run_foreground,
108    stop_daemon,
109};
110#[cfg(all(unix, feature = "daemon"))]
111pub use pidfile::PidFile;
112pub use update_check::UpdateCheckHandler;