|
| 1 | +/** |
| 2 | + * The reader contract every watch condition family shares, plus the duration formatting |
| 3 | + * they all label with. No IO of its own. |
| 4 | + */ |
| 5 | + |
| 6 | +import { formatDurationMilliseconds } from "@trigger.dev/core/v3/utils/durations"; |
| 7 | +import type { WatchCheckResult, WatchObservedOutcome } from "@internal/dashboard-agent-contracts"; |
| 8 | + |
| 9 | +/** The single run point-read. Postgres is authoritative for run state. */ |
| 10 | +export type WatchRunRow = { |
| 11 | + friendlyId: string; |
| 12 | + status: string; |
| 13 | + queue: string; |
| 14 | + createdAt: Date; |
| 15 | + /** Stamped when the run entered the queue. NULL while a run is delayed. */ |
| 16 | + queuedAt: Date | null; |
| 17 | + /** Set once the run is dequeued. */ |
| 18 | + startedAt: Date | null; |
| 19 | + completedAt: Date | null; |
| 20 | + delayUntil: Date | null; |
| 21 | +}; |
| 22 | + |
| 23 | +export type WatchQueueDepth = { |
| 24 | + /** Pending count for the queue, as of `asOf`. */ |
| 25 | + depth: number; |
| 26 | + source: "live_queue" | "queue_metrics"; |
| 27 | + /** A stale reading can never answer "drained". */ |
| 28 | + current: boolean; |
| 29 | + /** What instant the reading describes, when it isn't the live counter. */ |
| 30 | + asOf?: Date; |
| 31 | +}; |
| 32 | + |
| 33 | +/** |
| 34 | + * The oldest still-waiting run's age in one queue. A non-current age is wrong in both |
| 35 | + * directions, so `checkQueueOldestAge` refuses it rather than comparing it. |
| 36 | + */ |
| 37 | +export type WatchQueueOldestAge = { |
| 38 | + /** Age of the oldest run still waiting, in ms. Null when nothing is waiting. */ |
| 39 | + ageMs: number | null; |
| 40 | + source: "live_queue" | "queue_metrics"; |
| 41 | + current: boolean; |
| 42 | + asOf?: Date; |
| 43 | +}; |
| 44 | + |
| 45 | +/** What we know about the watched error's occurrences relative to `since`. */ |
| 46 | +export type WatchErrorRecurrence = { |
| 47 | + /** Earliest occurrence proven after `since`. Null with a `lastSeenAt` means not since. */ |
| 48 | + occurredAt: Date | null; |
| 49 | + /** How precisely `occurredAt` is known: to the millisecond, or to its minute. */ |
| 50 | + occurredAtPrecision: "exact" | "minute" | null; |
| 51 | + /** Occurrences after `since`. A lower bound when `countApproximate`. */ |
| 52 | + countSince: number; |
| 53 | + /** True when occurrences in the watch's creation minute can't be separated out. */ |
| 54 | + countApproximate: boolean; |
| 55 | + /** The fingerprint's most recent occurrence, whenever it was. */ |
| 56 | + lastSeenAt: Date | null; |
| 57 | +}; |
| 58 | + |
| 59 | +export type WatchHealthSeverity = "ok" | "warn" | "crit"; |
| 60 | + |
| 61 | +export type WatchHealthSnapshot = { |
| 62 | + /** `facts.trustworthy` from the health report. Untrustworthy never fires recovery. */ |
| 63 | + trustworthy: boolean; |
| 64 | + severity: WatchHealthSeverity; |
| 65 | +}; |
| 66 | + |
| 67 | +/** |
| 68 | + * The readers a check may use. Each may throw, which the caller turns into `unavailable`. |
| 69 | + * `null` means the source answered and there is nothing there. |
| 70 | + */ |
| 71 | +export type WatchCheckDeps = { |
| 72 | + /** Run point-read by public run id, scoped to the watch's environment. */ |
| 73 | + readRun: (runId: string) => Promise<WatchRunRow | null>; |
| 74 | + /** Does this queue exist in the watch's environment? */ |
| 75 | + queueExists: (queue: string) => Promise<boolean>; |
| 76 | + /** Current pending count, live run-queue first with a ClickHouse fallback. */ |
| 77 | + readQueueDepth: (queue: string) => Promise<WatchQueueDepth | null>; |
| 78 | + /** Age of the oldest run still waiting in the queue, right now. */ |
| 79 | + readQueueOldestAge: (queue: string) => Promise<WatchQueueOldestAge | null>; |
| 80 | + /** `null` means the fingerprint has no occurrences at all in this environment. */ |
| 81 | + readErrorRecurrence: (fingerprint: string, since: Date) => Promise<WatchErrorRecurrence | null>; |
| 82 | + /** The health report's current verdict for the watch's environment. */ |
| 83 | + readHealth: () => Promise<WatchHealthSnapshot | null>; |
| 84 | +}; |
| 85 | + |
| 86 | +export type WatchCheckInput = { |
| 87 | + now: Date; |
| 88 | + /** The recurrence window's start: the server-set `spec.since`, never caller-set. */ |
| 89 | + since: Date; |
| 90 | + /** |
| 91 | + * The previous check's facts, for the stateful kinds. A check's own facts are the only |
| 92 | + * storage for its state. Absent means no prior observation, never zero. |
| 93 | + */ |
| 94 | + previous?: Record<string, unknown> | null; |
| 95 | +}; |
| 96 | + |
| 97 | +export type WatchCheckOutcome = { |
| 98 | + result: WatchCheckResult; |
| 99 | + facts: Record<string, unknown>; |
| 100 | + /** Frozen onto the row by the resolving transition, so no surface re-reads the source. */ |
| 101 | + observed: WatchObservedOutcome; |
| 102 | +}; |
| 103 | + |
| 104 | +export function formatMs(ms: number): string { |
| 105 | + return formatDurationMilliseconds(ms, { style: "short", maxDecimalPoints: 0 }); |
| 106 | +} |
0 commit comments