Created
November 21, 2025 16:36
-
-
Save NuroDev/99023c80ff9344d45524add6226bbd1c to your computer and use it in GitHub Desktop.
⏲️ Schedule Controller - A minimal `Hono` style controller for Cloudflare Workers scheduled handler
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 interface ScheduleHandlerContext<E = Env> { | |
| /** | |
| * The scheduled event controller providing details about the cron trigger. | |
| */ | |
| controller: ScheduledController; | |
| /** | |
| * The environment bindings available to the worker. | |
| */ | |
| env: E; | |
| /** | |
| * The execution context for managing the lifetime of asynchronous tasks. | |
| */ | |
| executionCtx: ExecutionContext; | |
| } | |
| export type ScheduleHandler<E = Env> = ( | |
| context: ScheduleHandlerContext<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 | |
| * app.task('* /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 app = new ScheduleController<Env>(); | |
| * | |
| * // Register handlers for different schedules | |
| * app.task('* /30 * * * *', createTaskHandler(async (context) => { | |
| * console.info('Running every 30 minutes'); | |
| * // ... | |
| * })); | |
| * app.task('0 0 * * *', createTaskHandler(async (context) => { | |
| * console.info('Running daily at midnight'); | |
| * // ... | |
| * })); | |
| * | |
| * export default { | |
| * scheduled: app.scheduled | |
| * }; | |
| * ``` | |
| * | |
| * @example | |
| * ```ts | |
| * // Multiple handlers for the same schedule run in parallel | |
| * app | |
| * .task('* /30 * * * *', sendAnalyticsHandler) | |
| * .task('* /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 | |
| * app.task('0 * * * *', createTaskHandler(async (context) => { | |
| * const { env, controller } = context; | |
| * console.info(`Hourly task triggered at ${controller.scheduledTime}`); | |
| * await env.WORKFLOWS.create({ id: 'hourly-workflow' }); | |
| * })); | |
| * ``` | |
| */ | |
| task(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 | |
| * const app = new ScheduleController(); | |
| * | |
| * app.task('* /30 * * * *', () => { | |
| * console.info('Running every 30 minutes'); | |
| * }); | |
| * | |
| * export default { | |
| * scheduled: app.scheduled | |
| * } satisfies ExportedHandler; | |
| * ``` | |
| */ | |
| get scheduled(): ExportedHandlerScheduledHandler<E> { | |
| return async (controller, env, executionCtx): 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({ | |
| controller, | |
| env, | |
| executionCtx, | |
| }), | |
| ), | |
| ); | |
| }; | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
🦄 Example