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 RDS delivers to EventBridge. Every field is optional
11 * (the schema grows over time).
12 */
13export interface RdsEventDetail {
14 /** Categories the event belongs to (e.g. `failover`, `backup`). */
15 EventCategories?: string[];
16 /** The kind of resource the event is about (e.g. `CLUSTER`, `DB_INSTANCE`). */
17 SourceType?: string;
18 /** ARN of the instance/cluster/snapshot the event is about. */
19 SourceArn?: string;
20 /** When the event occurred (ISO 8601). */
21 Date?: string;
22 /** RDS event id, e.g. `RDS-EVENT-0170`. */
23 EventID?: string;
24 /** Identifier of the source resource. */
25 SourceIdentifier?: string;
26 /** Human-readable description of what happened. */
27 Message?: string;
28 /** Additional event fields (the schema grows over time). */
29 [key: string]: unknown;
30}
31
32/** An RDS EventBridge event delivered to the handler. */
33export type RdsEvent = EventRecord<RdsEventDetail>;
34
35/**
36 * Which RDS notifications to subscribe to. Each kind maps to one of the
37 * detail-types RDS publishes to the account's default bus.
38 */
39export type RdsEventKind =
40 | "db-instance"
41 | "db-cluster"
42 | "db-snapshot"
43 | "db-cluster-snapshot"
44 | "db-parameter-group"
45 | "db-security-group"
46 | "db-proxy"
47 | "blue-green-deployment";
48
49const DETAIL_TYPES: Record<RdsEventKind, string> = {
50 "db-instance": "RDS DB Instance Event",
51 "db-cluster": "RDS DB Cluster Event",
52 "db-snapshot": "RDS DB Snapshot Event",
53 "db-cluster-snapshot": "RDS DB Cluster Snapshot Event",
54 "db-parameter-group": "RDS DB Parameter Group Event",
55 "db-security-group": "RDS DB Security Group Event",
56 "db-proxy": "RDS DB Proxy Event",
57 "blue-green-deployment": "RDS Blue Green Deployment Event",
58};
59
60export interface RdsEventSourceProps extends EventRouteProps {
61 /**
62 * Logical id for the backing EventBridge rule.
63 * @default "RdsEvents"
64 */
65 id?: string;
66 /**
67 * Which notifications to subscribe to.
68 * @default all RDS detail-types
69 */
70 kinds?: readonly RdsEventKind[];
71 /**
72 * Restrict to events about specific instances, clusters, or snapshots
73 * (matched against the event's top-level `resources`, which carries the
74 * resource ARNs). Strongly recommended — the `aws.rds` source also
75 * carries events for Neptune and DocumentDB resources in the account.
76 */
77 resourceArns?: readonly string[];
78}
79
80/**
81 * Event source connecting RDS notifications to the hosting compute. RDS
82 * publishes instance, cluster, snapshot, parameter-group, and proxy
83 * lifecycle events — failovers, maintenance, completed backups,
84 * configuration changes — to the account's default EventBridge bus (source
85 * `aws.rds`); this subscribes the host Function to those events so it can
86 * alert on failovers or react to completed snapshots.
87 *
88 * RDS publishes to EventBridge automatically — no additional resource is
89 * created besides the EventBridge rule targeting the host. Because the
90 * `aws.rds` source is shared with Neptune and DocumentDB, pass
91 * `resourceArns` (e.g. the cluster ARN) to scope the rule to your RDS
92 * resources. Provide the host-specific implementation layer (e.g.
93 * `AWS.Lambda.EventSource`) on the Function effect.
94 *
95 * ### Consuming RDS Events
96 * **Example:** Alert on Cluster Failovers
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 * const clusterArn = yield* cluster.dbClusterArn;
104 * yield* AWS.RDS.consumeRdsEvents(
105 * { kinds: ["db-cluster"], resourceArns: [clusterArn] },
106 * (events) =>
107 * Stream.runForEach(events, (event) =>
108 * Effect.logError(
109 * `${event["detail-type"]}: ${event.detail.Message}`,
110 * ),
111 * ),
112 * );
113 * return {};
114 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
115 * );
116 * ```
117 */
118export const consumeRdsEvents = <StreamReq = never, Req = never>(
119 props: RdsEventSourceProps,
120 process: (
121 events: Stream.Stream<RdsEvent, never, StreamReq>,
122 ) => Effect.Effect<void, never, Req>,
123) =>
124 consumeBusEvents(
125 props.id ?? "RdsEvents",
126 {
127 source: ["aws.rds"],
128 ...(props.kinds !== undefined
129 ? { "detail-type": props.kinds.map((kind) => DETAIL_TYPES[kind]) }
130 : {}),
131 ...(props.resourceArns !== undefined
132 ? { resources: [...props.resourceArns] }
133 : {}),
134 },
135 { description: props.description, state: props.state },
136 process,
137 );