Scheduler
Contract
@modularityjs/scheduler defines the ScheduledJob interface, SchedulerService abstract class, and pool-based job discovery.
interface ScheduledJob {
readonly name: string;
readonly schedule: string; // Cron expression
readonly description?: string;
readonly lockTtlMs?: number; // Override default lock TTL
execute(): void | Promise<void>;
}abstract class SchedulerService {
abstract register(job: ScheduledJob): void;
abstract start(): void;
abstract stop(): Promise<void>;
abstract getJobNames(): string[];
abstract getJobs(): ReadonlyMap<string, JobInfo>;
abstract getNextRun(name: string): Date | null;
abstract runJob(name: string): Promise<void>;
}Drivers
Croner (@modularityjs/scheduler-croner)
Cron-based scheduler using Croner with distributed locking via LockService. Prevents concurrent execution of the same job across instances.
import { SchedulerModule } from '@modularityjs/scheduler';
import { SchedulerCronerModule } from '@modularityjs/scheduler-croner';
import { LockModule } from '@modularityjs/lock';
import { LockRedisModule } from '@modularityjs/lock-redis';
import { RedisModule } from '@modularityjs/redis';
const modules = [
RedisModule.forRoot({ host: 'localhost' }),
LockModule,
LockRedisModule,
SchedulerModule,
SchedulerCronerModule,
];The Croner driver requires a LockService provider. In single-instance setups, LockMemoryModule works. For multi-instance deployments, use a distributed lock driver like LockRedisModule.
Defining Jobs
Implement the ScheduledJob interface and register via ScheduledJobsPool:
import { Injectable } from '@modularityjs/di';
import { ScheduledJobsPool, SchedulerModule } from '@modularityjs/scheduler';
import type { ScheduledJob } from '@modularityjs/scheduler';
import { Module } from '@modularityjs/modularity';
@Injectable()
class BackupJob implements ScheduledJob {
readonly name = 'daily-backup';
readonly schedule = '0 2 * * *'; // 2:00 AM daily
readonly description = 'Daily database backup';
readonly lockTtlMs = 120_000; // 2-minute lock (default: 60s)
async execute() {
await performBackup();
}
}
@Module({
name: 'backup',
imports: [SchedulerModule],
providers: [BackupJob],
pools: [
{ pool: ScheduledJobsPool, key: 'daily-backup', useClass: BackupJob },
],
})
class BackupModule {}Distributed Locking
The Croner driver acquires a distributed lock before executing each job:
- Lock acquired -> job executes, lock released in
finally - Lock not acquired -> job skips (another instance is running it)
- Lock TTL expires -> lock auto-releases (safety against crashed instances)
Default lock TTL is 60 seconds. Override per job with lockTtlMs.
Programmatic Access
import { Inject, Injectable } from '@modularityjs/di';
import { SchedulerService } from '@modularityjs/scheduler';
@Injectable()
class AdminService {
constructor(
@Inject(SchedulerService) private readonly scheduler: SchedulerService,
) {}
listJobs() {
return this.scheduler.getJobs();
// Map<string, { name, schedule, description, running }>
}
nextRun(name: string) {
return this.scheduler.getNextRun(name);
}
async triggerManually(name: string) {
await this.scheduler.runJob(name);
}
}Graceful Shutdown
The driver waits up to 30 seconds for running jobs to complete before shutting down. Jobs that exceed this timeout are abandoned.