# 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 by `previousRun`. - `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 immutable `WallClock` to a `MutableWall` so the search loop can mutate without aliasing. ## Usage: Basic Example ```typescript 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 ```typescript 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 of `WallClock` minus `weekday`. - `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.