Skip to content

Zart is in active development — breaking API changes may occur despite our best efforts to keep contracts stable.

Getting Started

Here is a complete durable workflow in Zart. Every step persists its result — if your process dies and restarts, completed steps are skipped and the workflow continues from where it stopped.

use zart::{zart_durable, zart_step};
use zart::prelude::*;
// 1. Define steps as plain async functions
#[zart_step("send-email", retry = "exponential(3, 2s)")]
async fn send_email(email: &str) -> Result<(), StepError> {
mailer.send(email, "Welcome!").await
}
#[zart_step("setup-billing")]
async fn setup_billing(email: &str) -> Result<CustomerId, StepError> {
stripe.create_customer(email).await
}
// 2. Compose steps inside a durable handler — just call and await
#[zart_durable("onboarding")]
async fn onboarding(data: OnboardingData) -> Result<(), TaskError> {
send_email(&data.email).await?; // ✓ persisted, retried on failure
setup_billing(&data.email).await?; // ✓ persisted
Ok(())
}

No context objects to thread through your code. Steps look and feel like plain async functions — they just happen to be durable.

  • Rust 1.75 or later
  • A PostgreSQL 14+ database
  • A tokio runtime
Cargo.toml
[dependencies]
zart = "0.2" # includes zart-macros — #[zart_durable] and #[zart_step] are ready to use
async-trait = "0.1"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
use sqlx::PgPool;
use zart::PgBackend;
let pool = PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
let pg = PgBackend::new(pool);
pg.run_migrations().await?;

This creates the zart_tasks, zart_executions, zart_execution_runs, zart_steps, zart_step_attempts, and zart_pause_rules tables. Migrations are idempotent — safe to run on every deploy.

You can also run them from the CLI:

Terminal window
just migrate

Step 3: Register Handlers and Start a Worker

Section titled “Step 3: Register Handlers and Start a Worker”
use zart::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let pool = PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
let pg = zart::PgBackend::new(pool);
pg.run_migrations().await?;
let config = zart::WorkerConfig {
poll_interval: Duration::from_millis(200),
max_tasks_per_poll: 10,
max_concurrent_tasks: 4,
shutdown_timeout: Duration::from_secs(5),
orphan_timeout: Duration::from_secs(30),
..Default::default()
};
let worker = Arc::new(
zart::WorkerBuilder::from_backend(&pg)
.register_durable_task("onboarding", Onboarding)
.config(config)
.build(),
);
let w = worker.clone();
tokio::spawn(async move { w.run().await });
Ok(())
}

The worker polls PostgreSQL for due tasks and dispatches them to registered handlers. Multiple worker instances can run against the same database — SKIP LOCKED ensures each task is processed by exactly one worker.

Call this from anywhere — a web handler, a background task, a test:

let durable = DurableScheduler::from_backend(&pg);
durable
.start_for::<Onboarding>(
"signup-user-42", // idempotency key — safe to call twice
"onboarding",
&OnboardingData { email: "alice@example.com".into() },
)
.await?;

The simplest approach is wait_completion, which blocks until the execution completes and automatically deserializes the result to your output type:

let output: MyOutput = durable
.wait_completion("signup-user-42", Duration::from_secs(60), None)
.await?;
println!("Workflow completed with output: {:?}", output);

No manual serde_json::from_value needed — the type parameter tells Zart exactly what to expect.

You can also combine start and wait into a single call:

let output = durable
.start_and_wait_for::<Onboarding>("signup-user-42", "onboarding", &input, Duration::from_secs(60))
.await?;

The repository ships with runnable examples:

Terminal window
# Start PostgreSQL
docker compose up -d
# Run migrations
just migrate
# Try the sleep example
just example-sleep
# Try the brewery finder
just example-brewery

See the full Examples section for detailed walkthroughs.