@onyx/crons
Scheduled jobs on Cloudflare Cron Triggers, hosted in the onyx-web Worker.
@onyx/crons runs scheduled jobs on Cloudflare Cron Triggers. Handlers live in the package and a registry maps each cron expression to its handler. The Worker wiring and the wrangler triggers config are generated from that registry, so adding a cron never touches wrangler.jsonc and deploys pick it up automatically.
Defining a new cron
Three files inside private/crons/src are involved:
- Add the expression to
schedule.ts. This module is data-only and must never import handler code:
export const cronExpressions = ['0 3 * * *', '*/15 * * * *'] as const;- Write the handler in
src/crons/. A handler is aCronHandler, an async function receiving the sharedCronContext(currentlydb, aDatabase):
import type { CronContext } from '../define';
export async function myCron({ db }: CronContext): Promise<void> {
// quick D1 work here
}- Register it in
registry.ts. Thesatisfiesclause fails the build if the schedule and registry ever drift:
export const registry = {
'0 3 * * *': cleanupAuth,
'*/15 * * * *': myCron,
} as const satisfies Record<CronExpression, CronHandler>;Keep crons thin. A crashed cron invocation is simply gone, there is no retry, so anything heavier than a quick D1 pass should enqueue a job (@onyx/jobs) or start a workflow (@onyx/workflows).
Built-in cron
cleanup-auth runs daily at 03:00 UTC and deletes expired auth rows that Better Auth checks on read but never removes: sessions, verification tokens, device-flow codes, and expired pending invitations.
Local testing
Trigger a cron while vp dev is running:
curl "http://localhost:3000/cdn-cgi/handler/scheduled?cron=0+3+*+*+*"