| 1 | import * as Effect from "effect/Effect"; |
| 2 | import type * as Stream from "effect/Stream"; |
| 3 | import type { EventBus } from "../EventBridge/EventBus.ts"; |
| 4 | import { |
| 5 | consumeBusEvents, |
| 6 | type EventRecord, |
| 7 | type EventRouteProps, |
| 8 | } from "../EventBridge/EventSource.ts"; |
| 9 | import { EventIntegration } from "./EventIntegration.ts"; |
| 10 | |
| 11 | /** A partner event delivered to the handler through an event integration. */ |
| 12 | export type IntegrationEvent<Detail = unknown> = EventRecord<Detail>; |
| 13 | |
| 14 | export interface IntegrationEventsProps { |
| 15 | /** |
| 16 | * Physical name for the event integration. If omitted, a deterministic |
| 17 | * name is generated from the app, stage, and logical ID. |
| 18 | */ |
| 19 | name?: string; |
| 20 | /** |
| 21 | * Description of the event integration (1-1000 characters). |
| 22 | */ |
| 23 | description?: string; |
| 24 | /** |
| 25 | * The partner event source that pushes events to the EventBridge bus, |
| 26 | * e.g. `aws.partner/examplepartner.com`. Also used as the literal |
| 27 | * EventBridge rule pattern (`source`), so it must be a plain string — |
| 28 | * Output values cannot appear in event patterns because the pattern is |
| 29 | * also matched inside the deployed bundle at runtime. |
| 30 | */ |
| 31 | source: string; |
| 32 | /** |
| 33 | * The EventBridge bus the partner events are delivered to. Omit to use |
| 34 | * the account's default event bus. |
| 35 | */ |
| 36 | bus?: EventBus; |
| 37 | /** |
| 38 | * Additional `detail-type` values to narrow the EventBridge rule pattern. |
| 39 | * By default every event from `source` is delivered. |
| 40 | */ |
| 41 | detailType?: string[]; |
| 42 | /** |
| 43 | * Options forwarded to the backing EventBridge rule (description, state). |
| 44 | */ |
| 45 | rule?: EventRouteProps; |
| 46 | /** |
| 47 | * Tags to apply to the event integration. Merged with internal Alchemy |
| 48 | * tags. |
| 49 | */ |
| 50 | tags?: Record<string, string>; |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * Consume partner events on the host Function through an AppIntegrations |
| 55 | * {@link EventIntegration}. Creates the event integration (the metadata that |
| 56 | * connects the partner source to the EventBridge bus) and subscribes the |
| 57 | * host Function to the matching events via an EventBridge rule. |
| 58 | * |
| 59 | * Returns the created {@link EventIntegration} so it can be passed to other |
| 60 | * bindings (e.g. `ListEventIntegrationAssociations`). |
| 61 | * |
| 62 | * ### Consuming Integration Events |
| 63 | * **Example:** Consume Partner Events on a Lambda Function |
| 64 | * ```typescript |
| 65 | * import * as AWS from "alchemy/AWS"; |
| 66 | * import { consumeIntegrationEvents } from "alchemy/AWS/AppIntegrations"; |
| 67 | * |
| 68 | * // init — creates the EventIntegration + EventBridge rule and registers |
| 69 | * // the runtime handler (provide AWS.Lambda.EventSource on the Function) |
| 70 | * const integration = yield* consumeIntegrationEvents( |
| 71 | * "PartnerEvents", |
| 72 | * { source: "aws.partner/examplepartner.com" }, |
| 73 | * (events) => |
| 74 | * Stream.runForEach(events, (event) => |
| 75 | * Effect.log(event["detail-type"], event.detail), |
| 76 | * ), |
| 77 | * ); |
| 78 | * ``` |
| 79 | * |
| 80 | * **Example:** Narrow by Detail Type on a Custom Bus |
| 81 | * ```typescript |
| 82 | * const bus = yield* AWS.EventBridge.EventBus("PartnerBus"); |
| 83 | * yield* consumeIntegrationEvents( |
| 84 | * "PartnerEvents", |
| 85 | * { |
| 86 | * source: "aws.partner/examplepartner.com", |
| 87 | * bus, |
| 88 | * detailType: ["OrderCreated"], |
| 89 | * }, |
| 90 | * (events) => Stream.runForEach(events, handleOrder), |
| 91 | * ); |
| 92 | * ``` |
| 93 | */ |
| 94 | export const consumeIntegrationEvents = < |
| 95 | Detail = unknown, |
| 96 | StreamReq = never, |
| 97 | Req = never, |
| 98 | >( |
| 99 | id: string, |
| 100 | props: IntegrationEventsProps, |
| 101 | process: ( |
| 102 | events: Stream.Stream<IntegrationEvent<Detail>, never, StreamReq>, |
| 103 | ) => Effect.Effect<void, never, Req>, |
| 104 | ) => |
| 105 | Effect.gen(function* () { |
| 106 | // Persist the integration metadata connecting the partner source to the |
| 107 | // EventBridge bus. |
| 108 | const integration = yield* EventIntegration(id, { |
| 109 | name: props.name, |
| 110 | description: props.description, |
| 111 | source: props.source, |
| 112 | eventBridgeBus: props.bus ? props.bus.eventBusName : "default", |
| 113 | tags: props.tags, |
| 114 | }); |
| 115 | |
| 116 | // Subscribe the host Function to the matching partner events. The |
| 117 | // pattern uses only literal values so it round-trips through both the |
| 118 | // deploy-time rule and the runtime matcher. |
| 119 | const pattern = { |
| 120 | source: [props.source], |
| 121 | ...(props.detailType ? { "detail-type": props.detailType } : {}), |
| 122 | }; |
| 123 | yield* props.bus |
| 124 | ? consumeBusEvents( |
| 125 | `${id}-Events`, |
| 126 | props.bus, |
| 127 | pattern, |
| 128 | props.rule, |
| 129 | process, |
| 130 | ) |
| 131 | : consumeBusEvents(`${id}-Events`, pattern, props.rule, process); |
| 132 | |
| 133 | return integration; |
| 134 | }); |