Skip to main content

runledger_postgres/jobs/types/
schedules.rs

1use chrono::{DateTime, Utc};
2use runledger_core::jobs::{JobType, JobTypeName};
3use serde_json::Value;
4use sqlx::types::Uuid;
5
6/// Maximum accepted schedule jitter, in seconds.
7///
8/// The scheduler treats jitter as a deterministic spread applied to future fire
9/// cursors, and the persistence layer rejects larger values.
10pub const JOB_SCHEDULE_MAX_JITTER_SECONDS: i32 = 86_400;
11
12#[derive(Clone, Debug)]
13pub struct JobScheduleRecord {
14    /// Stable schedule row identifier.
15    pub id: Uuid,
16    /// Unique schedule name.
17    pub name: String,
18    /// Job type enqueued whenever the schedule fires.
19    pub job_type: JobTypeName,
20    /// Optional organization scope copied into jobs created by this schedule.
21    pub organization_id: Option<Uuid>,
22    /// JSON payload template copied into each scheduled job before runtime
23    /// schedule metadata is merged.
24    pub payload_template: Value,
25    /// UTC cron expression used by the runtime scheduler.
26    pub cron_expr: String,
27    /// Whether the runtime scheduler may claim this schedule.
28    ///
29    /// Schedule upserts preserve this value for existing rows; use
30    /// `set_job_schedule_active` to pause or resume a schedule intentionally.
31    pub is_active: bool,
32    /// Maximum deterministic jitter, in seconds, applied when computing the next
33    /// fire cursor. Must not exceed [`JOB_SCHEDULE_MAX_JITTER_SECONDS`].
34    pub max_jitter_seconds: i32,
35    /// Next UTC instant at which this schedule is due for materialization.
36    pub next_fire_at: DateTime<Utc>,
37}
38
39/// Input for creating or updating a cron-backed job schedule.
40///
41/// Schedules are keyed by `name`. Updating an existing schedule refreshes the
42/// stored job type, payload template, cron expression, and jitter, while leaving
43/// scheduler-managed state intact. `organization_id` and `is_active` apply only
44/// when a new schedule row is inserted. `next_fire_at` applies on insert and
45/// when the cron expression changes.
46///
47/// Cron expressions are interpreted in UTC and must be accepted by
48/// `cron::Schedule::from_str`, the same parser used by `runledger-runtime` when
49/// materializing due schedules. The upsert validator rejects blank or padded
50/// schedule names, blank or padded cron expressions, invalid cron expressions,
51/// negative jitter, and jitter above [`JOB_SCHEDULE_MAX_JITTER_SECONDS`].
52///
53/// This input does not encode a compile-time job catalog. The PostgreSQL schema
54/// requires a matching job-definition row for `job_type`, but this API does not
55/// prove that a worker process has registered a runtime handler for that job
56/// type.
57#[derive(Clone, Debug)]
58pub struct JobScheduleUpsert<'a> {
59    /// Stable unique schedule name without surrounding whitespace.
60    pub name: &'a str,
61    /// Job type to enqueue whenever the schedule fires.
62    pub job_type: JobType<'a>,
63    /// Optional organization scope for enqueued jobs on first insert.
64    pub organization_id: Option<Uuid>,
65    /// JSON payload copied into each job created by the scheduler.
66    pub payload_template: &'a Value,
67    /// UTC cron expression without surrounding whitespace, validated on upsert
68    /// and parsed again when the schedule fires.
69    pub cron_expr: &'a str,
70    /// Whether the runtime scheduler should claim this schedule on first insert.
71    pub is_active: bool,
72    /// Initial fire cursor for the scheduler, also used when changing cron syntax.
73    pub next_fire_at: DateTime<Utc>,
74    /// Maximum deterministic jitter applied when materializing a due schedule,
75    /// capped at [`JOB_SCHEDULE_MAX_JITTER_SECONDS`].
76    pub max_jitter_seconds: i32,
77}
78
79/// One catalog-owned schedule sync entry.
80#[derive(Clone, Debug)]
81pub struct JobScheduleCatalogSyncEntry<'a> {
82    /// Schedule definition fields to upsert. Unlike plain schedule upserts,
83    /// catalog sync treats `is_active` as the authoritative desired active state
84    /// for both inserts and conflicts.
85    pub upsert: JobScheduleUpsert<'a>,
86}
87
88/// Result returned by [`crate::jobs::sync_catalog_job_schedules_tx`].
89#[derive(Clone, Debug, Eq, PartialEq)]
90#[non_exhaustive]
91pub struct JobScheduleCatalogSyncReport {
92    /// Schedule names upserted and active state applied during this sync.
93    pub synced_schedule_names: Vec<String>,
94}