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 { Budget } from "./Budget.ts";
6import type { BudgetAction } from "./BudgetAction.ts";
7
8/**
9 * Shared scaffolding for the AWS Budgets 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 identifiers (`AccountId` + `BudgetName`, plus `ActionId` for
15 * action-scoped operations) is boilerplate.
16 *
17 * Budgets is a global service — its ARNs have no region component and the
18 * endpoint resolver routes every standard-partition region to the global
19 * endpoint.
20 */
21
22/**
23 * Build the impl Effect for a budget-scoped operation: the runtime callable
24 * injects the bound {@link Budget}'s `AccountId` and `BudgetName` and the
25 * deploy-time half grants `actions` on the budget ARN.
26 */
27export const makeBudgetHttpBinding = <
28 I extends { AccountId: string; BudgetName: string },
29 A,
30 E,
31 R,
32>(options: {
33 /** Fully-qualified binding tag, e.g. `AWS.Budgets.DescribeBudget`. */
34 tag: string;
35 /** The distilled operation; `AccountId`/`BudgetName` are injected. */
36 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
37 /** IAM actions granted on the budget ARN. */
38 actions: readonly string[];
39}) =>
40 Effect.gen(function* () {
41 const op = yield* options.operation;
42
43 return Effect.fn(function* (budget: Budget) {
44 const AccountId = yield* budget.accountId;
45 const BudgetName = yield* budget.budgetName;
46 if (!globalThis.__ALCHEMY_RUNTIME__) {
47 const host = yield* Binding.Host;
48 if (isBindingHost(host)) {
49 yield* host.bind`Allow(${host}, ${options.tag}(${budget}))`({
50 policyStatements: [
51 {
52 Effect: "Allow",
53 Action: [...options.actions],
54 Resource: [Output.interpolate`${budget.budgetArn}`],
55 },
56 ],
57 });
58 }
59 }
60 return Effect.fn(`${options.tag}(${budget.LogicalId})`)(function* (
61 request?: Omit<I, "AccountId" | "BudgetName">,
62 ) {
63 return yield* op({
64 ...request,
65 AccountId: yield* AccountId,
66 BudgetName: yield* BudgetName,
67 } as I);
68 });
69 });
70 });
71
72/**
73 * Build the impl Effect for a budget-action-scoped operation: the runtime
74 * callable injects the bound {@link BudgetAction}'s `AccountId`,
75 * `BudgetName`, and `ActionId`, and the deploy-time half grants `actions` on
76 * the action ARN.
77 */
78export const makeBudgetActionHttpBinding = <
79 I extends { AccountId: string; BudgetName: string; ActionId: string },
80 A,
81 E,
82 R,
83>(options: {
84 /** Fully-qualified binding tag, e.g. `AWS.Budgets.ExecuteBudgetAction`. */
85 tag: string;
86 /** The distilled operation; the action identifiers are injected. */
87 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
88 /** IAM actions granted on the budget action ARN. */
89 actions: readonly string[];
90}) =>
91 Effect.gen(function* () {
92 const op = yield* options.operation;
93
94 return Effect.fn(function* (action: BudgetAction) {
95 const AccountId = yield* action.accountId;
96 const BudgetName = yield* action.budgetName;
97 const ActionId = yield* action.actionId;
98 if (!globalThis.__ALCHEMY_RUNTIME__) {
99 const host = yield* Binding.Host;
100 if (isBindingHost(host)) {
101 yield* host.bind`Allow(${host}, ${options.tag}(${action}))`({
102 policyStatements: [
103 {
104 Effect: "Allow",
105 Action: [...options.actions],
106 Resource: [Output.interpolate`${action.actionArn}`],
107 },
108 ],
109 });
110 }
111 }
112 return Effect.fn(`${options.tag}(${action.LogicalId})`)(function* (
113 request: Omit<I, "AccountId" | "BudgetName" | "ActionId">,
114 ) {
115 return yield* op({
116 ...request,
117 AccountId: yield* AccountId,
118 BudgetName: yield* BudgetName,
119 ActionId: yield* ActionId,
120 } as I);
121 });
122 });
123 });