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 of an `ACM Certificate Approaching Expiration`
11 * EventBridge event. ACM emits one event per day per certificate starting
12 * `daysBeforeExpiry` days (see {@link AccountConfiguration}) before the
13 * certificate expires. The certificate's ARN arrives in the event's
14 * top-level `resources` array.
15 */
16export interface ExpiryEventDetail {
17 /** Days remaining until the certificate expires. */
18 DaysToExpiry?: number;
19 /** The certificate's common name, e.g. `example.com`. */
20 CommonName?: string;
21 /** Additional event fields (the schema grows over time). */
22 [key: string]: unknown;
23}
24
25/** An ACM expiry EventBridge event delivered to the handler. */
26export type ExpiryEvent = EventRecord<ExpiryEventDetail>;
27
28export interface ExpiryEventSourceProps extends EventRouteProps {
29 /**
30 * Logical id for the backing EventBridge rule.
31 * @default "AcmExpiryEvents"
32 */
33 id?: string;
34 /**
35 * Restrict to events about specific certificates (matched against the
36 * event's top-level `resources` — the certificate ARNs).
37 */
38 certificateArns?: readonly string[];
39}
40
41/**
42 * Event source connecting ACM certificate-expiry notifications to the
43 * hosting compute. ACM publishes one `ACM Certificate Approaching
44 * Expiration` event per day per certificate to the account's default
45 * EventBridge bus (source `aws.acm`) starting `daysBeforeExpiry` days before
46 * each certificate expires — the threshold managed by the
47 * {@link AccountConfiguration} resource. This subscribes the host Function
48 * to those events so it can alert on, rotate, or re-import certificates
49 * before they lapse.
50 *
51 * ACM publishes to EventBridge automatically — no additional resource is
52 * created besides the EventBridge rule targeting the host. EventBridge rules
53 * are regional: deploy the consuming Function in the certificate's region
54 * (`us-east-1` for certificates created by the {@link Certificate}
55 * resource). Provide the host-specific implementation layer (e.g.
56 * `AWS.Lambda.EventSource`) on the Function effect.
57 *
58 * ### Consuming Expiry Events
59 * **Example:** Alert Before Certificates Expire
60 * ```typescript
61 * import * as AWS from "alchemy/AWS";
62 *
63 * export default AlertFunction.make(
64 * { main: import.meta.url },
65 * Effect.gen(function* () {
66 * yield* AWS.ACM.consumeExpiryEvents({}, (events) =>
67 * Stream.runForEach(events, (event) =>
68 * Effect.log(
69 * `${event.detail.CommonName} expires in ${event.detail.DaysToExpiry} days`,
70 * ),
71 * ),
72 * );
73 * return {};
74 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
75 * );
76 * ```
77 *
78 * **Example:** Watch a Specific Certificate
79 * ```typescript
80 * yield* AWS.ACM.consumeExpiryEvents(
81 * { certificateArns: [certificateArn] },
82 * (events) =>
83 * Stream.runForEach(events, (event) => rotateCertificate(event)),
84 * );
85 * ```
86 */
87export const consumeExpiryEvents = <StreamReq = never, Req = never>(
88 props: ExpiryEventSourceProps,
89 process: (
90 events: Stream.Stream<ExpiryEvent, never, StreamReq>,
91 ) => Effect.Effect<void, never, Req>,
92) =>
93 consumeBusEvents(
94 props.id ?? "AcmExpiryEvents",
95 {
96 source: ["aws.acm"],
97 "detail-type": ["ACM Certificate Approaching Expiration"],
98 ...(props.certificateArns !== undefined
99 ? { resources: [...props.certificateArns] }
100 : {}),
101 },
102 { description: props.description, state: props.state },
103 process,
104 );