@cronvello/sdk 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/README.md +145 -8
- package/dist/cli.cjs +1092 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1088 -8
- package/dist/cli.js.map +1 -1
- package/dist/dev.cjs +1186 -0
- package/dist/dev.cjs.map +1 -0
- package/dist/dev.d.cts +318 -0
- package/dist/dev.d.ts +318 -0
- package/dist/dev.js +1173 -0
- package/dist/dev.js.map +1 -0
- package/dist/{dispatch-handler-DazGcFHz.d.cts → dispatch-handler-BoadpsL9.d.cts} +3 -4
- package/dist/{dispatch-handler-DazGcFHz.d.ts → dispatch-handler-BoadpsL9.d.ts} +3 -4
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.cjs +645 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.js +644 -7
- package/dist/index.js.map +1 -1
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/docs/dashboard.png +0 -0
- package/package.json +16 -3
package/dist/dev.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/cron-schedule.ts","../src/dev/engine.ts","../src/dev/dashboard-assets.ts","../src/dev/dashboard.ts"],"names":["http"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","file":"dev.cjs","sourcesContent":["/**\n * Cron schedule arithmetic for the local engine — next-occurrence, preview, and an upcoming-window\n * planner, all timezone-aware (IANA / DST) and **zero-dependency**.\n *\n * The cloud SDK only needs to *validate* a cron string (see `cron.ts`), because the server owns the\n * scheduler. The local engine has to actually *fire* jobs, so it needs the next time an expression\n * matches. This module is that calculator.\n *\n * How the timezone math works without a date library:\n * • A cron expression matches against **wall-clock** fields (the time a person reads off a clock in\n * the job's timezone), so the search is done on naive calendar fields and only converted to a\n * real epoch once a full match is found.\n * • To convert a wall-clock time in a zone to an absolute epoch we use the runtime's own IANA\n * database via `Intl.DateTimeFormat`: format a guess into the zone, measure the offset it\n * implies, and correct once. This is the standard offset-probe technique and it handles DST\n * transitions (a daily job at 04:00 stays at 04:00 local; the gap to the previous fire is 23h or\n * 25h across the spring/autumn switch).\n *\n * Supported syntax mirrors `validateCron`: 5-field crontab, an optional leading seconds field\n * (6 fields), the `@macros`, month/weekday names, and `*` / ranges / lists / steps. Day-of-month and\n * day-of-week combine with Vixie cron's OR semantics when both are restricted.\n */\n\nconst MACRO_EXPANSIONS: Record<string, string> = {\n \"@yearly\": \"0 0 1 1 *\",\n \"@annually\": \"0 0 1 1 *\",\n \"@monthly\": \"0 0 1 * *\",\n \"@weekly\": \"0 0 * * 0\",\n \"@daily\": \"0 0 * * *\",\n \"@midnight\": \"0 0 * * *\",\n \"@hourly\": \"0 * * * *\",\n};\n\nconst MONTH_NAMES = [\"jan\", \"feb\", \"mar\", \"apr\", \"may\", \"jun\", \"jul\", \"aug\", \"sep\", \"oct\", \"nov\", \"dec\"];\nconst DOW_NAMES = [\"sun\", \"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\"];\n\ninterface FieldSpec {\n min: number;\n max: number;\n names?: string[];\n /** Offset added to a resolved name index (months are 1-based, weekdays 0-based). */\n nameOffset: number;\n /** Map an expanded value into its canonical form (used to fold weekday 7 → 0). */\n fold?: (v: number) => number;\n label: string;\n}\n\nconst SECOND_SPEC: FieldSpec = { min: 0, max: 59, nameOffset: 0, label: \"second\" };\nconst MINUTE_SPEC: FieldSpec = { min: 0, max: 59, nameOffset: 0, label: \"minute\" };\nconst HOUR_SPEC: FieldSpec = { min: 0, max: 23, nameOffset: 0, label: \"hour\" };\nconst DOM_SPEC: FieldSpec = { min: 1, max: 31, nameOffset: 0, label: \"day-of-month\" };\nconst MONTH_SPEC: FieldSpec = { min: 1, max: 12, names: MONTH_NAMES, nameOffset: 1, label: \"month\" };\nconst DOW_SPEC: FieldSpec = { min: 0, max: 7, names: DOW_NAMES, nameOffset: 0, fold: (v) => v % 7, label: \"day-of-week\" };\n\n/** A cron expression compiled into the set of values each field matches. */\nexport interface ParsedCron {\n /** Allowed seconds. For a 5-field expression this is `{0}`. */\n seconds: Set<number>;\n minutes: Set<number>;\n hours: Set<number>;\n daysOfMonth: Set<number>;\n months: Set<number>;\n /** Allowed weekdays, 0 = Sunday … 6 = Saturday (7 is folded to 0). */\n daysOfWeek: Set<number>;\n /** True when the day-of-month field was anything other than `*`/`?`. */\n domRestricted: boolean;\n /** True when the day-of-week field was anything other than `*`/`?`. */\n dowRestricted: boolean;\n /** True for `@reboot`, which has no scheduled next time (the engine fires it once at start). */\n reboot: boolean;\n}\n\n/** Parse a cron expression into matchable value-sets. Throws on a malformed expression. */\nexport function parseCron(expr: string): ParsedCron {\n if (typeof expr !== \"string\" || !expr.trim()) throw new Error(\"cron expression is empty\");\n let trimmed = expr.trim();\n\n if (trimmed.startsWith(\"@\")) {\n const macro = trimmed.toLowerCase();\n if (macro === \"@reboot\") {\n return {\n seconds: new Set([0]), minutes: new Set(), hours: new Set(), daysOfMonth: new Set(),\n months: new Set(), daysOfWeek: new Set(), domRestricted: false, dowRestricted: false, reboot: true,\n };\n }\n const expanded = MACRO_EXPANSIONS[macro];\n if (!expanded) throw new Error(`unknown cron macro \"${trimmed}\" (try @daily, @hourly, …)`);\n trimmed = expanded;\n }\n\n const parts = trimmed.split(/\\s+/);\n if (parts.length !== 5 && parts.length !== 6) {\n throw new Error(`expected 5 fields (min hour dom month dow) or 6 with seconds, got ${parts.length}: \"${trimmed}\"`);\n }\n\n const hasSeconds = parts.length === 6;\n const [secRaw, minRaw, hourRaw, domRaw, monthRaw, dowRaw] = hasSeconds\n ? parts\n : [\"0\", ...parts];\n\n return {\n seconds: expandField(secRaw!, SECOND_SPEC),\n minutes: expandField(minRaw!, MINUTE_SPEC),\n hours: expandField(hourRaw!, HOUR_SPEC),\n daysOfMonth: expandField(domRaw!, DOM_SPEC),\n months: expandField(monthRaw!, MONTH_SPEC),\n daysOfWeek: expandField(dowRaw!, DOW_SPEC),\n domRestricted: isRestricted(domRaw!),\n dowRestricted: isRestricted(dowRaw!),\n reboot: false,\n };\n}\n\nfunction isRestricted(field: string): boolean {\n const f = field.trim();\n return f !== \"*\" && f !== \"?\";\n}\n\n/** Expand one comma-separated field (`*`, ranges, lists, steps, names) into its value set. */\nfunction expandField(raw: string, spec: FieldSpec): Set<number> {\n const set = new Set<number>();\n for (const term of raw.split(\",\")) {\n if (term === \"\") throw new Error(`empty term in ${spec.label} field \"${raw}\"`);\n\n let base = term;\n let step = 1;\n const slash = term.indexOf(\"/\");\n if (slash >= 0) {\n base = term.slice(0, slash);\n const stepStr = term.slice(slash + 1);\n if (!/^\\d+$/.test(stepStr) || Number(stepStr) === 0) {\n throw new Error(`invalid step \"${stepStr}\" in ${spec.label} field \"${raw}\"`);\n }\n step = Number(stepStr);\n }\n\n let lo: number | null;\n let hi: number | null;\n if (base === \"*\" || base === \"?\") {\n lo = spec.min;\n hi = spec.max;\n } else {\n const dash = base.indexOf(\"-\");\n if (dash > 0) {\n lo = resolveValue(base.slice(0, dash), spec);\n hi = resolveValue(base.slice(dash + 1), spec);\n } else {\n lo = resolveValue(base, spec);\n // A bare value with a step (e.g. \"5/15\") runs from the value up to the field max.\n hi = step > 1 ? spec.max : lo;\n }\n }\n\n if (lo === null || hi === null) {\n throw new Error(`value out of range in ${spec.label} field \"${raw}\" (${spec.min}-${spec.max})`);\n }\n if (lo > hi) throw new Error(`range start ${lo} is greater than end ${hi} in ${spec.label} field \"${raw}\"`);\n\n for (let v = lo; v <= hi; v += step) set.add(spec.fold ? spec.fold(v) : v);\n }\n return set;\n}\n\n/** Resolve a numeric or named token to its number, or null when out of range / unknown. */\nfunction resolveValue(token: string, spec: FieldSpec): number | null {\n const t = token.trim();\n if (/^\\d+$/.test(t)) {\n const n = Number(t);\n return n >= spec.min && n <= spec.max ? n : null;\n }\n if (spec.names) {\n const idx = spec.names.indexOf(t.toLowerCase());\n if (idx >= 0) return idx + spec.nameOffset;\n }\n return null;\n}\n\n// ─── Next-occurrence search ──────────────────────────────────────────────────\n\nexport interface NextOccurrenceOptions {\n /** Search strictly after this instant (epoch ms or Date). Defaults to now. */\n from?: Date | number;\n /** IANA timezone the expression is read in. Defaults to \"UTC\". */\n timeZone?: string;\n}\n\n/** How many years ahead the search gives up after — a guard against impossible expressions. */\nconst SEARCH_HORIZON_YEARS = 5;\n\n/**\n * The next instant the expression fires, strictly after `from`, in the given timezone — or `null`\n * when nothing matches within {@link SEARCH_HORIZON_YEARS} (e.g. Feb-30). Throws for `@reboot`.\n */\nexport function nextOccurrence(expr: string | ParsedCron, opts: NextOccurrenceOptions = {}): Date | null {\n const parsed = typeof expr === \"string\" ? parseCron(expr) : expr;\n if (parsed.reboot) throw new Error(\"@reboot has no scheduled next occurrence (the engine fires it once at start)\");\n\n const tz = opts.timeZone ?? \"UTC\";\n const fromMs = opts.from === undefined ? Date.now() : typeof opts.from === \"number\" ? opts.from : opts.from.getTime();\n\n // Move strictly forward by at least one whole second, then search on wall-clock fields.\n const cursor = Math.floor(fromMs / 1000) * 1000 + 1000;\n const p = getZonedParts(cursor, tz);\n const startYear = p.y;\n\n const hourArr = sorted(parsed.hours);\n const minuteArr = sorted(parsed.minutes);\n const secondArr = sorted(parsed.seconds);\n\n let guard = 0;\n while (guard++ < 1_000_000) {\n if (p.y > startYear + SEARCH_HORIZON_YEARS) return null;\n\n if (!parsed.months.has(p.mo)) {\n bumpMonth(p);\n continue;\n }\n if (p.d > daysInMonth(p.y, p.mo)) {\n bumpMonth(p);\n continue;\n }\n if (!dayMatches(parsed, p.y, p.mo, p.d)) {\n bumpDay(p);\n continue;\n }\n const nh = firstAtLeast(hourArr, p.h);\n if (nh === null) {\n bumpDay(p);\n continue;\n }\n if (nh !== p.h) {\n p.h = nh;\n p.mi = 0;\n p.s = 0;\n }\n const nmi = firstAtLeast(minuteArr, p.mi);\n if (nmi === null) {\n p.h += 1;\n p.mi = 0;\n p.s = 0;\n continue;\n }\n if (nmi !== p.mi) {\n p.mi = nmi;\n p.s = 0;\n }\n const ns = firstAtLeast(secondArr, p.s);\n if (ns === null) {\n p.mi += 1;\n p.s = 0;\n continue;\n }\n p.s = ns;\n\n // Full wall-clock match — convert to a real epoch in the zone.\n const epoch = zonedWallToEpoch(p, tz);\n // Guard against a DST fold mapping the candidate back to or before `from`.\n if (epoch <= fromMs) {\n p.s += 1;\n continue;\n }\n return new Date(epoch);\n }\n throw new Error(`nextOccurrence: search exceeded its iteration bound for \"${typeof expr === \"string\" ? expr : \"(parsed)\"}\"`);\n}\n\n/** Whether a day satisfies the day-of-month / day-of-week fields, with Vixie OR semantics. */\nfunction dayMatches(parsed: ParsedCron, y: number, mo: number, d: number): boolean {\n const domOk = parsed.daysOfMonth.has(d);\n const dowOk = parsed.daysOfWeek.has(weekdayOf(y, mo, d));\n if (parsed.domRestricted && parsed.dowRestricted) return domOk || dowOk;\n if (parsed.domRestricted) return domOk;\n if (parsed.dowRestricted) return dowOk;\n return true;\n}\n\ninterface WallParts {\n y: number;\n mo: number;\n d: number;\n h: number;\n mi: number;\n s: number;\n}\n\nfunction bumpMonth(p: WallParts): void {\n p.mo += 1;\n if (p.mo > 12) {\n p.mo = 1;\n p.y += 1;\n }\n p.d = 1;\n p.h = 0;\n p.mi = 0;\n p.s = 0;\n}\n\nfunction bumpDay(p: WallParts): void {\n p.d += 1;\n p.h = 0;\n p.mi = 0;\n p.s = 0;\n}\n\nfunction sorted(set: Set<number>): number[] {\n return [...set].sort((a, b) => a - b);\n}\n\n/** Smallest array element ≥ `v` (array is sorted ascending), or null. */\nfunction firstAtLeast(arr: number[], v: number): number | null {\n for (const x of arr) if (x >= v) return x;\n return null;\n}\n\nfunction daysInMonth(year: number, month: number): number {\n return new Date(Date.UTC(year, month, 0)).getUTCDate();\n}\n\nfunction weekdayOf(year: number, month: number, day: number): number {\n return new Date(Date.UTC(year, month - 1, day)).getUTCDay();\n}\n\n// ─── Timezone conversion (Intl-based, zero-dependency) ───────────────────────\n\nconst FORMATTER_CACHE = new Map<string, Intl.DateTimeFormat>();\n\nfunction formatterFor(timeZone: string): Intl.DateTimeFormat {\n let fmt = FORMATTER_CACHE.get(timeZone);\n if (!fmt) {\n fmt = new Intl.DateTimeFormat(\"en-US\", {\n timeZone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n });\n FORMATTER_CACHE.set(timeZone, fmt);\n }\n return fmt;\n}\n\n/** The wall-clock calendar fields a clock in `timeZone` shows at the given epoch. */\nfunction getZonedParts(epochMs: number, timeZone: string): WallParts {\n const parts = formatterFor(timeZone).formatToParts(new Date(epochMs));\n const m: Record<string, string> = {};\n for (const part of parts) if (part.type !== \"literal\") m[part.type] = part.value;\n let h = Number(m[\"hour\"]);\n if (h === 24) h = 0; // some runtimes render midnight as \"24\" under hour12:false\n return { y: Number(m[\"year\"]), mo: Number(m[\"month\"]), d: Number(m[\"day\"]), h, mi: Number(m[\"minute\"]), s: Number(m[\"second\"]) };\n}\n\n/** Offset (ms) the zone is ahead of UTC at the given instant. */\nfunction offsetAt(epochMs: number, timeZone: string): number {\n const p = getZonedParts(epochMs, timeZone);\n const asNaive = Date.UTC(p.y, p.mo - 1, p.d, p.h, p.mi, p.s);\n return asNaive - epochMs;\n}\n\n/** Convert wall-clock fields in `timeZone` to an absolute epoch, correcting for the zone's offset. */\nfunction zonedWallToEpoch(p: WallParts, timeZone: string): number {\n const asUTC = Date.UTC(p.y, p.mo - 1, p.d, p.h, p.mi, p.s);\n const o1 = offsetAt(asUTC, timeZone);\n let epoch = asUTC - o1;\n const o2 = offsetAt(epoch, timeZone);\n if (o2 !== o1) epoch = asUTC - o2;\n return epoch;\n}\n\n// ─── Preview & upcoming-window planning ──────────────────────────────────────\n\nexport interface PreviewOptions {\n /** Start the preview after this instant. Defaults to now. */\n from?: Date | number;\n /** IANA timezone. Defaults to the runtime's local zone. */\n timeZone?: string;\n /** How many fire times to return (default 5, capped at 100). */\n count?: number;\n}\n\n/** The next N times an expression fires — the engine of `cronvello preview`. */\nexport function previewSchedule(expr: string, opts: PreviewOptions = {}): Date[] {\n const parsed = parseCron(expr);\n const timeZone = opts.timeZone ?? localTimeZone();\n const count = Math.min(Math.max(Math.trunc(opts.count ?? 5), 1), 100);\n const out: Date[] = [];\n let from = opts.from === undefined ? Date.now() : typeof opts.from === \"number\" ? opts.from : opts.from.getTime();\n for (let i = 0; i < count; i++) {\n const next = nextOccurrence(parsed, { from, timeZone });\n if (!next) break;\n out.push(next);\n from = next.getTime();\n }\n return out;\n}\n\nexport interface UpcomingJob {\n key: string;\n schedule: string;\n timeZone: string;\n}\n\nexport interface UpcomingFire {\n key: string;\n time: Date;\n schedule: string;\n timeZone: string;\n}\n\nexport interface UpcomingOptions {\n /** Window start (defaults to now). */\n from?: Date | number;\n /** Window length in ms (defaults to 1 hour). */\n withinMs?: number;\n /** Cap per job so a per-second schedule can't flood the plan (default 50). */\n maxPerJob?: number;\n}\n\n/**\n * Every fire across a set of jobs within the next window, merged and sorted by time — what powers\n * `cronvello dev --dry-run`. Jobs whose schedule can't be parsed are skipped (a dev concern surfaced\n * elsewhere); `@reboot` jobs are skipped because they have no scheduled time.\n */\nexport function upcomingFires(jobs: UpcomingJob[], opts: UpcomingOptions = {}): UpcomingFire[] {\n const from = opts.from === undefined ? Date.now() : typeof opts.from === \"number\" ? opts.from : opts.from.getTime();\n const withinMs = opts.withinMs ?? 60 * 60 * 1000;\n const maxPerJob = Math.max(1, opts.maxPerJob ?? 50);\n const horizon = from + withinMs;\n\n const fires: UpcomingFire[] = [];\n for (const job of jobs) {\n let parsed: ParsedCron;\n try {\n parsed = parseCron(job.schedule);\n } catch {\n continue;\n }\n if (parsed.reboot) continue;\n let cursor = from;\n for (let i = 0; i < maxPerJob; i++) {\n const next = nextOccurrence(parsed, { from: cursor, timeZone: job.timeZone });\n if (!next || next.getTime() > horizon) break;\n fires.push({ key: job.key, time: next, schedule: job.schedule, timeZone: job.timeZone });\n cursor = next.getTime();\n }\n }\n fires.sort((a, b) => a.time.getTime() - b.time.getTime());\n return fires;\n}\n\n/** The runtime's local IANA zone, falling back to UTC if it can't be resolved. */\nexport function localTimeZone(): string {\n try {\n return new Intl.DateTimeFormat().resolvedOptions().timeZone || \"UTC\";\n } catch {\n return \"UTC\";\n }\n}\n","/**\n * The **local engine** — Cronvello running entirely on your machine, with no account, no cloud, and\n * no network. It is the piece that turns `@cronvello/sdk` from a thin client into a real cron tool:\n * give it your jobs and it computes each one's next fire time, runs the handler when it's due, and\n * enforces the production policies (overlap protection, per-run timeout, retry with backoff) locally\n * — exactly the guarantees the cloud gives you, on your laptop.\n *\n * Everything is driven through an injectable {@link EngineClock}, so the whole scheduler is\n * deterministic under test (a virtual clock can fast-forward days in microseconds) and uses the real\n * `setTimeout`/`Date.now` in production. The engine never touches the network.\n */\n\nimport { nextOccurrence } from \"../internal/cron-schedule.js\";\n\n/** The outcome of a single execution (which may have spanned several attempts). */\nexport type RunStatus = \"success\" | \"error\" | \"timed_out\" | \"skipped\";\n\n/** One job the engine should schedule. Derived from a `defineCronvello` registry, but standalone. */\nexport interface EngineJob {\n /** Stable registry key. */\n key: string;\n /** Cron expression (5- or 6-field, macros). `@reboot` fires once when the engine starts. */\n schedule: string;\n /** IANA timezone the schedule is read in. */\n timeZone: string;\n /** Abort a run that exceeds this many ms (the handler's `ctx.signal` aborts). 0/undefined = no limit. */\n timeoutMs?: number;\n /** Retries after the first failed attempt (default 0). */\n maxRetries?: number;\n /** Allow a fire while a previous run of the same job is still in flight (default false). */\n allowConcurrentRuns?: boolean;\n /** Optional human description (shown in the dev table). */\n description?: string;\n}\n\n/** A completed execution recorded in the in-memory history ring buffer. */\nexport interface RunRecord {\n key: string;\n source: \"local\";\n /** Epoch ms when the execution began. */\n startedAt: number;\n /** Epoch ms when it settled. */\n finishedAt: number;\n durationMs: number;\n status: RunStatus;\n /** How many attempts were made (1 + retries that ran). */\n attempts: number;\n /** Error message for a failed/timed-out run. */\n error?: string;\n /** The handler's return value for a successful run. */\n result?: unknown;\n}\n\n/** Lifecycle events the engine emits — the CLI renders them as a live feed. */\nexport type EngineEvent =\n | { type: \"engine-start\"; jobs: number; at: number }\n | { type: \"engine-stop\"; at: number }\n | { type: \"scheduled\"; key: string; at: number }\n | { type: \"fire\"; key: string; attempt: number; at: number }\n | { type: \"success\"; key: string; durationMs: number; attempts: number; result: unknown; at: number }\n | { type: \"error\"; key: string; durationMs: number; attempt: number; willRetry: boolean; error: string; at: number }\n | { type: \"timeout\"; key: string; durationMs: number; attempt: number; willRetry: boolean; at: number }\n | { type: \"retry\"; key: string; attempt: number; delayMs: number; at: number }\n | { type: \"skipped\"; key: string; reason: \"overlap\"; at: number };\n\n/** A snapshot of one job's scheduling state, for the dev table. */\nexport interface JobSnapshot {\n key: string;\n schedule: string;\n timeZone: string;\n description?: string;\n /** Next fire time, or null for `@reboot` / unschedulable jobs. */\n nextFire: Date | null;\n /** True while a run of this job is in flight. */\n running: boolean;\n}\n\n/**\n * The timer/clock seam. Production wires this to the global `setTimeout`/`Date.now`; tests inject a\n * virtual clock so scheduling is fully deterministic.\n */\nexport interface EngineClock {\n now(): number;\n setTimeout(fn: () => void, ms: number): unknown;\n clearTimeout(handle: unknown): void;\n}\n\n/** Runs a job's handler in-process (with lifecycle hooks). `signal` aborts on timeout. */\nexport type EngineRunner = (key: string, signal: AbortSignal) => Promise<unknown>;\n\nexport interface LocalEngineOptions {\n /** Timer source. Defaults to the real `setTimeout`/`Date.now`. */\n clock?: EngineClock;\n /** Receives every lifecycle event (for live rendering / metrics). */\n onEvent?: (event: EngineEvent) => void;\n /** Cap on the in-memory run history (default 100). Oldest records drop first. */\n historyLimit?: number;\n /** Backoff before retry attempt N (1-based: N=1 is the delay before the 2nd attempt). */\n backoff?: (attempt: number) => number;\n /** Install SIGINT/SIGTERM handlers that stop the engine cleanly (default false). */\n installSignalHandlers?: boolean;\n}\n\nconst DEFAULT_HISTORY_LIMIT = 100;\n/** Node's `setTimeout` overflows past ~24.8 days; clamp long waits and re-evaluate on wake. */\nconst MAX_TIMER_MS = 2 ** 31 - 1;\n\n/** Default retry backoff: 0.5s, 1s, 2s, 4s … capped at 30s. Deterministic (no jitter). */\nfunction defaultBackoff(attempt: number): number {\n return Math.min(30_000, 500 * 2 ** (attempt - 1));\n}\n\nconst realClock: EngineClock = {\n now: () => Date.now(),\n setTimeout: (fn, ms) => setTimeout(fn, ms),\n clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),\n};\n\n/** A timeout that crossed its budget — distinguished from a handler error so we can label the run. */\nclass RunTimeoutError extends Error {\n constructor(key: string, ms: number) {\n super(`job '${key}' timed out after ${ms}ms`);\n this.name = \"RunTimeoutError\";\n }\n}\n\ninterface JobState {\n job: EngineJob;\n nextFire: number | null;\n timer: unknown;\n running: boolean;\n isReboot: boolean;\n}\n\n/**\n * Construct a local engine. Call {@link LocalEngine.start} to begin the loop and\n * {@link LocalEngine.stop} for a clean shutdown.\n */\nexport function createLocalEngine(jobs: EngineJob[], runner: EngineRunner, options: LocalEngineOptions = {}): LocalEngine {\n return new LocalEngine(jobs, runner, options);\n}\n\nexport class LocalEngine {\n private readonly clock: EngineClock;\n /** Every event listener. The constructor's `onEvent` is registered as one of them. */\n private readonly listeners = new Set<(event: EngineEvent) => void>();\n /** Run after the engine has drained, e.g. to close a dashboard server. */\n private readonly closeHooks: Array<() => void | Promise<void>> = [];\n private readonly historyLimit: number;\n private readonly backoff: (attempt: number) => number;\n private readonly runner: EngineRunner;\n private readonly states: JobState[];\n private readonly history: RunRecord[] = [];\n private readonly inFlight = new Set<Promise<unknown>>();\n private started = false;\n private stopped = false;\n private signalCleanup: (() => void) | null = null;\n\n constructor(jobs: EngineJob[], runner: EngineRunner, options: LocalEngineOptions = {}) {\n this.clock = options.clock ?? realClock;\n if (options.onEvent) this.listeners.add(options.onEvent);\n this.historyLimit = Math.max(1, options.historyLimit ?? DEFAULT_HISTORY_LIMIT);\n this.backoff = options.backoff ?? defaultBackoff;\n this.runner = runner;\n this.states = jobs.map((job) => ({\n job,\n nextFire: null,\n timer: null,\n running: false,\n isReboot: job.schedule.trim().toLowerCase() === \"@reboot\",\n }));\n\n if (options.installSignalHandlers) this.installSignalHandlers();\n }\n\n /**\n * Subscribe to every lifecycle event (in addition to the constructor's `onEvent`). Returns an\n * unsubscribe function. Used by the local dashboard to fan events out to many SSE clients without\n * disturbing the scheduler. A listener that throws is isolated — it can't break the loop.\n */\n subscribe(listener: (event: EngineEvent) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Register a hook to run once, after {@link stop} has drained in-flight runs — e.g. to close a\n * dashboard server so `engine.stop()` tears everything down together. Hooks are awaited.\n */\n onStop(hook: () => void | Promise<void>): this {\n this.closeHooks.push(hook);\n return this;\n }\n\n /** Deliver an event to every listener, isolating each so one bad listener can't stall the loop. */\n private emit(event: EngineEvent): void {\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch {\n // A listener (e.g. a dead SSE connection) must never break scheduling.\n }\n }\n }\n\n /** Begin scheduling. Idempotent — a second call is a no-op. */\n start(): this {\n if (this.started) return this;\n this.started = true;\n this.stopped = false;\n this.emit({ type: \"engine-start\", jobs: this.states.length, at: this.clock.now() });\n\n for (const state of this.states) {\n if (state.isReboot) {\n // `@reboot` runs once at startup and is never rescheduled.\n this.launch(state);\n } else {\n this.scheduleNext(state);\n }\n }\n return this;\n }\n\n /**\n * Stop scheduling and wait for in-flight runs to settle. After this resolves no further handlers\n * will start. Safe to call from a signal handler.\n */\n async stop(): Promise<void> {\n if (this.stopped) {\n await Promise.allSettled([...this.inFlight]);\n return;\n }\n this.stopped = true;\n for (const state of this.states) {\n if (state.timer !== null) {\n this.clock.clearTimeout(state.timer);\n state.timer = null;\n }\n }\n this.signalCleanup?.();\n this.signalCleanup = null;\n await Promise.allSettled([...this.inFlight]);\n // Tell subscribers we're done before tearing down anything they depend on (e.g. SSE clients\n // see `engine-stop`, then the dashboard server's close hook ends their connections).\n this.emit({ type: \"engine-stop\", at: this.clock.now() });\n for (const hook of this.closeHooks.splice(0)) {\n try {\n await hook();\n } catch {\n // A failing shutdown hook must not prevent the engine from stopping.\n }\n }\n }\n\n /** The run history, newest first (a copy — safe to keep). */\n runs(): RunRecord[] {\n return [...this.history].reverse();\n }\n\n /** History entries for one job, newest first. */\n runsFor(key: string): RunRecord[] {\n return this.runs().filter((r) => r.key === key);\n }\n\n /** Current scheduling state of every job, for the dev table. */\n snapshot(): JobSnapshot[] {\n return this.states.map((s) => ({\n key: s.job.key,\n schedule: s.job.schedule,\n timeZone: s.job.timeZone,\n ...(s.job.description !== undefined ? { description: s.job.description } : {}),\n nextFire: s.nextFire !== null ? new Date(s.nextFire) : null,\n running: s.running,\n }));\n }\n\n /** The jobs this engine manages (read-only view). */\n jobs(): readonly EngineJob[] {\n return this.states.map((s) => s.job);\n }\n\n /** Number of runs currently in flight. */\n get activeRuns(): number {\n return this.inFlight.size;\n }\n\n // ── Scheduling ─────────────────────────────────────────────────────────────\n\n private scheduleNext(state: JobState): void {\n if (this.stopped) return;\n const now = this.clock.now();\n let next: Date | null;\n try {\n next = nextOccurrence(state.job.schedule, { from: now, timeZone: state.job.timeZone });\n } catch {\n // An unschedulable expression (e.g. a typo that slipped past define-time validation) simply\n // never fires locally; leave nextFire null so it shows as \"—\" in the table.\n state.nextFire = null;\n return;\n }\n if (!next) {\n state.nextFire = null;\n return;\n }\n state.nextFire = next.getTime();\n const delay = Math.max(0, Math.min(state.nextFire - now, MAX_TIMER_MS));\n state.timer = this.clock.setTimeout(() => this.onDue(state), delay);\n this.emit({ type: \"scheduled\", key: state.job.key, at: state.nextFire });\n }\n\n private onDue(state: JobState): void {\n state.timer = null;\n if (this.stopped) return;\n const now = this.clock.now();\n\n // Woke early because the wait was clamped (very distant fire) — just re-arm the timer.\n if (state.nextFire !== null && now < state.nextFire) {\n const delay = Math.max(0, Math.min(state.nextFire - now, MAX_TIMER_MS));\n state.timer = this.clock.setTimeout(() => this.onDue(state), delay);\n return;\n }\n\n if (state.running && !state.job.allowConcurrentRuns) {\n this.emit({ type: \"skipped\", key: state.job.key, reason: \"overlap\", at: now });\n this.record({\n key: state.job.key,\n source: \"local\",\n startedAt: now,\n finishedAt: now,\n durationMs: 0,\n status: \"skipped\",\n attempts: 0,\n });\n } else {\n this.launch(state);\n }\n // Keep the schedule ticking regardless of whether this fire ran or was skipped.\n this.scheduleNext(state);\n }\n\n /**\n * Run a job once, right now, by key — the local equivalent of \"run now\" in the cloud. Goes through\n * the exact same execution path as a scheduled fire (overlap protection, timeout, retry/backoff,\n * history + events), so a manual run shows up in the feed just like any other. Resolves with the\n * resulting {@link RunRecord}. Throws for an unknown key or after the engine has stopped.\n */\n async trigger(key: string): Promise<RunRecord> {\n if (this.stopped) throw new Error(\"cannot trigger a job on a stopped engine\");\n const state = this.states.find((s) => s.job.key === key);\n if (!state) throw new Error(`unknown job '${key}'`);\n\n if (state.running && !state.job.allowConcurrentRuns) {\n const now = this.clock.now();\n const skipped: RunRecord = { key, source: \"local\", startedAt: now, finishedAt: now, durationMs: 0, status: \"skipped\", attempts: 0 };\n this.emit({ type: \"skipped\", key, reason: \"overlap\", at: now });\n this.record(skipped);\n return skipped;\n }\n return this.launch(state);\n }\n\n /**\n * The run history as newline-delimited JSON (NDJSON), oldest run first — one record per line, the\n * natural shape for piping to a file or another tool. No trailing newline.\n */\n toNdjson(): string {\n return this.history.map((r) => JSON.stringify(r)).join(\"\\n\");\n }\n\n /** Start an execution and track it so {@link stop} can await it. Resolves with the run's record. */\n private launch(state: JobState): Promise<RunRecord> {\n const promise = this.execute(state);\n const tracked = promise.finally(() => {\n this.inFlight.delete(tracked);\n });\n this.inFlight.add(tracked);\n return tracked;\n }\n\n // ── Execution with timeout + retry/backoff ──────────────────────────────────\n\n private async execute(state: JobState): Promise<RunRecord> {\n const job = state.job;\n state.running = true;\n const startedAt = this.clock.now();\n const maxRetries = Math.max(0, job.maxRetries ?? 0);\n\n let attempt = 0;\n let status: RunStatus = \"error\";\n let error: string | undefined;\n let result: unknown;\n\n while (!this.stopped) {\n attempt++;\n this.emit({ type: \"fire\", key: job.key, attempt, at: this.clock.now() });\n try {\n result = await this.runOnce(job);\n status = \"success\";\n this.emit({ type: \"success\", key: job.key, durationMs: this.clock.now() - startedAt, attempts: attempt, result, at: this.clock.now() });\n error = undefined;\n break;\n } catch (err) {\n const timedOut = err instanceof RunTimeoutError;\n status = timedOut ? \"timed_out\" : \"error\";\n error = err instanceof Error ? err.message : String(err);\n const willRetry = attempt <= maxRetries && !this.stopped;\n const at = this.clock.now();\n if (timedOut) {\n this.emit({ type: \"timeout\", key: job.key, durationMs: at - startedAt, attempt, willRetry, at });\n } else {\n this.emit({ type: \"error\", key: job.key, durationMs: at - startedAt, attempt, willRetry, error, at });\n }\n if (!willRetry) break;\n const delayMs = this.backoff(attempt);\n this.emit({ type: \"retry\", key: job.key, attempt: attempt + 1, delayMs, at });\n await this.sleep(delayMs);\n }\n }\n\n const finishedAt = this.clock.now();\n const record: RunRecord = {\n key: job.key,\n source: \"local\",\n startedAt,\n finishedAt,\n durationMs: finishedAt - startedAt,\n status,\n attempts: attempt,\n ...(error !== undefined ? { error } : {}),\n ...(status === \"success\" ? { result } : {}),\n };\n this.record(record);\n state.running = false;\n return record;\n }\n\n /** A single attempt: run the handler, racing it against the per-job timeout. */\n private runOnce(job: EngineJob): Promise<unknown> {\n const controller = new AbortController();\n const handlerPromise = this.runner(job.key, controller.signal);\n const timeoutMs = job.timeoutMs;\n if (!timeoutMs || timeoutMs <= 0) return handlerPromise;\n\n return new Promise<unknown>((resolve, reject) => {\n let settled = false;\n const timer = this.clock.setTimeout(() => {\n if (settled) return;\n settled = true;\n controller.abort();\n reject(new RunTimeoutError(job.key, timeoutMs));\n }, timeoutMs);\n handlerPromise.then(\n (value) => {\n if (settled) return;\n settled = true;\n this.clock.clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n if (settled) return;\n settled = true;\n this.clock.clearTimeout(timer);\n reject(err);\n },\n );\n });\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => this.clock.setTimeout(resolve, ms));\n }\n\n private record(record: RunRecord): void {\n this.history.push(record);\n if (this.history.length > this.historyLimit) this.history.shift();\n }\n\n private installSignalHandlers(): void {\n const handler = (): void => {\n void this.stop().then(() => process.exit(0));\n };\n process.once(\"SIGINT\", handler);\n process.once(\"SIGTERM\", handler);\n this.signalCleanup = () => {\n process.removeListener(\"SIGINT\", handler);\n process.removeListener(\"SIGTERM\", handler);\n };\n }\n}\n","/**\n * The local dashboard's single-page UI — one self-contained HTML document with inline CSS and\n * vanilla JavaScript, no framework and no build step. It is served verbatim by\n * {@link ./dashboard.ts}, talks only to that server's same-origin JSON + SSE API, and never reaches\n * the network. Kept as a string constant (not a file read) so it works regardless of the bundled\n * `dist/` layout, and so it only loads when the dashboard is actually started.\n *\n * Branding follows BRAND.md: indigo primary, teal/green for healthy runs, amber for retries, red for\n * failures, slate neutrals, light + dark, monospace for cron expressions.\n */\nexport const DASHBOARD_HTML = `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<meta name=\"robots\" content=\"noindex\" />\n<title>Cronvello — local dashboard</title>\n<style>\n :root {\n --indigo: #4F46E5;\n --indigo-soft: #6366F1;\n --teal: #14B8A6;\n --green: #10B981;\n --amber: #F59E0B;\n --red: #EF4444;\n --bg: #f8fafc;\n --panel: #ffffff;\n --panel-2: #f1f5f9;\n --border: #e2e8f0;\n --text: #0f172a;\n --muted: #64748b;\n --shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n [data-theme=\"dark\"] {\n --bg: #0b1120;\n --panel: #111827;\n --panel-2: #0f172a;\n --border: #1f2937;\n --text: #e5e7eb;\n --muted: #94a3b8;\n --shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);\n }\n /* Dark-mode-first: follow the OS preference unless the user has explicitly chosen light. */\n @media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n --bg: #0b1120;\n --panel: #111827;\n --panel-2: #0f172a;\n --border: #1f2937;\n --text: #e5e7eb;\n --muted: #94a3b8;\n --shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);\n }\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; padding: 0; }\n body {\n background: var(--bg);\n color: var(--text);\n font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Inter, Roboto, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n }\n code, .mono { font-family: ui-monospace, \"SF Mono\", \"JetBrains Mono\", Menlo, Consolas, monospace; }\n header {\n display: flex; align-items: center; gap: 12px;\n padding: 14px 20px;\n border-bottom: 1px solid var(--border);\n background: var(--panel);\n position: sticky; top: 0; z-index: 10;\n }\n .logo {\n width: 28px; height: 28px; border-radius: 8px;\n display: grid; place-items: center;\n background: linear-gradient(135deg, var(--indigo), var(--teal));\n color: #fff; font-size: 17px; font-weight: 700;\n }\n .brand { font-weight: 700; letter-spacing: -0.01em; }\n .brand small { color: var(--muted); font-weight: 500; margin-left: 6px; }\n .spacer { flex: 1; }\n .pill {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 4px 10px; border-radius: 999px;\n font-size: 12px; font-weight: 600;\n border: 1px solid var(--border); background: var(--panel-2); color: var(--muted);\n }\n .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }\n .pill.live .dot { background: var(--green); box-shadow: 0 0 0 0 rgba(16,185,129,0.5); animation: pulse 1.8s infinite; }\n .pill.down .dot { background: var(--red); }\n @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(16,185,129,0.5); } 70% { box-shadow: 0 0 0 6px rgba(16,185,129,0); } 100% { box-shadow: 0 0 0 0 rgba(16,185,129,0); } }\n button {\n font: inherit; cursor: pointer; color: var(--text);\n background: var(--panel-2); border: 1px solid var(--border);\n border-radius: 8px; padding: 6px 12px;\n }\n button:hover { border-color: var(--indigo-soft); }\n button.primary { background: var(--indigo); color: #fff; border-color: var(--indigo); }\n button.primary:hover { background: var(--indigo-soft); }\n .layout { display: grid; grid-template-columns: 1.4fr 1fr; gap: 16px; padding: 16px 20px; align-items: start; }\n @media (max-width: 860px) { .layout { grid-template-columns: 1fr; } }\n .card { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); overflow: hidden; }\n .card > h2 { margin: 0; padding: 12px 16px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); border-bottom: 1px solid var(--border); }\n table { width: 100%; border-collapse: collapse; }\n th, td { text-align: left; padding: 10px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }\n th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 600; }\n tr:last-child td { border-bottom: none; }\n tbody tr { cursor: pointer; }\n tbody tr:hover { background: var(--panel-2); }\n .jobkey { font-weight: 600; }\n .jobdesc { color: var(--muted); font-size: 12px; }\n .count { font-variant-numeric: tabular-nums; }\n .badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; }\n .badge.success { background: rgba(16,185,129,0.15); color: var(--green); }\n .badge.error, .badge.timed_out { background: rgba(239,68,68,0.15); color: var(--red); }\n .badge.skipped { background: rgba(100,116,139,0.18); color: var(--muted); }\n .badge.running { background: rgba(79,70,229,0.15); color: var(--indigo-soft); }\n .badge.none { background: var(--panel-2); color: var(--muted); }\n .feed { max-height: 70vh; overflow-y: auto; }\n .ev { display: flex; gap: 10px; padding: 8px 16px; border-bottom: 1px solid var(--border); font-size: 13px; }\n .ev .glyph { width: 16px; text-align: center; flex: none; }\n .ev .body { flex: 1; min-width: 0; }\n .ev .when { color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }\n .ev.success .glyph { color: var(--green); }\n .ev.error .glyph, .ev.timeout .glyph { color: var(--red); }\n .ev.retry .glyph { color: var(--amber); }\n .ev.fire .glyph { color: var(--indigo-soft); }\n .ev.skipped .glyph, .ev.scheduled .glyph, .ev.engine .glyph { color: var(--muted); }\n .empty { padding: 24px 16px; color: var(--muted); text-align: center; }\n footer { padding: 12px 20px 28px; color: var(--muted); font-size: 12px; }\n .overlay { position: fixed; inset: 0; background: rgba(15,23,42,0.45); display: none; align-items: stretch; justify-content: flex-end; z-index: 30; }\n .overlay.open { display: flex; }\n .drawer { width: min(520px, 92vw); background: var(--panel); border-left: 1px solid var(--border); height: 100%; overflow-y: auto; box-shadow: var(--shadow); }\n .drawer header { background: var(--panel); }\n .drawer .pad { padding: 16px; }\n .kv { display: flex; gap: 8px; margin: 4px 0; }\n .kv span:first-child { color: var(--muted); min-width: 90px; }\n .section-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); margin: 18px 0 8px; }\n ul.plain { list-style: none; margin: 0; padding: 0; }\n ul.plain li { padding: 6px 0; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; gap: 8px; }\n</style>\n</head>\n<body>\n<header>\n <div class=\"logo\">↻</div>\n <div class=\"brand\">Cronvello <small>local dashboard</small></div>\n <div class=\"spacer\"></div>\n <span id=\"conn\" class=\"pill\"><span class=\"dot\"></span><span id=\"conn-text\">connecting…</span></span>\n <button id=\"theme-btn\" title=\"Toggle theme\">☼</button>\n</header>\n\n<div class=\"layout\">\n <section class=\"card\">\n <h2>Jobs — next fire & last status</h2>\n <table>\n <thead><tr><th>Job</th><th>Schedule</th><th>Timezone</th><th>Next fire</th><th>Last</th></tr></thead>\n <tbody id=\"jobs\"><tr><td colspan=\"5\" class=\"empty\">Loading jobs…</td></tr></tbody>\n </table>\n </section>\n\n <section class=\"card\">\n <h2>Live run feed</h2>\n <div id=\"feed\" class=\"feed\"><div class=\"empty\">Waiting for runs…</div></div>\n </section>\n</div>\n\n<footer>Local · no account · no cloud · no external network. The engine on this machine is the only source of truth.</footer>\n\n<div id=\"overlay\" class=\"overlay\">\n <div class=\"drawer\">\n <header>\n <div class=\"brand\" id=\"drawer-title\">Job</div>\n <div class=\"spacer\"></div>\n <button id=\"drawer-close\">Close</button>\n </header>\n <div class=\"pad\" id=\"drawer-body\"></div>\n </div>\n</div>\n\n<script>\n(function () {\n \"use strict\";\n var MAX_FEED = 200;\n var jobsByKey = {};\n\n function el(id) { return document.getElementById(id); }\n function esc(s) {\n return String(s == null ? \"\" : s).replace(/[&<>\"']/g, function (c) {\n return { \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" }[c];\n });\n }\n function pad(n) { return n < 10 ? \"0\" + n : \"\" + n; }\n function clockTime(ms) {\n var d = new Date(ms);\n return pad(d.getHours()) + \":\" + pad(d.getMinutes()) + \":\" + pad(d.getSeconds());\n }\n function fmtDuration(ms) {\n if (ms == null) return \"\";\n if (ms < 1000) return ms + \"ms\";\n return (ms / 1000).toFixed(ms < 10000 ? 2 : 1) + \"s\";\n }\n function countdown(iso) {\n if (!iso) return \"—\";\n var diff = new Date(iso).getTime() - Date.now();\n if (diff <= 0) return \"due now\";\n var s = Math.floor(diff / 1000);\n var d = Math.floor(s / 86400); s -= d * 86400;\n var h = Math.floor(s / 3600); s -= h * 3600;\n var m = Math.floor(s / 60); s -= m * 60;\n var out = \"in \";\n if (d) out += d + \"d \";\n if (d || h) out += h + \"h \";\n if (d || h || m) out += m + \"m \";\n out += s + \"s\";\n return out;\n }\n\n // ── Theme ──────────────────────────────────────────────────────────────────\n function applyTheme(t) {\n if (t === \"dark\" || t === \"light\") document.documentElement.setAttribute(\"data-theme\", t);\n else document.documentElement.removeAttribute(\"data-theme\");\n }\n function resolvedTheme() {\n var t = document.documentElement.getAttribute(\"data-theme\");\n if (t) return t;\n return window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n }\n try { applyTheme(localStorage.getItem(\"cronvello-theme\")); } catch (e) {}\n el(\"theme-btn\").addEventListener(\"click\", function () {\n var next = resolvedTheme() === \"dark\" ? \"light\" : \"dark\";\n applyTheme(next);\n try { localStorage.setItem(\"cronvello-theme\", next); } catch (e) {}\n });\n\n // ── Jobs table ─────────────────────────────────────────────────────────────\n function statusBadge(s) {\n if (!s) return '<span class=\"badge none\">—</span>';\n return '<span class=\"badge ' + esc(s) + '\">' + esc(s) + \"</span>\";\n }\n function renderJobs(jobs) {\n jobsByKey = {};\n if (!jobs.length) { el(\"jobs\").innerHTML = '<tr><td colspan=\"5\" class=\"empty\">No jobs registered.</td></tr>'; return; }\n var rows = \"\";\n for (var i = 0; i < jobs.length; i++) {\n var j = jobs[i];\n jobsByKey[j.key] = j;\n var last = j.running ? \"running\" : (j.lastStatus || \"\");\n rows +=\n '<tr data-key=\"' + esc(j.key) + '\">' +\n \"<td><div class=\\\\\"jobkey\\\\\">\" + esc(j.key) + \"</div>\" + (j.description ? '<div class=\"jobdesc\">' + esc(j.description) + \"</div>\" : \"\") + \"</td>\" +\n '<td><code>' + esc(j.schedule) + \"</code></td>\" +\n \"<td>\" + esc(j.timeZone) + \"</td>\" +\n '<td class=\"count\" data-next=\"' + esc(j.nextFire || \"\") + '\">' + countdown(j.nextFire) + \"</td>\" +\n \"<td>\" + (j.running ? '<span class=\"badge running\">running</span>' : statusBadge(j.lastStatus)) + \"</td>\" +\n \"</tr>\";\n }\n el(\"jobs\").innerHTML = rows;\n var trs = el(\"jobs\").querySelectorAll(\"tr[data-key]\");\n for (var k = 0; k < trs.length; k++) {\n trs[k].addEventListener(\"click\", function () { openDrawer(this.getAttribute(\"data-key\")); });\n }\n }\n function tickCountdowns() {\n var cells = el(\"jobs\").querySelectorAll(\"td[data-next]\");\n for (var i = 0; i < cells.length; i++) {\n var iso = cells[i].getAttribute(\"data-next\");\n cells[i].innerHTML = iso ? countdown(iso) : \"—\";\n }\n }\n\n function loadJobs() {\n return fetch(\"/api/jobs\").then(function (r) { return r.json(); }).then(function (jobs) {\n // Fold in each job's last run so the table can show a status without a per-row fetch.\n return fetch(\"/api/runs\").then(function (r) { return r.json(); }).then(function (runs) {\n var lastByKey = {};\n for (var i = 0; i < runs.length; i++) { if (!lastByKey[runs[i].key]) lastByKey[runs[i].key] = runs[i]; }\n for (var j = 0; j < jobs.length; j++) {\n var lr = lastByKey[jobs[j].key];\n jobs[j].lastStatus = lr ? lr.status : \"\";\n }\n renderJobs(jobs);\n });\n }).catch(function () {});\n }\n\n // ── Live feed ──────────────────────────────────────────────────────────────\n var glyphs = { fire: \"▷\", success: \"✓\", error: \"✗\", timeout: \"⏳\", retry: \"↻\", skipped: \"⊢\", scheduled: \"🕑\", \"engine-start\": \"⚡\", \"engine-stop\": \"■\" };\n function describe(ev) {\n switch (ev.type) {\n case \"fire\": return \"<strong>\" + esc(ev.key) + \"</strong> fired\" + (ev.attempt > 1 ? \" (attempt \" + ev.attempt + \")\" : \"\");\n case \"success\": return \"<strong>\" + esc(ev.key) + \"</strong> succeeded in \" + fmtDuration(ev.durationMs) + (ev.attempts > 1 ? \" after \" + ev.attempts + \" attempts\" : \"\");\n case \"error\": return \"<strong>\" + esc(ev.key) + \"</strong> errored: \" + esc(ev.error) + (ev.willRetry ? \" — will retry\" : \"\");\n case \"timeout\": return \"<strong>\" + esc(ev.key) + \"</strong> timed out after \" + fmtDuration(ev.durationMs) + (ev.willRetry ? \" — will retry\" : \"\");\n case \"retry\": return \"<strong>\" + esc(ev.key) + \"</strong> retrying (attempt \" + ev.attempt + \") in \" + fmtDuration(ev.delayMs);\n case \"skipped\": return \"<strong>\" + esc(ev.key) + \"</strong> skipped (overlap)\";\n case \"scheduled\": return \"<strong>\" + esc(ev.key) + \"</strong> scheduled\";\n case \"engine-start\": return \"Engine started — \" + ev.jobs + \" job(s)\";\n case \"engine-stop\": return \"Engine stopped\";\n default: return esc(ev.type);\n }\n }\n function feedClass(type) {\n if (type === \"engine-start\" || type === \"engine-stop\" || type === \"scheduled\") return \"engine\";\n return type;\n }\n function pushEvent(ev) {\n var feed = el(\"feed\");\n if (feed.firstChild && feed.firstChild.className === \"empty\") feed.innerHTML = \"\";\n var div = document.createElement(\"div\");\n div.className = \"ev \" + feedClass(ev.type);\n div.innerHTML =\n '<div class=\"glyph\">' + (glyphs[ev.type] || \"•\") + \"</div>\" +\n '<div class=\"body\">' + describe(ev) + \"</div>\" +\n '<div class=\"when\">' + clockTime(ev.at || Date.now()) + \"</div>\";\n feed.insertBefore(div, feed.firstChild);\n while (feed.childNodes.length > MAX_FEED) feed.removeChild(feed.lastChild);\n\n // A terminal event changes a job's last status / running flag — refresh the table.\n if (ev.type === \"success\" || ev.type === \"error\" || ev.type === \"timeout\" || ev.type === \"skipped\" || ev.type === \"scheduled\") {\n loadJobs();\n }\n }\n\n function setConn(state) {\n var pill = el(\"conn\"), text = el(\"conn-text\");\n pill.className = \"pill \" + state;\n text.textContent = state === \"live\" ? \"live\" : (state === \"down\" ? \"disconnected\" : \"connecting…\");\n }\n\n function connect() {\n setConn(\"connecting\");\n var src = new EventSource(\"/api/events\");\n src.onopen = function () { setConn(\"live\"); };\n src.onmessage = function (m) {\n try { pushEvent(JSON.parse(m.data)); } catch (e) {}\n };\n src.onerror = function () { setConn(\"down\"); };\n }\n\n // ── Job drawer ─────────────────────────────────────────────────────────────\n function openDrawer(key) {\n var job = jobsByKey[key];\n if (!job) return;\n el(\"drawer-title\").textContent = key;\n el(\"drawer-body\").innerHTML = '<div class=\"empty\">Loading…</div>';\n el(\"overlay\").classList.add(\"open\");\n\n Promise.all([\n fetch(\"/api/preview/\" + encodeURIComponent(key) + \"?n=5\").then(function (r) { return r.json(); }).catch(function () { return []; }),\n fetch(\"/api/runs/\" + encodeURIComponent(key)).then(function (r) { return r.json(); }).catch(function () { return []; })\n ]).then(function (res) {\n var fires = res[0] || [], runs = res[1] || [];\n var html =\n '<div class=\"kv\"><span>Schedule</span><code>' + esc(job.schedule) + \"</code></div>\" +\n '<div class=\"kv\"><span>Timezone</span><span>' + esc(job.timeZone) + \"</span></div>\" +\n (job.description ? '<div class=\"kv\"><span>Description</span><span>' + esc(job.description) + \"</span></div>\" : \"\") +\n '<div style=\"margin-top:14px\"><button class=\"primary\" id=\"run-now\">Run now</button></div>' +\n '<div class=\"section-title\">Next fire times</div>';\n if (fires.length) {\n html += '<ul class=\"plain\">';\n for (var i = 0; i < fires.length; i++) html += \"<li><span>\" + esc(new Date(fires[i]).toLocaleString()) + \"</span><span class=\\\\\"count\\\\\">\" + countdown(fires[i]) + \"</span></li>\";\n html += \"</ul>\";\n } else { html += '<div class=\"empty\">No upcoming fires.</div>'; }\n\n html += '<div class=\"section-title\">Recent runs</div>';\n if (runs.length) {\n html += '<ul class=\"plain\">';\n for (var j = 0; j < runs.length && j < 20; j++) {\n var run = runs[j];\n html += \"<li><span>\" + statusBadge(run.status) + \" <span class=\\\\\"when\\\\\">\" + esc(new Date(run.startedAt).toLocaleString()) + \"</span></span><span>\" + fmtDuration(run.durationMs) + \"</span></li>\";\n }\n html += \"</ul>\";\n } else { html += '<div class=\"empty\">No runs yet.</div>'; }\n\n el(\"drawer-body\").innerHTML = html;\n el(\"run-now\").addEventListener(\"click\", function () { runNow(key, this); });\n });\n }\n function closeDrawer() { el(\"overlay\").classList.remove(\"open\"); }\n el(\"drawer-close\").addEventListener(\"click\", closeDrawer);\n el(\"overlay\").addEventListener(\"click\", function (e) { if (e.target === el(\"overlay\")) closeDrawer(); });\n\n function runNow(key, btn) {\n btn.disabled = true; btn.textContent = \"Running…\";\n fetch(\"/api/trigger/\" + encodeURIComponent(key), { method: \"POST\", headers: { \"content-type\": \"application/json\" } })\n .then(function (r) { return r.json(); })\n .then(function () { btn.textContent = \"Ran ✓\"; setTimeout(function () { openDrawer(key); }, 600); loadJobs(); })\n .catch(function () { btn.disabled = false; btn.textContent = \"Run now\"; });\n }\n\n // ── Boot ───────────────────────────────────────────────────────────────────\n loadJobs();\n connect();\n setInterval(tickCountdowns, 1000);\n setInterval(loadJobs, 15000);\n})();\n</script>\n</body>\n</html>`;\n","/**\n * The **local dashboard** — a tiny `node:http` server (zero runtime dependencies) that observes a\n * running {@link LocalEngine} and renders it in a branded single-page UI. The engine stays the only\n * source of truth for scheduling; the dashboard only *reads* it (snapshot / history / live events)\n * and offers exactly one write path: a manual \"run now\" trigger.\n *\n * It is a developer tool, not a public server: it binds to `127.0.0.1` by default, never `0.0.0.0`,\n * speaks only to same-origin callers for mutations, makes no outbound network calls, and sends no\n * telemetry. Start it with {@link startDashboard}, or via `app.dev({ dashboard: true })` /\n * `cronvello dev --dashboard`.\n */\nimport http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport type { LocalEngine } from \"./engine.js\";\nimport { previewSchedule } from \"../internal/cron-schedule.js\";\nimport { DASHBOARD_HTML } from \"./dashboard-assets.js\";\n\nexport interface DashboardOptions {\n /** Port to listen on. Default `4747`. Use `0` to let the OS pick a free port (handy in tests). */\n port?: number;\n /** Host/interface to bind. Default `127.0.0.1` — keep it loopback; this is not a public server. */\n host?: string;\n}\n\nexport interface DashboardHandle {\n /** The local URL the dashboard is reachable at, e.g. `http://127.0.0.1:4747`. */\n url: string;\n /** The actual port bound (resolved even when `port: 0` was requested). */\n port: number;\n /** Stop the server and drop every live SSE connection. Idempotent. */\n close(): Promise<void>;\n}\n\nconst DEFAULT_PORT = 4747;\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst HEARTBEAT_MS = 15_000;\nconst MAX_PREVIEW = 50;\n\n/**\n * Start the dashboard server for an engine. Resolves once it is listening; rejects with a clear\n * message if the port is already in use. Bind defaults to loopback.\n */\nexport function startDashboard(engine: LocalEngine, options: DashboardOptions = {}): Promise<DashboardHandle> {\n const host = options.host ?? DEFAULT_HOST;\n const port = options.port ?? DEFAULT_PORT;\n const sseClients = new Set<http.ServerResponse>();\n\n const server = http.createServer((req, res) => handleRequest(engine, sseClients, req, res));\n\n return new Promise<DashboardHandle>((resolve, reject) => {\n const onError = (err: NodeJS.ErrnoException): void => {\n server.removeListener(\"error\", onError);\n if (err.code === \"EADDRINUSE\") {\n reject(new Error(`Cronvello dashboard: port ${port} on ${host} is already in use — pass a different --port.`));\n } else {\n reject(err);\n }\n };\n server.once(\"error\", onError);\n\n server.listen(port, host, () => {\n server.removeListener(\"error\", onError);\n const actualPort = (server.address() as AddressInfo).port;\n const shown = host === \"0.0.0.0\" || host === \"::\" ? \"127.0.0.1\" : host;\n const url = `http://${shown}:${actualPort}`;\n\n let closed = false;\n const close = (): Promise<void> => {\n if (closed) return Promise.resolve();\n closed = true;\n for (const client of sseClients) client.end();\n sseClients.clear();\n return new Promise<void>((res2) => {\n // closeAllConnections (Node ≥18.2) drops lingering keep-alive sockets so this resolves\n // promptly instead of waiting on idle connections.\n server.closeAllConnections?.();\n server.close(() => res2());\n });\n };\n\n resolve({ url, port: actualPort, close });\n });\n });\n}\n\nfunction handleRequest(\n engine: LocalEngine,\n sseClients: Set<http.ServerResponse>,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n): void {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const path = url.pathname;\n const method = req.method ?? \"GET\";\n\n // Landing page.\n if (method === \"GET\" && path === \"/\") {\n return sendHtml(res, DASHBOARD_HTML);\n }\n\n // Live event stream (Server-Sent Events).\n if (method === \"GET\" && path === \"/api/events\") {\n return openEventStream(engine, sseClients, req, res);\n }\n\n // Health.\n if (method === \"GET\" && path === \"/api/health\") {\n return sendJson(res, 200, { ok: true, jobs: engine.jobs().length, activeRuns: engine.activeRuns });\n }\n\n // Run history as NDJSON (check before the `/api/runs` prefix).\n if (method === \"GET\" && path === \"/api/runs.ndjson\") {\n res.writeHead(200, { \"content-type\": \"application/x-ndjson; charset=utf-8\", \"cache-control\": \"no-store\" });\n const body = engine.toNdjson();\n return void res.end(body.length ? body + \"\\n\" : \"\");\n }\n\n // Run history (all, or `?key=`).\n if (method === \"GET\" && path === \"/api/runs\") {\n const key = url.searchParams.get(\"key\");\n return sendJson(res, 200, key ? engine.runsFor(key) : engine.runs());\n }\n\n // Run history for one job.\n if (method === \"GET\" && path.startsWith(\"/api/runs/\")) {\n return sendJson(res, 200, engine.runsFor(decodeKey(path, \"/api/runs/\")));\n }\n\n // Upcoming fire times for one job.\n if (method === \"GET\" && path.startsWith(\"/api/preview/\")) {\n const key = decodeKey(path, \"/api/preview/\");\n const job = engine.snapshot().find((j) => j.key === key);\n if (!job) return sendJson(res, 404, { error: `unknown job '${key}'` });\n const n = clampCount(url.searchParams.get(\"n\"));\n try {\n const fires = previewSchedule(job.schedule, { timeZone: job.timeZone, count: n });\n return sendJson(res, 200, fires.map((d) => d.toISOString()));\n } catch {\n return sendJson(res, 200, []); // an unschedulable expression simply has no upcoming fires\n }\n }\n\n // Snapshot of all jobs.\n if (method === \"GET\" && path === \"/api/jobs\") {\n const jobs = engine.snapshot().map((j) => ({\n key: j.key,\n schedule: j.schedule,\n timeZone: j.timeZone,\n description: j.description,\n nextFire: j.nextFire ? j.nextFire.toISOString() : null,\n running: j.running,\n }));\n return sendJson(res, 200, jobs);\n }\n\n // Manual trigger — the one mutating path. Guard against cross-origin POSTs.\n if (method === \"POST\" && path.startsWith(\"/api/trigger/\")) {\n if (!isSameOrigin(req)) return sendJson(res, 403, { error: \"cross-origin request rejected\" });\n const key = decodeKey(path, \"/api/trigger/\");\n return void engine\n .trigger(key)\n .then((record) => sendJson(res, 200, record))\n .catch((err: Error) => {\n const status = /unknown job/.test(err.message) ? 404 : 400;\n sendJson(res, status, { error: err.message });\n });\n }\n\n sendJson(res, 404, { error: \"not found\" });\n}\n\n/** Open an SSE stream that forwards every engine event until the client disconnects. */\nfunction openEventStream(\n engine: LocalEngine,\n sseClients: Set<http.ServerResponse>,\n req: http.IncomingMessage,\n res: http.ServerResponse,\n): void {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream; charset=utf-8\",\n \"cache-control\": \"no-store\",\n connection: \"keep-alive\",\n \"x-accel-buffering\": \"no\",\n });\n res.write(\": connected\\n\\n\");\n sseClients.add(res);\n\n const unsubscribe = engine.subscribe((event) => {\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n });\n // Heartbeat comment keeps proxies from idling the connection out.\n const heartbeat = setInterval(() => res.write(\": ping\\n\\n\"), HEARTBEAT_MS);\n if (typeof heartbeat.unref === \"function\") heartbeat.unref();\n\n const cleanup = (): void => {\n clearInterval(heartbeat);\n unsubscribe();\n sseClients.delete(res);\n };\n res.on(\"close\", cleanup);\n req.on(\"close\", cleanup);\n}\n\n// ── helpers ────────────────────────────────────────────────────────────────\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body);\n res.writeHead(status, { \"content-type\": \"application/json; charset=utf-8\", \"cache-control\": \"no-store\" });\n res.end(payload);\n}\n\nfunction sendHtml(res: http.ServerResponse, html: string): void {\n res.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" });\n res.end(html);\n}\n\nfunction decodeKey(path: string, prefix: string): string {\n return decodeURIComponent(path.slice(prefix.length));\n}\n\nfunction clampCount(raw: string | null): number {\n const n = raw ? Number.parseInt(raw, 10) : 5;\n if (!Number.isFinite(n) || n < 1) return 5;\n return Math.min(n, MAX_PREVIEW);\n}\n\n/**\n * Reject a mutating request whose `Origin` points at a different host than the one it was sent to.\n * Loopback binding is the primary defence; this stops a page on another local origin from POSTing.\n */\nfunction isSameOrigin(req: http.IncomingMessage): boolean {\n const origin = req.headers.origin;\n if (!origin) return true; // non-browser clients (curl, tests) send no Origin\n try {\n return new URL(origin).host === req.headers.host;\n } catch {\n return false;\n }\n}\n"]}
|
package/dist/dev.d.cts
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **local engine** — Cronvello running entirely on your machine, with no account, no cloud, and
|
|
3
|
+
* no network. It is the piece that turns `@cronvello/sdk` from a thin client into a real cron tool:
|
|
4
|
+
* give it your jobs and it computes each one's next fire time, runs the handler when it's due, and
|
|
5
|
+
* enforces the production policies (overlap protection, per-run timeout, retry with backoff) locally
|
|
6
|
+
* — exactly the guarantees the cloud gives you, on your laptop.
|
|
7
|
+
*
|
|
8
|
+
* Everything is driven through an injectable {@link EngineClock}, so the whole scheduler is
|
|
9
|
+
* deterministic under test (a virtual clock can fast-forward days in microseconds) and uses the real
|
|
10
|
+
* `setTimeout`/`Date.now` in production. The engine never touches the network.
|
|
11
|
+
*/
|
|
12
|
+
/** The outcome of a single execution (which may have spanned several attempts). */
|
|
13
|
+
type RunStatus = "success" | "error" | "timed_out" | "skipped";
|
|
14
|
+
/** One job the engine should schedule. Derived from a `defineCronvello` registry, but standalone. */
|
|
15
|
+
interface EngineJob {
|
|
16
|
+
/** Stable registry key. */
|
|
17
|
+
key: string;
|
|
18
|
+
/** Cron expression (5- or 6-field, macros). `@reboot` fires once when the engine starts. */
|
|
19
|
+
schedule: string;
|
|
20
|
+
/** IANA timezone the schedule is read in. */
|
|
21
|
+
timeZone: string;
|
|
22
|
+
/** Abort a run that exceeds this many ms (the handler's `ctx.signal` aborts). 0/undefined = no limit. */
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
/** Retries after the first failed attempt (default 0). */
|
|
25
|
+
maxRetries?: number;
|
|
26
|
+
/** Allow a fire while a previous run of the same job is still in flight (default false). */
|
|
27
|
+
allowConcurrentRuns?: boolean;
|
|
28
|
+
/** Optional human description (shown in the dev table). */
|
|
29
|
+
description?: string;
|
|
30
|
+
}
|
|
31
|
+
/** A completed execution recorded in the in-memory history ring buffer. */
|
|
32
|
+
interface RunRecord {
|
|
33
|
+
key: string;
|
|
34
|
+
source: "local";
|
|
35
|
+
/** Epoch ms when the execution began. */
|
|
36
|
+
startedAt: number;
|
|
37
|
+
/** Epoch ms when it settled. */
|
|
38
|
+
finishedAt: number;
|
|
39
|
+
durationMs: number;
|
|
40
|
+
status: RunStatus;
|
|
41
|
+
/** How many attempts were made (1 + retries that ran). */
|
|
42
|
+
attempts: number;
|
|
43
|
+
/** Error message for a failed/timed-out run. */
|
|
44
|
+
error?: string;
|
|
45
|
+
/** The handler's return value for a successful run. */
|
|
46
|
+
result?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/** Lifecycle events the engine emits — the CLI renders them as a live feed. */
|
|
49
|
+
type EngineEvent = {
|
|
50
|
+
type: "engine-start";
|
|
51
|
+
jobs: number;
|
|
52
|
+
at: number;
|
|
53
|
+
} | {
|
|
54
|
+
type: "engine-stop";
|
|
55
|
+
at: number;
|
|
56
|
+
} | {
|
|
57
|
+
type: "scheduled";
|
|
58
|
+
key: string;
|
|
59
|
+
at: number;
|
|
60
|
+
} | {
|
|
61
|
+
type: "fire";
|
|
62
|
+
key: string;
|
|
63
|
+
attempt: number;
|
|
64
|
+
at: number;
|
|
65
|
+
} | {
|
|
66
|
+
type: "success";
|
|
67
|
+
key: string;
|
|
68
|
+
durationMs: number;
|
|
69
|
+
attempts: number;
|
|
70
|
+
result: unknown;
|
|
71
|
+
at: number;
|
|
72
|
+
} | {
|
|
73
|
+
type: "error";
|
|
74
|
+
key: string;
|
|
75
|
+
durationMs: number;
|
|
76
|
+
attempt: number;
|
|
77
|
+
willRetry: boolean;
|
|
78
|
+
error: string;
|
|
79
|
+
at: number;
|
|
80
|
+
} | {
|
|
81
|
+
type: "timeout";
|
|
82
|
+
key: string;
|
|
83
|
+
durationMs: number;
|
|
84
|
+
attempt: number;
|
|
85
|
+
willRetry: boolean;
|
|
86
|
+
at: number;
|
|
87
|
+
} | {
|
|
88
|
+
type: "retry";
|
|
89
|
+
key: string;
|
|
90
|
+
attempt: number;
|
|
91
|
+
delayMs: number;
|
|
92
|
+
at: number;
|
|
93
|
+
} | {
|
|
94
|
+
type: "skipped";
|
|
95
|
+
key: string;
|
|
96
|
+
reason: "overlap";
|
|
97
|
+
at: number;
|
|
98
|
+
};
|
|
99
|
+
/** A snapshot of one job's scheduling state, for the dev table. */
|
|
100
|
+
interface JobSnapshot {
|
|
101
|
+
key: string;
|
|
102
|
+
schedule: string;
|
|
103
|
+
timeZone: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
/** Next fire time, or null for `@reboot` / unschedulable jobs. */
|
|
106
|
+
nextFire: Date | null;
|
|
107
|
+
/** True while a run of this job is in flight. */
|
|
108
|
+
running: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The timer/clock seam. Production wires this to the global `setTimeout`/`Date.now`; tests inject a
|
|
112
|
+
* virtual clock so scheduling is fully deterministic.
|
|
113
|
+
*/
|
|
114
|
+
interface EngineClock {
|
|
115
|
+
now(): number;
|
|
116
|
+
setTimeout(fn: () => void, ms: number): unknown;
|
|
117
|
+
clearTimeout(handle: unknown): void;
|
|
118
|
+
}
|
|
119
|
+
/** Runs a job's handler in-process (with lifecycle hooks). `signal` aborts on timeout. */
|
|
120
|
+
type EngineRunner = (key: string, signal: AbortSignal) => Promise<unknown>;
|
|
121
|
+
interface LocalEngineOptions {
|
|
122
|
+
/** Timer source. Defaults to the real `setTimeout`/`Date.now`. */
|
|
123
|
+
clock?: EngineClock;
|
|
124
|
+
/** Receives every lifecycle event (for live rendering / metrics). */
|
|
125
|
+
onEvent?: (event: EngineEvent) => void;
|
|
126
|
+
/** Cap on the in-memory run history (default 100). Oldest records drop first. */
|
|
127
|
+
historyLimit?: number;
|
|
128
|
+
/** Backoff before retry attempt N (1-based: N=1 is the delay before the 2nd attempt). */
|
|
129
|
+
backoff?: (attempt: number) => number;
|
|
130
|
+
/** Install SIGINT/SIGTERM handlers that stop the engine cleanly (default false). */
|
|
131
|
+
installSignalHandlers?: boolean;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Construct a local engine. Call {@link LocalEngine.start} to begin the loop and
|
|
135
|
+
* {@link LocalEngine.stop} for a clean shutdown.
|
|
136
|
+
*/
|
|
137
|
+
declare function createLocalEngine(jobs: EngineJob[], runner: EngineRunner, options?: LocalEngineOptions): LocalEngine;
|
|
138
|
+
declare class LocalEngine {
|
|
139
|
+
private readonly clock;
|
|
140
|
+
/** Every event listener. The constructor's `onEvent` is registered as one of them. */
|
|
141
|
+
private readonly listeners;
|
|
142
|
+
/** Run after the engine has drained, e.g. to close a dashboard server. */
|
|
143
|
+
private readonly closeHooks;
|
|
144
|
+
private readonly historyLimit;
|
|
145
|
+
private readonly backoff;
|
|
146
|
+
private readonly runner;
|
|
147
|
+
private readonly states;
|
|
148
|
+
private readonly history;
|
|
149
|
+
private readonly inFlight;
|
|
150
|
+
private started;
|
|
151
|
+
private stopped;
|
|
152
|
+
private signalCleanup;
|
|
153
|
+
constructor(jobs: EngineJob[], runner: EngineRunner, options?: LocalEngineOptions);
|
|
154
|
+
/**
|
|
155
|
+
* Subscribe to every lifecycle event (in addition to the constructor's `onEvent`). Returns an
|
|
156
|
+
* unsubscribe function. Used by the local dashboard to fan events out to many SSE clients without
|
|
157
|
+
* disturbing the scheduler. A listener that throws is isolated — it can't break the loop.
|
|
158
|
+
*/
|
|
159
|
+
subscribe(listener: (event: EngineEvent) => void): () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Register a hook to run once, after {@link stop} has drained in-flight runs — e.g. to close a
|
|
162
|
+
* dashboard server so `engine.stop()` tears everything down together. Hooks are awaited.
|
|
163
|
+
*/
|
|
164
|
+
onStop(hook: () => void | Promise<void>): this;
|
|
165
|
+
/** Deliver an event to every listener, isolating each so one bad listener can't stall the loop. */
|
|
166
|
+
private emit;
|
|
167
|
+
/** Begin scheduling. Idempotent — a second call is a no-op. */
|
|
168
|
+
start(): this;
|
|
169
|
+
/**
|
|
170
|
+
* Stop scheduling and wait for in-flight runs to settle. After this resolves no further handlers
|
|
171
|
+
* will start. Safe to call from a signal handler.
|
|
172
|
+
*/
|
|
173
|
+
stop(): Promise<void>;
|
|
174
|
+
/** The run history, newest first (a copy — safe to keep). */
|
|
175
|
+
runs(): RunRecord[];
|
|
176
|
+
/** History entries for one job, newest first. */
|
|
177
|
+
runsFor(key: string): RunRecord[];
|
|
178
|
+
/** Current scheduling state of every job, for the dev table. */
|
|
179
|
+
snapshot(): JobSnapshot[];
|
|
180
|
+
/** The jobs this engine manages (read-only view). */
|
|
181
|
+
jobs(): readonly EngineJob[];
|
|
182
|
+
/** Number of runs currently in flight. */
|
|
183
|
+
get activeRuns(): number;
|
|
184
|
+
private scheduleNext;
|
|
185
|
+
private onDue;
|
|
186
|
+
/**
|
|
187
|
+
* Run a job once, right now, by key — the local equivalent of "run now" in the cloud. Goes through
|
|
188
|
+
* the exact same execution path as a scheduled fire (overlap protection, timeout, retry/backoff,
|
|
189
|
+
* history + events), so a manual run shows up in the feed just like any other. Resolves with the
|
|
190
|
+
* resulting {@link RunRecord}. Throws for an unknown key or after the engine has stopped.
|
|
191
|
+
*/
|
|
192
|
+
trigger(key: string): Promise<RunRecord>;
|
|
193
|
+
/**
|
|
194
|
+
* The run history as newline-delimited JSON (NDJSON), oldest run first — one record per line, the
|
|
195
|
+
* natural shape for piping to a file or another tool. No trailing newline.
|
|
196
|
+
*/
|
|
197
|
+
toNdjson(): string;
|
|
198
|
+
/** Start an execution and track it so {@link stop} can await it. Resolves with the run's record. */
|
|
199
|
+
private launch;
|
|
200
|
+
private execute;
|
|
201
|
+
/** A single attempt: run the handler, racing it against the per-job timeout. */
|
|
202
|
+
private runOnce;
|
|
203
|
+
private sleep;
|
|
204
|
+
private record;
|
|
205
|
+
private installSignalHandlers;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Cron schedule arithmetic for the local engine — next-occurrence, preview, and an upcoming-window
|
|
210
|
+
* planner, all timezone-aware (IANA / DST) and **zero-dependency**.
|
|
211
|
+
*
|
|
212
|
+
* The cloud SDK only needs to *validate* a cron string (see `cron.ts`), because the server owns the
|
|
213
|
+
* scheduler. The local engine has to actually *fire* jobs, so it needs the next time an expression
|
|
214
|
+
* matches. This module is that calculator.
|
|
215
|
+
*
|
|
216
|
+
* How the timezone math works without a date library:
|
|
217
|
+
* • A cron expression matches against **wall-clock** fields (the time a person reads off a clock in
|
|
218
|
+
* the job's timezone), so the search is done on naive calendar fields and only converted to a
|
|
219
|
+
* real epoch once a full match is found.
|
|
220
|
+
* • To convert a wall-clock time in a zone to an absolute epoch we use the runtime's own IANA
|
|
221
|
+
* database via `Intl.DateTimeFormat`: format a guess into the zone, measure the offset it
|
|
222
|
+
* implies, and correct once. This is the standard offset-probe technique and it handles DST
|
|
223
|
+
* transitions (a daily job at 04:00 stays at 04:00 local; the gap to the previous fire is 23h or
|
|
224
|
+
* 25h across the spring/autumn switch).
|
|
225
|
+
*
|
|
226
|
+
* Supported syntax mirrors `validateCron`: 5-field crontab, an optional leading seconds field
|
|
227
|
+
* (6 fields), the `@macros`, month/weekday names, and `*` / ranges / lists / steps. Day-of-month and
|
|
228
|
+
* day-of-week combine with Vixie cron's OR semantics when both are restricted.
|
|
229
|
+
*/
|
|
230
|
+
/** A cron expression compiled into the set of values each field matches. */
|
|
231
|
+
interface ParsedCron {
|
|
232
|
+
/** Allowed seconds. For a 5-field expression this is `{0}`. */
|
|
233
|
+
seconds: Set<number>;
|
|
234
|
+
minutes: Set<number>;
|
|
235
|
+
hours: Set<number>;
|
|
236
|
+
daysOfMonth: Set<number>;
|
|
237
|
+
months: Set<number>;
|
|
238
|
+
/** Allowed weekdays, 0 = Sunday … 6 = Saturday (7 is folded to 0). */
|
|
239
|
+
daysOfWeek: Set<number>;
|
|
240
|
+
/** True when the day-of-month field was anything other than `*`/`?`. */
|
|
241
|
+
domRestricted: boolean;
|
|
242
|
+
/** True when the day-of-week field was anything other than `*`/`?`. */
|
|
243
|
+
dowRestricted: boolean;
|
|
244
|
+
/** True for `@reboot`, which has no scheduled next time (the engine fires it once at start). */
|
|
245
|
+
reboot: boolean;
|
|
246
|
+
}
|
|
247
|
+
/** Parse a cron expression into matchable value-sets. Throws on a malformed expression. */
|
|
248
|
+
declare function parseCron(expr: string): ParsedCron;
|
|
249
|
+
interface NextOccurrenceOptions {
|
|
250
|
+
/** Search strictly after this instant (epoch ms or Date). Defaults to now. */
|
|
251
|
+
from?: Date | number;
|
|
252
|
+
/** IANA timezone the expression is read in. Defaults to "UTC". */
|
|
253
|
+
timeZone?: string;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The next instant the expression fires, strictly after `from`, in the given timezone — or `null`
|
|
257
|
+
* when nothing matches within {@link SEARCH_HORIZON_YEARS} (e.g. Feb-30). Throws for `@reboot`.
|
|
258
|
+
*/
|
|
259
|
+
declare function nextOccurrence(expr: string | ParsedCron, opts?: NextOccurrenceOptions): Date | null;
|
|
260
|
+
interface PreviewOptions {
|
|
261
|
+
/** Start the preview after this instant. Defaults to now. */
|
|
262
|
+
from?: Date | number;
|
|
263
|
+
/** IANA timezone. Defaults to the runtime's local zone. */
|
|
264
|
+
timeZone?: string;
|
|
265
|
+
/** How many fire times to return (default 5, capped at 100). */
|
|
266
|
+
count?: number;
|
|
267
|
+
}
|
|
268
|
+
/** The next N times an expression fires — the engine of `cronvello preview`. */
|
|
269
|
+
declare function previewSchedule(expr: string, opts?: PreviewOptions): Date[];
|
|
270
|
+
interface UpcomingJob {
|
|
271
|
+
key: string;
|
|
272
|
+
schedule: string;
|
|
273
|
+
timeZone: string;
|
|
274
|
+
}
|
|
275
|
+
interface UpcomingFire {
|
|
276
|
+
key: string;
|
|
277
|
+
time: Date;
|
|
278
|
+
schedule: string;
|
|
279
|
+
timeZone: string;
|
|
280
|
+
}
|
|
281
|
+
interface UpcomingOptions {
|
|
282
|
+
/** Window start (defaults to now). */
|
|
283
|
+
from?: Date | number;
|
|
284
|
+
/** Window length in ms (defaults to 1 hour). */
|
|
285
|
+
withinMs?: number;
|
|
286
|
+
/** Cap per job so a per-second schedule can't flood the plan (default 50). */
|
|
287
|
+
maxPerJob?: number;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Every fire across a set of jobs within the next window, merged and sorted by time — what powers
|
|
291
|
+
* `cronvello dev --dry-run`. Jobs whose schedule can't be parsed are skipped (a dev concern surfaced
|
|
292
|
+
* elsewhere); `@reboot` jobs are skipped because they have no scheduled time.
|
|
293
|
+
*/
|
|
294
|
+
declare function upcomingFires(jobs: UpcomingJob[], opts?: UpcomingOptions): UpcomingFire[];
|
|
295
|
+
/** The runtime's local IANA zone, falling back to UTC if it can't be resolved. */
|
|
296
|
+
declare function localTimeZone(): string;
|
|
297
|
+
|
|
298
|
+
interface DashboardOptions {
|
|
299
|
+
/** Port to listen on. Default `4747`. Use `0` to let the OS pick a free port (handy in tests). */
|
|
300
|
+
port?: number;
|
|
301
|
+
/** Host/interface to bind. Default `127.0.0.1` — keep it loopback; this is not a public server. */
|
|
302
|
+
host?: string;
|
|
303
|
+
}
|
|
304
|
+
interface DashboardHandle {
|
|
305
|
+
/** The local URL the dashboard is reachable at, e.g. `http://127.0.0.1:4747`. */
|
|
306
|
+
url: string;
|
|
307
|
+
/** The actual port bound (resolved even when `port: 0` was requested). */
|
|
308
|
+
port: number;
|
|
309
|
+
/** Stop the server and drop every live SSE connection. Idempotent. */
|
|
310
|
+
close(): Promise<void>;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Start the dashboard server for an engine. Resolves once it is listening; rejects with a clear
|
|
314
|
+
* message if the port is already in use. Bind defaults to loopback.
|
|
315
|
+
*/
|
|
316
|
+
declare function startDashboard(engine: LocalEngine, options?: DashboardOptions): Promise<DashboardHandle>;
|
|
317
|
+
|
|
318
|
+
export { type DashboardHandle, type DashboardOptions, type EngineClock, type EngineEvent, type EngineJob, type EngineRunner, type JobSnapshot, LocalEngine, type LocalEngineOptions, type NextOccurrenceOptions, type ParsedCron, type PreviewOptions, type RunRecord, type RunStatus, type UpcomingFire, type UpcomingJob, type UpcomingOptions, createLocalEngine, localTimeZone, nextOccurrence, parseCron, previewSchedule, startDashboard, upcomingFires };
|