Private packages

@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:

  1. 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;
  1. Write the handler in src/crons/. A handler is a CronHandler, an async function receiving the shared CronContext (currently db, a Database):
import type { CronContext } from '../define';

export async function myCron({ db }: CronContext): Promise<void> {
  // quick D1 work here
}
  1. Register it in registry.ts. The satisfies clause 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+*+*+*"

On this page