2.8 KiB
job-dispatcher
Purpose
Encapsulates the "hand a job to the executor" step of the scheduler's tick loop. The scheduler core decides which job runs next and when; this module is responsible for the how — wiring the per-attempt AbortController, tracking the in-flight promise, and updating per-job counters (runs, failures, lastRun) on settle. Splitting this out keeps the scheduler core under the 300-line file budget and isolates the small but error-prone bookkeeping work in one focused module.
Features
- Single
dispatch(job, scheduledAt, env)function — no class, no state of its own. - Captures the in-flight promise into the scheduler-owned
Set<Promise>forstop()drain. - Atomically transitions the job to
runningand registers a freshAbortControlleron the job record. - Invokes the scheduler's
afterRuncallback infinally, regardless of success or failure.
Usage: Basic Example
import { dispatch } from "@suaveplan/job-scheduler";
dispatch(scheduledJob, scheduledAt, {
clock,
logger,
events,
timers,
inFlight,
runningSet,
afterRun: (job) => {
// scheduler decides whether to re-enqueue or remove
},
});
Usage: Advanced Example
import { dispatch, type ScheduledJob } from "@suaveplan/job-scheduler";
function pump(jobs: Iterable<ScheduledJob>): void {
for (const job of jobs) {
if (job.status !== "pending") continue;
dispatch(job, job.nextRun ?? clock(), {
clock,
logger,
events,
timers,
inFlight,
runningSet,
afterRun: (j) => {
if (j.kind === "queued") {
// one-shot — let the scheduler garbage-collect
return;
}
// re-enqueue cron jobs by computing the next fire
},
});
}
}
API Reference
dispatch(job: ScheduledJob, scheduledAt: number, env: DispatchEnv): void— fires the job. No return value; all bookkeeping happens viaenv.interface ScheduledJob— internal record shape mutated in place.interface DispatchEnv—{ clock, logger, events, timers, inFlight, runningSet, afterRun }.
Implementation Notes
The afterRun callback is the scheduler's hook for "what happens once the job settles" — for queued jobs the scheduler typically deletes the entry; for cron jobs it computes the next nextRun and re-enqueues. Putting this branch in the scheduler core (rather than passing a flag to dispatch) keeps dispatch polymorphism-free and makes the lifecycle of every cron / queued job obvious at the call site. The promise is tracked in env.inFlight so JobScheduler.stop() can Promise.allSettled over the set and drain the in-flight work cleanly.