first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
# parseCron
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`parseCron` is the top-level orchestrator that consumes a raw cron
expression string and returns a frozen `CronExpression` whose `nextRun`,
`nextRuns`, `previousRun`, and `matches` methods drive every scheduling
query. The orchestrator macro-expands the input first, then dispatches
the resulting tokens through `parseField` per position, applies optional
Vixie strict-DoW semantics, and finally binds the result to the search
engine in `next-run.ts`. `isValidCron` is the non-throwing sibling: it
returns a boolean and guarantees no exception escape for any string
input, as mandated by the spec.
## Features
- 5-field default; 6-field opt-in via `seconds: true`; 7-field Quartz
opt-in via both `seconds` and `years` true.
- Macro expansion (`@daily`, `@hourly`, ...) handled before tokenization.
- `strictDoW` option enforces "either dom or dow, never both" Vixie
semantics.
- `nextRuns(count)` produces a strictly monotonic array.
- `matches(when, tz)` is a constant-time membership check against the
parsed field sets.
## Usage: Basic Example
```typescript
import { parseCron, isValidCron } from "@suaveplan/cron";
const expr = parseCron("*/15 * * * *");
console.log(expr.fields.minutes); // [0, 15, 30, 45]
console.log(expr.nextRun(Date.UTC(2026, 4, 11, 10, 7, 0), "UTC"));
// Date.UTC(2026, 4, 11, 10, 15, 0)
console.log(isValidCron("not a cron expression")); // false
console.log(isValidCron("0 9 * * MON-FRI")); // true
```
## Usage: Advanced Example
```typescript
import { parseCron } from "@suaveplan/cron";
const quartz = parseCron("0 0 12 * * MON-FRI 2026-2030", {
seconds: true,
years: true,
});
const runs = quartz.nextRuns(3, Date.UTC(2026, 0, 1), "America/New_York");
console.log(runs.map((t) => new Date(t).toISOString()));
const strict = parseCron("0 12 15 * 1", { strictDoW: true });
// Rejects expressions where dom AND dow are both restricted.
console.log(strict.matches(Date.UTC(2026, 0, 15, 12, 0, 0), "UTC"));
```
## API Reference
- `parseCron(expr: string, options?: ParseOptions): CronExpression` —
parses and returns the frozen expression handle.
- `isValidCron(expr: string, options?: ParseOptions): boolean` — never
throws.
- `interface CronExpression { expr; fields; nextRun; nextRuns;
previousRun; matches }`.
- `interface ParseOptions { seconds?; years?; strictDoW? }`.
- `interface CronFields { seconds?; minutes; hours; daysOfMonth; months;
daysOfWeek; years? }`.
## Implementation Notes
The orchestrator first invokes `expandMacro` so the rest of the pipeline
only ever sees canonical 5/6/7-field strings. Tokenization is whitespace-
based with the field count strictly validated against `ParseOptions`.
Each field is independently parsed via the shared `parseField`
tokenizer, which returns a sorted, deduplicated `readonly number[]`.
`strictDoW` enforcement runs after tokenization so the error context
carries the actual parsed sets for debug visibility. The returned
`CronExpression` is `Object.freeze`d and binds its methods over the
captured fields, so destructuring (`const { nextRun } = parseCron(...)`)
is safe. `isValidCron` is a tight try/catch around the same code path so
the two functions stay in lock-step with no semantic drift. All parse
calls flow through `recordParse` so an outcome attribute lands on every
invocation regardless of success or failure.