ferro_queue/lib.rs
1//! # Ferro Queue
2//!
3//! Background job queue system for the Ferro framework.
4//!
5//! Provides a Laravel-inspired queue system backed by the application database:
6//! - SQLite (`BEGIN IMMEDIATE`) and Postgres (`FOR UPDATE SKIP LOCKED`) atomic claim
7//! - Job delays, retries with full-jitter exponential backoff, and idempotency keys
8//! - Multiple named queues processed in priority order
9//! - Panic-isolated worker loop with SIGTERM graceful shutdown
10//! - Tenant-scoped job execution
11//!
12//! ## Example
13//!
14//! ```rust,ignore
15//! use ferro_queue::{Job, Queue, QueueConfig, WorkerLoop, WorkerConfig, Queueable};
16//! use serde::{Deserialize, Serialize};
17//!
18//! #[derive(Debug, Clone, Serialize, Deserialize)]
19//! struct SendEmail {
20//! to: String,
21//! subject: String,
22//! }
23//!
24//! #[async_trait::async_trait]
25//! impl Job for SendEmail {
26//! async fn handle(&self) -> Result<(), ferro_queue::Error> {
27//! println!("Sending email to {}: {}", self.to, self.subject);
28//! Ok(())
29//! }
30//! }
31//!
32//! // Initialise the queue at application start (once):
33//! // Queue::init(db_connection).await?;
34//!
35//! // Dispatch a job (sync mode by default; set QUEUE_CONNECTION=db for background):
36//! SendEmail { to: "user@example.com".into(), subject: "Hello".into() }
37//! .dispatch()
38//! .await?;
39//!
40//! // Dispatch with delay
41//! SendEmail { to: "user@example.com".into(), subject: "Reminder".into() }
42//! .delay(std::time::Duration::from_secs(60))
43//! .on_queue("emails")
44//! .dispatch()
45//! .await?;
46//! ```
47
48mod config;
49mod db;
50mod dispatcher;
51mod error;
52mod job;
53mod migration;
54mod worker;
55
56pub use config::QueueConfig;
57pub use db::{
58 claim, delete_job, enqueue, fail_job, get_delayed_jobs, get_failed_jobs, get_pending_jobs,
59 get_stats, reap_startup_claims, reaper, release_job, requeue_claimed_by, FailedJobInfo,
60 JobInfo, JobRow, JobState, Queue, QueueStats, SingleQueueStats,
61};
62pub use dispatcher::{
63 dispatch, dispatch_later, dispatch_to, register_tenant_capture_hook, PendingDispatch,
64};
65pub use error::Error;
66pub use job::{Job, JobPayload};
67pub use migration::CreateJobsTable;
68pub use worker::{TenantScopeProvider, Worker, WorkerConfig, WorkerLoop};
69
70/// Re-export async_trait for convenience
71pub use async_trait::async_trait;
72
73/// Trait for types that can be dispatched to a queue.
74pub trait Queueable: Job + serde::Serialize + serde::de::DeserializeOwned {
75 /// Create a pending dispatch for this job.
76 fn dispatch(self) -> PendingDispatch<Self>
77 where
78 Self: Sized,
79 {
80 PendingDispatch::new(self)
81 }
82
83 /// Dispatch this job with a delay.
84 fn delay(self, duration: std::time::Duration) -> PendingDispatch<Self>
85 where
86 Self: Sized,
87 {
88 PendingDispatch::new(self).delay(duration)
89 }
90
91 /// Dispatch this job to a specific queue.
92 fn on_queue(self, queue: &'static str) -> PendingDispatch<Self>
93 where
94 Self: Sized,
95 {
96 PendingDispatch::new(self).on_queue(queue)
97 }
98}
99
100/// Blanket implementation for all types that implement Job + Serialize + DeserializeOwned.
101impl<T> Queueable for T where T: Job + serde::Serialize + serde::de::DeserializeOwned {}