n8n/packages/workflow/src/cron.ts
Emilia e2054f984c
feat(core): Route poll triggers through the durable scheduler (no-changelog) (#34733)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 07:32:08 +00:00

85 lines
2.4 KiB
TypeScript

import type { CronExpression } from './interfaces';
import { randomInt } from './utils';
interface BaseTriggerTime<T extends string> {
mode: T;
}
interface CustomTrigger extends BaseTriggerTime<'custom'> {
cronExpression: CronExpression;
}
interface EveryX<U extends string> extends BaseTriggerTime<'everyX'> {
unit: U;
value: number;
}
type EveryMinute = BaseTriggerTime<'everyMinute'>;
type EveryXMinutes = EveryX<'minutes'>;
interface EveryHour extends BaseTriggerTime<'everyHour'> {
minute: number; // 0 - 59
}
type EveryXHours = EveryX<'hours'>;
interface EveryDay extends BaseTriggerTime<'everyDay'> {
hour: number; // 0 - 23
minute: number; // 0 - 59
}
interface EveryWeek extends BaseTriggerTime<'everyWeek'> {
hour: number; // 0 - 23
minute: number; // 0 - 59
weekday: number; // 0 - 6(Sun - Sat)
}
interface EveryMonth extends BaseTriggerTime<'everyMonth'> {
hour: number; // 0 - 23
minute: number; // 0 - 59
dayOfMonth: number; // 1 - 31
}
export type TriggerTime =
| CustomTrigger
| EveryMinute
| EveryXMinutes
| EveryHour
| EveryXHours
| EveryDay
| EveryWeek
| EveryMonth;
export const toCronExpression = (item: TriggerTime): CronExpression => {
const randomSecond = randomInt(60);
if (item.mode === 'everyMinute') return `${randomSecond} * * * * *`;
if (item.mode === 'everyHour') return `${randomSecond} ${item.minute} * * * *`;
if (item.mode === 'everyX') {
if (item.unit === 'minutes') return `${randomSecond} */${item.value} * * * *`;
const randomMinute = randomInt(60);
if (item.unit === 'hours') return `${randomSecond} ${randomMinute} */${item.value} * * *`;
}
if (item.mode === 'everyDay') return `${randomSecond} ${item.minute} ${item.hour} * * *`;
if (item.mode === 'everyWeek')
return `${randomSecond} ${item.minute} ${item.hour} * * ${item.weekday}`;
if (item.mode === 'everyMonth')
return `${randomSecond} ${item.minute} ${item.hour} ${item.dayOfMonth} * *`;
return item.cronExpression.trim() as CronExpression;
};
/**
* Whether a cron fires more than once a minute. A 6-field expression has
* seconds as its first field: a single fixed second is minute-granular, but
* anything else there (wildcard, step, range, or list) is sub-minute. 5-field
* crons have no seconds field and are always minute-granular.
*/
export const isSubMinuteCron = (expression: CronExpression): boolean => {
const fields = expression.trim().split(/\s+/);
if (fields.length !== 6) return false;
return !/^\d+$/.test(fields[0]);
};