1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import type { PolicyStatement } from "../IAM/Policy.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6import type { Transformer } from "./Transformer.ts";
7
8/**
9 * Shared scaffolding for B2BI HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
12 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two
13 * builders below. Everything except the operation, the IAM action list, and
14 * the injected identifier is boilerplate.
15 */
16
17/**
18 * Build the impl Effect for a transformer-scoped operation: the runtime
19 * callable injects the bound {@link Transformer}'s ID as `transformerId` and
20 * the deploy-time half grants `actions` on the transformer ARN.
21 */
22export const makeTransformerScopedHttpBinding = <
23 I extends { transformerId: string },
24 A,
25 E,
26 R,
27>(options: {
28 /** Fully-qualified binding tag, e.g. `AWS.B2BI.StartTransformerJob`. */
29 tag: string;
30 /** The distilled operation; `transformerId` is injected from the resource. */
31 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
32 /** IAM actions granted on the transformer ARN. */
33 actions: readonly string[];
34 /**
35 * Companion statements the operation needs beyond the transformer-scoped
36 * b2bi action — B2BI reaches into S3 through the caller's session
37 * (forward-access), so e.g. `StartTransformerJob` needs S3 access on the
38 * host.
39 */
40 companionStatements?: readonly PolicyStatement[];
41}) =>
42 Effect.gen(function* () {
43 const op = yield* options.operation;
44
45 return Effect.fn(function* (transformer: Transformer) {
46 const TransformerId = yield* transformer.transformerId;
47 if (!globalThis.__ALCHEMY_RUNTIME__) {
48 const host = yield* Binding.Host;
49 if (isBindingHost(host)) {
50 yield* host.bind`Allow(${host}, ${options.tag}(${transformer}))`({
51 policyStatements: [
52 {
53 Effect: "Allow",
54 Action: [...options.actions],
55 Resource: [Output.interpolate`${transformer.transformerArn}`],
56 },
57 ...(options.companionStatements ?? []),
58 ],
59 });
60 }
61 }
62 return Effect.fn(`${options.tag}(${transformer.LogicalId})`)(function* (
63 request: Omit<I, "transformerId">,
64 ) {
65 return yield* op({
66 ...request,
67 transformerId: yield* TransformerId,
68 } as I);
69 });
70 });
71 });
72
73/**
74 * Build the impl Effect for an account-level operation (no target resource).
75 * The deploy-time half grants `actions` on `*` — B2BI's test/generate
76 * operations don't support resource-level scoping.
77 */
78export const makeB2biAccountHttpBinding = <I, A, E, R>(options: {
79 /** Fully-qualified binding tag, e.g. `AWS.B2BI.TestMapping`. */
80 tag: string;
81 /** The distilled operation, invoked with the caller's request as-is. */
82 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
83 /** IAM actions granted on `*`. */
84 actions: readonly string[];
85}) =>
86 Effect.gen(function* () {
87 const op = yield* options.operation;
88
89 return Effect.fn(function* () {
90 if (!globalThis.__ALCHEMY_RUNTIME__) {
91 const host = yield* Binding.Host;
92 if (isBindingHost(host)) {
93 yield* host.bind`Allow(${host}, ${options.tag}())`({
94 policyStatements: [
95 {
96 Effect: "Allow",
97 Action: [...options.actions],
98 Resource: ["*"],
99 },
100 ],
101 });
102 }
103 }
104 return Effect.fn(options.tag)(function* (request: I) {
105 return yield* op(request);
106 });
107 });
108 });