| 1 | import * as eventbridge from "@distilled.cloud/aws/eventbridge"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; |
| 4 | import * as Binding from "../../Binding.ts"; |
| 5 | import { AWSEnvironment } from "../Environment.ts"; |
| 6 | import { isBindingHost } from "../Lambda/Function.ts"; |
| 7 | import type { Archive } from "./Archive.ts"; |
| 8 | import { StartReplay, type StartReplayRequest } from "./StartReplay.ts"; |
| 9 | |
| 10 | /** |
| 11 | * HTTP implementation of {@link StartReplay}. At deploy time it grants |
| 12 | * `events:StartReplay` on the account's replays (replay names are chosen at |
| 13 | * runtime, so the grant is the `replay/*` wildcard); at runtime it calls the |
| 14 | * EventBridge API with the host Function's credentials, injecting the bound |
| 15 | * archive's ARN as the `EventSourceArn`. Provide this layer on the Function |
| 16 | * using the binding. |
| 17 | */ |
| 18 | export const StartReplayHttp = Layer.effect( |
| 19 | StartReplay, |
| 20 | Effect.gen(function* () { |
| 21 | const startReplay = yield* eventbridge.startReplay; |
| 22 | |
| 23 | return Effect.fn(function* (archive: Archive) { |
| 24 | const ArchiveArn = yield* archive.archiveArn; |
| 25 | const EventSourceArn = yield* archive.eventSourceArn; |
| 26 | if (!globalThis.__ALCHEMY_RUNTIME__) { |
| 27 | const host = yield* Binding.Host; |
| 28 | if (isBindingHost(host)) { |
| 29 | const { accountId, region } = |
| 30 | yield* AWSEnvironment.current as unknown as Effect.Effect<{ |
| 31 | accountId: string; |
| 32 | region: string; |
| 33 | }>; |
| 34 | yield* host.bind`Allow(${host}, AWS.EventBridge.StartReplay(${archive}))`( |
| 35 | { |
| 36 | policyStatements: [ |
| 37 | { |
| 38 | Effect: "Allow", |
| 39 | Action: ["events:StartReplay"], |
| 40 | Resource: [ |
| 41 | `arn:aws:events:${region}:${accountId}:replay/*`, |
| 42 | // StartReplay also authorizes against the source archive. |
| 43 | archive.archiveArn, |
| 44 | // ...and against the DESTINATION event bus. EventBridge |
| 45 | // only allows replaying to the archive's source bus, so |
| 46 | // that bus ARN covers every legal destination. |
| 47 | archive.eventSourceArn, |
| 48 | ], |
| 49 | }, |
| 50 | ], |
| 51 | }, |
| 52 | ); |
| 53 | } |
| 54 | } |
| 55 | return Effect.fn(`AWS.EventBridge.StartReplay(${archive.LogicalId})`)( |
| 56 | function* (request: StartReplayRequest) { |
| 57 | return yield* startReplay({ |
| 58 | ...request, |
| 59 | EventSourceArn: yield* ArchiveArn, |
| 60 | // Default the destination to the archive's source bus — the only |
| 61 | // destination EventBridge accepts for a replay. |
| 62 | Destination: request.Destination ?? { |
| 63 | Arn: yield* EventSourceArn, |
| 64 | }, |
| 65 | }); |
| 66 | }, |
| 67 | ); |
| 68 | }); |
| 69 | }), |
| 70 | ); |