first commit
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user