Skip to content

Implement full 5-field cron matching in scheduled exports and isolate one schedule's failure from the rest #1509

Open

Description

@greatest0fallt1me

Summary

isValidCronExpression accepts steps (*/15), lists (0,30) and ranges (1-5), but computeNextRunAt only understands * or a single number for minute and hour. It ignores day-of-month, month and day-of-week entirely. A schedule */15 * * * * never matches: the loop spins 532,800 iterations, then falls back to "next minute". 0 9 * * 1 (Mondays) runs every day. In runDueSchedules, one schedule that throws stops the loop before its nextRunAt is advanced, so it fails first on every tick and blocks all later schedules. This issue implements correct cron matching and per-schedule failure isolation.

Background and evidence

In src/services/scheduledExports.ts:

  • Validation is broader than matching. isValidCronExpression (about lines 88-92) validates ^([*]|\d+|\*\/\d+|\d+-\d+)(,(...))*$ per field.
  • Matching handles almost nothing. computeNextRunAt (about lines 94-112) destructures only [minuteField, hourField] and compares next.getUTCMinutes() === Number(minuteField). Number('*/15') is NaN, so it never matches. The loop is bounded at 60 * 24 * 370 iterations, then return new Date(from.getTime() + 60_000).
  • One failure blocks the queue. runDueSchedules (about lines 273-289) does results.push(await this.runSchedule(schedule, now, signal)); await this.scheduleStore.update(schedule.id, { lastRunAt: now, nextRunAt: computeNextRunAt(...) }). If runSchedule throws, the update never happens and the loop exits.
  • Routes. src/routes/exports/schedules.ts accepts any cron string up to 100 characters (line 21) and relies on isValidCronExpression.

Scope

In scope

  1. Cron engine.
    • Implement a small, dependency-free 5-field matcher in src/lib/cron.ts: minute 0-59, hour 0-23, day-of-month 1-31, month 1-12, day-of-week 0-6 with 7 as Sunday. Support *, numbers, a-b, */n, a-b/n and comma lists.
    • Apply standard POSIX semantics for day-of-month OR day-of-week when both are restricted.
    • All in UTC.
  2. Efficient next-run. Compute nextRun by advancing field by field (skip to the next matching month, then day, hour, minute), not minute by minute. It returns null for impossible expressions such as 0 0 31 2 *, and validation rejects those.
  3. Validation parity. isValidCronExpression uses the same parser, so anything that validates can be scheduled. Out-of-range values (61 * * * *) are rejected with a field-specific error in the route.
  4. Failure isolation.
    • runDueSchedules catches per-schedule errors, records lastError / lastErrorAt, always advances nextRunAt, and continues with the remaining schedules. It returns per-schedule outcomes.
    • Aborts (signal.aborted) still stop the loop.
  5. Docs. Update docs/scheduled-exports.md with the supported syntax.

Out of scope

  • Time zones other than UTC.
  • Wiring the worker at boot.

Design constraints

  • Fast. nextRun is O(fields × small constant), and benchmarked under 1 ms for any valid expression.
  • Pure. The parser is pure and fully unit-testable; no new runtime dependency.
  • Shutdown. Abort behaviour stays cooperative.

Test plan

Add src/lib/cron.test.ts (table-driven, plus fast-check) and extend src/services/scheduledExports.test.ts.

# Scenario Expected
1 */15 * * * * from 10:07 10:15
2 0 9 * * 1 from Tuesday next Monday 09:00 UTC
3 0,30 8-10 * * * 08:00, 08:30 ... 10:30 sequence
4 0 0 1 * * from Jan 31 Feb 1 00:00
5 0 0 29 2 * next leap-year Feb 29
6 0 0 31 2 * validation rejects
7 61 * * * * validation rejects with a field error
8 Property nextRun(e, t) > t and matches e for random valid expressions
9 First of three schedules throws the other two run; the failing one's nextRunAt advanced and lastError set
10 Abort mid-loop loop stops; abort surfaced

Regression proof. Row 1 returns from + 1 minute (and row 2 returns tomorrow) on current main.

Acceptance criteria

  • Every test-plan row passes.
  • npm run lint, npm run typecheck, npm run build and npm test pass, and CI is green.
  • At least 95% line coverage on src/lib/cron.ts and 90% on the touched scheduledExports.ts code.

Getting started

git checkout -b fix/scheduled-exports-cron
npm ci && npx prisma generate
npm run test:unit -- src/services/scheduledExports

Read first: isValidCronExpression, computeNextRunAt and runDueSchedules in src/services/scheduledExports.ts, and src/routes/exports/schedules.ts.

PR requirements

  • Link this issue (Closes #<n>).
  • Include the benchmark numbers for nextRun.
  • Paste test output and coverage.
  • Example commit: fix(exports): implement full cron matching and isolate schedule failures.

Priority rationale

Medium. It is a self-contained module, but schedules currently run at the wrong times, and one bad schedule silently starves all the others.

Activity

  1. D0L4P0 commented on Oct 18, 2026

    @D0L4P0

    @D0L4P0 has applied to work on this issue as part of the Stellar Wave Program's 10th wave.

    Hello I would like to work on this issue please

    ℹ️ Repo Maintainers: To accept this application, review their application or assign @D0L4P0 to this issue.

  2. drips-wave commented on Oct 19, 2026

    @drips-wave

    Congratulations, @D0L4P0! 🎉 Your application was accepted by the repo's maintainers, and the issue is due on October 22, 2026.

    🧑‍💻 @D0L4P0: Please resolve the issue such that the repo's maintainers have enough time to review your contribution before the due date. You'll earn Points for completing the issue on-time, which will make you eligible for a share of the Stellar Wave Program's reward pool.

    Warning

    When opening a PR, please link it to this issue to ensure it gets tracked accurately. Points are awarded when this issue is marked as completed by the maintainer.

    🤠 Repo maintainers: Please keep an eye on the contributor's progress and review their work before the due date. You can manage this issue, including adjusting its complexity and points, here.

    🌊 Happy Wave 🌊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Stellar WaveStellar Wave Program issuebackendBackend service workperformancePerformance/efficiencypriority:mediumMedium implementation difficultytestingTests and coverage

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions