3.6 KiB
next-run-fields
Purpose
next-run-fields houses the low-level field-walking helpers used by the
forward and backward next-run search loops in next-run.ts. Splitting the
helpers into a sibling module keeps each file under the 300-line hard
limit while keeping the call sites in next-run.ts declarative — the
forward loop reads as snapForward → fromWallClock → verify, with the
fine-grained carry/rollover logic isolated here. None of these helpers
are part of the package's public API; they exist purely to support the
internal next-run engine.
Features
climbFields(wall, fields)— single forward step across seconds → minutes → hours → days, honoring per-field carries.climbFieldsBackward(wall, fields)— mirror walk used bypreviousRun.advanceUntilMonthMatches,advanceUntilDayMatches,advanceUntilYearMatches— coarse jump helpers that snap a wall-clock forward to the next legal month / day / year for the field set.nextValueAtLeast/previousValueAtMost— sorted-array lookup helpers shared by every field walker.copyWall— converts an immutableWallClockto aMutableWallso the search loop can mutate without aliasing.
Usage: Basic Example
import {
advanceUntilMonthMatches,
climbFields,
copyWall,
} from "@suaveplan/cron/next-run/next-run";
import { toWallClock } from "@suaveplan/cron";
const fields = {
minutes: [0],
hours: [9],
daysOfMonth: Array.from({ length: 31 }, (_, i) => i + 1),
months: [6, 12],
daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
};
const wall = copyWall(toWallClock(Date.now(), "UTC"));
climbFields(wall, fields);
advanceUntilMonthMatches(wall, fields);
console.log(wall.month);
Usage: Advanced Example
import {
climbFieldsBackward,
copyWall,
} from "@suaveplan/cron/next-run/next-run";
import { toWallClock } from "@suaveplan/cron";
const fields = {
minutes: [0, 15, 30, 45],
hours: [9, 10, 11, 12, 13, 14, 15, 16, 17],
daysOfMonth: Array.from({ length: 31 }, (_, i) => i + 1),
months: Array.from({ length: 12 }, (_, i) => i + 1),
daysOfWeek: [1, 2, 3, 4, 5],
};
const wall = copyWall(toWallClock(Date.UTC(2026, 4, 11, 12, 47), "UTC"));
climbFieldsBackward(wall, fields);
console.log(wall.hour, wall.minute); // 12, 45
API Reference
HORIZON_YEARS: 8,MS_PER_SECOND: 1000,MS_PER_MINUTE: 60000.MutableWall— mutable counterpart ofWallClockminusweekday.nextValueAtLeast(sorted, current),previousValueAtMost(sorted, current).daysInMonth(year, month)— calendar-correct day count.copyWall(w).climbFields(wall, fields),climbFieldsBackward(wall, fields).advanceUntilMonthMatches,advanceUntilDayMatches,advanceUntilYearMatches,yearMatches.
Implementation Notes
The forward and backward walkers are written as small step functions so
each carry case can be reasoned about independently. Day-of-month and
day-of-week interaction follows Vixie OR semantics: when one field is
the full range and the other is restricted, only the restricted field
matters; when both are restricted, either match is sufficient.
advanceUntilDayMatches walks one calendar day at a time so the OR
semantics fall out naturally rather than requiring a more elaborate
bitset intersection. Safety bounds in the loops cap iterations at
366 × (HORIZON_YEARS + 1) so pathological input cannot livelock the
search; the outer search functions in next-run.ts then convert the
resulting wall clock into an epoch instant and verify the result still
matches the field set, catching DST-gap edge cases.