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 { BackupVault } from "./BackupVault.ts";
6
7/**
8 * Shared scaffolding for AWS Backup 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, the IAM action list, and
13 * (for vault-scoped operations) the injected `BackupVaultName` is
14 * boilerplate. The three `Start*Job` bindings inject an IAM role and a
15 * PassRole grant, so they stay bespoke.
16 */
17
18/**
19 * Build the impl Effect for an account-level operation (job monitoring,
20 * protected-resource discovery, restore orchestration). The deploy-time half
21 * grants `actions` on `*` — backup job / restore job / copy job IAM actions
22 * do not support resource-level scoping.
23 */
24export const makeBackupAccountHttpBinding = <I, A, E, R>(options: {
25 /** Fully-qualified binding tag, e.g. `AWS.Backup.ListBackupJobs`. */
26 tag: string;
27 /** The distilled operation, invoked with the caller's request as-is. */
28 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
29 /** IAM actions granted on `*`. */
30 actions: readonly string[];
31}) =>
32 Effect.gen(function* () {
33 const op = yield* options.operation;
34
35 return Effect.fn(function* () {
36 if (!globalThis.__ALCHEMY_RUNTIME__) {
37 const host = yield* Binding.Host;
38 if (isBindingHost(host)) {
39 yield* host.bind`Allow(${host}, ${options.tag}())`({
40 policyStatements: [
41 {
42 Effect: "Allow",
43 Action: [...options.actions],
44 Resource: ["*"],
45 },
46 ],
47 });
48 }
49 }
50 return Effect.fn(options.tag)(function* (request?: I) {
51 return yield* op((request ?? {}) as I);
52 });
53 });
54 });
55
56/**
57 * Build the impl Effect for a vault-scoped operation: the runtime callable
58 * injects the bound {@link BackupVault}'s name as `BackupVaultName` and the
59 * deploy-time half grants `actions` on the vault ARN (or `*` for
60 * recovery-point actions, which authorize on the recovery point's underlying
61 * resource ARN — an EBS/RDS snapshot ARN — and cannot be scoped to the
62 * vault).
63 */
64export const makeBackupVaultHttpBinding = <
65 I extends { BackupVaultName: string },
66 A,
67 E,
68 R,
69>(options: {
70 /** Fully-qualified binding tag, e.g. `AWS.Backup.DescribeRecoveryPoint`. */
71 tag: string;
72 /** The distilled operation; `BackupVaultName` is injected from the vault. */
73 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
74 /** IAM actions granted on the vault ARN (or `*` — see `wildcardIam`). */
75 actions: readonly string[];
76 /**
77 * Grant on `*` instead of the vault ARN. Recovery-point actions authorize
78 * on the recovery point ARN (the underlying resource's snapshot ARN),
79 * which is unknowable at deploy time.
80 */
81 wildcardIam?: boolean;
82}) =>
83 Effect.gen(function* () {
84 const op = yield* options.operation;
85
86 return Effect.fn(function* (vault: BackupVault) {
87 const BackupVaultName = yield* vault.backupVaultName;
88 if (!globalThis.__ALCHEMY_RUNTIME__) {
89 const host = yield* Binding.Host;
90 if (isBindingHost(host)) {
91 yield* host.bind`Allow(${host}, ${options.tag}(${vault}))`({
92 policyStatements: [
93 {
94 Effect: "Allow",
95 Action: [...options.actions],
96 Resource: options.wildcardIam
97 ? ["*"]
98 : [Output.interpolate`${vault.backupVaultArn}`],
99 },
100 ],
101 });
102 }
103 }
104 return Effect.fn(`${options.tag}(${vault.LogicalId})`)(function* (
105 request?: Omit<I, "BackupVaultName">,
106 ) {
107 return yield* op({
108 ...request,
109 BackupVaultName: yield* BackupVaultName,
110 } as I);
111 });
112 });
113 });