spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1const MINUTE = 60_000;2const HOUR = 60 * MINUTE;3const DAY = 24 * HOUR;45/**6 * Approximate period of a 5-field cron expression (minute hour day-of-month month day-of-week),7 * used for staleness checks ("no success within 2× the schedule interval"). Exact evaluation is8 * not needed: monthly → 31 d, weekly → 7 d, daily → 1 d (÷ number of listed hours), hourly → 1 h9 * (or N minutes for step expressions). Returns null for malformed expressions.10 */11export function scheduleIntervalMs(cron: string | undefined | null): number | null {12 if (!cron) return null;13 const f = cron.trim().split(/\s+/);14 if (f.length !== 5) return null;15 const [minute, hour, dom, month, dow] = f as [string, string, string, string, string];16 if (month !== '*' || dom !== '*') return 31 * DAY;17 if (dow !== '*') return (7 * DAY) / Math.max(1, listCount(dow));18 if (hour !== '*') {19 const step = stepOf(hour);20 if (step) return step * HOUR;21 return DAY / Math.max(1, listCount(hour));22 }23 if (minute !== '*') {24 const step = stepOf(minute);25 if (step) return step * MINUTE;26 return HOUR / Math.max(1, listCount(minute));27 }28 return MINUTE;29}3031function listCount(field: string): number {32 return field.split(',').reduce((n, part) => {33 const m = /^(\d+)-(\d+)$/.exec(part);34 return n + (m ? Math.max(1, Number(m[2]) - Number(m[1]) + 1) : 1);35 }, 0);36}3738function stepOf(field: string): number | null {39 const m = /^\*\/(\d+)$/.exec(field);40 return m ? Math.max(1, Number(m[1])) : null;41}4243/** Stale = no success for more than `factor` × the schedule interval (default 2×). */44export function isStale(lastSuccessAt: Date | string | null | undefined, cron: string | undefined | null, now = Date.now(), factor = 2): { stale: boolean; ageMs: number | null; limitMs: number | null } {45 const limitMs = scheduleIntervalMs(cron);46 const ageMs = lastSuccessAt ? now - new Date(lastSuccessAt).getTime() : null;47 if (limitMs === null) return { stale: false, ageMs, limitMs: null };48 if (ageMs === null) return { stale: true, ageMs: null, limitMs: limitMs * factor };49 return { stale: ageMs > limitMs * factor, ageMs, limitMs: limitMs * factor };50}5152/** Human duration ("3 d 4 h", "12 min"). */53export function humanDuration(ms: number | null | undefined): string {54 if (ms === null || ms === undefined || !Number.isFinite(ms)) return '-';55 const s = Math.round(ms / 1000);56 if (s < 60) return `${s} s`;57 const m = Math.floor(s / 60);58 if (m < 60) return `${m} min`;59 const h = Math.floor(m / 60);60 if (h < 48) return `${h} h ${m % 60} min`;61 const d = Math.floor(h / 24);62 return `${d} d ${h % 24} h`;63}64