Scheduler

Schema-validated job scheduling with worker thread isolation, automatic retries, and event streaming.

Installation

npm install @cleverbrush/scheduler

Requires Node.js 16+ (uses worker_threads).

Key features

FeatureDescription
Worker thread isolationEach job runs in its own Worker thread — crashes never affect the scheduler
5 schedule typesMinute, day, week, month, year with configurable intervals
Automatic retriesmaxRetries for immediate retries; maxConsequentFails to auto-disable flaky jobs
Concurrency controlnoConcurrentRuns: true prevents overlapping executions
Event-drivenjob:start, job:end, job:error, job:timeout, job:message events with stdout/stderr streams
Timeout handlingPer-job timeout with automatic worker termination
Pluggable persistenceImplement IJobRepository for database-backed storage (default: in-memory)
Schema-validatedAll schedules validated with @cleverbrush/schema — export Schemas.ScheduleSchema for API validation

Basic usage

import { JobScheduler } from '@cleverbrush/scheduler';

const scheduler = new JobScheduler({
    rootFolder: '/path/to/jobs'
});

scheduler.addJob({
    id: 'cleanup',
    path: 'cleanup.js',
    schedule: { every: 'day', interval: 1, hour: 3, minute: 0 }
});

scheduler.addJob({
    id: 'weekly-report',
    path: 'report.js',
    schedule: {
        every: 'week',
        interval: 1,
        dayOfWeek: [1],  // Monday
        hour: 9,
        minute: 0
    },
    timeout: 1000 * 60 * 5,   // 5 min timeout
    maxRetries: 3,
    noConcurrentRuns: true
});

scheduler.start();

Event handling

scheduler.on('job:start', ({ jobId, instanceId, startDate, stdout, stderr }) => {
    console.log(`Job ${jobId} started at ${startDate}`);
    stdout.on('data', (chunk) => process.stdout.write(chunk));
});

scheduler.on('job:end', ({ jobId, endDate }) => {
    console.log(`Job ${jobId} completed at ${endDate}`);
});

scheduler.on('job:error', ({ jobId, error }) => {
    console.error(`Job ${jobId} failed:`, error);
});

scheduler.on('job:timeout', ({ jobId }) => {
    console.warn(`Job ${jobId} timed out — worker terminated`);
});

scheduler.on('job:message', ({ jobId, value }) => {
    console.log(`Message from ${jobId}:`, value);
});

Schedule types

TypeExampleRuns
minute{ every: "minute", interval: 15 }Every 15 minutes
day{ every: "day", interval: 1, hour: 3, minute: 0 }Daily at 03:00
week{ every: "week", interval: 1, dayOfWeek: [1, 5], hour: 9, minute: 0 }Mon & Fri at 09:00
month{ every: "month", interval: 1, dayOfMonth: 1, hour: 0, minute: 0 }1st of each month at midnight
year{ every: "year", interval: 1, month: 1, dayOfMonth: 1, hour: 0, minute: 0 }Jan 1 each year at midnight

Schedule calculator

Use ScheduleCalculator standalone to preview or test schedule dates without running jobs:

import { ScheduleCalculator } from '@cleverbrush/scheduler';

const calc = new ScheduleCalculator(
    { every: 'week', interval: 1, dayOfWeek: [1], hour: 9, minute: 0 },
    new Date('2025-01-01')
);

while (calc.hasNext()) {
    const next = calc.next();
    console.log(next); // 2025-01-06T09:00:00, 2025-01-13T09:00:00, ...
    if (next > someCutoff) break;
}

Custom persistence

By default jobs are stored in memory. Implement IJobRepository for durable storage:

const scheduler = new JobScheduler({
    rootFolder: '/path/to/jobs',
    persistRepository: myDatabaseRepository
});