UNPKG

@penkov/tasks_queue

Version:

A lightweight PostgreSQL-backed task queue system with scheduling, retries, backoff strategies, and priority handling. Designed for efficiency and observability in modern Node.js applications.

69 lines (68 loc) 3.08 kB
import { option } from "scats"; import { CronExpressionUtils } from "./cron-expression-utils.js"; import { MissedRunStrategy, TaskPeriodType } from "./tasks-model.js"; export class PeriodicScheduleUtils { static resolveRepeatInterval(task, periodType) { if (periodType === TaskPeriodType.cron) { return null; } const periodicTask = task; if (periodicTask.period === undefined || periodicTask.period === null) { throw new Error(`Periodic task with type='${periodType}' must define 'period' in milliseconds`); } return periodicTask.period; } static resolveCronExpression(task, periodType) { if (periodType !== TaskPeriodType.cron) { return null; } const cronTask = task; const cronExpression = option(cronTask.cronExpression) .map((value) => value.trim()) .filter((value) => value.length > 0); if (cronExpression.isEmpty) { throw new Error("Periodic task with type='cron' must define non-empty 'cronExpression'"); } return cronExpression.get; } static calculateNextStartAfter(task, now) { return task.repeatType === TaskPeriodType.cron ? this.calculateNextCronStartAfter(task, now) : this.calculateNextIntervalStartAfter(task, now); } static calculateNextCronStartAfter(task, now) { const cronExpression = task.cronExpression.match({ some: (value) => value, none: () => { throw new Error("Cron periodic task must define 'cron_expression'"); }, }); const baseDate = task.missedRunsStrategy === MissedRunStrategy.catch_up ? task.startAfter : now; return CronExpressionUtils.nextExecutionDate(cronExpression, baseDate); } static calculateNextIntervalStartAfter(task, now) { const repeatInterval = task.repeatInterval.match({ some: (value) => value, none: () => { throw new Error(`Periodic task with type='${task.repeatType}' must define 'repeat_interval'`); }, }); switch (task.repeatType) { case TaskPeriodType.fixed_rate: return task.missedRunsStrategy === MissedRunStrategy.catch_up ? new Date(task.startAfter.getTime() + repeatInterval) : this.calculateFixedRateSkipMissedNextStartAfter(task.initialStart, now, repeatInterval); case TaskPeriodType.fixed_delay: return new Date(now.getTime() + repeatInterval); default: throw new Error(`Unsupported periodic task type: ${task.repeatType}`); } } static calculateFixedRateSkipMissedNextStartAfter(initialStart, now, repeatInterval) { const elapsed = now.getTime() - initialStart.getTime(); const periods = Math.max(1, Math.floor(elapsed / repeatInterval) + 1); return new Date(initialStart.getTime() + periods * repeatInterval); } }