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 commonly-consumed AWS Organizations event names. Organizations
11 * publishes CloudTrail-backed events — `CreateAccountResult` arrives as an
12 * AWS service event when an asynchronous `CreateAccount` request completes;
13 * the management operations (account moves, OU changes, policy attachments,
14 * handshakes) arrive as AWS API call events.
15 */
16export type OrganizationsEventName =
17 | "CreateAccountResult"
18 | "CreateOrganizationalUnit"
19 | "DeleteOrganizationalUnit"
20 | "MoveAccount"
21 | "RemoveAccountFromOrganization"
22 | "CloseAccount"
23 | "InviteAccountToOrganization"
24 | "AcceptHandshake"
25 | "DeclineHandshake"
26 | "CancelHandshake"
27 | "AttachPolicy"
28 | "DetachPolicy"
29 | "TagResource"
30 | "UntagResource";
31
32/**
33 * The `detail` payload AWS Organizations delivers to EventBridge. The events
34 * are published as CloudTrail records (service events for asynchronous
35 * outcomes like `CreateAccountResult`, API call events for management
36 * operations), so the detail carries the CloudTrail record shape; the
37 * asynchronous outcome lives under `serviceEventDetails`.
38 */
39export interface OrganizationsEventDetail {
40 /** The event name, e.g. `CreateAccountResult` or `MoveAccount`. */
41 eventName?: OrganizationsEventName | (string & {});
42 /** Always `organizations.amazonaws.com`. */
43 eventSource?: string;
44 /** Always `us-east-1` — Organizations is a global service homed there. */
45 awsRegion?: string;
46 /** When the operation completed. */
47 eventTime?: string;
48 /** For API call events, the request parameters of the operation. */
49 requestParameters?: Record<string, unknown> | null;
50 /** For API call events, the response elements of the operation. */
51 responseElements?: Record<string, unknown> | null;
52 /**
53 * For service events (e.g. `CreateAccountResult`), the asynchronous
54 * outcome — `createAccountStatus.state` is `SUCCEEDED` or `FAILED`.
55 */
56 serviceEventDetails?: Record<string, unknown>;
57 /** Additional CloudTrail record fields (the schema grows over time). */
58 [key: string]: unknown;
59}
60
61/** An AWS Organizations EventBridge event delivered to the handler. */
62export type OrganizationsEvent = EventRecord<OrganizationsEventDetail>;
63
64export interface OrganizationsEventSourceProps extends EventRouteProps {
65 /**
66 * Logical id for the backing EventBridge rule.
67 * @default "OrganizationsEvents"
68 */
69 id?: string;
70 /**
71 * Which event names to subscribe to (matched against `detail.eventName`),
72 * e.g. `["CreateAccountResult"]`.
73 * @default all Organizations events
74 */
75 events?: readonly (OrganizationsEventName | (string & {}))[];
76}
77
78/**
79 * Event source connecting AWS Organizations events to the hosting compute.
80 * Organizations publishes CloudTrail-backed events — asynchronous
81 * account-creation outcomes (`CreateAccountResult`) and every management
82 * operation (account moves, OU changes, policy attachments, handshake
83 * responses) — to the **management account's** default EventBridge bus in
84 * **us-east-1** (source `aws.organizations`); this subscribes the host
85 * Function to those events so it can drive account-vending, baseline, or
86 * compliance automation.
87 *
88 * Organizations publishes to EventBridge automatically — no additional
89 * resource is created besides the EventBridge rule targeting the host. The
90 * rule must be deployed in us-east-1 of the management account to receive
91 * events. Provide the host-specific implementation layer (e.g.
92 * `AWS.Lambda.EventSource`) on the Function effect.
93 *
94 * ### Consuming Organization Events
95 * **Example:** React to Completed Account Creations
96 * ```typescript
97 * import * as AWS from "alchemy/AWS";
98 *
99 * export default VendingFunction.make(
100 * { main: import.meta.url },
101 * Effect.gen(function* () {
102 * yield* AWS.Organizations.consumeOrganizationsEvents(
103 * { events: ["CreateAccountResult"] },
104 * (events) =>
105 * Stream.runForEach(events, (event) =>
106 * Effect.log(
107 * `account creation: ${JSON.stringify(
108 * event.detail.serviceEventDetails,
109 * )}`,
110 * ),
111 * ),
112 * );
113 * return {};
114 * }).pipe(Effect.provide(AWS.Lambda.EventSource)),
115 * );
116 * ```
117 */
118export const consumeOrganizationsEvents = <StreamReq = never, Req = never>(
119 props: OrganizationsEventSourceProps,
120 process: (
121 events: Stream.Stream<OrganizationsEvent, never, StreamReq>,
122 ) => Effect.Effect<void, never, Req>,
123) =>
124 consumeBusEvents(
125 props.id ?? "OrganizationsEvents",
126 {
127 source: ["aws.organizations"],
128 ...(props.events !== undefined
129 ? { detail: { eventName: [...props.events] } }
130 : {}),
131 },
132 { description: props.description, state: props.state },
133 process,
134 );