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

yatta/func/jobs.tsts
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.

yatta/func/workers.tsts
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

Two stylests
// 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

EnqueueOptionsts
{  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.

DLQts
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

yatta/func/cron.tsts
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, 5 specific, */2 every two, 1-5 range, 1,5 list
  • 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.