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 { Task } from "./Task.ts";
6
7/**
8 * Shared scaffolding for AWS DataSync HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
11 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two
12 * builders below. Everything except the operation and the IAM action is
13 * boilerplate.
14 *
15 * DataSync authorizes task-addressed actions against the *task* ARN
16 * (`arn:…:task/task-id`) and execution-addressed actions against the
17 * *task execution* ARN (`arn:…:task/task-id/execution/exec-id`), so every
18 * builder grants on both the bound task's ARN and its execution pattern.
19 */
20const taskPolicyStatement = (task: Task, actions: readonly string[]) => ({
21 Effect: "Allow" as const,
22 Action: [...actions],
23 Resource: [
24 Output.interpolate`${task.taskArn}`,
25 Output.map(task.taskArn, (arn) => `${arn}/execution/*`),
26 ],
27});
28
29/**
30 * Build the impl Effect for an operation whose input carries a `TaskArn`
31 * field: the runtime callable injects the bound {@link Task}'s ARN and the
32 * deploy-time half grants `actions` on the task ARN (and its execution
33 * pattern).
34 */
35export const makeDataSyncTaskHttpBinding = <
36 I extends { TaskArn?: string },
37 A,
38 E,
39 R,
40>(options: {
41 /** Fully-qualified binding tag, e.g. `AWS.DataSync.StartTaskExecution`. */
42 tag: string;
43 /** The distilled operation; `TaskArn` is injected from the task. */
44 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
45 /** IAM actions granted on the task ARN + execution pattern. */
46 actions: readonly string[];
47}) =>
48 Effect.gen(function* () {
49 const op = yield* options.operation;
50
51 return Effect.fn(function* (task: Task) {
52 // Outputs yield a DEFERRED effect — resolve again per invocation below.
53 const TaskArn = yield* task.taskArn;
54 if (!globalThis.__ALCHEMY_RUNTIME__) {
55 const host = yield* Binding.Host;
56 if (isBindingHost(host)) {
57 yield* host.bind`Allow(${host}, ${options.tag}(${task}))`({
58 policyStatements: [taskPolicyStatement(task, options.actions)],
59 });
60 }
61 }
62 return Effect.fn(`${options.tag}(${task.LogicalId})`)(function* (
63 request?: Omit<I, "TaskArn">,
64 ) {
65 return yield* op({ ...request, TaskArn: yield* TaskArn } as I);
66 });
67 });
68 });
69
70/**
71 * Build the impl Effect for a task-anchored operation whose input addresses
72 * a task *execution* by ARN (returned by `StartTaskExecution`): the request
73 * passes through as-is and the deploy-time half grants `actions` on the
74 * bound task's ARN + execution pattern.
75 */
76export const makeDataSyncTaskExecutionHttpBinding = <I, A, E, R>(options: {
77 /** Fully-qualified binding tag, e.g. `AWS.DataSync.CancelTaskExecution`. */
78 tag: string;
79 /** The distilled operation, invoked with the caller's request as-is. */
80 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
81 /** IAM actions granted on the task ARN + execution pattern. */
82 actions: readonly string[];
83}) =>
84 Effect.gen(function* () {
85 const op = yield* options.operation;
86
87 return Effect.fn(function* (task: Task) {
88 if (!globalThis.__ALCHEMY_RUNTIME__) {
89 const host = yield* Binding.Host;
90 if (isBindingHost(host)) {
91 yield* host.bind`Allow(${host}, ${options.tag}(${task}))`({
92 policyStatements: [taskPolicyStatement(task, options.actions)],
93 });
94 }
95 }
96 return Effect.fn(`${options.tag}(${task.LogicalId})`)(function* (
97 request: I,
98 ) {
99 return yield* op(request);
100 });
101 });
102 });