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