// rrule@2.8.1 lacks a proper "exports" field, so named ESM imports fail at // runtime with NodeNext resolution. Use the default import and destructure. import rrulePkg from "rrule"; import type { RRule as RRuleType } from "rrule"; const { RRule, rrulestr } = rrulePkg as unknown as typeof import("rrule"); import { DateTime } from "luxon"; export const MIN_INTERVAL_MS = 5 * 60 * 1000; export function parseRRule(rule: string): RRuleType { const parsed = rrulestr(rule); if (!(parsed instanceof RRule)) { throw new Error("Compound RRULE/RRSET not supported"); } return parsed; } export function nextOccurrence(rule: string, timezone: string, after: Date): Date | null { const parsed = parseRRule(rule); const afterInZone = DateTime.fromJSDate(after).setZone(timezone).toJSDate(); const next = parsed.after(afterInZone, false); return next ?? null; } export type IntervalCheck = { ok: true } | { ok: false; reason: string }; export function validateMinInterval(rule: string, timezone: string): IntervalCheck { const parsed = parseRRule(rule); const now = new Date(); const first = parsed.after(now, false); if (!first) return { ok: true }; const second = parsed.after(first, false); if (!second) return { ok: true }; const gap = second.getTime() - first.getTime(); if (gap < MIN_INTERVAL_MS) { return { ok: false, reason: `Recurrence fires every ${Math.round(gap / 1000)}s; minimum interval is ${MIN_INTERVAL_MS / 1000}s.`, }; } return { ok: true }; }