Skip to main content

Recurring jobs

A recurring job runs on a cron schedule rather than on demand. Define it with defineRecurringJob. The worker announces the schedule when it registers, and the server triggers the job on the cadence you specify.

import { defineRecurringJob } from "@zeridion/flare/worker";

const nightlyCleanup = defineRecurringJob({
jobType: "NightlyCleanup",
cron: "0 3 * * *",
timezone: "UTC",
queue: "maintenance",
async handle(ctx) {
await db.sessions.deleteExpired({ signal: ctx.signal });
},
});

Options

OptionTypeRequiredDefaultDescription
jobTypestringyesThe routing key. Must be unique within the worker.
cronstringyesA standard five-field cron expression (minute, hour, day-of-month, month, day-of-week).
timezonestringnoserver defaultThe IANA timezone the cron is interpreted in (for example "UTC", "America/New_York").
queuestringno"default"The queue the triggered job runs on.
maxAttemptsnumberno3Attempts per triggered run.
timeoutSecondsnumberno1800Per-run timeout.

Always set a timezone for non-UTC schedules

The timezone field is part of the schedule. If you omit it, the cron is interpreted in the server's default timezone — which means a "0 9 * * *" ("9 AM daily") schedule will not fire at 9 AM in your local time unless that default happens to match. Set timezone explicitly whenever the cadence is time-of-day sensitive:

const morningDigest = defineRecurringJob({
jobType: "MorningDigest",
cron: "0 9 * * *",
timezone: "America/New_York", // 9 AM Eastern, not UTC
async handle(ctx) {
await sendDigest({ signal: ctx.signal });
},
});

The handler

A recurring handler takes only the job context — there is no payload. The same contract as a payload job applies: return to acknowledge success, throw to acknowledge failure, honour ctx.signal, and stay idempotent (a run can be delivered more than once).

const rebuildSearchIndex = defineRecurringJob({
jobType: "RebuildSearchIndex",
cron: "*/30 * * * *", // every 30 minutes
timezone: "UTC",
timeoutSeconds: 600,
async handle(ctx) {
for (const batch of batches()) {
if (ctx.signal.aborted) break; // cooperative cancellation
await indexBatch(batch, { signal: ctx.signal });
ctx.reportProgress(progressSoFar());
}
},
});

Registering the schedule

You don't call any extra method — listing the recurring definition in the worker's jobs array is enough. The worker includes it in the schedules it announces on startup:

const worker = new FlareWorker({
apiKey: process.env.FLARE_API_KEY,
jobs: [nightlyCleanup, morningDigest],
});
await worker.run();

See also