Skip to content

Repository files navigation

Kuron

GitHubnpmnpmBundle SizeBundle Size

A lightweight framework for Cloudflare Workers scheduled jobs with a Hono-inspired API.

Features

  • 🎯 Hono-like API - Familiar and intuitive interface
  • 🔗 Middleware Support - Chain middleware for logging, auth, etc.
  • 🎭 Error Handling - Global error handler with context
  • 🔐 Type Safety - Full TypeScript support with type inference
  • 🎨 Context Variables - Share data across middleware and handlers
  • Zero Dependencies - Built on standard Cloudflare Workers APIs

Installation

npm install kuron

Kuron's types reference the Workers globals ScheduledController and ExecutionContext. If your project does not already generate worker-configuration.d.ts via wrangler types, install the types package alongside it:

npm install -D @cloudflare/workers-types

Quick Start

import{Cron}from'kuron';constcron=newCron().schedule('0 15 * * *',async(c)=>{console.log('☕️ Good afternoon! Time for your daily 3 PM cleanup routine ✨');// Your job logic here});exportdefaultcron;

Or using a named function for better logging:

import{Cron}from'kuron';constcron=newCron().schedule('0 15 * * *',asyncfunctionafternoonCleanup(c){console.log('Running job:',c.name);// "afternoonCleanup"// Your job logic here});exportdefaultcron;

API Reference

new Cron<Environment>()

Creates a new Cron instance.

Type Parameters:

  • Environment: Object with Bindings and Variables properties (similar to Hono)

.schedule(pattern, handler)

Register a scheduled job with a cron pattern.

Parameters:

  • pattern: Cron pattern string (e.g., '0 15 * * *')
  • handler: Async function to execute when the cron triggers

Returns:this (chainable)

cron.schedule('*/5 * * * *',async(c)=>{console.log('Runs every 5 minutes');});

.use(middleware)

Register middleware to run before job handlers.

Parameters:

  • middleware: Function with signature (c, next) => Promise<void>

Returns:this (chainable)

// Logging middlewarecron.use(async(c,next)=>{console.log('Job started:',c.cron,c.name ? `(${c.name})` : '');conststart=Date.now();awaitnext();constduration=Date.now()-start;console.log(`Job completed in ${duration}ms`);});// Auth middlewarecron.use(async(c,next)=>{if(!c.env.API_KEY){thrownewError('API_KEY not configured');}awaitnext();});

Call next() at most once per middleware. Skipping it short-circuits the chain and the job handler never runs; calling it twice throws next() called multiple times rather than silently running the handler again with part of the chain skipped.

.onError(handler)

Register a global error handler.

Parameters:

  • handler: Function with signature (error, context) => Promise<void>

Returns:this (chainable)

cron.onError((err,c)=>{console.error('Job failed:',{cron: c.cron,name: c.name,error: err.message,stack: err.stack,});// Optional: Send to error tracking service// await sendToSentry(err);});

The handler is called once per failing job, with that job's own context. It also receives the "no jobs registered for this pattern" misconfiguration, so a pattern that drifts out of sync with wrangler.toml reaches your alerting instead of only console.warn.

Without .onError(), a failing job is logged and rethrown so the Workers runtime can retry it — one failure rethrows as-is, several are combined into an AggregateError. Registering .onError() marks errors as handled, which means the invocation reports success and the runtime will not retry it. Rethrow from inside the handler if you want retries.

Jobs sharing a pattern run in sequence and are isolated from each other: one throwing does not cancel the rest, and each gets a fresh context, so variables set by middleware during one job are never visible to the next.

Context Object (c)

The context object passed to handlers and middleware:

interfaceCronContext<E,Pextendsstring=string>extendsScheduledController{// Environment bindings (secrets, KV namespaces, etc.)env: E['Bindings'];// Custom variables shared between middleware and handlersvar: E['Variables'];// Cloudflare Workers execution contextexecutionCtx: ExecutionContext;// Cron pattern for this job (e.g., "0 15 * * *")cron: P;// Name of the handler function (if provided as a named function)name?: string;// ScheduledController properties (inherited)// - scheduledTime: number// - noRetry(): void// Get a variableget: <KextendskeyofE['Variables']>(key: K)=>E['Variables'][K];// Set a variableset: <KextendskeyofE['Variables']>(key: K,value: E['Variables'][K])=>void;}

Properties:

  • env: Access environment bindings (secrets, KV, D1, etc.)
  • var: Access/modify custom variables
  • executionCtx: Cloudflare Workers ExecutionContext for waitUntil() and passThroughOnException()
  • cron: The cron pattern string that triggered this job
  • name: Optional name of the handler function (captured from function.name)
  • scheduledTime: Unix timestamp (ms) when the job was scheduled (inherited from ScheduledController)
  • noRetry(): Call to prevent automatic retries on failure (inherited from ScheduledController)
  • get(key): Get a custom variable
  • set(key, value): Set a custom variable

Advanced Examples

Multiple Jobs

// Using anonymous functionsconstcron=newCron<Environment>().schedule('0 0 * * *',async(c)=>{console.log('Daily midnight job');}).schedule('0 12 * * *',async(c)=>{console.log('Daily noon job');}).schedule('0 0 * * SUN',async(c)=>{console.log('Weekly Sunday job');});

Middleware Chain

constcron=newCron<Environment>()// Timing middleware.use(async(c,next)=>{conststart=Date.now();awaitnext();console.log(`Duration: ${Date.now()-start}ms`);})// Setup middleware.use(async(c,next)=>{c.set('startTime',newDate().toISOString());awaitnext();})// Cleanup middleware.use(async(c,next)=>{awaitnext();console.log('Cleanup completed');}).schedule('0 * * * *',async(c)=>{conststartTime=c.get('startTime');console.log('Job started at:',startTime);});

Sharing Data via Variables

interfaceMyEnvironment{Bindings: Env;Variables: {db: Database;requestId: string;};}constcron=newCron<MyEnvironment>().use(async(c,next)=>{// Initialize DB connection in middlewareconstdb=awaitinitDatabase(c.env.DATABASE_URL);c.set('db',db);c.set('requestId',crypto.randomUUID());awaitnext();}).schedule('0 15 * * *',async(c)=>{// Access DB from middlewareconstdb=c.get('db');constrequestId=c.get('requestId');console.log('Request ID:',requestId);awaitdb.query('...');});

Error Recovery

constcron=newCron<Environment>().use(async(c,next)=>{try{awaitnext();}catch(err){console.error('Middleware caught error:',err);// Optionally rethrow or handlethrowerr;}}).schedule('0 * * * *',async(c)=>{// Job logic that might failawaitriskyOperation();}).onError(async(err,c)=>{// Global error handlingawaitreportToErrorService({error: err,cron: c.cron,name: c.name,timestamp: newDate().toISOString(),});});

Integration with Hono

import{OpenAPIHono}from'@hono/zod-openapi';import{Cron}from'kuron';constapp=newOpenAPIHono<Environment>().get('/health',(c)=>c.text('OK'));constcron=newCron<Environment>().schedule('0 15 * * *',async(c)=>{awaitsyncData(c.env);});exportdefault{fetch: app.fetch,scheduled: cron.scheduled,};

Cron Pattern Syntax

Cloudflare Workers supports standard cron syntax:

┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of the month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of the week (1 - 7) (Sunday to Saturday)
│ │ │ │ │
* * * * *

Examples:

  • 0 15 * * * - Daily at 3:00 PM UTC
  • */5 * * * * - Every 5 minutes
  • 0 0 * * SUN - Every Sunday at midnight
  • 0 0 1 * * - First day of every month at midnight
  • 30 2 * * MON-FRI - 2:30 AM UTC, Monday through Friday
  • 59 23 LW * * - 11:59 PM UTC on the last weekday of the month
  • 0 18 * * FRIL - 6:00 PM UTC on the last Friday of the month

Weekdays are numbered 1 (Sunday) through 7 (Saturday), which differs from the 0-6 convention used by Unix cron. Prefer three-letter abbreviations such as SUN or MON-FRI to avoid the ambiguity. Cloudflare also accepts Quartz extensions like L, LW, and 6L.

Patterns are validated when you call .schedule(), so a malformed pattern throws at startup rather than silently never firing.

TypeScript Tips

Type Inference

The Cron framework automatically infers types from your Environment:

interfaceMyEnvironment{Bindings: {DATABASE_URL: string;};Variables: {userId: string;};}constcron=newCron<MyEnvironment>();cron.schedule('0 * * * *',async(c)=>{// TypeScript knows these types!c.env.DATABASE_URL;// stringc.var.userId;// stringc.get('userId');// stringc.cron;// "0 * * * *" (exact literal type!)c.name;// string | undefinedc.scheduledTime;// number});

Pattern Type Tracking

Similar to how Hono tracks route paths, the Cron framework tracks cron patterns in the type system:

// Single patternconstdailyCron=newCron<MyEnvironment>().schedule('0 15 * * *',async(c)=>{// c.cron has type: "0 15 * * *"console.log(c.cron);});// Type: Cron<MyEnvironment, "0 15 * * *">// Multiple patternsconstmultiCron=newCron<MyEnvironment>().schedule('0 * * * *',async(c)=>{// c.cron has type: "0 * * * *"}).schedule('0 0 * * *',async(c)=>{// c.cron has type: "0 0 * * *"});// Type: Cron<MyEnvironment, "0 * * * *" | "0 0 * * *">// Extract pattern typestypeExtractPatterns<T>=TextendsCron<any, infer P> ? P : never;typePatterns=ExtractPatterns<typeofmultiCron>;// Result: "0 * * * *" | "0 0 * * *"

Benefits:

  • 🎯 Type Safety: Catch typos in pattern comparisons at compile time
  • 💡 IntelliSense: Better autocomplete and hover information
  • 📚 Self-Documenting: See all patterns in type hints
  • 🔄 Safe Refactoring: Rename patterns with confidence

Best Practices

  1. Use Named Functions: Define handlers as named functions instead of anonymous arrow functions for better logging and debugging via c.name
  2. Use Middleware for Common Logic: Extract shared setup, logging, and cleanup into middleware
  3. Handle Errors Gracefully: Always implement .onError() for production workloads
  4. Keep Jobs Idempotent: Jobs should be safe to retry in case of failures
  5. Use ExecutionContext: Call c.executionCtx.waitUntil() for background tasks
  6. Log Extensively: Use middleware for consistent logging across all jobs, including c.name and c.cron
  7. Test Locally: Use Wrangler to test scheduled triggers locally

Comparison with Hono

FeatureHonoCron Framework
Entry pointapp.fetchcron.scheduled
RoutingURL patternsCron patterns
ContextRequest-basedSchedule-based
Middleware
Variables
Error handling
Type safety

License

MIT

About

A lightweight framework for Cloudflare Workers scheduled jobs with a Hono-inspired API.

Topics

Resources

Stars

22 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages