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 ApiGateway capabilities
9 * (`CreateApiKeyHttp`, `GetUsageHttp`, `FlushStageCacheHttp`, …).
10 *
11 * NOT exported from `index.ts` — this is internal plumbing, like
12 * Cloudflare's `BucketHttp.ts`.
13 *
14 * API Gateway's control plane authorizes by HTTP verb (`apigateway:GET`,
15 * `apigateway:POST`, …) against path-shaped ARNs
16 * (`arn:aws:apigateway:{region}::/usageplans/{id}/keys`), so every
17 * capability reduces to { verb, ARN paths, operation }. Everything else —
18 * the runtime guard, host resolution, region lookup, and the
19 * `host.bind` policy registration — is identical and lives here.
20 */
21
22/** API Gateway authorizes control-plane calls by HTTP verb. */
23export type ApiGatewayIamVerb = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
24
25/**
26 * Deploy-time half of an ApiGateway HTTP binding: resolves the host
27 * Function, and registers an IAM policy statement for
28 * `apigateway:{verb}` over the given API Gateway ARN paths. A no-op at
29 * runtime (`__ALCHEMY_RUNTIME__`) and for non-Lambda hosts.
30 *
31 * @param cap fully-qualified capability name, e.g. `AWS.ApiGateway.GetUsage`
32 * @param target the bound resource (or a string literal for account-scoped
33 * capabilities) — used for the binding's identity label
34 * @param verb the API Gateway IAM verb the capability requires
35 * @param paths builds the ARN path list once the deploy region is known;
36 * entries may be `Output`-derived (e.g. `Output.interpolate`)
37 */
38export const registerApiGatewayBinding = Effect.fn(function* (opts: {
39 cap: string;
40 target: unknown;
41 verb: ApiGatewayIamVerb;
42 paths: (region: string) => ReadonlyArray<Input<string>>;
43}) {
44 if (globalThis.__ALCHEMY_RUNTIME__) return;
45 const host = yield* Binding.Host;
46 if (!isBindingHost(host)) return;
47 const { region } = yield* AWSEnvironment.current as unknown as Effect.Effect<{
48 region: string;
49 }>;
50 yield* host.bind`Allow(${host}, ${opts.cap}(${opts.target}))`({
51 policyStatements: [
52 {
53 Effect: "Allow",
54 Action: [`apigateway:${opts.verb}`],
55 Resource: [...opts.paths(region)],
56 },
57 ],
58 });
59});