Files
skills/docs/foundation/flow/job-scheduler/src/scheduler/cron-rescheduler.md
2026-05-29 08:20:56 +01:00

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 emits job:scheduling_exhausted once and marks the job cancelled when the expression is exhausted.
  • applyCatchupAndReenqueue(...) — enqueues up to maxMissedRuns past fires (those at now-or-later are skipped) before re-enqueuing the next future fire.
  • Closed ReenqueueOutcome = "scheduled" | "exhausted" return for caller branching.
  • OTel cron_exhausted_total counter 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.