1import type * as apprunner from "@distilled.cloud/aws/apprunner";
2import * as Effect from "effect/Effect";
3import type * as Stream from "effect/Stream";
4import {
5 consumeBusEvents,
6 type EventRecord,
7 type EventRouteProps,
8} from "../EventBridge/EventSource.ts";
9
10/**
11 * The `detail` payload App Runner delivers to EventBridge when a service's
12 * lifecycle status changes (e.g. `OPERATION_IN_PROGRESS` -> `RUNNING`).
13 */
14export interface ServiceStatusChangeDetail {
15 /** ID of the App Runner service whose status changed. */
16 serviceId: string;
17 /** Name of the App Runner service. */
18 serviceName: string;
19 /** The status the service transitioned into (e.g. `RUNNING`, `PAUSED`). */
20 currentStatus: apprunner.ServiceStatus;
21 /** The status the service transitioned out of. */
22 previousStatus?: apprunner.ServiceStatus;
23}
24
25/**
26 * The `detail` payload App Runner delivers to EventBridge when an
27 * asynchronous operation (deployment, pause, resume, create, delete,
28 * update) changes status.
29 */
30export interface OperationStatusChangeDetail {
31 /** ID of the App Runner service the operation ran on. */
32 serviceId: string;
33 /** Name of the App Runner service. */
34 serviceName: string;
35 /** ID of the operation (matches `OperationId` from the initiating call). */
36 operationId?: string;
37 /**
38 * New status of the operation, e.g. `DeploymentCompletedSuccessfully`,
39 * `PauseServiceCompletedSuccessfully`, `DeploymentFailed`.
40 */
41 operationStatus: string;
42}
43
44/** A service-status-change EventBridge event delivered to the handler. */
45export type ServiceStatusChangeEvent = EventRecord<ServiceStatusChangeDetail>;
46
47/** An operation-status-change EventBridge event delivered to the handler. */
48export type OperationStatusChangeEvent =
49 EventRecord<OperationStatusChangeDetail>;
50
51export interface ServiceStatusChangeEventSourceProps extends EventRouteProps {
52 /**
53 * Logical id prefix for the backing EventBridge rule.
54 * @default "AppRunner"
55 */
56 id?: string;
57 /**
58 * Only deliver events whose `currentStatus` is one of these values. Omit
59 * to receive every status transition.
60 */
61 currentStatus?: apprunner.ServiceStatus[];
62}
63
64export interface OperationStatusChangeEventSourceProps extends EventRouteProps {
65 /**
66 * Logical id prefix for the backing EventBridge rule.
67 * @default "AppRunner"
68 */
69 id?: string;
70 /**
71 * Only deliver events whose `operationStatus` is one of these values
72 * (e.g. `["DeploymentCompletedSuccessfully", "DeploymentFailed"]`). Omit
73 * to receive every operation transition.
74 */
75 operationStatus?: string[];
76}
77
78/**
79 * Deliver App Runner service status changes (`aws.apprunner` /
80 * `"AppRunner Service Status Change"` EventBridge events) to the host
81 * Function — e.g. to alert when a service unexpectedly leaves `RUNNING`.
82 *
83 * The EventBridge pattern matches every App Runner service in the account
84 * and region (the pattern must be literal — `Output` values such as
85 * `service.serviceId` do not resolve inside the deployed bundle); inspect
86 * `event.detail.serviceName` / `event.detail.serviceId` in the handler if
87 * multiple services share the Function. Provide `AWS.Lambda.EventSource` on
88 * the Function effect to implement the subscription.
89 *
90 * ### Reacting To Status Changes
91 * **Example:** Alert when a Service Pauses
92 * ```typescript
93 * yield* AWS.AppRunner.consumeServiceStatusChanges(
94 * { currentStatus: ["PAUSED"] },
95 * (events) =>
96 * Stream.runForEach(events, (event) =>
97 * Effect.log(`${event.detail.serviceName} is paused`),
98 * ),
99 * );
100 * ```
101 */
102export const consumeServiceStatusChanges = <StreamReq = never, Req = never>(
103 props: ServiceStatusChangeEventSourceProps,
104 process: (
105 events: Stream.Stream<ServiceStatusChangeEvent, never, StreamReq>,
106 ) => Effect.Effect<void, never, Req>,
107) =>
108 Effect.gen(function* () {
109 const { id, currentStatus, ...routeProps } = props;
110
111 yield* consumeBusEvents(
112 `${id ?? "AppRunner"}-ServiceStatusChanges`,
113 {
114 source: ["aws.apprunner"],
115 "detail-type": ["AppRunner Service Status Change"],
116 ...(currentStatus && currentStatus.length > 0
117 ? { detail: { currentStatus } }
118 : {}),
119 },
120 routeProps,
121 process,
122 );
123 });
124
125/**
126 * Deliver App Runner operation status changes (`aws.apprunner` /
127 * `"AppRunner Service Operation Status Change"` EventBridge events) to the
128 * host Function — e.g. to notify a chat channel when a deployment completes
129 * or fails.
130 *
131 * The EventBridge pattern matches every App Runner service in the account
132 * and region; inspect `event.detail.serviceName` / `event.detail.operationId`
133 * in the handler if multiple services share the Function. Provide
134 * `AWS.Lambda.EventSource` on the Function effect to implement the
135 * subscription.
136 *
137 * ### Reacting To Deployments
138 * **Example:** Alert on Failed Deployments
139 * ```typescript
140 * yield* AWS.AppRunner.consumeOperationStatusChanges(
141 * { operationStatus: ["DeploymentFailed"] },
142 * (events) =>
143 * Stream.runForEach(events, (event) =>
144 * Effect.log(`deployment failed on ${event.detail.serviceName}`),
145 * ),
146 * );
147 * ```
148 */
149export const consumeOperationStatusChanges = <StreamReq = never, Req = never>(
150 props: OperationStatusChangeEventSourceProps,
151 process: (
152 events: Stream.Stream<OperationStatusChangeEvent, never, StreamReq>,
153 ) => Effect.Effect<void, never, Req>,
154) =>
155 Effect.gen(function* () {
156 const { id, operationStatus, ...routeProps } = props;
157
158 yield* consumeBusEvents(
159 `${id ?? "AppRunner"}-OperationStatusChanges`,
160 {
161 source: ["aws.apprunner"],
162 "detail-type": ["AppRunner Service Operation Status Change"],
163 ...(operationStatus && operationStatus.length > 0
164 ? { detail: { operationStatus } }
165 : {}),
166 },
167 routeProps,
168 process,
169 );
170 });