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 Amazon SageMaker delivers to EventBridge. SageMaker
11 * mirrors the corresponding `Describe*` output in PascalCase; only the
12 * fields shared across the state-change event kinds are declared here, the
13 * rest stay open (the schema grows over time).
14 */
15export interface SageMakerEventDetail {
16 /** Endpoint state changes: the endpoint's name. */
17 EndpointName?: string;
18 /** Endpoint state changes: the endpoint's status (e.g. `IN_SERVICE`, `FAILED`). */
19 EndpointStatus?: string;
20 /** Feature group state changes: the feature group's name. */
21 FeatureGroupName?: string;
22 /** Feature group state changes: the feature group's status. */
23 FeatureGroupStatus?: string;
24 /** Job state changes: the job's status (e.g. `Completed`, `Failed`). */
25 TrainingJobStatus?: string;
26 ProcessingJobStatus?: string;
27 TransformJobStatus?: string;
28 /** Failure detail accompanying `Failed` statuses. */
29 FailureReason?: string;
30 /** Additional event fields (the schema grows over time). */
31 [key: string]: unknown;
32}
33
34/** A SageMaker EventBridge event delivered to the handler. */
35export type SageMakerEvent = EventRecord<SageMakerEventDetail>;
36
37/** Which SageMaker notifications to subscribe to. */
38export type SageMakerEventKind =
39 | "endpoint"
40 | "endpoint-deployment"
41 | "feature-group"
42 | "model-package"
43 | "training-job"
44 | "processing-job"
45 | "transform-job"
46 | "hyperparameter-tuning-job"
47 | "pipeline-execution"
48 | "pipeline-step"
49 | "image";
50
51const DETAIL_TYPES: Record<SageMakerEventKind, string> = {
52 endpoint: "SageMaker Endpoint State Change",
53 "endpoint-deployment": "SageMaker Endpoint Deployment State Change",
54 "feature-group": "SageMaker Feature Group State Change",
55 "model-package": "SageMaker Model Package State Change",
56 "training-job": "SageMaker Training Job State Change",
57 "processing-job": "SageMaker Processing Job State Change",
58 "transform-job": "SageMaker Transform Job State Change",
59 "hyperparameter-tuning-job":
60 "SageMaker HyperParameter Tuning Job State Change",
61 "pipeline-execution":
62 "SageMaker Model Building Pipeline Execution Status Change",
63 "pipeline-step":
64 "SageMaker Model Building Pipeline Execution Step Status Change",
65 image: "SageMaker Image State Change",
66};
67
68export interface SageMakerEventSourceProps extends EventRouteProps {
69 /**
70 * Logical id for the backing EventBridge rule.
71 * @default "SageMakerEvents"
72 */
73 id?: string;
74 /**
75 * Which SageMaker notifications to subscribe to: endpoint / endpoint
76 * deployment / feature group / model package state changes and the
77 * training, processing, transform, tuning, pipeline, and image job
78 * lifecycles.
79 * @default ["endpoint"]
80 */
81 kinds?: readonly SageMakerEventKind[];
82 /**
83 * Restrict to events about specific SageMaker resources (matched against
84 * the event's top-level `resources`, which carries the resource ARN).
85 */
86 resourceArns?: readonly string[];
87}
88
89/**
90 * Event source connecting Amazon SageMaker state changes to the hosting
91 * compute. SageMaker publishes endpoint, feature-group, model-package, and
92 * job lifecycle transitions to the account's default EventBridge bus
93 * (source `aws.sagemaker`); this subscribes the host Function to those
94 * events so it can react — e.g. alert when an endpoint drops out of
95 * `InService` or kick off downstream work when a training job completes.
96 *
97 * SageMaker publishes to EventBridge automatically — no additional resource
98 * is created besides the EventBridge rule targeting the host. Provide the
99 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
100 * Function effect.
101 *
102 * ### Consuming SageMaker Events
103 * **Example:** Alert When an Endpoint Fails
104 * ```typescript
105 * import * as AWS from "alchemy/AWS";
106 *
107 * export default AlertFunction.make(
108 * { main: import.meta.url },
109 * Effect.gen(function* () {
110 * yield* AWS.SageMaker.consumeSageMakerEvents(
111 * { kinds: ["endpoint"] },
112 * (events) =>
113 * Stream.runForEach(events, (event) =>
114 * event.detail.EndpointStatus === "FAILED"
115 * ? Effect.logError(
116 * `endpoint ${event.detail.EndpointName} failed: ${event.detail.FailureReason}`,
117 * )
118 * : Effect.void,
119 * ),
120 * );
121 * return {};
122 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
123 * );
124 * ```
125 */
126export const consumeSageMakerEvents = <StreamReq = never, Req = never>(
127 props: SageMakerEventSourceProps,
128 process: (
129 events: Stream.Stream<SageMakerEvent, never, StreamReq>,
130 ) => Effect.Effect<void, never, Req>,
131) =>
132 consumeBusEvents(
133 props.id ?? "SageMakerEvents",
134 {
135 source: ["aws.sagemaker"],
136 "detail-type": (props.kinds ?? (["endpoint"] as const)).map(
137 (kind) => DETAIL_TYPES[kind],
138 ),
139 ...(props.resourceArns !== undefined
140 ? { resources: [...props.resourceArns] }
141 : {}),
142 },
143 { description: props.description, state: props.state },
144 process,
145 );