Task Scheduling Guide
Guren provides a fluent API for defining scheduled tasks within your application. Instead of managing multiple cron entries, you can define your entire task schedule in code.
Task Scheduling Guide
Guren provides a fluent API for defining scheduled tasks within your application. Instead of managing multiple cron entries, you can define your entire task schedule in code.
The standard vNext path is: import scheduling APIs from @guren/core, register schedules centrally, and keep feature code focused on the jobs or commands being scheduled.
Core Concepts
- Scheduler – Manages and runs scheduled tasks at the appropriate times.
- Schedule – Builder for defining tasks with a fluent API.
- ScheduledTask – Individual task with its schedule and configuration.
- Cron Expression – Standard cron syntax for defining when tasks run.
Basic Usage
Quick Start
import { Scheduler } from '@guren/core'
const scheduler = new Scheduler()
scheduler.schedule((schedule) => {
// Run a callback every day at 3 AM
schedule.call(async () => {
await cleanupOldSessions()
}).daily().at('03:00').name('cleanup-sessions')
})
scheduler.start()
Running the Scheduler
Start the scheduler in your application bootstrap:
// app.ts
import { Scheduler } from '@guren/core'
const scheduler = new Scheduler({
timezone: 'Asia/Tokyo',
checkInterval: 60000, // Check every 60 seconds
logger: console.log,
})
// Define schedules
scheduler.schedule((schedule) => {
schedule.call(() => console.log('Hello!')).everyMinute()
})
// Start when app boots
scheduler.start()
// Stop on shutdown
process.on('SIGTERM', () => {
scheduler.stop()
})
Defining Schedules
Callbacks
scheduler.schedule((schedule) => {
schedule.call(async () => {
// Your task logic
await sendDailyReports()
}).daily().at('09:00')
})
Jobs
Dispatch queued jobs on a schedule:
import { SendWeeklyDigestJob } from '@/app/Jobs/SendWeeklyDigestJob'
scheduler.schedule((schedule) => {
schedule.job(SendWeeklyDigestJob, { userId: 'all' })
.weekly()
.sundays()
.at('09:00')
})
Shell Commands
scheduler.schedule((schedule) => {
schedule.command('bunx guren db:backup')
.daily()
.at('02:00')
.name('database-backup')
})
Frequency Options
Minutes
schedule.call(task).everyMinute() // Every minute
schedule.call(task).everyTwoMinutes() // Every 2 minutes
schedule.call(task).everyThreeMinutes() // Every 3 minutes
schedule.call(task).everyFourMinutes() // Every 4 minutes
schedule.call(task).everyFiveMinutes() // Every 5 minutes
schedule.call(task).everyTenMinutes() // Every 10 minutes
schedule.call(task).everyFifteenMinutes()// Every 15 minutes
schedule.call(task).everyThirtyMinutes() // Every 30 minutes
Hours
schedule.call(task).hourly() // Every hour at :00
schedule.call(task).hourlyAt(15) // Every hour at :15
schedule.call(task).everyTwoHours() // Every 2 hours
schedule.call(task).everyThreeHours() // Every 3 hours
schedule.call(task).everyFourHours() // Every 4 hours
schedule.call(task).everySixHours() // Every 6 hours
Days
schedule.call(task).daily() // Every day at midnight
schedule.call(task).dailyAt('13:00') // Every day at 1 PM
schedule.call(task).at('13:00') // Alias for dailyAt
schedule.call(task).twiceDaily(1, 13) // At 1 AM and 1 PM
Weeks
schedule.call(task).weekly() // Every Sunday at midnight
schedule.call(task).weeklyOn(1, '08:00') // Every Monday at 8 AM
// Day shortcuts
schedule.call(task).daily().sundays()
schedule.call(task).daily().mondays()
schedule.call(task).daily().tuesdays()
schedule.call(task).daily().wednesdays()
schedule.call(task).daily().thursdays()
schedule.call(task).daily().fridays()
schedule.call(task).daily().saturdays()
// Weekdays and weekends
schedule.call(task).daily().weekdays() // Monday-Friday
schedule.call(task).daily().weekends() // Saturday-Sunday
Months and Years
schedule.call(task).monthly() // 1st of month at midnight
schedule.call(task).monthlyOn(15, '09:00') // 15th at 9 AM
schedule.call(task).lastDayOfMonth('18:00') // Last day at 6 PM
schedule.call(task).quarterly() // Jan, Apr, Jul, Oct 1st
schedule.call(task).yearly() // Jan 1st at midnight
schedule.call(task).yearlyOn(6, 15, '12:00') // June 15th at noon
Custom Cron
// Standard cron format: minute hour day-of-month month day-of-week
schedule.call(task).cron('0 */2 * * *') // Every 2 hours
schedule.call(task).cron('30 9 * * 1-5') // 9:30 AM weekdays
schedule.call(task).cron('0 0 1,15 * *') // 1st and 15th at midnight
Task Configuration
Task Names
schedule.call(sendReports)
.daily()
.name('send-daily-reports') // Unique identifier for the task
Timezones
schedule.call(task)
.daily()
.at('09:00')
.tz('Asia/Tokyo') // Run at 9 AM Tokyo time
// Or using setTimezone
schedule.call(task)
.daily()
.setTimezone('America/New_York')
Preventing Overlaps
Prevent a task from running if a previous instance is still executing:
schedule.call(longRunningTask)
.everyMinute()
.preventOverlapping() // Skip if previous run hasn't finished
// With expiration (auto-unlock after 10 minutes)
schedule.call(task)
.everyMinute()
.preventOverlapping(600000) // 10 minutes in ms
Running on One Server
For multi-server deployments, ensure task runs on only one server:
schedule.call(task)
.daily()
.runOnOneServer() // Requires distributed lock (Redis)
Conditional Execution
// Run only if condition is true
schedule.call(task)
.daily()
.when(() => process.env.NODE_ENV === 'production')
// Skip if condition is true
schedule.call(task)
.daily()
.skip(() => isMaintenanceMode())
Lifecycle Hooks
schedule.call(sendEmails)
.daily()
.at('09:00')
.before(() => console.log('Starting email send...'))
.after(() => console.log('Email send complete'))
.onSuccess(() => metrics.increment('emails.sent'))
.onFailure((error) => {
alerting.notify('Email send failed', error)
})
Scheduler API
const scheduler = new Scheduler()
// Define tasks
scheduler.schedule((schedule) => { ... })
// Add pre-built task
scheduler.addTask(scheduledTask)
// Start/stop
scheduler.start()
scheduler.stop()
// Check status
scheduler.getIsRunning()
// Get tasks
scheduler.getTasks() // All tasks
scheduler.getDueTasks() // Tasks due now
scheduler.getTask('task-name') // Task by name
scheduler.count() // Number of tasks
// Manage tasks
scheduler.removeTask('task-name') // Remove by name
scheduler.clear() // Remove all tasks
// Manual execution
await scheduler.runDueTasks() // Run all due tasks now
CLI Integration
Run the scheduler from the command line:
# Start the scheduler
bunx guren schedule:work
# Start with custom timezone
bunx guren schedule:work --timezone=Asia/Tokyo
# List scheduled tasks
bunx guren schedule:list
# Run a specific task immediately
bunx guren schedule:run cleanup-sessions
Testing
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'
import { Scheduler, Schedule } from '@guren/core'
describe('Scheduling', () => {
let scheduler: Scheduler
beforeEach(() => {
scheduler = new Scheduler()
})
afterEach(() => {
scheduler.stop()
})
test('schedules daily task', () => {
scheduler.schedule((schedule) => {
schedule.call(() => {}).daily().at('09:00').name('test-task')
})
expect(scheduler.count()).toBe(1)
expect(scheduler.getTask('test-task')).toBeDefined()
})
test('identifies due tasks', () => {
const mockTask = mock(() => {})
scheduler.schedule((schedule) => {
schedule.call(mockTask).everyMinute()
})
const dueTasks = scheduler.getDueTasks(new Date())
expect(dueTasks.length).toBeGreaterThan(0)
})
test('runs due tasks', async () => {
let executed = false
scheduler.schedule((schedule) => {
schedule.call(() => { executed = true }).everyMinute()
})
await scheduler.runDueTasks()
expect(executed).toBe(true)
})
test('respects when condition', async () => {
let executed = false
scheduler.schedule((schedule) => {
schedule.call(() => { executed = true })
.everyMinute()
.when(() => false) // Never run
})
await scheduler.runDueTasks()
expect(executed).toBe(false)
})
})
Best Practices
Name your tasks: Always use
.name()for easier debugging and management.Use appropriate frequencies: Don't schedule tasks more often than needed.
Set timezones explicitly: Avoid ambiguity by setting timezone for time-sensitive tasks.
Prevent overlapping for long tasks: Use
.preventOverlapping()for tasks that may take longer than their interval.Handle failures gracefully: Use
.onFailure()to log errors and send alerts.Test schedules: Write tests to verify your task scheduling logic.
Monitor task execution: Log task runs and track success/failure metrics.
Use jobs for heavy work: Dispatch jobs instead of running heavy tasks directly in the scheduler.