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 Systems Manager delivers to EventBridge when a
11 * Parameter Store parameter changes (`Parameter Store Change`) or a
12 * parameter policy fires (`Parameter Store Policy Action`). Fields not
13 * shared by every event kind are optional (the schema grows over time).
14 */
15export interface ParameterEventDetail {
16 /** The parameter name, e.g. `/my-app/prod/db-url`. */
17 name?: string;
18 /** The parameter type (`String`, `StringList`, `SecureString`). */
19 type?: string;
20 /** What happened: `Create`, `Update`, `Delete`, or `LabelParameterVersion`. */
21 operation?: string;
22 /** The parameter description, when present. */
23 description?: string;
24 /** Policy events: the policy type that fired (`Expiration`, `ExpirationNotification`, `NoChangeNotification`). */
25 "policy-type"?: string;
26 /** Policy events: the policy content that fired. */
27 "policy-content"?: string;
28 /** Policy events: the action taken by the policy. */
29 "action-reason"?: string;
30 /** Additional event fields (the schema grows over time). */
31 [key: string]: unknown;
32}
33
34/** An SSM Parameter Store EventBridge event delivered to the handler. */
35export type ParameterEvent = EventRecord<ParameterEventDetail>;
36
37/** Which SSM Parameter Store events to subscribe to. */
38export type ParameterEventKind = "change" | "policy-action";
39
40const DETAIL_TYPES: Record<ParameterEventKind, string> = {
41 change: "Parameter Store Change",
42 "policy-action": "Parameter Store Policy Action",
43};
44
45export interface ParameterEventSourceProps extends EventRouteProps {
46 /**
47 * Logical id for the backing EventBridge rule.
48 * @default "SSMParameterEvents"
49 */
50 id?: string;
51 /**
52 * Which Parameter Store events to subscribe to.
53 * @default all kinds
54 */
55 kinds?: readonly ParameterEventKind[];
56 /**
57 * Restrict the subscription to specific parameter names. Omit to receive
58 * events for every parameter in the account/region.
59 */
60 names?: readonly string[];
61}
62
63/**
64 * Event source connecting SSM Parameter Store changes to the hosting compute.
65 * Systems Manager publishes every parameter create/update/delete (and
66 * parameter-policy action, e.g. expiration) to the account's default
67 * EventBridge bus (source `aws.ssm`); this subscribes the host Function to
68 * those events so it can react to configuration changes — refresh caches,
69 * fan out notifications, or audit secret rotation.
70 *
71 * Parameter Store publishes to EventBridge automatically — no additional
72 * resource is created besides the EventBridge rule targeting the host.
73 * Provide the host-specific implementation layer (e.g.
74 * `AWS.Lambda.EventSource`) on the Function effect.
75 *
76 * ### Consuming Parameter Events
77 * **Example:** React To Parameter Changes
78 * ```typescript
79 * import * as AWS from "alchemy/AWS";
80 *
81 * export default ConfigWatcher.make(
82 * { main: import.meta.url },
83 * Effect.gen(function* () {
84 * yield* AWS.SSM.consumeParameterEvents(
85 * { kinds: ["change"] },
86 * (events) =>
87 * Stream.runForEach(events, (event) =>
88 * Effect.log(
89 * `${event.detail.operation}: ${event.detail.name}`,
90 * ),
91 * ),
92 * );
93 * return {};
94 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
95 * );
96 * ```
97 */
98export const consumeParameterEvents = <StreamReq = never, Req = never>(
99 props: ParameterEventSourceProps,
100 process: (
101 events: Stream.Stream<ParameterEvent, never, StreamReq>,
102 ) => Effect.Effect<void, never, Req>,
103) =>
104 consumeBusEvents(
105 props.id ?? "SSMParameterEvents",
106 {
107 source: ["aws.ssm"],
108 "detail-type": (
109 props.kinds ?? (Object.keys(DETAIL_TYPES) as ParameterEventKind[])
110 ).map((kind) => DETAIL_TYPES[kind]),
111 ...(props.names !== undefined && props.names.length > 0
112 ? { detail: { name: [...props.names] } }
113 : {}),
114 },
115 { description: props.description, state: props.state },
116 process,
117 );