Last active
October 19, 2025 23:15
-
-
Save NuroDev/4b9651170af37f0f7ee22d93a0f88042 to your computer and use it in GitHub Desktop.
⏲️ Schedule Controller - A minimal `Hono` style controller for Cloudflare Workers `scheduled`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
| { | |
| "$schema": "node_modules/wrangler/config-schema.json", | |
| "name": "example-scheduled-worker", | |
| "main": "src/index.ts", | |
| "compatibility_date": "2025-10-01", | |
| "triggers": { | |
| "crons": [ | |
| "0 8 * * *", | |
| "*/30 * * * *" | |
| ] | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { createTaskHandler, ScheduleController } from 'path/to/schedule-controller'; | |
| // Create a task with some logic you want to run. | |
| const sendEmailsTask = createTaskHandler(async (c): Promise<void> => { | |
| console.info('Scheduled: Sending email digests to users'); | |
| c.controller.cron; // 0 8 * * * | |
| // ... email sending logic here ... | |
| }); | |
| const scheduled = new ScheduleController() | |
| // Register the task to run every day at 8 AM | |
| .handler('0 8 * * *', sendEmailsTask) | |
| // Or create the task handler inline | |
| .handler('*/30 * * * *', () => { | |
| console.log('Scheduled: Running task every 30 minutes'); | |
| }); | |
| export default { | |
| scheduled: scheduled.process, | |
| } satisfies ExportedHandler<Env>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export type ScheduleHandler<E = Env> = ( | |
| context: ExecutionContext & { | |
| controller: ScheduledController; | |
| env: E; | |
| }, | |
| ) => void | Promise<void>; | |
| /** | |
| * Creates a type-safe scheduled task handler with proper type inference. | |
| * | |
| * This is a helper function that provides better IDE autocomplete and type checking | |
| * when defining scheduled task handlers. It's an identity function that returns the | |
| * handler unchanged but with explicit type information. | |
| * | |
| * @template E - The environment bindings type (defaults to Env) | |
| * @param handler - The scheduled task handler function to wrap | |
| * @returns The same handler with explicit ScheduleHandler type | |
| * | |
| * @example | |
| * ```ts | |
| * const handler = createTaskHandler(async (c) => { | |
| * console.info(`Running cron: ${c.controller.cron}`); | |
| * // ... | |
| * }); | |
| * | |
| * // Register with schedule controller | |
| * scheduleController.handler('* /30 * * * *', handler); | |
| * ``` | |
| * | |
| * @example | |
| * ```ts | |
| * // With custom environment type | |
| * interface CustomEnv extends Env { | |
| * MY_KV: KVNamespace; | |
| * } | |
| * | |
| * const customHandler = createTaskHandler<CustomEnv>(async (context) => { | |
| * const value = await context.env.MY_KV.get('key'); | |
| * console.info(`Got value: ${value}`); | |
| * }); | |
| * ``` | |
| */ | |
| export const createTaskHandler = <E = Env>( | |
| handler: ScheduleHandler<E>, | |
| ): ScheduleHandler<E> => handler; | |
| /** | |
| * Controller for managing and executing scheduled tasks in Cloudflare Workers. | |
| * | |
| * This class provides a fluent API for registering cron job handlers and automatically | |
| * matches them to the appropriate cron schedule at runtime. Multiple handlers can be | |
| * registered for the same schedule and will be executed in parallel. | |
| * | |
| * @template E - The environment bindings type (defaults to Env) | |
| * | |
| * @example | |
| * ```ts | |
| * // Create a schedule controller | |
| * const scheduleController = new ScheduleController<Env>(); | |
| * | |
| * // Register handlers for different schedules | |
| * scheduleController | |
| * .handler('* /30 * * * *', createTaskHandler(async (context) => { | |
| * console.info('Running every 30 minutes'); | |
| * // ... | |
| * })) | |
| * .handler('0 0 * * *', createTaskHandler(async (context) => { | |
| * console.info('Running daily at midnight'); | |
| * // ... | |
| * })); | |
| * | |
| * // Export the processor in your worker | |
| * export default { | |
| * scheduled: scheduleController.process | |
| * }; | |
| * ``` | |
| * | |
| * @example | |
| * ```ts | |
| * // Multiple handlers for the same schedule run in parallel | |
| * scheduleController | |
| * .handler('* /30 * * * *', sendAnalyticsHandler) | |
| * .handler('* /30 * * * *', pruneCacheHandler); | |
| * ``` | |
| */ | |
| export class ScheduleController<E = Env> { | |
| private _jobs: Map<string, Array<ScheduleHandler<E>>> = new Map(); | |
| /** | |
| * Registers a scheduled task handler for a specific cron schedule. | |
| * | |
| * Multiple handlers can be registered for the same schedule - they will all | |
| * execute in parallel when the cron triggers. | |
| * | |
| * @param schedule - Cron expression (e.g., '* /30 * * * *' for every 30 minutes) | |
| * @param handler - The handler function to execute when the cron triggers | |
| * @returns The ScheduleController instance for method chaining | |
| * | |
| * @example | |
| * ```ts | |
| * scheduleController.handler('0 * * * *', createTaskHandler(async (context) => { | |
| * const { env, controller } = context; | |
| * console.info(`Hourly task triggered at ${controller.scheduledTime}`); | |
| * await env.WORKFLOWS.create({ id: 'hourly-workflow' }); | |
| * })); | |
| * ``` | |
| */ | |
| handler = (schedule: string, handler: ScheduleHandler<E>): this => { | |
| const existingHandlers = this._jobs.get(schedule) ?? []; | |
| this._jobs.set(schedule, existingHandlers.concat(handler)); | |
| return this; | |
| }; | |
| /** | |
| * Gets the Cloudflare Workers scheduled event handler. | |
| * | |
| * This getter returns a function compatible with Cloudflare Workers' | |
| * `ExportedHandlerScheduledHandler` interface. When a cron trigger fires, | |
| * it matches the cron expression to registered handlers and executes all | |
| * matching handlers in parallel. | |
| * | |
| * If no handlers match the triggered cron schedule, a warning is logged. | |
| * | |
| * @returns A scheduled event handler for Cloudflare Workers | |
| * | |
| * @example | |
| * ```ts | |
| * // In your worker's main file (src/index.ts) | |
| * const scheduleController = new ScheduleController(); | |
| * | |
| * // Register handlers... | |
| * scheduleController.handler('* /30 * * * *', myHandler); | |
| * | |
| * // Export for Cloudflare Workers | |
| * export default { | |
| * fetch: app.fetch, | |
| * scheduled: scheduleController.process | |
| * }; | |
| * ``` | |
| */ | |
| get process(): ExportedHandlerScheduledHandler<E> { | |
| return async (controller, env, ctx): Promise<void> => { | |
| const jobs = this._jobs.get(controller.cron) ?? []; | |
| if (!jobs.length) | |
| return console.warn( | |
| `No cron job found for schedule "${controller.cron}"`, | |
| ); | |
| await Promise.all( | |
| jobs.map((handler) => | |
| handler( | |
| Object.assign( | |
| {}, | |
| { | |
| controller, | |
| env, | |
| }, | |
| ctx, | |
| ), | |
| ), | |
| ), | |
| ); | |
| }; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment