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 CodeBuild delivers to EventBridge when a build
11 * changes state or completes a phase. CodeBuild uses hyphenated keys;
12 * fields not shared by every event kind are optional (the schema grows
13 * over time).
14 */
15export interface BuildEventDetail {
16 /** The build's id (`{project-name}:{uuid}`). */
17 "build-id"?: string;
18 /** Name of the project the build belongs to. */
19 "project-name"?: string;
20 /**
21 * State-change events: the new status — `IN_PROGRESS`, `SUCCEEDED`,
22 * `FAILED`, or `STOPPED`.
23 */
24 "build-status"?: string;
25 /** Phase-change events: the phase that just completed, e.g. `BUILD`. */
26 "completed-phase"?: string;
27 /** Phase-change events: the completed phase's status. */
28 "completed-phase-status"?: string;
29 /** Deep details: source, environment, phases, logs, … */
30 "additional-information"?: Record<string, unknown>;
31 /** Additional event fields (the schema grows over time). */
32 [key: string]: unknown;
33}
34
35/** A CodeBuild EventBridge event delivered to the handler. */
36export type BuildEvent = EventRecord<BuildEventDetail>;
37
38/** Which CodeBuild notifications to subscribe to. */
39export type BuildEventKind = "state" | "phase";
40
41const DETAIL_TYPES: Record<BuildEventKind, string> = {
42 state: "CodeBuild Build State Change",
43 phase: "CodeBuild Build Phase Change",
44};
45
46export interface BuildEventSourceProps extends EventRouteProps {
47 /**
48 * Logical id for the backing EventBridge rule.
49 * @default "CodeBuildEvents"
50 */
51 id?: string;
52 /**
53 * Which notifications to subscribe to: build state changes, per-phase
54 * completions, or both.
55 * @default ["state"]
56 */
57 kinds?: readonly BuildEventKind[];
58 /**
59 * Restrict to events about specific projects (matched against the
60 * event's `project-name`).
61 */
62 projectNames?: readonly string[];
63}
64
65/**
66 * Event source connecting CodeBuild build notifications to the hosting
67 * compute. CodeBuild publishes every build state change (and per-phase
68 * completion) to the account's default EventBridge bus (source
69 * `aws.codebuild`); this subscribes the host Function to those events so it
70 * can alert on failed builds or chain post-build automation.
71 *
72 * CodeBuild publishes to EventBridge automatically — no additional resource
73 * is created besides the EventBridge rule targeting the host. Provide the
74 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
75 * Function effect.
76 *
77 * ### Consuming Build Events
78 * **Example:** Alert On Failed Builds
79 * ```typescript
80 * import * as AWS from "alchemy/AWS";
81 *
82 * export default AlertFunction.make(
83 * { main: import.meta.url },
84 * Effect.gen(function* () {
85 * yield* AWS.CodeBuild.consumeBuildEvents(
86 * { kinds: ["state"] },
87 * (events) =>
88 * Stream.runForEach(events, (event) =>
89 * event.detail["build-status"] === "FAILED"
90 * ? Effect.log(`build ${event.detail["build-id"]} failed`)
91 * : Effect.void,
92 * ),
93 * );
94 * return {};
95 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
96 * );
97 * ```
98 */
99export const consumeBuildEvents = <StreamReq = never, Req = never>(
100 props: BuildEventSourceProps,
101 process: (
102 events: Stream.Stream<BuildEvent, never, StreamReq>,
103 ) => Effect.Effect<void, never, Req>,
104) =>
105 consumeBusEvents(
106 props.id ?? "CodeBuildEvents",
107 {
108 source: ["aws.codebuild"],
109 "detail-type": (props.kinds ?? (["state"] as const)).map(
110 (kind) => DETAIL_TYPES[kind],
111 ),
112 ...(props.projectNames !== undefined
113 ? { detail: { "project-name": [...props.projectNames] } }
114 : {}),
115 },
116 { description: props.description, state: props.state },
117 process,
118 );