spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1import { and, desc, eq, inArray, isNull, sql } from 'drizzle-orm';2import type { Database } from './client.js';3import { systemAlerts } from './schema/ext-ops.js';45export type AlertSeverity = 'info' | 'warn' | 'critical';6export type AlertStatus = 'open' | 'acknowledged' | 'resolved';78export interface AlertInput {9 kind: string;10 severity: AlertSeverity;11 connectorId?: string | null;12 message: string;13 detail?: Record<string, unknown>;14}1516export interface SystemAlert {17 id: number;18 kind: string;19 severity: string;20 connectorId: string | null;21 message: string;22 detail: Record<string, unknown>;23 firstSeenAt: Date;24 lastSeenAt: Date;25 count: number;26 status: string;27 resolvedAt: Date | null;28}2930const ACTIVE: AlertStatus[] = ['open', 'acknowledged'];3132/**33 * Raise (or bump) an internal alert (CLAUDE.md §170). Deduplicated on kind + connector + message34 * among open/acknowledged rows: a repeat increments `count`, refreshes `last_seen_at`, `severity`35 * and `detail`. Returns the alert id. Inside failure handlers use `raiseAlertSafe`: an alert36 * failure must never mask the original error.37 */38export async function raiseAlert(db: Database, input: AlertInput): Promise<number> {39 const connectorId = input.connectorId ?? null;40 const detail = input.detail ?? {};41 const where = and(eq(systemAlerts.kind, input.kind), eq(systemAlerts.message, input.message), connectorId === null ? isNull(systemAlerts.connectorId) : eq(systemAlerts.connectorId, connectorId), inArray(systemAlerts.status, ACTIVE));42 const [existing] = await db.select({ id: systemAlerts.id }).from(systemAlerts).where(where).orderBy(desc(systemAlerts.lastSeenAt)).limit(1);43 if (existing) {44 await db45 .update(systemAlerts)46 .set({ count: sql`${systemAlerts.count} + 1`, lastSeenAt: new Date(), severity: input.severity, detail })47 .where(eq(systemAlerts.id, existing.id));48 return existing.id;49 }50 const [row] = await db.insert(systemAlerts).values({ kind: input.kind, severity: input.severity, connectorId, message: input.message, detail }).returning({ id: systemAlerts.id });51 return row!.id;52}5354/** Like `raiseAlert` but swallows (and returns) the error — for use inside failure handlers. */55export async function raiseAlertSafe(db: Database, input: AlertInput): Promise<number | Error> {56 try {57 return await raiseAlert(db, input);58 } catch (e) {59 return e as Error;60 }61}6263/**64 * Resolve open/acknowledged alerts of one kind (or several) for a connector — or for all65 * connectors when `connectorId` is undefined; `null` targets alerts without a connector.66 * Returns the number of rows resolved.67 */68export async function resolveAlerts(db: Database, kind: string | string[], connectorId?: string | null): Promise<number> {69 const kinds = Array.isArray(kind) ? kind : [kind];70 if (!kinds.length) return 0;71 const conds = [inArray(systemAlerts.kind, kinds), inArray(systemAlerts.status, ACTIVE)];72 if (connectorId === null) conds.push(isNull(systemAlerts.connectorId));73 else if (connectorId !== undefined) conds.push(eq(systemAlerts.connectorId, connectorId));74 const rows = await db75 .update(systemAlerts)76 .set({ status: 'resolved', resolvedAt: new Date() })77 .where(and(...conds))78 .returning({ id: systemAlerts.id });79 return rows.length;80}8182/** Open (and optionally acknowledged) alerts, most severe and most recent first. */83export async function listAlerts(db: Database, opts: { status?: AlertStatus | 'active'; connectorId?: string; limit?: number } = {}): Promise<SystemAlert[]> {84 const status = opts.status ?? 'active';85 const conds = status === 'active' ? [inArray(systemAlerts.status, ACTIVE)] : [eq(systemAlerts.status, status)];86 if (opts.connectorId) conds.push(eq(systemAlerts.connectorId, opts.connectorId));87 const rows = await db88 .select()89 .from(systemAlerts)90 .where(and(...conds))91 .orderBy(sql`CASE ${systemAlerts.severity} WHEN 'critical' THEN 0 WHEN 'warn' THEN 1 ELSE 2 END`, desc(systemAlerts.lastSeenAt))92 .limit(opts.limit ?? 200);93 return rows as SystemAlert[];94}95