SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
2.6 KB · 64 lines typescript
Raw Blame History
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