// Applying health events in place. // // health.changed / health.stale already carry the new value in their payload, // so a view that renders health does not need to refetch anything: it can // patch the one entity it already holds. Refetching the whole fleet because a // single container went degraded costs a full round trip, re-runs the parent // grouping, and churns the table on every transition — for information the // event had already delivered. // // NOTE: match on entity_id, never on data.slug. The scheduler emits // health.changed with entity_id = the observed entity but data.slug = the // *check's* slug (e.g. "check:ping:host:hubris:0"), so the slug in the // payload does not identify the thing whose health changed. import type { Entity, EntityHealth } from '$lib/api' import type { OikosEvent } from '$lib/stores/events' const HEALTH_EVENTS = new Set(['health.changed', 'health.stale']) export function isHealthEvent(ev: OikosEvent): boolean { return HEALTH_EVENTS.has(ev.type) } /** The new health an event reports, or null if it isn't a usable health event. */ export function healthFromEvent(ev: OikosEvent): EntityHealth | null { if (!isHealthEvent(ev) || !ev.entity_id) return null const to = (ev.data as Record | undefined)?.to return typeof to === 'string' ? (to as EntityHealth) : null } /** * Returns a copy of `list` with the event's entity patched, or the original * array when the event doesn't apply — so callers can assign unconditionally * without forcing a re-render for an entity they aren't showing. */ export function applyHealthEvent(list: Entity[], ev: OikosEvent): Entity[] { const health = healthFromEvent(ev) if (!health) return list const i = list.findIndex((e) => e.id === ev.entity_id) if (i < 0) return list if (list[i].health === health) return list const next = list.slice() // ev.ts is when the check ran, which is exactly what "checked N ago" means. next[i] = { ...next[i], health, last_check_at: ev.ts } return next } /** Single-entity form, for a detail view holding one entity. */ export function applyHealthEventTo(entity: Entity | null, ev: OikosEvent): Entity | null { if (!entity || ev.entity_id !== entity.id) return entity const health = healthFromEvent(ev) if (!health || entity.health === health) return entity return { ...entity, health, last_check_at: ev.ts } }