pub mod memory;
pub mod mock;
pub mod stream;
pub use memory::{MemoryAttempt, MemoryBackend};
pub use mock::{CallRecord, MockBackend};
pub use stream::StreamBackend;
use crate::model::{Job, JobListItem, NewJob};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::pin::Pin;
use uuid::Uuid;
pub type NotificationStream = Pin<Box<dyn futures_core::Stream<Item = ()> + Send>>;
#[async_trait]
pub trait StorageBackend: Send + Sync {
fn as_stream(&self) -> Option<&dyn StreamBackend> {
None
}
async fn run_migrations(&self) -> anyhow::Result<()>;
async fn health_check(&self) -> anyhow::Result<()>;
async fn enqueue(&self, job: NewJob) -> anyhow::Result<Uuid>;
async fn subscribe(&self, queue: &str) -> anyhow::Result<NotificationStream>;
async fn lease_jobs_batch(
&self,
queue: &str,
worker_id: &str,
lease_seconds: i64,
batch_size: i64,
) -> anyhow::Result<Vec<Job>>;
async fn lease_jobs_batch_with_ordering(
&self,
queue: &str,
worker_id: &str,
lease_seconds: i64,
batch_size: i64,
ordering: crate::model::QueueOrdering,
) -> anyhow::Result<Vec<Job>> {
let _ = ordering;
self.lease_jobs_batch(queue, worker_id, lease_seconds, batch_size)
.await
}
async fn reap_expired_locks(&self) -> anyhow::Result<u64>;
async fn start_attempts_batch(
&self,
dataset_ids: &[String],
job_ids: &[Uuid],
worker_id: &str,
) -> anyhow::Result<Vec<(Uuid, Uuid, i32)>>;
async fn mark_succeeded(
&self,
job_id: Uuid,
attempt_id: Uuid,
worker_id: &str,
latency_ms: i32,
) -> anyhow::Result<()>;
async fn mark_succeeded_batch(
&self,
dataset_id: &str,
updates: &[(Uuid, Uuid, i32)],
worker_id: &str,
) -> anyhow::Result<()>;
#[allow(clippy::too_many_arguments)]
async fn reschedule_for_retry(
&self,
job_id: Uuid,
attempt_id: Uuid,
worker_id: &str,
latency_ms: i32,
next_run_at: DateTime<Utc>,
error_code: &str,
error_message: &str,
attempt_no: i32,
) -> anyhow::Result<()>;
#[allow(clippy::too_many_arguments)]
async fn mark_dlq(
&self,
job_id: Uuid,
attempt_id: Uuid,
worker_id: &str,
latency_ms: i32,
reason_code: &str,
error_code: &str,
error_message: &str,
attempt_no: i32,
) -> anyhow::Result<()>;
async fn archive_succeeded_older_than(
&self,
cutoff: DateTime<Utc>,
limit: i64,
) -> anyhow::Result<u64>;
async fn delete_history_for_succeeded_older_than(
&self,
cutoff: DateTime<Utc>,
limit: i64,
) -> anyhow::Result<(u64, u64)>;
async fn perform_maintenance(&self) -> anyhow::Result<()> {
Ok(())
}
async fn extend_lease(
&self,
_job_id: Uuid,
_worker_id: &str,
_lease_seconds: i64,
) -> anyhow::Result<bool> {
Ok(true)
}
async fn get_job(&self, job_id: Uuid) -> anyhow::Result<Option<Job>>;
async fn list_jobs(
&self,
queue: Option<&str>,
status: Option<&str>,
limit: i64,
cursor_created_at: Option<DateTime<Utc>>,
cursor_id: Option<Uuid>,
) -> anyhow::Result<Vec<JobListItem>>;
async fn replay_job(
&self,
job_id: Uuid,
override_queue: Option<&str>,
override_run_at: Option<DateTime<Utc>>,
) -> anyhow::Result<Uuid>;
async fn dequeue_and_lease(
&self,
queue: &str,
worker_id: &str,
lease_seconds: i64,
batch_size: i64,
) -> anyhow::Result<Vec<Job>> {
self.lease_jobs_batch(queue, worker_id, lease_seconds, batch_size)
.await
}
async fn complete_job(
&self,
job_id: Uuid,
attempt_id: Uuid,
worker_id: &str,
latency_ms: i32,
) -> anyhow::Result<()> {
self.mark_succeeded(job_id, attempt_id, worker_id, latency_ms)
.await
}
#[allow(clippy::too_many_arguments)]
async fn retry_job(
&self,
job_id: Uuid,
attempt_id: Uuid,
worker_id: &str,
latency_ms: i32,
next_run_at: DateTime<Utc>,
error_code: &str,
error_message: &str,
attempt_no: i32,
) -> anyhow::Result<()> {
self.reschedule_for_retry(
job_id,
attempt_id,
worker_id,
latency_ms,
next_run_at,
error_code,
error_message,
attempt_no,
)
.await
}
#[allow(clippy::too_many_arguments)]
async fn fail_job(
&self,
job_id: Uuid,
attempt_id: Uuid,
worker_id: &str,
latency_ms: i32,
reason_code: &str,
error_code: &str,
error_message: &str,
attempt_no: i32,
) -> anyhow::Result<()> {
self.mark_dlq(
job_id,
attempt_id,
worker_id,
latency_ms,
reason_code,
error_code,
error_message,
attempt_no,
)
.await
}
}