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 EC2 Image Builder delivers to EventBridge.
11 * Image-state-change events report the build's new state (`BUILDING`,
12 * `TESTING`, `DISTRIBUTING`, `AVAILABLE`, `CANCELLED`, `FAILED`, …);
13 * workflow-step-waiting events identify the `WAIT_FOR_ACTION` step that is
14 * paused. Fields not shared by every event kind are optional (the schema
15 * grows over time).
16 */
17export interface ImageEventDetail {
18 /** The state the image build transitioned to (image-state-change). */
19 state?: { status?: string; reason?: string };
20 /** Additional event fields (the schema grows over time). */
21 [key: string]: unknown;
22}
23
24/** An Image Builder EventBridge event delivered to the handler. */
25export type ImageEvent = EventRecord<ImageEventDetail>;
26
27/** Which Image Builder notifications to subscribe to. */
28export type ImageEventKind = "image-state-change" | "workflow-step-waiting";
29
30const DETAIL_TYPES: Record<ImageEventKind, string> = {
31 "image-state-change": "EC2 Image Builder Image State Change",
32 "workflow-step-waiting": "EC2 Image Builder Workflow Step Waiting",
33};
34
35export interface ImageEventSourceProps extends EventRouteProps {
36 /**
37 * Logical id for the backing EventBridge rule.
38 * @default "ImageBuilderImageEvents"
39 */
40 id?: string;
41 /**
42 * Which notifications to subscribe to: image state changes (a build
43 * progressing through `BUILDING` → … → `AVAILABLE`/`FAILED`/`CANCELLED`)
44 * and/or workflow steps pausing on `WAIT_FOR_ACTION`.
45 * @default ["image-state-change"]
46 */
47 kinds?: readonly ImageEventKind[];
48 /**
49 * Restrict to events about specific images. Matched as a prefix against
50 * the event's top-level `resources`, so an image version ARN also matches
51 * its build versions (`{imageVersionArn}/{build}`).
52 */
53 imageArns?: readonly string[];
54}
55
56/**
57 * Event source connecting EC2 Image Builder notifications to the hosting
58 * compute. Image Builder publishes image state changes (and workflow
59 * step-waiting notifications) to the account's default EventBridge bus
60 * (source `aws.imagebuilder`); this subscribes the host Function to those
61 * events so it can react when a build finishes, fails, or pauses for manual
62 * action.
63 *
64 * Image Builder publishes to EventBridge automatically — no additional
65 * resource is created besides the EventBridge rule targeting the host.
66 * Provide the host-specific implementation layer (e.g.
67 * `AWS.Lambda.EventSource`) on the Function effect.
68 *
69 * ### Consuming Image Events
70 * **Example:** React When a Build Completes
71 * ```typescript
72 * import * as AWS from "alchemy/AWS";
73 *
74 * export default AlertFunction.make(
75 * { main: import.meta.url },
76 * Effect.gen(function* () {
77 * yield* AWS.ImageBuilder.consumeImageEvents(
78 * { kinds: ["image-state-change"] },
79 * (events) =>
80 * Stream.runForEach(events, (event) =>
81 * event.detail.state?.status === "AVAILABLE"
82 * ? Effect.log(`new image ready: ${event.resources[0]}`)
83 * : Effect.log(`build ${event.detail.state?.status}`),
84 * ),
85 * );
86 * return {};
87 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
88 * );
89 * ```
90 */
91export const consumeImageEvents = <StreamReq = never, Req = never>(
92 props: ImageEventSourceProps,
93 process: (
94 events: Stream.Stream<ImageEvent, never, StreamReq>,
95 ) => Effect.Effect<void, never, Req>,
96) =>
97 consumeBusEvents(
98 props.id ?? "ImageBuilderImageEvents",
99 {
100 source: ["aws.imagebuilder"],
101 "detail-type": (props.kinds ?? (["image-state-change"] as const)).map(
102 (kind) => DETAIL_TYPES[kind],
103 ),
104 ...(props.imageArns !== undefined
105 ? { resources: props.imageArns.map((arn) => ({ prefix: arn })) }
106 : {}),
107 },
108 { description: props.description, state: props.state },
109 process,
110 );