Files
2026-05-27 23:47:53 +01:00

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> for stop() drain.
  • Atomically transitions the job to running and registers a fresh AbortController on the job record.
  • Invokes the scheduler's afterRun callback in finally, 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 via env.
  • 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.