import cronParser from "cron-parser"; import cron from "node-cron"; import type { CronJob } from "./types.ts"; export class CronScheduler { private scheduledTasks = new Map(); private jobs = new Map(); schedule(job: CronJob, callback: () => void | Promise): boolean { if (!cron.validate(job.schedule)) return false; this.unschedule(job.id); const task = cron.schedule(job.schedule, () => { job.lastRun = new Date(); Promise.resolve(callback()) .catch((err: unknown) => console.error(`Cron job '${job.id}' failed:`, err)) .finally(() => this.updateNextRun(job)); }); this.scheduledTasks.set(job.id, task); this.jobs.set(job.id, job); this.updateNextRun(job); return true; } unschedule(jobId: string): boolean { const task = this.scheduledTasks.get(jobId); if (!task) return false; task.stop(); this.scheduledTasks.delete(jobId); this.jobs.delete(jobId); return true; } startAll(): void { for (const task of this.scheduledTasks.values()) task.start(); } stopAll(): void { for (const task of this.scheduledTasks.values()) task.stop(); } getJob(jobId: string): CronJob | undefined { return this.jobs.get(jobId); } getAllJobs(): CronJob[] { return Array.from(this.jobs.values()); } private updateNextRun(job: CronJob): void { try { const interval = cronParser.parseExpression(job.schedule); job.nextRun = interval.next().toDate(); } catch { // invalid expression — leave nextRun unchanged } } static validateExpression(expression: string): boolean { return cron.validate(expression); } }