workflow-cron-sleep
v1.0.2
Published
Sleep until the next cron expression occurrence in Vercel Workflows
Maintainers
Readme
workflow-cron-sleep
Sleep until the next cron expression occurrence in Vercel Workflows.
What This Does
cronSleep pauses your workflow until the next single occurrence of a cron expression. It is not a recurring cron job scheduler.
// This sleeps ONCE until the next 9 AM, then continues execution
await cronSleep("0 9 * * *", { timezone: "America/New_York" })If you need recurring behavior, you'll need to structure your workflows to handle that (see Recurring Workflows below).
Installation
npm install workflow-cron-sleepQuick Start
import { cronSleep } from "workflow-cron-sleep"
async function myWorkflow() {
"use workflow"
// Sleep until the next 9 AM Eastern Time
await cronSleep("0 9 * * *", { timezone: "America/New_York" })
// This runs once at 9 AM, then the workflow ends
console.log("It's 9 AM!")
}Timezone Behavior
Important: When no timezone is specified, cronSleep uses the local system timezone of the server running your workflow. On Vercel, this depends on the region your function is deployed to.
For predictable behavior, always specify a timezone:
// Recommended: Explicit timezone
await cronSleep("0 9 * * *", { timezone: "America/New_York" })
// UTC for timezone-agnostic scheduling
await cronSleep("0 14 * * *", { timezone: "UTC" })
// Not recommended: Server's local timezone (varies by region)
await cronSleep("0 9 * * *")API
cronSleep(cronExpression, options?)
Sleeps until the next occurrence of a cron expression.
Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| cronExpression | string | A valid cron expression (e.g., "0 9 * * *") |
| options.timezone | string (optional) | IANA timezone (e.g., "America/New_York", "UTC") |
Returns
Promise<void> - Resolves when the workflow resumes at the scheduled time.
Throws
Throws an error if the cron expression is invalid or has no future occurrences.
Cron Expression Reference
| Expression | Description |
|------------|-------------|
| 0 9 * * * | Every day at 9:00 AM |
| 30 8 * * 1 | Every Monday at 8:30 AM |
| 0 0 1 * * | First day of every month at midnight |
| 0 */2 * * * | Every 2 hours |
| 0 9 * * 1-5 | Weekdays at 9:00 AM |
| 59 23 L * * | Last day of every month at 11:59 PM |
Recurring Workflows
Since cronSleep only sleeps until the next occurrence, you need to design your workflow architecture to handle recurring schedules.
Pattern: While Loop Scheduler
The simplest approach is a single workflow with a while (true) loop that sleeps and triggers your business logic workflow repeatedly:
workflows/report-scheduler.ts - The scheduler workflow
import { cronSleep } from "workflow-cron-sleep"
import { triggerReportProcessor } from "./steps"
export interface ReportSchedulerInput {
reportId: string
cronExpression: string
}
export async function reportScheduler(input: ReportSchedulerInput) {
"use workflow"
const { reportId, cronExpression } = input
while (true) {
// Wait for the next scheduled time
await cronSleep(cronExpression, { timezone: "America/New_York" })
// Trigger the processor workflow
await triggerReportProcessor(reportId)
}
}workflows/steps.ts - Step function to start the processor
import { start } from "workflow/api"
import { reportProcessorWorkflow } from "./report-processor"
export async function triggerReportProcessor(reportId: string) {
"use step"
await start(reportProcessorWorkflow, [{ reportId }])
}workflows/report-processor.ts - The processor workflow (business logic)
export interface ReportProcessorInput {
reportId: string
}
export async function reportProcessorWorkflow(input: ReportProcessorInput) {
"use workflow"
const { reportId } = input
// Your business logic here - completely agnostic to scheduling
await generateReport(reportId)
await sendNotifications(reportId)
}Starting and Canceling
import { start, getRun } from "workflow/api"
import { reportScheduler } from "./workflows/report-scheduler"
// Start the cron scheduler
const run = await start(reportScheduler, [{
reportId: "daily-report",
cronExpression: "0 9 * * *"
}])
// Save the run ID to cancel later
await db.cronJobs.create({ runId: run.id })
// To cancel the cron (stops the while loop)
const currentRun = getRun(run.id)
await currentRun.cancel()This pattern keeps your scheduling and business logic decoupled. The scheduler runs forever, triggering your processor workflow on schedule. Save the runId so you can cancel if the cron expression changes or the job is no longer needed.
Requirements
workflowpackage (Vercel Workflows SDK)
License
MIT
