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 AWS Config delivers to EventBridge. Compliance-change
11 * events carry `configRuleName` and the new/old evaluation results;
12 * configuration-item-change events carry `configurationItem` /
13 * `configurationItemDiff`. Fields not shared by every event kind are
14 * optional (the schema grows over time).
15 */
16export interface ConfigEventDetail {
17 /** Compliance events: the name of the rule that changed compliance. */
18 configRuleName?: string;
19 /** Compliance events: the ARN of the rule. */
20 configRuleARN?: string;
21 /** The type of resource the event is about, e.g. `AWS::S3::Bucket`. */
22 resourceType?: string;
23 /** The id of the resource the event is about. */
24 resourceId?: string;
25 /**
26 * The Config notification type, e.g. `ComplianceChangeNotification` or
27 * `ConfigurationItemChangeNotification`.
28 */
29 messageType?: string;
30 /** Compliance events: the new evaluation result (`complianceType`, …). */
31 newEvaluationResult?: Record<string, unknown>;
32 /** Compliance events: the previous evaluation result. */
33 oldEvaluationResult?: Record<string, unknown>;
34 /** Configuration-item events: the recorded configuration item. */
35 configurationItem?: Record<string, unknown>;
36 /** Configuration-item events: the diff against the previous item. */
37 configurationItemDiff?: Record<string, unknown>;
38 /** Additional event fields (the schema grows over time). */
39 [key: string]: unknown;
40}
41
42/** An AWS Config EventBridge event delivered to the handler. */
43export type ConfigEvent = EventRecord<ConfigEventDetail>;
44
45/** Which AWS Config notifications to subscribe to. */
46export type ConfigEventKind =
47 | "compliance"
48 | "configuration-item"
49 | "snapshot-delivery"
50 | "history-delivery";
51
52const DETAIL_TYPES: Record<ConfigEventKind, string> = {
53 compliance: "Config Rules Compliance Change",
54 "configuration-item": "Config Configuration Item Change",
55 "snapshot-delivery": "Config Configuration Snapshot Delivery Status",
56 "history-delivery": "Config Configuration History Delivery Status",
57};
58
59export interface ConfigEventSourceProps extends EventRouteProps {
60 /**
61 * Logical id for the backing EventBridge rule.
62 * @default "ConfigEvents"
63 */
64 id?: string;
65 /**
66 * Which notifications to subscribe to: rule compliance changes,
67 * configuration item changes, or snapshot/history delivery status.
68 * @default ["compliance"]
69 */
70 kinds?: readonly ConfigEventKind[];
71 /**
72 * Restrict to compliance events about specific Config rules (matched
73 * against the event's `configRuleName`).
74 */
75 configRuleNames?: readonly string[];
76 /**
77 * Restrict to events about specific resource types (matched against the
78 * event's `resourceType`).
79 */
80 resourceTypes?: readonly string[];
81}
82
83/**
84 * Event source connecting AWS Config notifications to the hosting compute.
85 * AWS Config publishes rule compliance changes and configuration item
86 * changes to the account's default EventBridge bus (source `aws.config`);
87 * this subscribes the host Function to those events so it can alert on
88 * noncompliant resources or chain remediation automation.
89 *
90 * AWS Config publishes to EventBridge automatically — no additional
91 * resource is created besides the EventBridge rule targeting the host.
92 * Provide the host-specific implementation layer (e.g.
93 * `AWS.Lambda.EventSource`) on the Function effect.
94 *
95 * ### Consuming Config Events
96 * **Example:** Alert On Noncompliant Resources
97 * ```typescript
98 * import * as AWS from "alchemy/AWS";
99 *
100 * export default AlertFunction.make(
101 * { main: import.meta.url },
102 * Effect.gen(function* () {
103 * yield* AWS.Config.consumeConfigEvents(
104 * { kinds: ["compliance"] },
105 * (events) =>
106 * Stream.runForEach(events, (event) =>
107 * Effect.log(
108 * `${event.detail.configRuleName}: ${event.detail.resourceId} is now ` +
109 * `${(event.detail.newEvaluationResult as any)?.complianceType}`,
110 * ),
111 * ),
112 * );
113 * return {};
114 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
115 * );
116 * ```
117 */
118export const consumeConfigEvents = <StreamReq = never, Req = never>(
119 props: ConfigEventSourceProps,
120 process: (
121 events: Stream.Stream<ConfigEvent, never, StreamReq>,
122 ) => Effect.Effect<void, never, Req>,
123) => {
124 const detail = {
125 ...(props.configRuleNames !== undefined
126 ? { configRuleName: [...props.configRuleNames] }
127 : {}),
128 ...(props.resourceTypes !== undefined
129 ? { resourceType: [...props.resourceTypes] }
130 : {}),
131 };
132 return consumeBusEvents(
133 props.id ?? "ConfigEvents",
134 {
135 source: ["aws.config"],
136 "detail-type": (props.kinds ?? (["compliance"] as const)).map(
137 (kind) => DETAIL_TYPES[kind],
138 ),
139 ...(Object.keys(detail).length > 0 ? { detail } : {}),
140 },
141 { description: props.description, state: props.state },
142 process,
143 );
144};