Skip to content

Cron Scheduler

The Cron Scheduler module provides task scheduling with full cron expression support, retries, and error handling.

Overview

Schedule recurring tasks using familiar cron syntax:

  • 5-field expressions - minute, hour, day, month, weekday
  • 6-field expressions - adds seconds
  • Named values - MON, JAN, etc.
  • Ranges and steps - 1-5, */15
  • Automatic retries - configurable retry policy

Installation

typescript
import {
  Scheduler,
  createScheduler,
  parseCron,
  JobConfig,
} from '@aiversum/aiv-agents/cron';

Basic Usage

Creating a Scheduler

typescript
const scheduler = createScheduler({
  timezone: 'Europe/Warsaw',
  defaultRetries: 3,
});

// Add a job
scheduler.addJob({
  id: 'daily-report',
  schedule: '0 9 * * *', // Every day at 9:00 AM
  handler: async () => {
    console.log('Generating daily report...');
    // Your logic here
  },
});

// Start the scheduler
scheduler.start();

One-Time Execution

typescript
// Execute a job immediately
await scheduler.runJob('daily-report');

// Execute with custom context
await scheduler.runJob('daily-report', { force: true });

Cron Expression Syntax

5-Field Format (Standard)

┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12 or JAN-DEC)
│ │ │ │ ┌───────────── day of week (0-6 or SUN-SAT)
│ │ │ │ │
* * * * *

6-Field Format (With Seconds)

┌───────────── second (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12 or JAN-DEC)
│ │ │ │ │ ┌───────────── day of week (0-6 or SUN-SAT)
│ │ │ │ │ │
* * * * * *

Examples

ExpressionDescription
* * * * *Every minute
0 * * * *Every hour at minute 0
0 9 * * *Every day at 9:00 AM
0 9 * * MON-FRIWeekdays at 9:00 AM
*/15 * * * *Every 15 minutes
0 0 1 * *First day of each month
30 4 * * SUNSundays at 4:30 AM
0 0,12 * * *Midnight and noon
0 9-17 * * *Every hour 9 AM to 5 PM

Special Characters

CharacterDescriptionExample
*Any value* * * * *
,List of values1,15,30 * * * *
-Range9-17 * * * *
/Step*/5 * * * *

Named Values

Days of Week:SUN, MON, TUE, WED, THU, FRI, SAT

Months:JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC

Job Configuration

Full Configuration

typescript
interface JobConfig {
  // Required
  id: string;              // Unique job identifier
  schedule: string;        // Cron expression
  handler: JobHandler;     // Async function to execute

  // Optional
  name?: string;           // Human-readable name
  description?: string;    // Job description
  enabled?: boolean;       // Enable/disable (default: true)
  timezone?: string;       // Override scheduler timezone

  // Retry policy
  retries?: number;        // Max retry attempts
  retryDelay?: number;     // Delay between retries (ms)
  backoff?: 'linear' | 'exponential';

  // Timeout
  timeout?: number;        // Max execution time (ms)

  // Concurrency
  allowConcurrent?: boolean; // Allow parallel runs

  // Metadata
  tags?: string[];         // For filtering/grouping
  priority?: number;       // Execution priority
}

Example: Complex Job

typescript
scheduler.addJob({
  id: 'email-digest',
  name: 'Daily Email Digest',
  description: 'Send daily summary email to subscribers',
  schedule: '0 8 * * MON-FRI',
  timezone: 'Europe/Warsaw',

  handler: async (context) => {
    const { jobId, runId, attempt } = context;
    console.log(`Running job ${jobId} (attempt ${attempt})`);

    // Fetch data
    const data = await fetchDailyData();

    // Send emails
    await sendDigest(data);

    return { sent: data.length };
  },

  // Retry 3 times with exponential backoff
  retries: 3,
  retryDelay: 5000,
  backoff: 'exponential',

  // Timeout after 5 minutes
  timeout: 300000,

  // Don't allow parallel runs
  allowConcurrent: false,

  tags: ['email', 'daily'],
  priority: 10,
});

Scheduler Events

typescript
scheduler.on('job:start', ({ jobId, runId }) => {
  console.log(`Job ${jobId} started (run: ${runId})`);
});

scheduler.on('job:complete', ({ jobId, runId, result, duration }) => {
  console.log(`Job ${jobId} completed in ${duration}ms`);
});

scheduler.on('job:error', ({ jobId, runId, error, attempt, willRetry }) => {
  console.error(`Job ${jobId} failed:`, error);
  if (willRetry) {
    console.log(`Will retry (attempt ${attempt})`);
  }
});

scheduler.on('job:retry', ({ jobId, attempt, delay }) => {
  console.log(`Retrying job ${jobId} in ${delay}ms (attempt ${attempt})`);
});

scheduler.on('scheduler:start', () => {
  console.log('Scheduler started');
});

scheduler.on('scheduler:stop', () => {
  console.log('Scheduler stopped');
});

Managing Jobs

List Jobs

typescript
const jobs = scheduler.listJobs();
// Returns array of job configs

const activeJobs = scheduler.listJobs({ enabled: true });
const emailJobs = scheduler.listJobs({ tags: ['email'] });

Get Job Status

typescript
const status = scheduler.getJobStatus('daily-report');
// {
//   id: 'daily-report',
//   enabled: true,
//   lastRun: Date,
//   lastResult: 'success' | 'failure',
//   nextRun: Date,
//   runCount: 42,
//   failureCount: 2,
// }

Update Job

typescript
// Disable a job
scheduler.updateJob('daily-report', { enabled: false });

// Change schedule
scheduler.updateJob('daily-report', { schedule: '0 10 * * *' });

Remove Job

typescript
scheduler.removeJob('daily-report');

Cron Parser

Use the parser directly for validation:

typescript
import { parseCron, getNextRun, isValidCron } from '@aiversum/aiv-agents/cron';

// Validate expression
const valid = isValidCron('0 9 * * MON');
console.log(valid); // true

// Parse expression
const parsed = parseCron('0 9 * * MON');
console.log(parsed);
// {
//   minute: [0],
//   hour: [9],
//   dayOfMonth: [1-31],
//   month: [1-12],
//   dayOfWeek: [1], // Monday
// }

// Get next run time
const next = getNextRun('0 9 * * MON');
console.log(next); // Date object for next Monday 9:00

Execution History

typescript
// Get recent executions
const history = scheduler.getHistory('daily-report', { limit: 10 });

// Each entry:
// {
//   runId: string,
//   startTime: Date,
//   endTime: Date,
//   duration: number,
//   result: 'success' | 'failure',
//   error?: string,
//   attempt: number,
// }

Best Practices

1. Use Meaningful IDs

typescript
// Good
scheduler.addJob({ id: 'send-weekly-report', ... });
scheduler.addJob({ id: 'cleanup-temp-files', ... });

// Bad
scheduler.addJob({ id: 'job1', ... });
scheduler.addJob({ id: 'cron', ... });

2. Handle Errors Gracefully

typescript
scheduler.addJob({
  id: 'important-job',
  schedule: '0 * * * *',
  handler: async () => {
    try {
      await riskyOperation();
    } catch (error) {
      // Log and optionally notify
      console.error('Job failed:', error);
      await sendAlert(error);
      throw error; // Re-throw to trigger retry
    }
  },
  retries: 3,
});

3. Use Timeouts

typescript
scheduler.addJob({
  id: 'api-call',
  schedule: '*/5 * * * *',
  timeout: 30000, // 30 seconds max
  handler: async () => {
    // Will be cancelled if takes > 30s
    await longRunningApiCall();
  },
});

4. Avoid Concurrent Runs (When Needed)

typescript
scheduler.addJob({
  id: 'database-backup',
  schedule: '0 2 * * *',
  allowConcurrent: false, // Prevent overlapping runs
  handler: async () => {
    await performBackup();
  },
});

API Reference

See the Scheduler API Reference for complete documentation.

Testing

bash
# Run cron module tests
npm test -- --grep "cron"

# Test count: 86 tests

Released under the MIT License.