4.9 KiB
4.9 KiB
Tasks: primitives-cron
Change ID: primitives-cron
Package: @suaveplan/cron
Target path: packages/primitives/cron
Prerequisites on Verdaccio: @suaveplan/error, @suaveplan/types
1. Scaffold
- Create directory tree per
proposal.md. package.jsonat0.1.0,"sideEffects": false, exports root +./parse+./scheduler+./testingsubpaths.- Deps:
@suaveplan/error workspace:^,@suaveplan/types workspace:^,zod catalog:forms,@opentelemetry/api catalog:observability,tslib catalog:build. - DevDeps:
vitest catalog:testing,@vitest/coverage-istanbul catalog:testing,fast-check catalog:testing,@suaveplan/testing workspace:^. tsconfig.jsonextendinglibrary.json(universal-tier — NOlibrary-node.json).vitest.config.tsusingcreateTestConfig()from@suaveplan/testing/config.- Scripts:
lint,typecheck,test,test:coverage,build,docs:check.
2. Types + errors
src/types/cron.types.ts:CronFields,CronExpression,ParseOptions,CronSchedulerOptions. Zod schemas at the boundary.src/types/cron.types.md(≥200 words, both examples, full API reference).src/error-codes.ts: 5 codes per proposal viadefineCodes("CRON", [...]); eagerregisterCronErrorCodes()call.
3. Field + macro parsing
src/parse/cron-fields/cron-fields.ts: tokenize one field (range / list / step /*/ numeric alias JAN-DEC + SUN-SAT). Validate per-field range. ThrowCRON.FIELD_OUT_OF_RANGE/CRON.UNSUPPORTED_SYNTAX.src/parse/macros/macros.ts: expand@yearly/@annually/@monthly/@weekly/@daily/@midnight/@hourly→ canonical 5-field expression.src/parse/cron-expression/cron-expression.ts: orchestrator. 5/6/7-field detection perParseOptions.parseCron(expr, options)returns frozenCronExpressionwith boundnextRun/nextRuns/previousRun/matches.isValidCronnon-throwing variant.- Tests for every parser file. Co-located
.mdfor every impl file.
4. Next-run + timezone
src/next-run/timezone/timezone.ts:wallClockComponents(epochMs, tz)+ inverseepochMsForWallClock(components, tz)viaIntl.DateTimeFormatwithtimeZone+hourCycle: 'h23'. DST spring-forward = next valid minute. DST fall-back = first occurrence.CRON.TIMEZONE_INVALIDon bad IANA name.src/next-run/next-run/next-run.ts: bit-set field-match algorithm (croner-style). Bounded forward search horizon (default: 8 years per Quartz convention) →CRON.NEXT_RUN_NOT_FOUNDon exhaustion (e.g.0 0 31 2 *).previousRunmirror.- Tests including known-impossible expressions, leap-day, DST crossings (US
America/New_York, EUEurope/London, SHAustralia/Sydney).
5. Optional in-process scheduler
src/scheduler/cron-scheduler/cron-scheduler.ts: thinsetTimeout-based driver (NOT a full job runner). InjectableClock. Handler errors routed tooptions.onError(default: swallow).start/stopidempotent.stop()awaits in-flight handlers.- Test with
FakeClockfrom@suaveplan/testing.
6. OTel surface
src/otel/cron-otel.ts:- Histograms:
cron.parse.duration_ms{outcome},cron.next_run.duration_ms{outcome,tz}. - Counters:
cron.parse_total{outcome},cron.next_run_total{outcome,tz},cron.dst_transition_total{kind: 'spring-forward'|'fall-back', tz}.
- Histograms:
- Co-located
.md.
7. Integration + property + vectors
tests/vectors.test.ts: ≥30 known (expression, after, tz, expected-nextRun) tuples. Cross-check at least 10 against croner's documented behaviour for confidence (research-only — no runtime dep).tests/property.test.ts(fast-check):nextRun(parseCron(e), t) > tfor all parseableeand finitet.nextRuns(parseCron(e), n, t)is strictly monotonic increasing.matches(parseCron(e), nextRun(parseCron(e), t))is true.previousRun(parseCron(e), nextRun(parseCron(e), t)) <= twhen both finite.
tests/integration.test.ts:CronSchedulerover 3 jobs with different intervals underFakeClock; verify fire order + counts + stop drain;{ timeout: 30000 }.
8. Docs + verify + publish
- Root
README.md: parse quick-start,nextRun(tz)example,CronSchedulerexample, FakeClock test recipe, full API table, comparison-with-croner note (why we wrote our own). bunx turbo lint --filter=@suaveplan/cron— zero errors.bunx turbo typecheck --filter=@suaveplan/cron— zero errors.bunx turbo test --filter=@suaveplan/cron— all green.bunx turbo test:coverage --filter=@suaveplan/cron— 100%.bunx turbo docs:check --filter=@suaveplan/cron— 100%.bun scripts/folder-reorg/generate-manifest.tsto refreshMANIFEST.md.- Update
CLAUDE.mdpackage listing under "Time / sequencing / validation". bun scripts/publish.ts --package @suaveplan/cron.- Archive to
openspec/archive/2026-05-11-primitives-cron/.