Cron Package

The @hazeljs/cron package provides scheduled task capabilities for your HazelJS applications. It allows you to run functions at specified intervals using cron expressions, with support for job management, error handling, and execution tracking.

Purpose

Many applications need to run periodic tasks like data cleanup, report generation, cache warming, or sending notifications. Implementing scheduled tasks requires managing cron expressions, handling errors, tracking execution, and ensuring tasks don't overlap. The @hazeljs/cron package simplifies this by providing:

  • Cron Expressions: Support for standard cron syntax for flexible scheduling
  • Decorator-Based: Use @Cron decorator to mark methods as scheduled tasks
  • Job Management: Start, stop, enable, and disable jobs programmatically
  • Error Handling: Built-in error handling with callbacks
  • Execution Tracking: Monitor job execution, run counts, and timing

Architecture

The package uses a service-based approach with job registration and lifecycle management:

Loading diagram...

Key Components

  1. CronService: Manages all scheduled jobs
  2. CronJob: Individual job instance with execution logic
  3. @Cron Decorator: Declarative way to create scheduled tasks
  4. Job Options: Configuration for execution behavior

Advantages

1. Simple API

Use decorators to create scheduled tasks—no need to manually manage timers or intervals.

2. Flexible Scheduling

Support for standard cron expressions allows precise control over when tasks run.

3. Job Management

Programmatically control jobs—start, stop, enable, or disable them as needed.

4. Error Resilience

Built-in error handling ensures one failing job doesn't crash your application.

5. Monitoring

Track job execution, run counts, and timing for better observability.

6. Production Features

Support for max runs, run-on-init, and execution callbacks for complex scenarios.

Installation

npm install @hazeljs/cron

Quick Start

Basic Setup

import { HazelModule } from '@hazeljs/core';
import { CronModule } from '@hazeljs/cron';

@HazelModule({
  imports: [CronModule],
})
export class AppModule {}

Cron Service

Registering Jobs

Use the CronService to register and manage scheduled jobs:

import { Injectable, OnModuleInit } from '@hazeljs/core';
import { CronService } from '@hazeljs/cron';

@Injectable()
export class ScheduledTasksService implements OnModuleInit {
  constructor(private readonly cronService: CronService) {}

  onModuleInit() {
    // Run every minute
    this.cronService.registerJob(
      'cleanup-temp-files',
      '0 * * * * *', // Every minute
      async () => {
        await this.cleanupTempFiles();
      },
      {
        enabled: true,
        runOnInit: false,
      }
    );

    // Run every hour
    this.cronService.registerJob(
      'send-reports',
      '0 0 * * * *', // Every hour
      async () => {
        await this.sendHourlyReports();
      }
    );
  }

  private async cleanupTempFiles() {
    // Cleanup logic
  }

  private async sendHourlyReports() {
    // Report sending logic
  }
}

@Cron Decorator

The @Cron decorator is a method decorator that marks methods as scheduled tasks. It automatically registers them with the CronService when the module initializes.

Understanding @Cron Decorator

Purpose: Declaratively define scheduled tasks using cron expressions. The framework automatically manages job lifecycle, execution, and error handling.

How it works:

  1. Metadata Storage: Stores cron configuration in method metadata
  2. Module Initialization: When module loads, CronService scans for @Cron decorators
  3. Job Registration: Each decorated method is registered as a cron job
  4. Automatic Scheduling: Jobs are scheduled based on cron expressions
  5. Execution Management: Framework handles timing, execution, and error recovery

Configuration Options:

interface CronOptions {
  cronTime: string;        // Cron expression (required)
  name?: string;           // Job name (default: ClassName.methodName)
  enabled?: boolean;       // Enable/disable job (default: true)
  runOnInit?: boolean;     // Run immediately on startup (default: false)
  maxRuns?: number;        // Maximum number of executions (optional)
  onComplete?: () => void;  // Callback after successful execution
  onError?: (error: Error) => void; // Error handler callback
}

Example with Detailed Explanation:

import { Injectable } from '@hazeljs/core';
import { Cron } from '@hazeljs/cron';

@Injectable()
export class TaskService {
  // @Cron is a method decorator that marks this method as a scheduled task
  @Cron({
    cronTime: '0 0 * * * *', // Cron expression: every hour at minute 0
    // Format: second minute hour day month weekday
    // '0 0 * * * *' = at 0 seconds, 0 minutes, every hour
    name: 'hourly-task',     // Custom name for this job
    enabled: true,            // Job is active (can be disabled programmatically)
    runOnInit: false,        // Don't run immediately on startup
    // Set to true if you want the task to run when the app starts
  })
  async hourlyTask() {
    // This method will be called every hour
    // Framework handles:
    // - Scheduling the execution
    // - Error handling
    // - Preventing overlapping executions
    // - Logging execution status
    console.log('Running hourly task');
  }

  @Cron({
    cronTime: '0 0 0 * * *', // Every day at midnight (00:00:00)
    name: 'daily-backup',
    runOnInit: true,          // Run immediately when app starts
    // Useful for ensuring backup runs even if app restarts
  })
  async dailyBackup() {
    // Runs once per day at midnight
    // Also runs immediately when application starts (runOnInit: true)
    console.log('Running daily backup');
  }

  @Cron({
    cronTime: '0 */5 * * * *', // Every 5 minutes
    // */5 means "every 5 units"
    name: 'health-check',
    maxRuns: 10,               // Stop after 10 executions
    // Useful for one-time tasks or limited-run jobs
    onError: (error) => {
      // Custom error handling
      console.error('Health check failed:', error);
      // Could send alert, log to monitoring service, etc.
    },
  })
  async healthCheck() {
    // Runs every 5 minutes, but stops after 10 runs
    // After 10 executions, the job is automatically stopped
    console.log('Running health check');
  }

  @Cron({
    cronTime: '0 0 9 * * 1', // Every Monday at 9 AM
    // 1 = Monday (0 = Sunday, 1 = Monday, ..., 6 = Saturday)
    name: 'weekly-report',
    onComplete: () => {
      // Callback after successful execution
      console.log('Weekly report generated successfully');
    },
  })
  async generateWeeklyReport() {
    // Runs every Monday at 9:00 AM
    // onComplete callback executes after method finishes successfully
    console.log('Generating weekly report');
  }
}

Cron Expression Format:

The cron expression uses 6 fields: second minute hour day month weekday

┌───────────── second (0 - 59)
│ ┌─────────── minute (0 - 59)
│ │ ┌───────── hour (0 - 23)
│ │ │ ┌─────── day of month (1 - 31)
│ │ │ │ ┌───── month (1 - 12)
│ │ │ │ │ ┌─── day of week (0 - 7, 0 or 7 is Sunday)
│ │ │ │ │ │
* * * * * *

Special Characters:

  • * - Any value (every second, every minute, etc.)
  • , - Value list separator (e.g., 1,3,5 = 1, 3, or 5)
  • - - Range (e.g., 1-5 = 1 through 5)
  • / - Step values (e.g., */5 = every 5 units)
  • ? - No specific value (used in day fields)

Common Patterns Explained:

// Every second
'* * * * * *'
// Every field is *, so it runs every second

// Every 30 seconds
'*/30 * * * * *'
// */30 in seconds field = every 30 seconds

// Every minute (at :00 seconds)
'0 * * * * *'
// 0 in seconds = at second 0, * in minutes = every minute

// Every 5 minutes
'0 */5 * * * *'
// At second 0, every 5 minutes

// Every hour (at :00:00)
'0 0 * * * *'
// At second 0, minute 0, every hour

// Every day at midnight
'0 0 0 * * *'
// At 00:00:00 every day

// Every Monday at 9 AM
'0 0 9 * * 1'
// At 09:00:00, every Monday

// First day of month at midnight
'0 0 0 1 * *'
// At 00:00:00 on the 1st of every month

Job Lifecycle:

  1. Registration: Job registered when module initializes
  2. Scheduling: Framework calculates next execution time from cron expression
  3. Execution: Method called at scheduled time
  4. Completion: onComplete callback fired if provided
  5. Rescheduling: Next execution time calculated for subsequent runs
  6. Stopping: Job stops if maxRuns reached or manually stopped

Error Handling:

@Cron({
  cronTime: '0 * * * * *',
  name: 'error-prone-task',
  onError: (error: Error) => {
    // Custom error handling
    // This prevents the error from crashing your application
    console.error('Task failed:', error);
    
    // You can:
    // - Send alerts
    // - Log to monitoring service
    // - Retry logic
    // - Notify administrators
  },
})
async errorProneTask() {
  // If this throws an error, onError callback is called
  // The job continues to run on schedule despite errors
  throw new Error('Something went wrong');
}

Best Practices:

  1. Use descriptive names: Makes debugging and monitoring easier
  2. Handle errors: Always provide onError callback for critical tasks
  3. Avoid overlapping executions: Framework prevents this, but be aware of long-running tasks
  4. Test cron expressions: Use online cron validators before deploying
  5. Monitor execution: Track job execution times and frequencies
  6. Use runOnInit carefully: Only for tasks safe to run immediately on startup

Cron Expression Format

Cron expressions use the format: second minute hour day month weekday

* * * * * *
│ │ │ │ │ │
│ │ │ │ │ └─── Day of week (0-7, 0 or 7 is Sunday)
│ │ │ │ └───── Month (1-12)
│ │ │ └─────── Day of month (1-31)
│ │ └───────── Hour (0-23)
│ └─────────── Minute (0-59)
└───────────── Second (0-59)

Common Patterns

// Every second
'* * * * * *'

// Every 5 seconds
'*/5 * * * * *'

// Every minute
'0 * * * * *'

// Every 5 minutes
'0 */5 * * * *'

// Every hour
'0 0 * * * *'

// Every day at midnight
'0 0 0 * * *'

// Every Monday at 9 AM
'0 0 9 * * 1'

// Every first day of month at midnight
'0 0 0 1 * *'

Job Options

interface CronOptions {
  cronTime: string;        // Cron expression
  name?: string;           // Job name
  enabled?: boolean;       // Enable/disable job
  runOnInit?: boolean;     // Run immediately on registration
  maxRuns?: number;        // Maximum number of runs
  onComplete?: () => void; // Callback on completion
  onError?: (error: Error) => void; // Error handler
}

Job Management

Start/Stop Jobs

@Injectable()
export class JobManagerService {
  constructor(private readonly cronService: CronService) {}

  startJob(name: string) {
    this.cronService.startJob(name);
  }

  stopJob(name: string) {
    this.cronService.stopJob(name);
  }

  enableJob(name: string) {
    this.cronService.enableJob(name);
  }

  disableJob(name: string) {
    this.cronService.disableJob(name);
  }
}

Get Job Status

// Get status of a specific job
const status = this.cronService.getJobStatus('hourly-task');
console.log({
  name: status.name,
  isRunning: status.isRunning,
  lastExecution: status.lastExecution,
  nextExecution: status.nextExecution,
  runCount: status.runCount,
  enabled: status.enabled,
});

// Get status of all jobs
const allStatuses = this.cronService.getAllJobStatuses();

Job Control

// Stop all jobs
this.cronService.stopAll();

// Start all jobs
this.cronService.startAll();

// Clear all jobs
this.cronService.clearAll();

// Get job count
const count = this.cronService.getJobCount();

Error Handling

Handle errors in cron jobs:

@Cron({
  cronTime: '0 * * * * *',
  name: 'error-prone-task',
  onError: (error: Error) => {
    console.error('Cron job error:', error);
    // Send alert, log to monitoring service, etc.
  },
})
async errorProneTask() {
  // This might throw an error
  throw new Error('Something went wrong');
}

Complete Example

import { Injectable, OnModuleInit } from '@hazeljs/core';
import { CronService, Cron } from '@hazeljs/cron';

@Injectable()
export class ScheduledTasksService implements OnModuleInit {
  constructor(private readonly cronService: CronService) {}

  onModuleInit() {
    // Register jobs programmatically
    this.cronService.registerJob(
      'cleanup',
      '0 0 2 * * *', // Daily at 2 AM
      async () => {
        await this.cleanupOldData();
      },
      {
        enabled: true,
        onError: (error) => {
          console.error('Cleanup job failed:', error);
        },
      }
    );
  }

  // Use decorator for scheduled tasks
  @Cron({
    cronTime: '0 */10 * * * *', // Every 10 minutes
    name: 'sync-data',
    runOnInit: true, // Run immediately on startup
  })
  async syncData() {
    console.log('Syncing data...');
    // Sync logic
  }

  @Cron({
    cronTime: '0 0 * * * *', // Every hour
    name: 'send-notifications',
    maxRuns: 24, // Stop after 24 runs (24 hours)
  })
  async sendNotifications() {
    console.log('Sending notifications...');
    // Notification logic
  }

  private async cleanupOldData() {
    // Cleanup logic
  }
}

Best Practices

  1. Use descriptive names: Give your cron jobs meaningful names for easier debugging.

  2. Handle errors: Always implement error handlers to prevent job failures from crashing your application.

  3. Monitor execution: Track job execution times and frequencies.

  4. Limit runs: Use maxRuns for jobs that should only run a certain number of times.

  5. Test locally: Test cron expressions before deploying to production.

  6. Use runOnInit carefully: Only enable runOnInit for jobs that are safe to run immediately on startup.

What's Next?

  • Learn about Cache for caching cron job results
  • Explore Config for cron configuration
  • Check out Prisma for database operations in cron jobs