stasis-rs 0.1.0

Durable AI orchestration framework with runtime jobs, lineage, and memory integration
Documentation
use chrono::{DateTime, Utc};
use chrono_tz::Tz;
use cron::Schedule;
use std::str::FromStr;

use crate::domain::errors::{Result, StasisError};

#[derive(Clone, Debug)]
pub struct RecurringDefinition {
    pub id: String,
    pub queue: String,
    pub job_type: String,
    pub payload_template_ref: String,
    pub cron_expr: String,
    pub timezone: String,
    pub jitter_seconds: i64,
    pub enabled: bool,
    pub max_attempts: u32,
    pub next_run_at: DateTime<Utc>,
    pub last_run_at: Option<DateTime<Utc>>,
    pub lease_owner: Option<String>,
    pub lease_expires_at: Option<DateTime<Utc>>,
}

impl RecurringDefinition {
    pub fn compute_next_run_at(&self, from: DateTime<Utc>) -> Result<DateTime<Utc>> {
        let schedule = Schedule::from_str(&self.cron_expr).map_err(|e| {
            StasisError::PortFailure(format!(
                "invalid cron expression for recurring_id={}: {}",
                self.id, e
            ))
        })?;

        let tz: Tz = self.timezone.parse().map_err(|e| {
            StasisError::PortFailure(format!(
                "invalid timezone for recurring_id={}: {}",
                self.id, e
            ))
        })?;

        let local_from = from.with_timezone(&tz);
        let next_local = schedule.after(&local_from).next().ok_or_else(|| {
            StasisError::PortFailure(format!(
                "could not compute next run for recurring_id={}",
                self.id
            ))
        })?;

        Ok(next_local.with_timezone(&Utc))
    }
}