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 CodePipeline delivers to EventBridge when a pipeline,
11 * stage, or action execution changes state. Fields not shared by every
12 * event kind are optional (the schema grows over time).
13 */
14export interface PipelineEventDetail {
15 /** Name of the pipeline the event belongs to. */
16 pipeline?: string;
17 /** Structure version of the pipeline. */
18 version?: number;
19 /** Id of the pipeline execution. */
20 "execution-id"?: string;
21 /**
22 * The new state — `STARTED`, `SUCCEEDED`, `FAILED`, `CANCELED`,
23 * `STOPPED`, `STOPPING`, `SUPERSEDED`, or `RESUMED`.
24 */
25 state?: string;
26 /** Stage/action events: the stage the event belongs to. */
27 stage?: string;
28 /** Action events: the action the event belongs to. */
29 action?: string;
30 /** Pipeline events: what triggered the execution. */
31 "execution-trigger"?: Record<string, unknown>;
32 /** Additional event fields (the schema grows over time). */
33 [key: string]: unknown;
34}
35
36/** A CodePipeline EventBridge event delivered to the handler. */
37export type PipelineEvent = EventRecord<PipelineEventDetail>;
38
39/** Which CodePipeline notifications to subscribe to. */
40export type PipelineEventKind = "execution" | "stage" | "action";
41
42const DETAIL_TYPES: Record<PipelineEventKind, string> = {
43 execution: "CodePipeline Pipeline Execution State Change",
44 stage: "CodePipeline Stage Execution State Change",
45 action: "CodePipeline Action Execution State Change",
46};
47
48export interface PipelineEventSourceProps extends EventRouteProps {
49 /**
50 * Logical id for the backing EventBridge rule.
51 * @default "CodePipelineEvents"
52 */
53 id?: string;
54 /**
55 * Which notifications to subscribe to: pipeline-execution, stage-execution,
56 * or action-execution state changes (any combination).
57 * @default ["execution"]
58 */
59 kinds?: readonly PipelineEventKind[];
60 /**
61 * Restrict to events about specific pipelines (matched against the
62 * event's `pipeline`).
63 */
64 pipelineNames?: readonly string[];
65}
66
67/**
68 * Event source connecting CodePipeline execution notifications to the
69 * hosting compute. CodePipeline publishes every pipeline, stage, and action
70 * execution state change to the account's default EventBridge bus (source
71 * `aws.codepipeline`); this subscribes the host Function to those events so
72 * it can alert on failed deployments or chain post-release automation.
73 *
74 * CodePipeline publishes to EventBridge automatically — no additional
75 * resource is created besides the EventBridge rule targeting the host.
76 * Provide the host-specific implementation layer (e.g.
77 * `AWS.Lambda.EventSource`) on the Function effect.
78 *
79 * ### Consuming Pipeline Events
80 * **Example:** Alert On Failed Executions
81 * ```typescript
82 * import * as AWS from "alchemy/AWS";
83 *
84 * export default AlertFunction.make(
85 * { main: import.meta.url },
86 * Effect.gen(function* () {
87 * yield* AWS.CodePipeline.consumePipelineEvents(
88 * { kinds: ["execution"] },
89 * (events) =>
90 * Stream.runForEach(events, (event) =>
91 * event.detail.state === "FAILED"
92 * ? Effect.log(`pipeline ${event.detail.pipeline} failed`)
93 * : Effect.void,
94 * ),
95 * );
96 * return {};
97 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
98 * );
99 * ```
100 */
101export const consumePipelineEvents = <StreamReq = never, Req = never>(
102 props: PipelineEventSourceProps,
103 process: (
104 events: Stream.Stream<PipelineEvent, never, StreamReq>,
105 ) => Effect.Effect<void, never, Req>,
106) =>
107 consumeBusEvents(
108 props.id ?? "CodePipelineEvents",
109 {
110 source: ["aws.codepipeline"],
111 "detail-type": (props.kinds ?? (["execution"] as const)).map(
112 (kind) => DETAIL_TYPES[kind],
113 ),
114 ...(props.pipelineNames !== undefined
115 ? { detail: { pipeline: [...props.pipelineNames] } }
116 : {}),
117 },
118 { description: props.description, state: props.state },
119 process,
120 );