Engines
Job queue
Work that must not block a request: email, exports, cleanup. Jobs persist in SQLite with leases, retries and a dead-letter queue. Events can pipe straight into a job.
Setup
import { createJobs, SQLiteJobStore } from "yatta/jobs"; export interface AppJobs { "send-email": { to: string; subject: string; body: string }; "cleanup-stale-tokens": { maxAgeDays?: number };} declare module "yatta/jobs" { interface JobRegister extends AppJobs {}} export const jobs = createJobs({ store: new SQLiteJobStore("Database/jobs.db"),});Handlers
Handlers run in a worker pool, isolated from the HTTP event loop. Register them once, in a file that is imported at boot.
import { jobs } from "./jobs";import { mailer } from "./mail"; jobs.handle("send-email", async ({ data }) => { await mailer.send({ to: data.to, subject: data.subject, text: data.body, });}); jobs.handle("cleanup-stale-tokens", async ({ data, log }) => { log(`Cleaning tokens older than ${data.maxAgeDays ?? 7} days`);}); // Start the poolexport const defaultWorker = jobs.worker("default", { concurrency: 5, pollInterval: "1s", lockDuration: "60s",});Enqueuing
// Directawait jobs.enqueue("send-email", { to: "ada@example.com", subject: "Welcome", body: "Hello there",}); // Fluent, when you need optionsawait jobs .job("send-email") .with({ to: user.email, subject: "Welcome", body: "Hello" }) .delay("10m") .priority("high") .unique(`welcome:${user.id}`) .save();Options
{ queue: "default", // target queue delay: "5m", // wait before running runAt: Date, // or an exact time attempts: 3, // then the DLQ priority: "high", // low | normal | high | critical timeout: "30s", // abort a runaway handler uniqueKey: "welcome:42", // dedupe while active retry: { type: "exponential", // or "fixed" delay: 1000, factor: 2, jitter: true, maxDelay: "1h", },}Retries and the dead-letter queue
A failing handler is retried with exponential backoff and jitter. After the attempt limit it moves to the DLQ, where it can be inspected and replayed.
const dead = await jobs.dlq.list("default");await jobs.dlq.retry(deadJob.id); // back on the queueawait jobs.dlq.purge("default"); // discard const stats = await jobs.metrics("default");// { queued, delayed, running, completed, dead }Cron
import { createCron } from "yatta/jobs";import { jobs } from "./jobs"; export const cron = createCron(); // Standard 5-field Vixie syntax.cron.schedule("nightly-cleanup", "0 0 * * *", async () => { await jobs.enqueue("cleanup-stale-tokens");}); // Shorthand for plain intervals.cron.every("10m", () => { console.log("[cron] heartbeat");});*every value,5specific,*/2every two,1-5range,1,5list- Field order: minute, hour, day-of-month, month, day-of-week
- Timezone-aware schedules via
Intl.DateTimeFormat
Scheduled work
A schedule is just a way to enqueue a job on a timer — the handler and its retry policy are the same as any other job.