/** * The event block: what used to be hardcoded, as data. Built from the * notification the same way for every kind, so a handler reads what it is * answering without parsing the wire payload. */ import { getActionDefaults } from "../scope/actions"; import type { Notification } from "../scope/index"; import { traceContextOf } from "./tracing"; export interface EventBlock { version: 1; /** `service`, `link`, `scope`, or any kind the event block names. */ kind: string; action: { id: string; slug: string; type: string; mode: "tracked" | "data" }; entity: { kind: string; id: string; nrn?: string }; package: { slug: string; version: string }; trace?: { trace_id: string; parent_run_id?: string }; /** `event()` handlers: the notification's action name. */ name?: string; /** `event()` handlers: the notification's source. */ source?: string; /** `event()` handlers: the notification's context. */ context?: Record; } export function buildEvent( notification: Notification, opts: { kind: string; action: string; packageSlug: string; packageVersion: string; } ): EventBlock { const n = notification as Notification & { link?: { id?: unknown } | null; scope?: { id?: unknown }; event?: { kind?: string } | string; }; const kind = (typeof n.event === "object" && n.event?.kind) || opts.kind; const entityId = kind === "link" ? n.link?.id : kind === "scope" ? n.parameters?.scope_id ?? n.scope?.id : n.service?.id; const trace = traceContextOf(notification as Record); return { version: 1, kind, action: { id: String(n.id ?? ""), slug: String(n.slug ?? opts.action), type: String(n.type ?? "custom"), mode: getActionDefaults(opts.action).lifecycle ? "tracked" : "data", }, entity: { kind, id: entityId != null ? String(entityId) : "", nrn: n.entity_nrn, }, package: { slug: opts.packageSlug, version: opts.packageVersion }, ...(trace ? { trace: { trace_id: trace.traceId, ...(trace.parentRunId ? { parent_run_id: trace.parentRunId } : {}), }, } : {}), }; }