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 { Server } from "./Server.ts";
6import type { User } from "./User.ts";
7
8/**
9 * Shared scaffolding for the AWS Transfer Family runtime bindings.
10 *
11 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
12 * thin `Layer.effect(Cap, makeTransfer…HttpBinding({ … }))` over one of the
13 * three builders below. Everything except the operation, the IAM action
14 * list, and the injected identifier(s) is boilerplate.
15 */
16
17/**
18 * Build the impl Effect for a server-scoped Transfer operation: the runtime
19 * callable injects the bound {@link Server}'s ID as `ServerId` and the
20 * deploy-time half grants `actions` on the server's ARN.
21 */
22export const makeTransferServerHttpBinding = <
23 I extends { ServerId?: string },
24 A,
25 E,
26 R,
27>(options: {
28 /** Fully-qualified binding tag, e.g. `AWS.Transfer.StartServer`. */
29 tag: string;
30 /** The distilled operation; `ServerId` is injected from the server. */
31 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
32 /** IAM actions granted on the server ARN. */
33 actions: readonly string[];
34 /**
35 * Override the IAM resource derived from the server. Most server-scoped
36 * operations authorize against the server ARN; a few operations use a
37 * related resource type instead (for example, TestIdentityProvider uses a
38 * user ARN even though ServerId is the injected request identifier).
39 */
40 resource?: (server: Server) => string | Output.Output<string>;
41}) =>
42 Effect.gen(function* () {
43 const op = yield* options.operation;
44
45 return Effect.fn(function* (server: Server) {
46 // Outputs yield a DEFERRED effect — resolve again per invocation below.
47 const ServerId = yield* server.serverId;
48 if (!globalThis.__ALCHEMY_RUNTIME__) {
49 const host = yield* Binding.Host;
50 if (isBindingHost(host)) {
51 yield* host.bind`Allow(${host}, ${options.tag}(${server}))`({
52 policyStatements: [
53 {
54 Effect: "Allow",
55 Action: [...options.actions],
56 Resource: [
57 options.resource?.(server) ??
58 Output.interpolate`${server.arn}`,
59 ],
60 },
61 ],
62 });
63 }
64 }
65 return Effect.fn(`${options.tag}(${server.LogicalId})`)(function* (
66 request?: Omit<I, "ServerId">,
67 ) {
68 return yield* op({ ...request, ServerId: yield* ServerId } as I);
69 });
70 });
71 });
72
73/**
74 * Build the impl Effect for a user-scoped Transfer operation: the runtime
75 * callable injects the bound {@link User}'s `ServerId` + `UserName` and the
76 * deploy-time half grants `actions` on the user's ARN
77 * (`arn:…:user/{serverId}/{userName}`).
78 */
79export const makeTransferUserHttpBinding = <
80 I extends { ServerId?: string; UserName?: string },
81 A,
82 E,
83 R,
84>(options: {
85 /** Fully-qualified binding tag, e.g. `AWS.Transfer.ImportSshPublicKey`. */
86 tag: string;
87 /** The distilled operation; `ServerId` + `UserName` are injected. */
88 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
89 /** IAM actions granted on the user ARN. */
90 actions: readonly string[];
91}) =>
92 Effect.gen(function* () {
93 const op = yield* options.operation;
94
95 return Effect.fn(function* (user: User) {
96 // Outputs yield a DEFERRED effect — resolve again per invocation below.
97 const ServerId = yield* user.serverId;
98 const UserName = yield* user.userName;
99 if (!globalThis.__ALCHEMY_RUNTIME__) {
100 const host = yield* Binding.Host;
101 if (isBindingHost(host)) {
102 yield* host.bind`Allow(${host}, ${options.tag}(${user}))`({
103 policyStatements: [
104 {
105 Effect: "Allow",
106 Action: [...options.actions],
107 Resource: [Output.interpolate`${user.arn}`],
108 },
109 ],
110 });
111 }
112 }
113 return Effect.fn(`${options.tag}(${user.LogicalId})`)(function* (
114 request?: Omit<I, "ServerId" | "UserName">,
115 ) {
116 return yield* op({
117 ...request,
118 ServerId: yield* ServerId,
119 UserName: yield* UserName,
120 } as I);
121 });
122 });
123 });
124
125/**
126 * Build the impl Effect for an account-level Transfer operation (no
127 * resource argument): the deploy-time half grants `actions` on `*`. Used
128 * for operations that authorize against ARNs unknowable at deploy time
129 * (e.g. `SendWorkflowStepState` authorizes on the workflow's own ARN, and
130 * the workflow/execution ids arrive at runtime inside the step event).
131 */
132export const makeTransferAccountHttpBinding = <I, A, E, R>(options: {
133 /** Fully-qualified binding tag, e.g. `AWS.Transfer.SendWorkflowStepState`. */
134 tag: string;
135 /** The distilled operation, invoked with the caller's request as-is. */
136 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
137 /** IAM actions granted on `*`. */
138 actions: readonly string[];
139}) =>
140 Effect.gen(function* () {
141 const op = yield* options.operation;
142
143 return Effect.fn(function* () {
144 if (!globalThis.__ALCHEMY_RUNTIME__) {
145 const host = yield* Binding.Host;
146 if (isBindingHost(host)) {
147 yield* host.bind`Allow(${host}, ${options.tag}())`({
148 policyStatements: [
149 {
150 Effect: "Allow",
151 Action: [...options.actions],
152 Resource: ["*"],
153 },
154 ],
155 });
156 }
157 }
158 return Effect.fn(options.tag)(function* (request: I) {
159 return yield* op(request);
160 });
161 });
162 });