runledger-postgres 0.3.0

PostgreSQL persistence layer for the Runledger durable job and workflow system
Documentation
use chrono::{DateTime, Utc};
use runledger_core::jobs::{
    JobEventType, JobFailureKind, JobStage, JobStatus, JobType, JobTypeName,
};
use serde_json::Value;
use sqlx::types::Uuid;

#[derive(Debug, Clone)]
pub struct JobDefinitionUpsert<'a> {
    pub job_type: JobType<'a>,
    pub version: i32,
    pub max_attempts: i32,
    pub default_timeout_seconds: i32,
    pub default_priority: i32,
    pub is_enabled: bool,
}

#[derive(Debug, Clone)]
pub struct JobDefinitionRecord {
    pub job_type: JobTypeName,
    pub version: i32,
    pub max_attempts: i32,
    pub default_timeout_seconds: i32,
    pub default_priority: i32,
    pub is_enabled: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Schedule row that blocks a job-definition catalog sync.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JobScheduleJobTypeReference {
    /// Active schedule name.
    pub schedule_name: String,
    /// Job type referenced by the active schedule.
    pub job_type: JobTypeName,
}

#[derive(Debug, Clone)]
pub struct JobDefinitionListFilter<'a> {
    /// Admin list query input used for escaped `ILIKE` substring matching, not a canonical
    /// persisted identifier boundary.
    pub job_type: Option<&'a str>,
    pub limit: i64,
    pub offset: i64,
}

#[derive(Debug, Clone)]
pub struct JobDefinitionUpdate {
    pub max_attempts: Option<i32>,
    pub default_timeout_seconds: Option<i32>,
    pub default_priority: Option<i32>,
    pub is_enabled: Option<bool>,
}

#[derive(Debug, Clone)]
pub struct JobRuntimeConfigUpsert<'a> {
    pub job_type: JobType<'a>,
    pub schema_version: i32,
    pub config: &'a Value,
    pub updated_by_user_id: Option<Uuid>,
}

#[derive(Debug, Clone)]
pub struct JobRuntimeConfigRecord {
    pub job_type: JobTypeName,
    pub schema_version: i32,
    pub config: Value,
    pub updated_by_user_id: Option<Uuid>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
pub struct JobEnqueue<'a> {
    pub job_type: JobType<'a>,
    pub organization_id: Option<Uuid>,
    pub payload: &'a Value,
    pub priority: Option<i32>,
    pub max_attempts: Option<i32>,
    pub timeout_seconds: Option<i32>,
    /// For keyed enqueues, this value is part of the stored idempotency request
    /// snapshot. Retries must pass the same scheduled time as the original
    /// enqueue instead of recomputing a fresh timestamp.
    pub next_run_at: Option<DateTime<Utc>>,
    pub idempotency_key: Option<&'a str>,
    pub stage: Option<JobStage>,
}

#[derive(Debug, Clone)]
pub struct JobScheduleRecord {
    /// Stable schedule row identifier.
    pub id: Uuid,
    /// Unique schedule name.
    pub name: String,
    /// Job type enqueued whenever the schedule fires.
    pub job_type: JobTypeName,
    /// Optional organization scope copied into jobs created by this schedule.
    pub organization_id: Option<Uuid>,
    /// JSON payload template copied into each scheduled job before runtime
    /// schedule metadata is merged.
    pub payload_template: Value,
    /// UTC cron expression used by the runtime scheduler.
    pub cron_expr: String,
    /// Whether the runtime scheduler may claim this schedule.
    ///
    /// Schedule upserts preserve this value for existing rows; use
    /// `set_job_schedule_active` to pause or resume a schedule intentionally.
    pub is_active: bool,
    /// Maximum deterministic jitter, in seconds, applied when computing the next
    /// fire cursor.
    pub max_jitter_seconds: i32,
    /// Next UTC instant at which this schedule is due for materialization.
    pub next_fire_at: DateTime<Utc>,
}

/// Input for creating or updating a cron-backed job schedule.
///
/// Schedules are keyed by `name`. Updating an existing schedule refreshes the
/// stored job type, payload template, cron expression, and jitter, while leaving
/// scheduler-managed state intact. `organization_id` and `is_active` apply only
/// when a new schedule row is inserted. `next_fire_at` applies on insert and
/// when the cron expression changes.
///
/// Cron expressions are interpreted in UTC and must be accepted by
/// `cron::Schedule::from_str`, the same parser used by `runledger-runtime` when
/// materializing due schedules. The upsert validator rejects blank or padded
/// schedule names, blank or padded cron expressions, invalid cron expressions,
/// negative jitter, and jitter above 86,400 seconds.
///
/// This input does not encode a compile-time job catalog. The PostgreSQL schema
/// requires a matching job-definition row for `job_type`, but this API does not
/// prove that a worker process has registered a runtime handler for that job
/// type.
#[derive(Debug, Clone)]
pub struct JobScheduleUpsert<'a> {
    /// Stable unique schedule name without surrounding whitespace.
    pub name: &'a str,
    /// Job type to enqueue whenever the schedule fires.
    pub job_type: JobType<'a>,
    /// Optional organization scope for enqueued jobs on first insert.
    pub organization_id: Option<Uuid>,
    /// JSON payload copied into each job created by the scheduler.
    pub payload_template: &'a Value,
    /// UTC cron expression without surrounding whitespace, validated on upsert
    /// and parsed again when the schedule fires.
    pub cron_expr: &'a str,
    /// Whether the runtime scheduler should claim this schedule on first insert.
    pub is_active: bool,
    /// Initial fire cursor for the scheduler, also used when changing cron syntax.
    pub next_fire_at: DateTime<Utc>,
    /// Maximum deterministic jitter applied when materializing a due schedule,
    /// capped at 86,400 seconds.
    pub max_jitter_seconds: i32,
}

#[derive(Debug, Clone)]
pub struct JobQueueRecord {
    pub id: Uuid,
    pub job_type: JobTypeName,
    pub organization_id: Option<Uuid>,
    pub payload: Value,
    pub status: JobStatus,
    pub priority: i32,
    pub run_number: i32,
    pub attempt: i32,
    pub max_attempts: i32,
    pub timeout_seconds: i32,
    pub next_run_at: DateTime<Utc>,
    pub lease_expires_at: Option<DateTime<Utc>>,
    pub last_heartbeat_at: Option<DateTime<Utc>>,
    pub worker_id: Option<String>,
    pub started_at: Option<DateTime<Utc>>,
    pub finished_at: Option<DateTime<Utc>>,
    pub stage: JobStage,
    pub progress_done: Option<i64>,
    pub progress_total: Option<i64>,
    pub progress_pct: Option<f64>,
    pub checkpoint: Option<Value>,
    pub idempotency_key: Option<String>,
    pub status_reason: Option<String>,
    pub last_error_code: Option<String>,
    pub last_error_message: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
pub struct JobEventRecord {
    pub id: i64,
    pub job_id: Uuid,
    pub run_number: i32,
    pub attempt: Option<i32>,
    pub event_type: JobEventType,
    pub stage: Option<JobStage>,
    pub progress_done: Option<i64>,
    pub progress_total: Option<i64>,
    pub payload: Value,
    pub occurred_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
pub struct ReapedTerminalLeaseRecord {
    pub job_id: Uuid,
    pub job_type: JobTypeName,
    pub organization_id: Option<Uuid>,
    pub run_number: i32,
    pub attempt: i32,
    pub payload: Value,
}

#[derive(Debug, Clone)]
pub struct ReapExpiredLeasesResult {
    pub processed: i64,
    pub terminal_dead_lettered: Vec<ReapedTerminalLeaseRecord>,
}

#[derive(Debug, Clone)]
pub struct JobMetricsRecord {
    pub job_type: JobTypeName,
    pub pending_count: i64,
    pub leased_count: i64,
    pub stale_leases: i64,
    pub succeeded_24h: i64,
    pub retryable_24h: i64,
    pub terminal_24h: i64,
    pub panicked_24h: i64,
    pub timeout_24h: i64,
    pub dead_lettered_24h: i64,
    pub p50_duration_ms_24h: Option<f64>,
    pub p95_duration_ms_24h: Option<f64>,
}

#[derive(Debug, Clone)]
pub struct JobLogRecord {
    pub id: i64,
    pub job_id: Uuid,
    pub run_number: i32,
    pub attempt: Option<i32>,
    pub level: String,
    pub message: String,
    pub payload: Value,
    pub occurred_at: DateTime<Utc>,
}

#[derive(Debug, Clone)]
pub struct JobLogRecordInput {
    pub job_id: Uuid,
    pub run_number: i32,
    pub attempt: Option<i32>,
    pub level: String,
    pub message: String,
    pub payload: Value,
}

#[derive(Debug, Clone)]
pub struct JobProgressUpdate<'a> {
    pub stage: Option<JobStage>,
    pub progress_done: Option<i64>,
    pub progress_total: Option<i64>,
    pub checkpoint: Option<&'a Value>,
}

#[derive(Debug, Clone)]
pub struct JobFailureUpdate<'a> {
    pub kind: JobFailureKind,
    pub code: &'a str,
    pub message: &'a str,
    pub retry_delay_ms: Option<i32>,
}

#[derive(Debug, Clone)]
pub struct JobListFilter<'a> {
    pub organization_id: Option<Uuid>,
    pub status: Option<JobStatus>,
    /// Admin list query input used for `ILIKE` substring matching, not a canonical persisted
    /// identifier boundary.
    pub job_type: Option<&'a str>,
    pub limit: i64,
    pub offset: i64,
}

#[derive(Debug, Clone)]
pub struct JobRuntimeConfigListFilter<'a> {
    /// Admin query filter string used for listing/runtime-config lookup filters, not a canonical
    /// persisted identifier boundary.
    pub job_type: Option<&'a str>,
    pub limit: i64,
    pub offset: i64,
}