Skip to main content

runledger_postgres/jobs/
types.rs

1use chrono::{DateTime, Utc};
2use runledger_core::jobs::{
3    JobEventType, JobFailureKind, JobStage, JobStatus, JobType, JobTypeName,
4};
5use serde_json::Value;
6use sqlx::types::Uuid;
7
8#[derive(Debug, Clone)]
9pub struct JobDefinitionUpsert<'a> {
10    pub job_type: JobType<'a>,
11    pub version: i32,
12    pub max_attempts: i32,
13    pub default_timeout_seconds: i32,
14    pub default_priority: i32,
15    pub is_enabled: bool,
16}
17
18#[derive(Debug, Clone)]
19pub struct JobDefinitionRecord {
20    pub job_type: JobTypeName,
21    pub version: i32,
22    pub max_attempts: i32,
23    pub default_timeout_seconds: i32,
24    pub default_priority: i32,
25    pub is_enabled: bool,
26    pub created_at: DateTime<Utc>,
27    pub updated_at: DateTime<Utc>,
28}
29
30/// Schedule row that blocks a job-definition catalog sync.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct JobScheduleJobTypeReference {
33    /// Active schedule name.
34    pub schedule_name: String,
35    /// Job type referenced by the active schedule.
36    pub job_type: JobTypeName,
37}
38
39#[derive(Debug, Clone)]
40pub struct JobDefinitionListFilter<'a> {
41    /// Admin list query input used for escaped `ILIKE` substring matching, not a canonical
42    /// persisted identifier boundary.
43    pub job_type: Option<&'a str>,
44    pub limit: i64,
45    pub offset: i64,
46}
47
48#[derive(Debug, Clone)]
49pub struct JobDefinitionUpdate {
50    pub max_attempts: Option<i32>,
51    pub default_timeout_seconds: Option<i32>,
52    pub default_priority: Option<i32>,
53    pub is_enabled: Option<bool>,
54}
55
56#[derive(Debug, Clone)]
57pub struct JobRuntimeConfigUpsert<'a> {
58    pub job_type: JobType<'a>,
59    pub schema_version: i32,
60    pub config: &'a Value,
61    pub updated_by_user_id: Option<Uuid>,
62}
63
64#[derive(Debug, Clone)]
65pub struct JobRuntimeConfigRecord {
66    pub job_type: JobTypeName,
67    pub schema_version: i32,
68    pub config: Value,
69    pub updated_by_user_id: Option<Uuid>,
70    pub created_at: DateTime<Utc>,
71    pub updated_at: DateTime<Utc>,
72}
73
74#[derive(Debug, Clone)]
75pub struct JobEnqueue<'a> {
76    pub job_type: JobType<'a>,
77    pub organization_id: Option<Uuid>,
78    pub payload: &'a Value,
79    pub priority: Option<i32>,
80    pub max_attempts: Option<i32>,
81    pub timeout_seconds: Option<i32>,
82    /// For keyed enqueues, this value is part of the stored idempotency request
83    /// snapshot. Retries must pass the same scheduled time as the original
84    /// enqueue instead of recomputing a fresh timestamp.
85    pub next_run_at: Option<DateTime<Utc>>,
86    pub idempotency_key: Option<&'a str>,
87    pub stage: Option<JobStage>,
88}
89
90#[derive(Debug, Clone)]
91pub struct JobScheduleRecord {
92    /// Stable schedule row identifier.
93    pub id: Uuid,
94    /// Unique schedule name.
95    pub name: String,
96    /// Job type enqueued whenever the schedule fires.
97    pub job_type: JobTypeName,
98    /// Optional organization scope copied into jobs created by this schedule.
99    pub organization_id: Option<Uuid>,
100    /// JSON payload template copied into each scheduled job before runtime
101    /// schedule metadata is merged.
102    pub payload_template: Value,
103    /// UTC cron expression used by the runtime scheduler.
104    pub cron_expr: String,
105    /// Whether the runtime scheduler may claim this schedule.
106    ///
107    /// Schedule upserts preserve this value for existing rows; use
108    /// `set_job_schedule_active` to pause or resume a schedule intentionally.
109    pub is_active: bool,
110    /// Maximum deterministic jitter, in seconds, applied when computing the next
111    /// fire cursor.
112    pub max_jitter_seconds: i32,
113    /// Next UTC instant at which this schedule is due for materialization.
114    pub next_fire_at: DateTime<Utc>,
115}
116
117/// Input for creating or updating a cron-backed job schedule.
118///
119/// Schedules are keyed by `name`. Updating an existing schedule refreshes the
120/// stored job type, payload template, cron expression, and jitter, while leaving
121/// scheduler-managed state intact. `organization_id` and `is_active` apply only
122/// when a new schedule row is inserted. `next_fire_at` applies on insert and
123/// when the cron expression changes.
124///
125/// Cron expressions are interpreted in UTC and must be accepted by
126/// `cron::Schedule::from_str`, the same parser used by `runledger-runtime` when
127/// materializing due schedules. The upsert validator rejects blank or padded
128/// schedule names, blank or padded cron expressions, invalid cron expressions,
129/// negative jitter, and jitter above 86,400 seconds.
130///
131/// This input does not encode a compile-time job catalog. The PostgreSQL schema
132/// requires a matching job-definition row for `job_type`, but this API does not
133/// prove that a worker process has registered a runtime handler for that job
134/// type.
135#[derive(Debug, Clone)]
136pub struct JobScheduleUpsert<'a> {
137    /// Stable unique schedule name without surrounding whitespace.
138    pub name: &'a str,
139    /// Job type to enqueue whenever the schedule fires.
140    pub job_type: JobType<'a>,
141    /// Optional organization scope for enqueued jobs on first insert.
142    pub organization_id: Option<Uuid>,
143    /// JSON payload copied into each job created by the scheduler.
144    pub payload_template: &'a Value,
145    /// UTC cron expression without surrounding whitespace, validated on upsert
146    /// and parsed again when the schedule fires.
147    pub cron_expr: &'a str,
148    /// Whether the runtime scheduler should claim this schedule on first insert.
149    pub is_active: bool,
150    /// Initial fire cursor for the scheduler, also used when changing cron syntax.
151    pub next_fire_at: DateTime<Utc>,
152    /// Maximum deterministic jitter applied when materializing a due schedule,
153    /// capped at 86,400 seconds.
154    pub max_jitter_seconds: i32,
155}
156
157#[derive(Debug, Clone)]
158pub struct JobQueueRecord {
159    pub id: Uuid,
160    pub job_type: JobTypeName,
161    pub organization_id: Option<Uuid>,
162    pub payload: Value,
163    pub status: JobStatus,
164    pub priority: i32,
165    pub run_number: i32,
166    pub attempt: i32,
167    pub max_attempts: i32,
168    pub timeout_seconds: i32,
169    pub next_run_at: DateTime<Utc>,
170    pub lease_expires_at: Option<DateTime<Utc>>,
171    pub last_heartbeat_at: Option<DateTime<Utc>>,
172    pub worker_id: Option<String>,
173    pub started_at: Option<DateTime<Utc>>,
174    pub finished_at: Option<DateTime<Utc>>,
175    pub stage: JobStage,
176    pub progress_done: Option<i64>,
177    pub progress_total: Option<i64>,
178    pub progress_pct: Option<f64>,
179    pub checkpoint: Option<Value>,
180    pub idempotency_key: Option<String>,
181    pub status_reason: Option<String>,
182    pub last_error_code: Option<String>,
183    pub last_error_message: Option<String>,
184    pub created_at: DateTime<Utc>,
185    pub updated_at: DateTime<Utc>,
186}
187
188#[derive(Debug, Clone)]
189pub struct JobEventRecord {
190    pub id: i64,
191    pub job_id: Uuid,
192    pub run_number: i32,
193    pub attempt: Option<i32>,
194    pub event_type: JobEventType,
195    pub stage: Option<JobStage>,
196    pub progress_done: Option<i64>,
197    pub progress_total: Option<i64>,
198    pub payload: Value,
199    pub occurred_at: DateTime<Utc>,
200}
201
202#[derive(Debug, Clone)]
203pub struct ReapedTerminalLeaseRecord {
204    pub job_id: Uuid,
205    pub job_type: JobTypeName,
206    pub organization_id: Option<Uuid>,
207    pub run_number: i32,
208    pub attempt: i32,
209    pub payload: Value,
210}
211
212#[derive(Debug, Clone)]
213pub struct ReapExpiredLeasesResult {
214    pub processed: i64,
215    pub terminal_dead_lettered: Vec<ReapedTerminalLeaseRecord>,
216}
217
218#[derive(Debug, Clone)]
219pub struct JobMetricsRecord {
220    pub job_type: JobTypeName,
221    pub pending_count: i64,
222    pub leased_count: i64,
223    pub stale_leases: i64,
224    pub succeeded_24h: i64,
225    pub retryable_24h: i64,
226    pub terminal_24h: i64,
227    pub panicked_24h: i64,
228    pub timeout_24h: i64,
229    pub dead_lettered_24h: i64,
230    pub p50_duration_ms_24h: Option<f64>,
231    pub p95_duration_ms_24h: Option<f64>,
232}
233
234#[derive(Debug, Clone)]
235pub struct JobLogRecord {
236    pub id: i64,
237    pub job_id: Uuid,
238    pub run_number: i32,
239    pub attempt: Option<i32>,
240    pub level: String,
241    pub message: String,
242    pub payload: Value,
243    pub occurred_at: DateTime<Utc>,
244}
245
246#[derive(Debug, Clone)]
247pub struct JobLogRecordInput {
248    pub job_id: Uuid,
249    pub run_number: i32,
250    pub attempt: Option<i32>,
251    pub level: String,
252    pub message: String,
253    pub payload: Value,
254}
255
256#[derive(Debug, Clone)]
257pub struct JobProgressUpdate<'a> {
258    pub stage: Option<JobStage>,
259    pub progress_done: Option<i64>,
260    pub progress_total: Option<i64>,
261    pub checkpoint: Option<&'a Value>,
262}
263
264#[derive(Debug, Clone)]
265pub struct JobFailureUpdate<'a> {
266    pub kind: JobFailureKind,
267    pub code: &'a str,
268    pub message: &'a str,
269    pub retry_delay_ms: Option<i32>,
270}
271
272#[derive(Debug, Clone)]
273pub struct JobListFilter<'a> {
274    pub organization_id: Option<Uuid>,
275    pub status: Option<JobStatus>,
276    /// Admin list query input used for `ILIKE` substring matching, not a canonical persisted
277    /// identifier boundary.
278    pub job_type: Option<&'a str>,
279    pub limit: i64,
280    pub offset: i64,
281}
282
283#[derive(Debug, Clone)]
284pub struct JobRuntimeConfigListFilter<'a> {
285    /// Admin query filter string used for listing/runtime-config lookup filters, not a canonical
286    /// persisted identifier boundary.
287    pub job_type: Option<&'a str>,
288    pub limit: i64,
289    pub offset: i64,
290}