1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type { Input } from "../../Input.ts";
4import { AWSEnvironment } from "../Environment.ts";
5import { 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. */
27export 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 */
41export 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});