Skip to main content

cja/
lib.rs

1#![doc(html_favicon_url = "https://cja.app/docs/favicon.svg")]
2//! # CJA — Cron, Jobs and Axum
3//!
4//! > **ALPHA**: CJA is under active development. APIs may change between releases.
5//!
6//! A Rust web framework that combines background job processing, cron scheduling,
7//! and an HTTP server built on [Axum](https://docs.rs/axum).
8//!
9//! ## Overview
10//!
11//! CJA provides three integrated subsystems:
12//!
13//! - **[`jobs`]** — PostgreSQL-backed background job queue with automatic retries,
14//!   exponential backoff, dead letter queue, and graceful shutdown
15//! - **[`cron`]** — Scheduled task execution with both interval-based and cron expression
16//!   scheduling, timezone support
17//! - **[`server`]** — HTTP server on Axum with encrypted cookie management, database-backed
18//!   sessions, and zero-downtime reload via systemfd
19//!
20//! All three share a single [`app_state::AppState`] and coordinate shutdown via
21//! [`tokio_util::sync::CancellationToken`].
22//!
23//! ## Getting Started
24//!
25//! Add CJA to your project:
26//!
27//! ```bash
28//! cargo add cja
29//! ```
30//!
31//! ### 1. Implement `AppState`
32//!
33//! ```rust
34//! use cja::app_state::AppState;
35//! use cja::server::cookies::CookieKey;
36//!
37//! #[derive(Clone)]
38//! struct MyAppState {
39//!     db: sqlx::PgPool,
40//!     cookie_key: CookieKey,
41//! }
42//!
43//! impl AppState for MyAppState {
44//!     fn version(&self) -> &str { "1.0.0" }
45//!     fn db(&self) -> &sqlx::PgPool { &self.db }
46//!     fn cookie_key(&self) -> &CookieKey { &self.cookie_key }
47//! }
48//! ```
49//!
50//! [`app_state::AppState`] is cloned per request — wrap expensive resources in `Arc`.
51//!
52//! ### 2. Set Up the Database
53//!
54//! CJA requires `PostgreSQL`. Set `DATABASE_URL` and run migrations on startup:
55//!
56//! ```rust,ignore
57//! let pool = sqlx::postgres::PgPoolOptions::new()
58//!     .max_connections(5)
59//!     .connect(&std::env::var("DATABASE_URL")?)
60//!     .await?;
61//!
62//! // Run CJA's built-in migrations (jobs, crons, sessions, dead_letter_jobs)
63//! cja::db::run_migrations(&pool).await?;
64//! ```
65//!
66//! ### 3. Wire Up `main()`
67//!
68//! ```rust,ignore
69//! use cja::setup::{setup_sentry, setup_tracing};
70//! use cja::tasks::{ShutdownBudget, Supervisor};
71//!
72//! fn main() -> cja::Result<()> {
73//!     let _sentry_guard = setup_sentry();
74//!     tokio::runtime::Builder::new_multi_thread()
75//!         .enable_all()
76//!         .build()?
77//!         .block_on(run())
78//! }
79//!
80//! async fn run() -> cja::Result<()> {
81//!     // Keep the Eyes handle alive for graceful shutdown
82//!     let _eyes_handle = setup_tracing("my-app")?;
83//!
84//!     let app_state = MyAppState { /* ... */ };
85//!
86//!     // Registers SIGTERM/SIGINT and owns the shutdown sequence
87//!     let mut supervisor = Supervisor::new(ShutdownBudget::from_env())?;
88//!     let shutdown = supervisor.shutdown_token();
89//!
90//!     // HTTP server: stops accepting connections when shutdown starts
91//!     supervisor.spawn("server",
92//!         cja::server::run_server_until(
93//!             routes(app_state.clone()),
94//!             shutdown.clone().cancelled_owned(),
95//!         )
96//!     );
97//!
98//!     // Job worker: stops claiming jobs, drains the in-flight one, then
99//!     // releases its lock so another instance picks it up immediately
100//!     supervisor.spawn("jobs",
101//!         cja::jobs::worker::job_worker(
102//!             app_state.clone(),
103//!             Jobs,  // your job registry (see jobs module docs)
104//!             std::time::Duration::from_secs(60),
105//!             cja::jobs::DEFAULT_MAX_RETRIES,
106//!             shutdown.clone(),
107//!             Default::default(),
108//!         )
109//!     );
110//!
111//!     // Returns Ok after a signal, Err if a task died on its own
112//!     supervisor.run().await
113//! }
114//! ```
115//!
116//! See `crates/cja.app/` in the repository for a complete working example.
117//!
118//! ## Configuration
119//!
120//! | Variable | Required | Default | Description |
121//! |----------|----------|---------|-------------|
122//! | `DATABASE_URL` | Yes | — | `PostgreSQL` connection string |
123//! | `PORT` | No | `3000` | HTTP server port |
124//! | `COOKIE_KEY` | No | Auto-generated | Base64-encoded 64-byte cookie encryption key. **Must be set in production** for session persistence across restarts. |
125//! | `SERVER_DISABLED` | No | `false` | Set to `"true"` to skip starting the HTTP server |
126//! | `JOBS_DISABLED` | No | `false` | Set to `"true"` to skip starting the job worker |
127//! | `CRON_DISABLED` | No | `false` | Set to `"true"` to skip starting the cron worker |
128//! | `RUST_LOG` | No | `info` | Tracing filter directive |
129//! | `CJA_SHUTDOWN_JOB_DRAIN_SECS` | No | `2` | How long an in-flight job may run after a shutdown signal before it is dropped and unlocked |
130//! | `CJA_SHUTDOWN_EXIT_GRACE_SECS` | No | `2` | Extra time for tasks to exit after the job drain. Drain + grace must fit the platform's kill window (Cloud Run 10s fixed, Fly 5s default) |
131//! | `JSON_LOGS` | No | — | If set, outputs JSON logs instead of tree format |
132//! | `SENTRY_DSN` | No | — | Enables Sentry error tracking |
133//! | `HONEYCOMB_API_KEY` | No | — | Enables OpenTelemetry export to Honeycomb |
134//! | `EYES_ORG_ID` | No | — | Eyes tracing org ID (both `ORG_ID` and `APP_ID` required) |
135//! | `EYES_APP_ID` | No | — | Eyes tracing app ID |
136//! | `EYES_URL` | No | `https://eyes.coreyja.com` | Eyes server URL |
137//!
138//! ## Feature Flags
139//!
140//! ```toml
141//! [features]
142//! default = ["cron", "jobs"]
143//! cron = ["jobs"]           # cron depends on jobs
144//! jobs = []
145//! testing = []              # enables mock OAuth for tests
146//! ```
147//!
148//! Disabling `jobs` at compile time also disables `cron`.
149//!
150//! ## Re-exports
151//!
152//! CJA re-exports key dependencies so you don't need to add them directly:
153//!
154//! ```rust
155//! use cja::eyes_subscriber;
156//! use cja::sqlx;
157//! use cja::uuid;
158//! use cja::color_eyre;
159//! use cja::maud;
160//! use cja::chrono;
161//! use cja::chrono_tz;
162//! ```
163
164pub use eyes_subscriber;
165pub mod assets;
166pub use sqlx;
167pub use uuid;
168
169#[cfg(feature = "cron")]
170pub mod cron;
171#[cfg(feature = "jobs")]
172pub mod eyes_manifest;
173#[cfg(feature = "jobs")]
174pub mod jobs;
175pub mod server;
176#[cfg(feature = "testing")]
177pub mod testing;
178
179pub mod app_state;
180pub mod setup;
181
182pub use color_eyre;
183pub use color_eyre::Result;
184
185pub use maud;
186
187pub mod db;
188pub mod tasks;
189
190pub use chrono;
191pub use chrono_tz;