| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Binding from "../../Binding.ts"; |
| 3 | import type { Input } from "../../Input.ts"; |
| 4 | import { AWSEnvironment } from "../Environment.ts"; |
| 5 | import { isBindingHost } from "../Lambda/Function.ts"; |
| 6 | |
| 7 | /** |
| 8 | * Shared HTTP-binding scaffolding for the ApiGatewayV2 capabilities |
| 9 | * (`ExportApiHttp`, `ResetAuthorizersCacheHttp`, …). |
| 10 | * |
| 11 | * NOT exported from `index.ts` — this is internal plumbing, like |
| 12 | * Cloudflare's `BucketHttp.ts`. |
| 13 | * |
| 14 | * API Gateway's control plane (v2 included) authorizes by HTTP verb |
| 15 | * (`apigateway:GET`, `apigateway:DELETE`, …) against path-shaped ARNs |
| 16 | * (`arn:aws:apigateway:{region}::/apis/{apiId}/exports/{specification}`), |
| 17 | * so every capability reduces to { verb, ARN paths, operation }. |
| 18 | * Everything else — the runtime guard, host resolution, region lookup, |
| 19 | * and the `host.bind` policy registration — is identical and lives here. |
| 20 | * |
| 21 | * The `@connections` data plane (`execute-api:ManageConnections`) is the |
| 22 | * one genuinely different capability and stays bespoke in |
| 23 | * `ManageConnectionsHttp.ts`. |
| 24 | */ |
| 25 | |
| 26 | /** API Gateway authorizes control-plane calls by HTTP verb. */ |
| 27 | export type ApiGatewayV2IamVerb = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; |
| 28 | |
| 29 | /** |
| 30 | * Deploy-time half of an ApiGatewayV2 HTTP binding: resolves the host |
| 31 | * Function, and registers an IAM policy statement for |
| 32 | * `apigateway:{verb}` over the given API Gateway ARN paths. A no-op at |
| 33 | * runtime (`__ALCHEMY_RUNTIME__`) and for non-Lambda hosts. |
| 34 | * |
| 35 | * @param cap fully-qualified capability name, e.g. `AWS.ApiGatewayV2.ExportApi` |
| 36 | * @param target the bound resource — used for the binding's identity label |
| 37 | * @param verb the API Gateway IAM verb the capability requires |
| 38 | * @param paths builds the ARN path list once the deploy region is known; |
| 39 | * entries may be `Output`-derived (e.g. `Output.interpolate`) |
| 40 | */ |
| 41 | export const registerApiGatewayV2Binding = Effect.fn(function* (opts: { |
| 42 | cap: string; |
| 43 | target: unknown; |
| 44 | verb: ApiGatewayV2IamVerb; |
| 45 | paths: (region: string) => ReadonlyArray<Input<string>>; |
| 46 | }) { |
| 47 | if (globalThis.__ALCHEMY_RUNTIME__) return; |
| 48 | const host = yield* Binding.Host; |
| 49 | if (!isBindingHost(host)) return; |
| 50 | const { region } = yield* AWSEnvironment.current as unknown as Effect.Effect<{ |
| 51 | region: string; |
| 52 | }>; |
| 53 | yield* host.bind`Allow(${host}, ${opts.cap}(${opts.target}))`({ |
| 54 | policyStatements: [ |
| 55 | { |
| 56 | Effect: "Allow", |
| 57 | Action: [`apigateway:${opts.verb}`], |
| 58 | Resource: [...opts.paths(region)], |
| 59 | }, |
| 60 | ], |
| 61 | }); |
| 62 | }); |