1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { App } from "./App.ts";
6
7/**
8 * Shared scaffolding for Amplify's app-scoped HTTP bindings. Every Amplify
9 * data-plane operation takes the `appId` of the {@link App} it acts on and is
10 * authorized by a single `amplify:*` IAM action over an app-derived ARN — this
11 * factory captures that shape once so each `{Op}Http.ts` is a thin one-call
12 * export.
13 *
14 * NOT exported from `index.ts` — internal scaffolding only.
15 */
16export interface AmplifyHttpBindingOptions<
17 I extends { appId: string },
18 A,
19 E,
20 R,
21> {
22 /** Capability name, e.g. `"StartJob"` — used for the bind sid and span name. */
23 name: string;
24 /** The distilled Amplify operation implementing the capability. */
25 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
26 /** IAM actions granted to the host, e.g. `["amplify:StartJob"]`. */
27 actions: string[];
28 /** IAM resource ARNs (derived from the bound app) the actions apply to. */
29 resources: (app: App) => Array<string | Output.Output<string>>;
30}
31
32/**
33 * Build the init Effect of an Amplify app-scoped HTTP binding: resolves the
34 * operation once at layer init, grants the IAM statement on the binding host
35 * at deploy time, and returns a runtime callable that injects the bound app's
36 * `appId` into every request.
37 */
38export const makeAmplifyHttpBinding = <I extends { appId: string }, A, E, R>(
39 options: AmplifyHttpBindingOptions<I, A, E, R>,
40) =>
41 Effect.gen(function* () {
42 const operation = yield* options.operation;
43
44 return Effect.fn(function* (app: App) {
45 const AppId = yield* app.appId;
46 if (!globalThis.__ALCHEMY_RUNTIME__) {
47 const host = yield* Binding.Host;
48 if (isBindingHost(host)) {
49 yield* host.bind`Allow(${host}, AWS.Amplify.${options.name}(${app}))`(
50 {
51 policyStatements: [
52 {
53 Effect: "Allow",
54 Action: options.actions,
55 Resource: options.resources(app),
56 },
57 ],
58 },
59 );
60 }
61 }
62 return Effect.fn(`AWS.Amplify.${options.name}(${app.LogicalId})`)(
63 function* (request: Omit<I, "appId">) {
64 // Omit<I, "appId"> + the injected appId reconstructs I; TS cannot
65 // prove that for a generic I, hence the assertion.
66 return yield* operation({
67 ...request,
68 appId: yield* AppId,
69 } as unknown as I);
70 },
71 );
72 });
73 });