1import type * as Effect from "effect/Effect";
2import type * as Stream from "effect/Stream";
3import {
4 consumeBusEvents,
5 type EventRecord,
6 type EventRouteProps,
7} from "../EventBridge/EventSource.ts";
8
9/**
10 * The `detail` payload X-Ray delivers to EventBridge when an insight is
11 * created, updated, or closed. Delivery requires an X-Ray group with both
12 * `insightsEnabled` and `notificationsEnabled`.
13 */
14export interface InsightEventDetail {
15 /** The id of the insight the event is about. */
16 InsightId?: string;
17 /** Name of the X-Ray group the insight belongs to. */
18 GroupName?: string;
19 /** ARN of the X-Ray group the insight belongs to. */
20 GroupARN?: string;
21 /** The service at the root of the detected anomaly. */
22 RootCauseServiceId?: {
23 Name?: string;
24 Type?: string;
25 AccountId?: string;
26 [key: string]: unknown;
27 };
28 /** The categories the insight applies to, e.g. `FAULT`. */
29 Categories?: string[];
30 /** Insight state: `ACTIVE` or `CLOSED`. */
31 State?: string;
32 /** Time the insight started. */
33 StartTime?: string | number;
34 /** Time the insight ended (`null` while the insight is active). */
35 EndTime?: string | number | null;
36 /** Human-readable insight summary. */
37 Summary?: string;
38 /** Additional event fields (the schema grows over time). */
39 [key: string]: unknown;
40}
41
42/** An X-Ray insight EventBridge event delivered to the handler. */
43export type InsightEvent = EventRecord<InsightEventDetail>;
44
45export interface InsightEventSourceProps extends EventRouteProps {
46 /**
47 * Logical id for the backing EventBridge rule.
48 * @default "XRayInsightEvents"
49 */
50 id?: string;
51 /**
52 * Restrict to insights of the given X-Ray groups (matched against the
53 * event detail's `GroupName`).
54 */
55 groupNames?: readonly string[];
56 /**
57 * Restrict to insights in the given states (matched against the event
58 * detail's `State`: `ACTIVE`, `CLOSED`).
59 */
60 states?: readonly string[];
61}
62
63/**
64 * Event source connecting X-Ray insight notifications to the hosting
65 * compute. When an X-Ray group has insights and notifications enabled
66 * (`Group({ insightsEnabled: true, notificationsEnabled: true })`), X-Ray
67 * publishes insight lifecycle updates to the account's default EventBridge
68 * bus (source `aws.xray`, detail-type `AWS X-Ray Insight Update`); this
69 * subscribes the host Function to those events so it can page, annotate
70 * incidents, or drive remediation.
71 *
72 * X-Ray publishes to EventBridge automatically — no additional resource is
73 * created besides the EventBridge rule targeting the host. Provide the
74 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
75 * Function effect.
76 *
77 * ### Consuming Insight Events
78 * **Example:** Alert on Active Insights
79 * ```typescript
80 * import * as AWS from "alchemy/AWS";
81 *
82 * export default AlertFunction.make(
83 * { main: import.meta.url },
84 * Effect.gen(function* () {
85 * yield* AWS.XRay.consumeInsightEvents(
86 * { states: ["ACTIVE"] },
87 * (events) =>
88 * Stream.runForEach(events, (event) =>
89 * Effect.logError(
90 * `X-Ray insight ${event.detail.InsightId}: ${event.detail.Summary}`,
91 * ),
92 * ),
93 * );
94 * return {};
95 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
96 * );
97 * ```
98 */
99export const consumeInsightEvents = <StreamReq = never, Req = never>(
100 props: InsightEventSourceProps,
101 process: (
102 events: Stream.Stream<InsightEvent, never, StreamReq>,
103 ) => Effect.Effect<void, never, Req>,
104) =>
105 consumeBusEvents(
106 props.id ?? "XRayInsightEvents",
107 {
108 source: ["aws.xray"],
109 "detail-type": ["AWS X-Ray Insight Update"],
110 ...(props.groupNames !== undefined || props.states !== undefined
111 ? {
112 detail: {
113 ...(props.groupNames !== undefined
114 ? { GroupName: [...props.groupNames] }
115 : {}),
116 ...(props.states !== undefined
117 ? { State: [...props.states] }
118 : {}),
119 },
120 }
121 : {}),
122 },
123 { description: props.description, state: props.state },
124 process,
125 );