relayr 0.4.4

Cron scheduler with a delegated-flavour syntax
Documentation

We're dsplce.co, check out our work on our website: dsplce.co πŸ–€

relayr

Rust crates.io Downloads crates.io Size docs.rs License crates.io

πŸƒβ€β™‚οΈ Effortless delegated cron jobs β€” scheduled tasks in Rust, made simple.

relayr lets you register cron jobs across your codebase without the manual boilerplate. Annotate a function with a macro and it gets auto-discovered and scheduled at runtime β€” no central list to keep in sync, no giant match block to grow.

This crate is a wrapper around async-cron-scheduler to use it in a delegated flavour. If you're not after the delegated way of defining your cron jobs, you're probably better off using that directly.

βΈ»

πŸ–€ Features

  • One macro is the whole registration. Slap #[cron("...")] on a function and you're done β€” define the job right where it lives, not in some central registry you'll forget to update.
  • No manual wiring. Jobs are discovered at compile time via inventory, so there's no match block and no "ah, I forgot to add it to the list".
  • Typos fail the build, not prod. Literal cron patterns are validated at compile time β€” a malformed schedule won't sneak past you to surface at 3am.
  • Fully async. Built on top of tokio; your jobs are plain async fns.
  • Schedules from the environment. Point a job at an env var instead of a literal and change its cadence without a recompile.
  • One callback catches everything. Register a single error handler and whatever any job throws lands there.
  • Timezone-aware. Run under Local, Utc, or any chrono-tz zone.

βΈ»

Table of Contents

βΈ»

πŸ“¦ Installation

Add it with cargo:

cargo add relayr

Or drop it into your Cargo.toml by hand:

relayr = "0.4"

That pulls in the core scheduler, inventory, and the macro support β€” nothing else to wire up.

βΈ»

πŸ§ͺ Usage

Define a job

Annotate any async fn that takes a JobId and returns anyhow::Result<()>. The cron pattern accepts an optional leading seconds field, so 1/1 * * * * * means "every second":

use relayr::prelude::*;
use chrono::Local;

#[cron("1/1 * * * * *")]
async fn print_every_second(_: JobId) -> anyhow::Result<()> {
    println!("πŸ–€ Hello from relayr!");
    Ok(())
}

#[tokio::main]
async fn main() {
    relayr::run::<Local>().await
}

That's it. When relayr::run() starts it picks up every function decorated with #[cron(...)] and schedules it β€” no registration step, no boilerplate. Swap Local for Utc or any chrono-tz zone to run on a different timezone.

Schedule from an environment variable

Sometimes you don't want the cadence baked into the binary β€” staging should sweep hourly, prod every five minutes, that sort of thing. Pass a bare identifier (an env var name) instead of a string literal, and relayr resolves the pattern at runtime:

use relayr::prelude::*;

// reads the cron pattern from the CLEANUP_SCHEDULE environment variable at startup
#[cron(CLEANUP_SCHEDULE)]
async fn cleanup(_: JobId) -> anyhow::Result<()> {
    // ...
    Ok(())
}

One caveat worth knowing: literal patterns are validated at compile time, but env-var ones can only be checked once the value is read β€” so if CLEANUP_SCHEDULE is unset or malformed when relayr::run() starts, it'll panic rather than silently skip the job.

Handle errors with a callback

Jobs return a Result, and each one runs in its own task β€” so a single job blowing up never takes the scheduler (or the others) down with it. Register one callback before run() and every error lands there, tagged with the job's id and name:

use relayr::prelude::*;
use chrono::Local;

#[cron("1/1 * * * * *")]
async fn flaky(_: JobId) -> anyhow::Result<()> {
    anyhow::bail!("something went wrong");
}

#[tokio::main]
async fn main() {
    relayr::set_error_callback(|job_id: JobId, job_name: &'static str, error: anyhow::Error| async move {
        eprintln!("job {job_name:?} ({job_id:?}) failed: {error:?}");
    })
    .await;

    relayr::run::<Local>().await
}

job_name is the function's name, so you get readable logs for free without naming anything twice.

How it works

  • You annotate functions with #[cron("cron pattern")] (or #[cron(ENV_VAR_NAME)]).
  • Under the hood the macro registers your function in a global inventory.
  • When relayr::run::<Tz>() is called, it:
    • spins up a scheduler for the timezone you pass,
    • iterates over every discovered job,
    • resolves each pattern (literal or from the environment) and inserts it.

No manual wiring. No giant match blocks. Just clean, delegated jobs.

βΈ»

πŸ› οΈ Requirements

  • Rust (2024 edition): relayr uses the 2024 edition, so a reasonably recent toolchain is needed.
  • An async runtime: jobs run on tokio, which comes bundled as a dependency β€” you just need #[tokio::main] (or your own runtime) at the entry point.

βΈ»

πŸ“ Repo & Contributions

πŸ› οΈ Repo: https://github.com/dsplce-co/relayr πŸ“¦ Crate: https://crates.io/crates/relayr

PRs welcome! Let's make scheduled Rust ✨clean and effortless.

βΈ»

πŸ“„ License

MIT or Apache-2.0, at your option.