1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { Endpoint } from "./Endpoint.ts";
6import type { ReplicationInstance } from "./ReplicationInstance.ts";
7
8/**
9 * Shared HTTP scaffolding for the DMS runtime bindings.
10 *
11 * Every capability follows the same shape — resolve the distilled operation,
12 * register an IAM policy statement on the binding host, and return a runtime
13 * callable. The only variation is the operation, the IAM action(s), and the
14 * identifier(s) injected from the bound resource: an {@link Endpoint}'s ARN,
15 * a {@link ReplicationInstance}'s ARN, both (connection-shaped operations
16 * like `TestConnection`/`RefreshSchemas`), or none (account-level describes).
17 *
18 * DMS `Describe*` actions do not support resource-level IAM permissions, so
19 * describe-shaped capabilities grant on `*`; mutating capabilities
20 * (`TestConnection`, `RefreshSchemas`, `RebootReplicationInstance`) grant on
21 * the bound resource ARN(s).
22 *
23 * NOT exported from `index.ts`.
24 */
25
26/**
27 * Build the impl Effect for an endpoint-scoped operation: the runtime
28 * callable injects the bound {@link Endpoint}'s ARN as `EndpointArn`. DMS
29 * describe actions are not resource-scoped in IAM, so the deploy-time half
30 * grants `actions` on `*`.
31 */
32export const makeDmsEndpointScopedHttpBinding = <
33 I extends { EndpointArn: string },
34 A,
35 E,
36 R,
37>(options: {
38 /** Fully-qualified binding tag, e.g. `AWS.DMS.DescribeSchemas`. */
39 tag: string;
40 /** The distilled operation; `EndpointArn` is injected from the resource. */
41 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
42 /** IAM actions granted on `*` (DMS describes are not resource-scoped). */
43 actions: readonly string[];
44}) =>
45 Effect.gen(function* () {
46 const op = yield* options.operation;
47
48 return Effect.fn(function* (endpoint: Endpoint) {
49 const EndpointArn = yield* endpoint.endpointArn;
50 if (!globalThis.__ALCHEMY_RUNTIME__) {
51 const host = yield* Binding.Host;
52 if (isBindingHost(host)) {
53 yield* host.bind`Allow(${host}, ${options.tag}(${endpoint}))`({
54 policyStatements: [
55 {
56 Effect: "Allow",
57 Action: [...options.actions],
58 Resource: ["*"],
59 },
60 ],
61 });
62 }
63 }
64 return Effect.fn(`${options.tag}(${endpoint.LogicalId})`)(function* (
65 request?: Omit<I, "EndpointArn">,
66 ) {
67 return yield* op({
68 ...request,
69 EndpointArn: yield* EndpointArn,
70 } as I);
71 });
72 });
73 });
74
75/**
76 * Build the impl Effect for a replication-instance-scoped operation: the
77 * runtime callable injects the bound {@link ReplicationInstance}'s ARN as
78 * `ReplicationInstanceArn`. Mutating actions (`RebootReplicationInstance`)
79 * support resource-level IAM and grant on the instance ARN; describe actions
80 * grant on `*` (set `iam: "wildcard"`).
81 */
82export const makeDmsInstanceScopedHttpBinding = <
83 I extends { ReplicationInstanceArn: string },
84 A,
85 E,
86 R,
87>(options: {
88 /** Fully-qualified binding tag, e.g. `AWS.DMS.RebootReplicationInstance`. */
89 tag: string;
90 /** The distilled operation; `ReplicationInstanceArn` is injected. */
91 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
92 /** IAM actions granted. */
93 actions: readonly string[];
94 /**
95 * `"resource"` grants on the instance ARN (actions with resource-level IAM
96 * support); `"wildcard"` grants on `*` (DMS describes).
97 */
98 iam: "resource" | "wildcard";
99}) =>
100 Effect.gen(function* () {
101 const op = yield* options.operation;
102
103 return Effect.fn(function* (instance: ReplicationInstance) {
104 const ReplicationInstanceArn = yield* instance.replicationInstanceArn;
105 if (!globalThis.__ALCHEMY_RUNTIME__) {
106 const host = yield* Binding.Host;
107 if (isBindingHost(host)) {
108 yield* host.bind`Allow(${host}, ${options.tag}(${instance}))`({
109 policyStatements: [
110 {
111 Effect: "Allow",
112 Action: [...options.actions],
113 Resource:
114 options.iam === "resource"
115 ? [Output.interpolate`${instance.replicationInstanceArn}`]
116 : ["*"],
117 },
118 ],
119 });
120 }
121 }
122 return Effect.fn(`${options.tag}(${instance.LogicalId})`)(function* (
123 request?: Omit<I, "ReplicationInstanceArn">,
124 ) {
125 return yield* op({
126 ...request,
127 ReplicationInstanceArn: yield* ReplicationInstanceArn,
128 } as I);
129 });
130 });
131 });
132
133/**
134 * Build the impl Effect for a connection-shaped operation that targets a
135 * (replication instance, endpoint) pair — `TestConnection` and
136 * `RefreshSchemas`. Both ARNs are injected from the bound resources and the
137 * deploy-time half grants `actions` on both ARNs (these actions support
138 * resource-level IAM on both resource types).
139 */
140export const makeDmsConnectionScopedHttpBinding = <
141 I extends { ReplicationInstanceArn: string; EndpointArn: string },
142 A,
143 E,
144 R,
145>(options: {
146 /** Fully-qualified binding tag, e.g. `AWS.DMS.TestConnection`. */
147 tag: string;
148 /** The distilled operation; both ARNs are injected from the resources. */
149 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
150 /** IAM actions granted on the instance + endpoint ARNs. */
151 actions: readonly string[];
152}) =>
153 Effect.gen(function* () {
154 const op = yield* options.operation;
155
156 return Effect.fn(function* (
157 instance: ReplicationInstance,
158 endpoint: Endpoint,
159 ) {
160 const ReplicationInstanceArn = yield* instance.replicationInstanceArn;
161 const EndpointArn = yield* endpoint.endpointArn;
162 if (!globalThis.__ALCHEMY_RUNTIME__) {
163 const host = yield* Binding.Host;
164 if (isBindingHost(host)) {
165 yield* host.bind`Allow(${host}, ${options.tag}(${instance}, ${endpoint}))`(
166 {
167 policyStatements: [
168 {
169 Effect: "Allow",
170 Action: [...options.actions],
171 Resource: [
172 Output.interpolate`${instance.replicationInstanceArn}`,
173 Output.interpolate`${endpoint.endpointArn}`,
174 ],
175 },
176 ],
177 },
178 );
179 }
180 }
181 return Effect.fn(
182 `${options.tag}(${instance.LogicalId}, ${endpoint.LogicalId})`,
183 )(function* (
184 request?: Omit<I, "ReplicationInstanceArn" | "EndpointArn">,
185 ) {
186 return yield* op({
187 ...request,
188 ReplicationInstanceArn: yield* ReplicationInstanceArn,
189 EndpointArn: yield* EndpointArn,
190 } as I);
191 });
192 });
193 });
194
195/**
196 * Build the impl Effect for an account-level operation (no target resource).
197 * The deploy-time half grants `actions` on `*`.
198 */
199export const makeDmsAccountHttpBinding = <I, A, E, R>(options: {
200 /** Fully-qualified binding tag, e.g. `AWS.DMS.DescribeConnections`. */
201 tag: string;
202 /** The distilled operation, invoked with the caller's request as-is. */
203 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
204 /** IAM actions granted on `*`. */
205 actions: readonly string[];
206}) =>
207 Effect.gen(function* () {
208 const op = yield* options.operation;
209
210 return Effect.fn(function* () {
211 if (!globalThis.__ALCHEMY_RUNTIME__) {
212 const host = yield* Binding.Host;
213 if (isBindingHost(host)) {
214 yield* host.bind`Allow(${host}, ${options.tag}())`({
215 policyStatements: [
216 {
217 Effect: "Allow",
218 Action: [...options.actions],
219 Resource: ["*"],
220 },
221 ],
222 });
223 }
224 }
225 return Effect.fn(options.tag)(function* (request?: I) {
226 return yield* op((request ?? {}) as I);
227 });
228 });
229 });