@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
JavaScript
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);
}
}