| 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Binding from "../../Binding.ts"; |
| 3 | import { isBindingHost } from "../Lambda/Function.ts"; |
| 4 | import type { AnomalyMonitor } from "./AnomalyMonitor.ts"; |
| 5 | import { pinCe } from "./common.ts"; |
| 6 | import type { CostCategory } from "./CostCategory.ts"; |
| 7 | |
| 8 | /** |
| 9 | * Shared HTTP scaffolding for the Cost Explorer runtime 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 builders |
| 13 | * below. Everything except the operation, the IAM action, and (for the |
| 14 | * scoped builders) the injected ARN is boilerplate. |
| 15 | * |
| 16 | * Cost Explorer is a global service served exclusively from the `us-east-1` |
| 17 | * endpoint, so every operation is resolved with the Region pinned via |
| 18 | * {@link pinCe} — exactly like the resource providers in `common.ts`. |
| 19 | * |
| 20 | * Per the `ce` service authorization reference, most query actions support |
| 21 | * no resource types, so the account-level builder grants on |
| 22 | * `Resource: ["*"]`. `ce:GetAnomalies` authorizes on the `anomalymonitor` |
| 23 | * resource type, so the anomaly-monitor builder grants on the bound |
| 24 | * monitor's ARN. |
| 25 | */ |
| 26 | |
| 27 | /** |
| 28 | * Build the impl Effect for an account-level Cost Explorer operation (the |
| 29 | * cost/usage queries, forecasts, recommendations, and cost allocation tag |
| 30 | * operations — none of which are resource-scoped). |
| 31 | */ |
| 32 | export const makeCostExplorerHttpBinding = < |
| 33 | I extends object, |
| 34 | A, |
| 35 | // `{ _tag: string }` bound so `pinCe` (whose throttle retry inspects |
| 36 | // `_tag`) applies without collapsing the inferred error union. |
| 37 | E extends { _tag: string }, |
| 38 | R, |
| 39 | >(options: { |
| 40 | /** |
| 41 | * Short capability name used in the binding sid and runtime span, e.g. |
| 42 | * `"GetCostAndUsage"`. |
| 43 | */ |
| 44 | capability: string; |
| 45 | /** IAM actions granted on `Resource: ["*"]`. */ |
| 46 | iamActions: readonly string[]; |
| 47 | /** The distilled operation implementing the capability. */ |
| 48 | operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>; |
| 49 | }) => |
| 50 | Effect.gen(function* () { |
| 51 | const op = yield* pinCe(options.operation); |
| 52 | |
| 53 | return Effect.fn(function* () { |
| 54 | if (!globalThis.__ALCHEMY_RUNTIME__) { |
| 55 | const host = yield* Binding.Host; |
| 56 | if (isBindingHost(host)) { |
| 57 | yield* host.bind`Allow(${host}, AWS.CostExplorer.${options.capability}())`( |
| 58 | { |
| 59 | policyStatements: [ |
| 60 | { |
| 61 | Effect: "Allow", |
| 62 | Action: [...options.iamActions], |
| 63 | Resource: ["*"], |
| 64 | }, |
| 65 | ], |
| 66 | }, |
| 67 | ); |
| 68 | } |
| 69 | } |
| 70 | return Effect.fn(`AWS.CostExplorer.${options.capability}`)(function* ( |
| 71 | request?: I, |
| 72 | ) { |
| 73 | // The region must also be pinned at the call site: the yield-time |
| 74 | // snapshot is only a fallback — the calling fiber's ambient Region |
| 75 | // (the host Function's own region) wins over it. |
| 76 | return yield* pinCe(op((request ?? {}) as I)); |
| 77 | }); |
| 78 | }); |
| 79 | }); |
| 80 | |
| 81 | /** |
| 82 | * Build the impl Effect for an operation scoped to one |
| 83 | * {@link AnomalyMonitor}. The runtime callable injects the bound monitor's |
| 84 | * ARN as the request's `MonitorArn`; the deploy-time half grants |
| 85 | * `iamActions` on the monitor's ARN. |
| 86 | */ |
| 87 | export const makeAnomalyMonitorHttpBinding = < |
| 88 | I extends { MonitorArn?: string }, |
| 89 | A, |
| 90 | // See makeCostExplorerHttpBinding. |
| 91 | E extends { _tag: string }, |
| 92 | R, |
| 93 | >(options: { |
| 94 | /** |
| 95 | * Short capability name used in the binding sid and runtime span, e.g. |
| 96 | * `"GetAnomalies"`. |
| 97 | */ |
| 98 | capability: string; |
| 99 | /** IAM actions granted on the monitor ARN. */ |
| 100 | iamActions: readonly string[]; |
| 101 | /** The distilled operation; `MonitorArn` is injected. */ |
| 102 | operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>; |
| 103 | }) => |
| 104 | Effect.gen(function* () { |
| 105 | const op = yield* pinCe(options.operation); |
| 106 | |
| 107 | return Effect.fn(function* (monitor: AnomalyMonitor) { |
| 108 | const MonitorArn = yield* monitor.monitorArn; |
| 109 | if (!globalThis.__ALCHEMY_RUNTIME__) { |
| 110 | const host = yield* Binding.Host; |
| 111 | if (isBindingHost(host)) { |
| 112 | yield* host.bind`Allow(${host}, AWS.CostExplorer.${options.capability}(${monitor}))`( |
| 113 | { |
| 114 | policyStatements: [ |
| 115 | { |
| 116 | Effect: "Allow", |
| 117 | Action: [...options.iamActions], |
| 118 | Resource: [monitor.monitorArn], |
| 119 | }, |
| 120 | ], |
| 121 | }, |
| 122 | ); |
| 123 | } |
| 124 | } |
| 125 | return Effect.fn( |
| 126 | `AWS.CostExplorer.${options.capability}(${monitor.LogicalId})`, |
| 127 | )(function* (request: Omit<I, "MonitorArn">) { |
| 128 | // Call-site region pin — see makeCostExplorerHttpBinding above. |
| 129 | return yield* pinCe( |
| 130 | op({ |
| 131 | ...request, |
| 132 | MonitorArn: yield* MonitorArn, |
| 133 | } as I), |
| 134 | ); |
| 135 | }); |
| 136 | }); |
| 137 | }); |
| 138 | |
| 139 | /** |
| 140 | * Build the impl Effect for an operation bound to one {@link CostCategory}. |
| 141 | * The runtime callable injects the bound category's ARN as the request's |
| 142 | * `CostCategoryArn`. The deploy-time half grants `iamActions` on |
| 143 | * `Resource: ["*"]` — per the `ce` service authorization reference, |
| 144 | * `ce:ListCostCategoryResourceAssociations` supports no resource types, so |
| 145 | * a grant scoped to the category ARN would never match. |
| 146 | */ |
| 147 | export const makeCostCategoryHttpBinding = < |
| 148 | I extends { CostCategoryArn?: string }, |
| 149 | A, |
| 150 | // See makeCostExplorerHttpBinding. |
| 151 | E extends { _tag: string }, |
| 152 | R, |
| 153 | >(options: { |
| 154 | /** |
| 155 | * Short capability name used in the binding sid and runtime span, e.g. |
| 156 | * `"ListCostCategoryResourceAssociations"`. |
| 157 | */ |
| 158 | capability: string; |
| 159 | /** IAM actions granted on `Resource: ["*"]` (no resource types). */ |
| 160 | iamActions: readonly string[]; |
| 161 | /** The distilled operation; `CostCategoryArn` is injected. */ |
| 162 | operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>; |
| 163 | }) => |
| 164 | Effect.gen(function* () { |
| 165 | const op = yield* pinCe(options.operation); |
| 166 | |
| 167 | return Effect.fn(function* (category: CostCategory) { |
| 168 | const CostCategoryArn = yield* category.costCategoryArn; |
| 169 | if (!globalThis.__ALCHEMY_RUNTIME__) { |
| 170 | const host = yield* Binding.Host; |
| 171 | if (isBindingHost(host)) { |
| 172 | yield* host.bind`Allow(${host}, AWS.CostExplorer.${options.capability}(${category}))`( |
| 173 | { |
| 174 | policyStatements: [ |
| 175 | { |
| 176 | Effect: "Allow", |
| 177 | Action: [...options.iamActions], |
| 178 | Resource: ["*"], |
| 179 | }, |
| 180 | ], |
| 181 | }, |
| 182 | ); |
| 183 | } |
| 184 | } |
| 185 | return Effect.fn( |
| 186 | `AWS.CostExplorer.${options.capability}(${category.LogicalId})`, |
| 187 | )(function* (request?: Omit<I, "CostCategoryArn">) { |
| 188 | // Call-site region pin — see makeCostExplorerHttpBinding above. |
| 189 | return yield* pinCe( |
| 190 | op({ |
| 191 | ...request, |
| 192 | CostCategoryArn: yield* CostCategoryArn, |
| 193 | } as I), |
| 194 | ); |
| 195 | }); |
| 196 | }); |
| 197 | }); |