@samveys/scheduler

Version
1.0.2Nov 5, 2026
Install
pnpm add @samveys/scheduler

From packages/scheduler/README.md in the framework repository.

Define scheduled tasks in code and run them from one cron entry.

text
* * * * * cd /srv/app && svs schedule:run

Define tasks

ts
import { cache, defineCacheConfig, memoryStore } from '@samveys/cache'
import { scheduler } from '@samveys/scheduler'

export const plugins = [
  cache(defineCacheConfig({ default: 'main', stores: { main: memoryStore() } })),
  scheduler({
    define(schedule) {
      schedule
        .call('reports', async () => {
          console.log('building reports')
        })
        .dailyAt('02:00')
        .timezone('Asia/Jakarta')
        .withoutOverlapping()
        .onOneServer()
        .pingBefore('https://monitor.example.com/start')
        .thenPing('https://monitor.example.com/done')

      schedule.call('heartbeat', () => {}).cron('*/5 * * * *')
      schedule.call('cleanup', () => {}).hourly()
      schedule.call('tick', () => {}).everyMinute()
    },
  }),
]
  • Cron: five fields (minute hour day-of-month month day-of-week) with *, lists, ranges and steps; 7 is Sunday. When both day fields are restricted, either may match (as in Vixie cron). Fields are evaluated with Temporal in the task's zone (default UTC), so dailyAt('02:00') with Asia/Jakarta runs at 19:00 UTC; a wall-clock time that does not exist on a spring-forward day does not run that day.
  • withoutOverlapping(ttlMs?): a cache lock; a run that starts while the previous one still holds it is skipped (skipped-overlap). The lock is released when the task ends and expires after ttlMs (default 24 h) if the process dies.
  • onOneServer(): each due minute is claimed with cache.lock(...). Servers that share a cache store (a database store, not a memory store) never both run the task for that minute.
  • pingBefore / thenPing: GET requests; a failed ping never fails the task, and thenPing only follows success.

Locks need the cache() plugin; without it the task fails with SVS_E102 and a message saying so.

Commands

sh
svs schedule:run    # run what is due this minute, once, and exit (exit code 1 if a task failed)
svs schedule:list   # name, cron, zone, next run

Parsing and next run

ts
import { nextCronRun, parseCron } from '@samveys/scheduler'

const cron = parseCron('0 9 * * 1-5')
const next = nextCronRun(cron, Temporal.Now.instant(), 'Europe/Berlin')
console.log(next.toString())