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 ElastiCache delivers to EventBridge. The schema is
11 * loosely specified and differs slightly per event kind, so every field is
12 * optional (the schema grows over time).
13 */
14export interface CacheEventDetail {
15 /** Human-readable description of what happened. */
16 message?: string;
17 /** Additional event fields (the schema grows over time). */
18 [key: string]: unknown;
19}
20
21/** An ElastiCache EventBridge event delivered to the handler. */
22export type CacheEvent = EventRecord<CacheEventDetail>;
23
24/**
25 * Which ElastiCache notifications to subscribe to. Each kind maps to one of
26 * the detail-types ElastiCache publishes to the account's default bus.
27 */
28export type CacheEventKind =
29 | "cache-created"
30 | "cache-creation-failed"
31 | "cache-updated"
32 | "cache-update-failed"
33 | "cache-deleted"
34 | "cache-limit-approaching"
35 | "snapshot-created"
36 | "snapshot-creation-failed"
37 | "snapshot-copy-failed"
38 | "snapshot-export-failed";
39
40const DETAIL_TYPES: Record<CacheEventKind, string> = {
41 "cache-created": "Cache Created",
42 "cache-creation-failed": "Cache Creation Failed",
43 "cache-updated": "Cache Updated",
44 "cache-update-failed": "Cache Update Failed",
45 "cache-deleted": "Cache Deleted",
46 "cache-limit-approaching": "Cache Limit Approaching",
47 "snapshot-created": "Snapshot Created",
48 "snapshot-creation-failed": "Snapshot Creation Failed",
49 "snapshot-copy-failed": "Snapshot Copy Failed",
50 "snapshot-export-failed": "Snapshot Export Failed",
51};
52
53export interface CacheEventSourceProps extends EventRouteProps {
54 /**
55 * Logical id for the backing EventBridge rule.
56 * @default "ElastiCacheEvents"
57 */
58 id?: string;
59 /**
60 * Which notifications to subscribe to.
61 * @default all ElastiCache detail-types
62 */
63 kinds?: readonly CacheEventKind[];
64 /**
65 * Restrict to events about specific caches or snapshots (matched against
66 * the event's top-level `resources`, which carries the resource ARNs).
67 */
68 resourceArns?: readonly string[];
69}
70
71/**
72 * Event source connecting ElastiCache notifications to the hosting compute.
73 * ElastiCache publishes cache and snapshot lifecycle events — creations,
74 * updates, deletions, failures, approaching usage limits — to the account's
75 * default EventBridge bus (source `aws.elasticache`); this subscribes the
76 * host Function to those events so it can alert on failed snapshots or a
77 * cache nearing its usage limits.
78 *
79 * ElastiCache publishes to EventBridge automatically — no additional
80 * resource is created besides the EventBridge rule targeting the host.
81 * Provide the host-specific implementation layer (e.g.
82 * `AWS.Lambda.EventSource`) on the Function effect.
83 *
84 * ### Consuming Cache Events
85 * **Example:** Alert When a Cache Approaches Its Usage Limit
86 * ```typescript
87 * import * as AWS from "alchemy/AWS";
88 *
89 * export default AlertFunction.make(
90 * { main: import.meta.url },
91 * Effect.gen(function* () {
92 * yield* AWS.ElastiCache.consumeCacheEvents(
93 * { kinds: ["cache-limit-approaching", "snapshot-creation-failed"] },
94 * (events) =>
95 * Stream.runForEach(events, (event) =>
96 * Effect.logError(
97 * `${event["detail-type"]}: ${event.resources.join(", ")}`,
98 * ),
99 * ),
100 * );
101 * return {};
102 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
103 * );
104 * ```
105 */
106export const consumeCacheEvents = <StreamReq = never, Req = never>(
107 props: CacheEventSourceProps,
108 process: (
109 events: Stream.Stream<CacheEvent, never, StreamReq>,
110 ) => Effect.Effect<void, never, Req>,
111) =>
112 consumeBusEvents(
113 props.id ?? "ElastiCacheEvents",
114 {
115 source: ["aws.elasticache"],
116 ...(props.kinds !== undefined
117 ? { "detail-type": props.kinds.map((kind) => DETAIL_TYPES[kind]) }
118 : {}),
119 ...(props.resourceArns !== undefined
120 ? { resources: [...props.resourceArns] }
121 : {}),
122 },
123 { description: props.description, state: props.state },
124 process,
125 );