2.7 KiB
cron-rescheduler
Purpose
Houses the two cron-related decisions the scheduler makes after a job settles or resumes: (1) compute the next fire from the cron expression, OR (2) recognise that the expression has no future fire and gracefully cancel the job. The module also implements optional catchup-missed-fires logic for resume-after-pause scenarios. Extracting this here keeps the scheduler core small and isolates the only place where CRON.NEXT_RUN_NOT_FOUND is consumed.
Features
reenqueueCron(job, env, tz)— pushes the next fire onto the queue, or emitsjob:scheduling_exhaustedonce and marks the job cancelled when the expression is exhausted.applyCatchupAndReenqueue(...)— enqueues up tomaxMissedRunspast fires (those atnow-or-later are skipped) before re-enqueuing the next future fire.- Closed
ReenqueueOutcome = "scheduled" | "exhausted"return for caller branching. - OTel
cron_exhausted_totalcounter is incremented exactly once per exhausted job.
Usage: Basic Example
import { reenqueueCron } from "@suaveplan/job-scheduler";
const outcome = reenqueueCron(job, env, "UTC");
if (outcome === "exhausted") {
// job has been marked cancelled; bus has emitted the event
}
Usage: Advanced Example
import {
applyCatchupAndReenqueue,
reenqueueCron,
} from "@suaveplan/job-scheduler";
function onResume(job: ScheduledJob): void {
if (job.kind !== "cron") return;
const outcome = applyCatchupAndReenqueue(
job,
env,
job.tz ?? "UTC",
/* catchupMissed */ true,
/* maxMissedRuns */ 3
);
if (outcome === "exhausted") {
// log + surface to ops dashboard
}
}
function onComplete(job: ScheduledJob): void {
reenqueueCron(job, env, job.tz ?? "UTC");
}
API Reference
type ReenqueueOutcome = "scheduled" | "exhausted".interface CronEnv { clock, events, queue, expressionOf }.reenqueueCron(job, env, tz): ReenqueueOutcome.applyCatchupAndReenqueue(job, env, tz, catchupMissed, maxMissedRuns): ReenqueueOutcome.
Implementation Notes
expressionOf is a thin lookup callback rather than a direct map reference so the scheduler can keep the CronExpression cache private — exposing it as a parameter would tempt callers to mutate the cache by side channel. The catchup branch uses expression.nextRuns(maxMissedRuns, lastRun, tz) from @suaveplan/cron rather than rolling its own iteration, ensuring DST transitions and timezone edge cases are handled identically to the rest of the cron stack. Errors other than CRON.NEXT_RUN_NOT_FOUND rethrow — they indicate a programming bug (e.g., an invalid tz that somehow slipped past validation) and must surface, not be swallowed.