import type { Task } from "./task.ts" export interface RunLogEntry { name: string time: Date } export interface ErrLogEntry { name: string time: Date reason: string } export interface SchedulerOptions { tasks: Task[] maxLogNumber?: number | undefined } export class Scheduler { protected options: SchedulerOptions protected isRunning: boolean protected maxLogNumber: number protected timers: Set> protected runLog: RunLogEntry[] = [] protected errLog: ErrLogEntry[] = [] constructor(options: SchedulerOptions) { this.options = options this.isRunning = false this.maxLogNumber = options.maxLogNumber ?? 100 this.timers = new Set() } addTask(task: Task): void { this.options.tasks.push(task) } addRunLog(runLog: RunLogEntry): void { this.runLog.push(runLog) if (this.runLog.length > this.maxLogNumber) { this.runLog = this.runLog.slice(this.maxLogNumber * -1) } } addErrLog(errLog: ErrLogEntry): void { this.errLog.push(errLog) if (this.errLog.length > this.maxLogNumber) { this.errLog = this.errLog.slice(this.maxLogNumber * -1) } } getRunLog(): RunLogEntry[] { return this.runLog } getErrLog(): ErrLogEntry[] { return this.errLog } protected scheduleTask(task: Task): void { const nextRun = task.getCron().nextRun() if (nextRun === undefined) { return } const delay = Math.max(nextRun.getTime() - Date.now(), 0) const timer = setTimeout(() => { this.timers.delete(timer) if (this.isRunning !== true) { return } this.scheduleTask(task) void task .run() .catch((reason: unknown) => { this.addErrLog({ name: task.getName(), time: new Date(), reason: String(reason), }) }) .finally(() => { this.addRunLog({ name: task.getName(), time: new Date(), }) }) }, delay) this.timers.add(timer) } run(): void { if (this.isRunning === true) { throw new Error("Scheduler is already running") } else { this.isRunning = true } for (const task of this.options.tasks) { this.scheduleTask(task) } } }