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 IoT Greengrass V2 delivers to EventBridge.
11 * Deployment-status events describe a deployment landing on a core device
12 * (`deploymentId`, `coreDeviceExecutionStatus`); component-status events
13 * describe an installed component's lifecycle state (`componentName`,
14 * `componentLifecycleState`, e.g. `BROKEN`). Fields not shared by every
15 * event kind are optional (the schema grows over time).
16 */
17export interface GreengrassEventDetail {
18 /** The core device thing name the event is about. */
19 coreDeviceThingName?: string;
20 /** Deployment-status events: the id of the deployment. */
21 deploymentId?: string;
22 /**
23 * Deployment-status events: the per-device execution status, e.g.
24 * `SUCCEEDED`, `FAILED`, `REJECTED`, `TIMED_OUT`.
25 */
26 coreDeviceExecutionStatus?: string;
27 /** Component-status events: the name of the installed component. */
28 componentName?: string;
29 /** Component-status events: the version of the installed component. */
30 componentVersion?: string;
31 /**
32 * Component-status events: the component's lifecycle state, e.g.
33 * `RUNNING`, `ERRORED`, `BROKEN`.
34 */
35 componentLifecycleState?: string;
36 /** Additional event fields (the schema grows over time). */
37 [key: string]: unknown;
38}
39
40/** An IoT Greengrass V2 EventBridge event delivered to the handler. */
41export type GreengrassEvent = EventRecord<GreengrassEventDetail>;
42
43/** Which IoT Greengrass V2 notifications to subscribe to. */
44export type GreengrassEventKind = "deployment-status" | "component-status";
45
46const DETAIL_TYPES: Record<GreengrassEventKind, string> = {
47 "deployment-status": "Greengrass V2 Effective Deployment Status Change",
48 "component-status": "Greengrass V2 Installed Component Status Change",
49};
50
51export interface GreengrassEventSourceProps extends EventRouteProps {
52 /**
53 * Logical id for the backing EventBridge rule.
54 * @default "GreengrassEvents"
55 */
56 id?: string;
57 /**
58 * Which notifications to subscribe to: per-device deployment status
59 * changes (a deployment succeeding/failing on a core device) and/or
60 * installed component status changes (a component entering `BROKEN`).
61 * @default ["deployment-status"]
62 */
63 kinds?: readonly GreengrassEventKind[];
64}
65
66/**
67 * Event source connecting IoT Greengrass V2 notifications to the hosting
68 * compute. Greengrass publishes per-core-device deployment execution status
69 * (`SUCCEEDED`, `FAILED`, …) and installed-component lifecycle changes (most
70 * importantly a component dropping into `BROKEN`) to the account's default
71 * EventBridge bus (source `aws.greengrass`); this subscribes the host
72 * Function to those events so it can alert on failed rollouts or broken
73 * edge software.
74 *
75 * Greengrass publishes to EventBridge automatically — no additional resource
76 * is created besides the EventBridge rule targeting the host. Provide the
77 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
78 * Function effect.
79 *
80 * ### Consuming Greengrass Events
81 * **Example:** Alert When A Rollout Fails On A Device
82 * ```typescript
83 * import * as AWS from "alchemy/AWS";
84 *
85 * export default AlertFunction.make(
86 * { main: import.meta.url },
87 * Effect.gen(function* () {
88 * yield* AWS.GreengrassV2.consumeGreengrassEvents(
89 * { kinds: ["deployment-status"] },
90 * (events) =>
91 * Stream.runForEach(events, (event) =>
92 * event.detail.coreDeviceExecutionStatus === "FAILED"
93 * ? Effect.logError(
94 * `deployment ${event.detail.deploymentId} failed on ${event.detail.coreDeviceThingName}`,
95 * )
96 * : Effect.void,
97 * ),
98 * );
99 * return {};
100 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
101 * );
102 * ```
103 */
104export const consumeGreengrassEvents = <StreamReq = never, Req = never>(
105 props: GreengrassEventSourceProps,
106 process: (
107 events: Stream.Stream<GreengrassEvent, never, StreamReq>,
108 ) => Effect.Effect<void, never, Req>,
109) =>
110 consumeBusEvents(
111 props.id ?? "GreengrassEvents",
112 {
113 source: ["aws.greengrass"],
114 "detail-type": (props.kinds ?? (["deployment-status"] as const)).map(
115 (kind) => DETAIL_TYPES[kind],
116 ),
117 },
118 { description: props.description, state: props.state },
119 process,
120 );