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 of a `Glue Job State Change` EventBridge event.
11 * Emitted on the account's default bus (source `aws.glue`) when a job run
12 * reaches `SUCCEEDED`, `FAILED`, `TIMEOUT`, or `STOPPED`.
13 */
14export interface JobEventDetail {
15 /** The name of the Glue job the run belongs to. */
16 jobName?: string;
17 /** The severity of the notification (`INFO`, `WARN`, `ERROR`). */
18 severity?: string;
19 /** The state the job run transitioned to. */
20 state?: "SUCCEEDED" | "FAILED" | "TIMEOUT" | "STOPPED" | (string & {});
21 /** The id of the job run. */
22 jobRunId?: string;
23 /** A human-readable message (e.g. the failure reason). */
24 message?: string;
25 /** Additional event fields (the schema grows over time). */
26 [key: string]: unknown;
27}
28
29/** A Glue job state-change EventBridge event delivered to the handler. */
30export type JobEvent = EventRecord<JobEventDetail>;
31
32export interface JobEventSourceProps extends EventRouteProps {
33 /**
34 * Logical id for the backing EventBridge rule.
35 * @default "GlueJobEvents"
36 */
37 id?: string;
38 /**
39 * Restrict to runs of specific jobs (matched against `detail.jobName`).
40 * Omit to receive events for every Glue job in the account.
41 */
42 jobNames?: readonly string[];
43 /**
44 * Restrict to specific terminal states (matched against `detail.state`).
45 * @default all states
46 */
47 states?: readonly ("SUCCEEDED" | "FAILED" | "TIMEOUT" | "STOPPED")[];
48}
49
50/**
51 * Event source connecting Glue job run state changes to the hosting
52 * compute. Glue publishes `Glue Job State Change` events to the account's
53 * default EventBridge bus (source `aws.glue`) whenever a run reaches
54 * `SUCCEEDED`, `FAILED`, `TIMEOUT`, or `STOPPED`; this subscribes the host
55 * Function to those events so it can chain pipelines or alert on failures.
56 *
57 * Glue publishes to EventBridge automatically — no additional resource is
58 * created besides the EventBridge rule targeting the host. Provide the
59 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
60 * Function effect.
61 *
62 * ### Consuming Job Events
63 * **Example:** Alert on Failed Runs
64 * ```typescript
65 * import * as AWS from "alchemy/AWS";
66 *
67 * export default AlertFunction.make(
68 * { main: import.meta.url },
69 * Effect.gen(function* () {
70 * yield* AWS.Glue.consumeJobEvents(
71 * { states: ["FAILED", "TIMEOUT"] },
72 * (events) =>
73 * Stream.runForEach(events, (event) =>
74 * Effect.logError(
75 * `glue run failed: ${event.detail.jobName} — ${event.detail.message}`,
76 * ),
77 * ),
78 * );
79 * return {};
80 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
81 * );
82 * ```
83 */
84export const consumeJobEvents = <StreamReq = never, Req = never>(
85 props: JobEventSourceProps,
86 process: (
87 events: Stream.Stream<JobEvent, never, StreamReq>,
88 ) => Effect.Effect<void, never, Req>,
89) =>
90 consumeBusEvents(
91 props.id ?? "GlueJobEvents",
92 {
93 source: ["aws.glue"],
94 "detail-type": ["Glue Job State Change"],
95 ...(props.jobNames !== undefined || props.states !== undefined
96 ? {
97 detail: {
98 ...(props.jobNames !== undefined
99 ? { jobName: [...props.jobNames] }
100 : {}),
101 ...(props.states !== undefined
102 ? { state: [...props.states] }
103 : {}),
104 },
105 }
106 : {}),
107 },
108 { description: props.description, state: props.state },
109 process,
110 );