Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
# 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.