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 AWS Backup delivers to EventBridge when a backup,
11 * restore, or copy job — or a recovery point / vault — changes state. Job
12 * events carry the job id and `state`; recovery-point events carry the
13 * recovery point ARN and `status`. Fields not shared by every event kind are
14 * optional (the schema grows over time).
15 */
16export interface BackupEventDetail {
17 /** Backup-job events: the backup job id. */
18 backupJobId?: string;
19 /** Restore-job events: the restore job id. */
20 restoreJobId?: string;
21 /** Copy-job events: the copy job id. */
22 copyJobId?: string;
23 /** Job events: the new job state, e.g. `COMPLETED`, `FAILED`, `ABORTED`. */
24 state?: string;
25 /** Recovery-point events: the new status, e.g. `COMPLETED`, `EXPIRED`. */
26 status?: string;
27 /** Recovery-point events: the recovery point ARN. */
28 recoveryPointArn?: string;
29 /** Vault / recovery-point events: the backup vault name. */
30 backupVaultName?: string;
31 /** Vault events: the backup vault ARN. */
32 backupVaultArn?: string;
33 /** The ARN of the resource being backed up / restored. */
34 resourceArn?: string;
35 /** The type of the resource, e.g. `DynamoDB`, `EBS`. */
36 resourceType?: string;
37 /** The IAM role AWS Backup assumed for the job. */
38 iamRoleArn?: string;
39 /** Additional event fields (the schema grows over time). */
40 [key: string]: unknown;
41}
42
43/** An AWS Backup EventBridge event delivered to the handler. */
44export type BackupEvent = EventRecord<BackupEventDetail>;
45
46/** Which AWS Backup state-change events to subscribe to. */
47export type BackupEventKind =
48 | "backup-job"
49 | "restore-job"
50 | "copy-job"
51 | "recovery-point"
52 | "backup-vault"
53 | "backup-plan";
54
55const DETAIL_TYPES: Record<BackupEventKind, string> = {
56 "backup-job": "Backup Job State Change",
57 "restore-job": "Restore Job State Change",
58 "copy-job": "Copy Job State Change",
59 "recovery-point": "Recovery Point State Change",
60 "backup-vault": "Backup Vault State Change",
61 "backup-plan": "Backup Plan State Change",
62};
63
64export interface BackupEventSourceProps extends EventRouteProps {
65 /**
66 * Logical id for the backing EventBridge rule.
67 * @default "BackupEvents"
68 */
69 id?: string;
70 /**
71 * Which state-change events to subscribe to.
72 * @default all kinds
73 */
74 kinds?: readonly BackupEventKind[];
75}
76
77/**
78 * Event source connecting AWS Backup state changes to the hosting compute.
79 * AWS Backup publishes every backup / restore / copy job, recovery point,
80 * vault, and plan state change to the account's default EventBridge bus
81 * (source `aws.backup`); this subscribes the host Function to those events
82 * so it can alert on failed jobs or chain post-backup automation.
83 *
84 * AWS Backup publishes to EventBridge automatically — no additional resource
85 * is created besides the EventBridge rule targeting the host. Provide the
86 * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the
87 * Function effect.
88 *
89 * ### Consuming Backup Events
90 * **Example:** Alert On Failed Backup Jobs
91 * ```typescript
92 * import * as AWS from "alchemy/AWS";
93 *
94 * export default AlertFunction.make(
95 * { main: import.meta.url },
96 * Effect.gen(function* () {
97 * yield* AWS.Backup.consumeBackupEvents(
98 * { kinds: ["backup-job"] },
99 * (events) =>
100 * Stream.runForEach(events, (event) =>
101 * event.detail.state === "FAILED"
102 * ? Effect.log(`backup job ${event.detail.backupJobId} failed`)
103 * : Effect.void,
104 * ),
105 * );
106 * return {};
107 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
108 * );
109 * ```
110 */
111export const consumeBackupEvents = <StreamReq = never, Req = never>(
112 props: BackupEventSourceProps,
113 process: (
114 events: Stream.Stream<BackupEvent, never, StreamReq>,
115 ) => Effect.Effect<void, never, Req>,
116) =>
117 consumeBusEvents(
118 props.id ?? "BackupEvents",
119 {
120 source: ["aws.backup"],
121 "detail-type": (
122 props.kinds ?? (Object.keys(DETAIL_TYPES) as BackupEventKind[])
123 ).map((kind) => DETAIL_TYPES[kind]),
124 },
125 { description: props.description, state: props.state },
126 process,
127 );