Skip to content

Rust API Overview

Zart’s Rust API is organized into three layers that build on each other. You can use all three together, or drop down to a lower layer when you need more control.

┌─────────────────────────────────────────────────────────┐
│ Macro Layer #[zart_durable], z_step!, z_wait_event! │
│ (zart-macros) Ergonomic async fn → TaskHandler │
├─────────────────────────────────────────────────────────┤
│ Execution Layer TaskHandler, TaskContext, TaskRegistry │
│ (zart) Durable steps, retries, events, sleep │
├─────────────────────────────────────────────────────────┤
│ Scheduler Layer Scheduler, DurableScheduler, Worker │
│ (zart + backend) PostgreSQL / SQLite / MySQL polling │
└─────────────────────────────────────────────────────────┘

Responsible for persisting and claiming executions. Implements SKIP LOCKED polling so multiple workers can run concurrently without coordination.

TypeRole
SchedulerTrait — poll for due tasks, mark complete/failed
DurableSchedulerTrait — schedule new executions, deliver events
PostgresSchedulerConcrete — PostgreSQL backend
SqliteSchedulerConcrete — SQLite backend
WorkerDrives the poll loop, dispatches to TaskRegistry

Where your workflow logic lives. A TaskHandler receives a TaskContext and calls methods on it to execute durable steps.

TypeRole
TaskHandlerTrait you implement — defines Data, Output, run
TaskContext<S>Passed into run — the API for steps, sleep, events
TaskRegistryMaps task name strings to TaskHandler instances
RetryConfigPer-step retry policy (none / fixed / exponential)
TaskErrorError type for workflow failures

Optional. The zart-macros crate provides proc-macros that transform an ordinary async fn into a full TaskHandler implementation, removing the boilerplate of the trait impl.

MacroPurpose
#[zart_durable]Marks an async fn as a durable workflow
z_step!Executes a named, persisted step
z_step_with_retry!Step with inline retry config
z_wait_event!Durably waits for an external event
z_durable_loop!Stateful loop with persistent iteration counter
use zart::{TaskHandler, TaskContext, TaskError, TaskRegistry, Worker, WorkerConfig};
use zart::{DurableScheduler, RetryConfig};
use zart_postgres::PostgresScheduler;
use async_trait::async_trait;
use std::time::Duration;
// 1. Implement TaskHandler
struct MyWorkflow;
#[async_trait]
impl TaskHandler for MyWorkflow {
type Data = MyInput;
type Output = MyOutput;
async fn run(
&self,
ctx: &mut TaskContext<impl Scheduler>,
data: MyInput,
) -> Result<MyOutput, TaskError> {
let result = ctx.step("step-one", || async {
do_something(&data).await
}).await?;
Ok(MyOutput { result })
}
}
// 2. Register and start a worker
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let scheduler = PostgresScheduler::connect(&std::env::var("DATABASE_URL")?).await?;
let mut registry = TaskRegistry::new();
registry.register("my-workflow", MyWorkflow);
let worker = Worker::new(scheduler.clone(), registry, WorkerConfig::default());
// Schedule an execution
scheduler.schedule("my-workflow", MyInput { /* ... */ }).await?;
// Run the worker (blocks until shutdown signal)
worker.run().await
}
  1. ScheduleDurableScheduler::schedule() inserts a row into zart_executions with status pending.
  2. Claim — Worker polls with SELECT … FOR UPDATE SKIP LOCKED. One worker owns one execution at a time.
  3. RunTaskHandler::run() is called. Each ctx.step() call checks zart_steps for an existing result.
    • Step hit — stored result deserialized and returned without calling the closure.
    • Step miss — closure is called, result serialized and written to zart_steps, then returned.
  4. Complete — execution status set to completed, output stored.
  5. Fail — if run returns Err, execution is retried up to max_retries(). Final failure sets status failed.