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 KMS delivers to EventBridge. Rotation and
11 * deletion events carry the affected key's id; fields not shared by every
12 * event kind are optional (the schema grows over time).
13 */
14export interface KeyEventDetail {
15 /** The id of the KMS key the event is about. */
16 "key-id"?: string;
17 /** Deletion events: when the key material was destroyed. */
18 "deletion-time"?: string;
19 /** Rotation events: when the key material was rotated. */
20 "rotation-time"?: string;
21 /** Imported key material expiration events: when the material expired. */
22 "expiration-time"?: string;
23 /** Additional event fields (the schema grows over time). */
24 [key: string]: unknown;
25}
26
27/** A KMS EventBridge event delivered to the handler. */
28export type KeyEvent = EventRecord<KeyEventDetail>;
29
30/** Which KMS notifications to subscribe to. */
31export type KeyEventKind =
32 | "rotation"
33 | "deletion"
34 | "imported-key-material-expiration";
35
36const DETAIL_TYPES: Record<KeyEventKind, string> = {
37 rotation: "KMS CMK Rotation",
38 deletion: "KMS CMK Deletion",
39 "imported-key-material-expiration": "KMS Imported Key Material Expiration",
40};
41
42export interface KeyEventSourceProps extends EventRouteProps {
43 /**
44 * Logical id for the backing EventBridge rule.
45 * @default "KMSKeyEvents"
46 */
47 id?: string;
48 /**
49 * Which notifications to subscribe to: automatic key-material rotations,
50 * completed key deletions (the end of the pending-deletion window), and/or
51 * imported key material expirations.
52 * @default all three kinds
53 */
54 kinds?: readonly KeyEventKind[];
55 /**
56 * Restrict to events about specific KMS keys (matched against the event's
57 * top-level `resources`, which contains the key's ARN).
58 */
59 keyArns?: readonly string[];
60}
61
62/**
63 * Event source connecting AWS KMS notifications to the hosting compute. KMS
64 * publishes key-material rotations, completed key deletions (the moment the
65 * pending-deletion window elapses and the material is destroyed), and
66 * imported key material expirations to the account's default EventBridge bus
67 * (source `aws.kms`); this subscribes the host Function to those events so
68 * it can audit rotations or react before/when a key becomes unusable.
69 *
70 * KMS publishes to EventBridge automatically — no additional resource is
71 * created besides the EventBridge rule targeting the host. Provide the
72 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
73 * Function effect.
74 *
75 * ### Consuming Key Events
76 * **Example:** Audit Rotations and Deletions
77 * ```typescript
78 * import * as AWS from "alchemy/AWS";
79 *
80 * export default AuditFunction.make(
81 * { main: import.meta.url },
82 * Effect.gen(function* () {
83 * yield* AWS.KMS.consumeKeyEvents(
84 * { kinds: ["rotation", "deletion"] },
85 * (events) =>
86 * Stream.runForEach(events, (event) =>
87 * Effect.logInfo(
88 * `${event["detail-type"]}: ${event.detail["key-id"]}`,
89 * ),
90 * ),
91 * );
92 * return {};
93 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
94 * );
95 * ```
96 */
97export const consumeKeyEvents = <StreamReq = never, Req = never>(
98 props: KeyEventSourceProps,
99 process: (
100 events: Stream.Stream<KeyEvent, never, StreamReq>,
101 ) => Effect.Effect<void, never, Req>,
102) =>
103 consumeBusEvents(
104 props.id ?? "KMSKeyEvents",
105 {
106 source: ["aws.kms"],
107 "detail-type": (
108 props.kinds ??
109 (["rotation", "deletion", "imported-key-material-expiration"] as const)
110 ).map((kind) => DETAIL_TYPES[kind]),
111 ...(props.keyArns !== undefined ? { resources: [...props.keyArns] } : {}),
112 },
113 { description: props.description, state: props.state },
114 process,
115 );