1import type * as Effect from "effect/Effect";
2import type * as Stream from "effect/Stream";
3import * as Binding from "../../Binding.ts";
4import type { Application } from "./Application.ts";
5import type { Environment } from "./Environment.ts";
6
7/**
8 * The deployment notification action points an AppConfig deployment emits as
9 * it progresses. These are the `ON_*` subset of extension action points —
10 * fire-and-forget notifications that cannot modify the deployment.
11 */
12export type DeploymentEventType =
13 | "ON_DEPLOYMENT_START"
14 | "ON_DEPLOYMENT_STEP"
15 | "ON_DEPLOYMENT_BAKING"
16 | "ON_DEPLOYMENT_COMPLETE"
17 | "ON_DEPLOYMENT_ROLLED_BACK";
18
19/**
20 * The payload AppConfig sends when it invokes the host function at a
21 * deployment action point (the extension invocation payload, verbatim).
22 */
23export interface DeploymentEventRecord {
24 /** Unique id of this extension invocation. */
25 InvocationId: string;
26 /** The action point that fired, e.g. `"OnDeploymentStart"`. */
27 Type: string;
28 /** The application the deployment belongs to. */
29 Application?: { Id?: string; Name?: string };
30 /** The environment being deployed to. */
31 Environment?: { Id?: string; Name?: string };
32 /** The configuration profile being deployed. */
33 ConfigurationProfile?: { Id?: string; Name?: string };
34 /** The deployment's number within the environment. */
35 DeploymentNumber?: number;
36 /** Description of the deployment. */
37 Description?: string;
38 /** Parameter values supplied by the extension association. */
39 Parameters?: Record<string, string>;
40}
41
42export interface DeploymentEventSourceProps {
43 /**
44 * The deployment action points to subscribe to.
45 * @default all five `ON_DEPLOYMENT_*` action points
46 */
47 events?: DeploymentEventType[];
48}
49
50type DeploymentEventsHandler<Req> = (
51 events: Stream.Stream<DeploymentEventRecord>,
52) => Effect.Effect<void, never, Req>;
53
54/**
55 * Subscribe an Effect handler to AppConfig deployment events for an
56 * {@link Application} or {@link Environment}.
57 *
58 * At deploy time this provisions an AppConfig extension targeting the host
59 * function (plus the IAM role AppConfig assumes to invoke it) and associates
60 * the extension with the target resource. At runtime the handler receives
61 * each deployment notification as a {@link DeploymentEventRecord}.
62 *
63 * @param target The application or environment whose deployments to observe.
64 * @param props Optional event-source configuration (which action points).
65 * @param process The handler invoked with a stream of deployment events.
66 *
67 * @example
68 * ```typescript
69 * yield* AppConfig.consumeDeploymentEvents(env, (events) =>
70 * events.pipe(
71 * Stream.runForEach((event) =>
72 * Effect.log(`${event.Type}: deployment ${event.DeploymentNumber}`),
73 * ),
74 * ),
75 * );
76 * ```
77 *
78 * @example Only completed deployments
79 * ```typescript
80 * yield* AppConfig.consumeDeploymentEvents(
81 * env,
82 * { events: ["ON_DEPLOYMENT_COMPLETE"] },
83 * (events) =>
84 * events.pipe(Stream.runForEach((event) => Effect.log(event.Type))),
85 * );
86 * ```
87 */
88export function consumeDeploymentEvents<Req = never>(
89 target: Application | Environment,
90 process: DeploymentEventsHandler<Req>,
91): Effect.Effect<void, never, DeploymentEventSource>;
92export function consumeDeploymentEvents<Req = never>(
93 target: Application | Environment,
94 props: DeploymentEventSourceProps,
95 process: DeploymentEventsHandler<Req>,
96): Effect.Effect<void, never, DeploymentEventSource>;
97export function consumeDeploymentEvents<Req = never>(
98 target: Application | Environment,
99 propsOrProcess: DeploymentEventSourceProps | DeploymentEventsHandler<Req>,
100 maybeProcess?: DeploymentEventsHandler<Req>,
101): Effect.Effect<void, never, DeploymentEventSource> {
102 const [props, process] =
103 typeof propsOrProcess === "function"
104 ? [{} as DeploymentEventSourceProps, propsOrProcess]
105 : [propsOrProcess, maybeProcess!];
106 return DeploymentEventSource.use((source) => source(target, props, process));
107}
108
109/**
110 * Event source connecting AppConfig deployment notifications to the hosting
111 * compute. The contract is a `Binding.Service`; the Lambda implementation
112 * layer is `Lambda.AppConfigDeploymentEventSource` (extension + association +
113 * invoke role at deploy time, payload dispatch at runtime). Consume it
114 * through the {@link consumeDeploymentEvents} helper.
115 * ### Consuming Deployment Events
116 * **Example:** React to Deployments of an Environment
117 * ```typescript
118 * export default MyFunction.make(
119 * { main: import.meta.url },
120 * Effect.gen(function* () {
121 * const app = yield* AppConfig.Application("App", {});
122 * const env = yield* AppConfig.Environment("Env", {
123 * applicationId: app.applicationId,
124 * });
125 *
126 * yield* AppConfig.consumeDeploymentEvents(env, (events) =>
127 * events.pipe(
128 * Stream.runForEach((event) =>
129 * Effect.log(`deployment event: ${event.Type}`),
130 * ),
131 * ),
132 * );
133 * }).pipe(Effect.provide(Lambda.AppConfigDeploymentEventSource)),
134 * );
135 * ```
136 *
137 * @binding
138 */
139export interface DeploymentEventSource extends Binding.Service<
140 DeploymentEventSource,
141 "AWS.AppConfig.DeploymentEventSource",
142 DeploymentEventSourceService
143> {}
144
145export const DeploymentEventSource = Binding.Service<DeploymentEventSource>(
146 "AWS.AppConfig.DeploymentEventSource",
147);
148
149export type DeploymentEventSourceService = <Req = never>(
150 target: Application | Environment,
151 props: DeploymentEventSourceProps,
152 process: (
153 events: Stream.Stream<DeploymentEventRecord>,
154 ) => Effect.Effect<void, never, Req>,
155) => Effect.Effect<void, never, never>;