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 * A query execution state, as reported in the EventBridge event detail.
11 */
12export type QueryState =
13 | "QUEUED"
14 | "RUNNING"
15 | "SUCCEEDED"
16 | "FAILED"
17 | "CANCELLED";
18
19/**
20 * The `detail` payload Athena delivers to EventBridge on every query
21 * execution state transition (`Athena Query State Change`).
22 */
23export interface QueryStateChangeDetail {
24 /** The state the query transitioned into. */
25 currentState: QueryState;
26 /** The state the query transitioned out of (absent on the first event). */
27 previousState?: QueryState;
28 /** Id of the query execution. */
29 queryExecutionId: string;
30 /** Statement type: `DDL`, `DML`, or `UTILITY`. */
31 statementType?: string;
32 /** Name of the workgroup the query ran in. */
33 workgroupName?: string;
34 /** Monotonic sequence number of this transition within the execution. */
35 sequenceNumber?: string;
36 /** Version of the event schema. */
37 versionId?: string;
38 /** Error details when `currentState` is `FAILED`. */
39 athenaError?: {
40 errorCategory?: number;
41 errorType?: number;
42 errorMessage?: string;
43 retryable?: boolean;
44 };
45 /** Additional event fields (the schema grows over time). */
46 [key: string]: unknown;
47}
48
49/** An Athena query state change EventBridge event delivered to the handler. */
50export type QueryStateChangeEvent = EventRecord<QueryStateChangeDetail>;
51
52export interface QueryStateChangesProps extends EventRouteProps {
53 /**
54 * Logical id for the backing EventBridge rule.
55 * @default "AthenaQueryStateChanges"
56 */
57 id?: string;
58 /**
59 * Only deliver transitions into these states (e.g. `["SUCCEEDED",
60 * "FAILED"]` for terminal outcomes).
61 * @default all states
62 */
63 states?: QueryState[];
64 /**
65 * Only deliver events for queries that ran in these workgroups (by name).
66 * @default all workgroups
67 */
68 workGroups?: string[];
69}
70
71/**
72 * Event source connecting Athena query state changes to the hosting compute.
73 * Athena publishes every query execution state transition to the account's
74 * default EventBridge bus (source `aws.athena`, detail-type `Athena Query
75 * State Change`); this subscribes the host Function to those events so it can
76 * react to completed or failed queries without polling.
77 *
78 * Athena publishes to EventBridge automatically — no additional resource is
79 * created besides the EventBridge rule targeting the host. Provide the
80 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
81 * Function effect.
82 *
83 * ### Consuming Query State Changes
84 * **Example:** React to Terminal Query Outcomes
85 * ```typescript
86 * import * as AWS from "alchemy/AWS";
87 *
88 * export default EtlFunction.make(
89 * { main: import.meta.url },
90 * Effect.gen(function* () {
91 * yield* AWS.Athena.consumeQueryStateChanges(
92 * { states: ["SUCCEEDED", "FAILED"] },
93 * (events) =>
94 * Stream.runForEach(events, (event) =>
95 * Effect.log(
96 * `query ${event.detail.queryExecutionId} → ${event.detail.currentState}`,
97 * ),
98 * ),
99 * );
100 * return {};
101 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
102 * );
103 * ```
104 */
105export const consumeQueryStateChanges = <StreamReq = never, Req = never>(
106 props: QueryStateChangesProps,
107 process: (
108 events: Stream.Stream<QueryStateChangeEvent, never, StreamReq>,
109 ) => Effect.Effect<void, never, Req>,
110) =>
111 consumeBusEvents(
112 props.id ?? "AthenaQueryStateChanges",
113 {
114 source: ["aws.athena"],
115 "detail-type": ["Athena Query State Change"],
116 ...(props.states || props.workGroups
117 ? {
118 detail: {
119 ...(props.states ? { currentState: [...props.states] } : {}),
120 ...(props.workGroups
121 ? { workgroupName: [...props.workGroups] }
122 : {}),
123 },
124 }
125 : {}),
126 },
127 { description: props.description, state: props.state },
128 process,
129 );