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 { ServiceLevelObjective } from "./ServiceLevelObjective.ts";
6
7/**
8 * Shared scaffolding for Application Signals 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 builders
12 * below. Everything except the operation, the IAM action list, and (for
13 * SLO-scoped operations) the injected identifier is boilerplate.
14 */
15
16/**
17 * Build the impl Effect for an account-level operation (the service
18 * discovery, audit, and change-event read APIs). The deploy-time half
19 * grants `actions` on `*` because the Application Signals discovery
20 * actions do not support resource-level scoping.
21 */
22export const makeApplicationSignalsAccountHttpBinding = <I, A, E, R>(options: {
23 /** Fully-qualified binding tag, e.g. `AWS.ApplicationSignals.ListServices`. */
24 tag: string;
25 /** The distilled operation, invoked with the caller's request as-is. */
26 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
27 /** IAM actions granted on `*`. */
28 actions: readonly string[];
29}) =>
30 Effect.gen(function* () {
31 const op = yield* options.operation;
32
33 return Effect.fn(function* () {
34 if (!globalThis.__ALCHEMY_RUNTIME__) {
35 const host = yield* Binding.Host;
36 if (isBindingHost(host)) {
37 yield* host.bind`Allow(${host}, ${options.tag}())`({
38 policyStatements: [
39 {
40 Effect: "Allow",
41 Action: [...options.actions],
42 Resource: ["*"],
43 },
44 ],
45 });
46 }
47 }
48 return Effect.fn(options.tag)(function* (request: I) {
49 return yield* op(request);
50 });
51 });
52 });
53
54/**
55 * Build the impl Effect for an SLO-scoped operation taking a single `Id`
56 * (`GetServiceLevelObjective`, `ListServiceLevelObjectiveExclusionWindows`):
57 * the runtime callable injects the bound SLO's ARN as `Id` and the
58 * deploy-time half grants `actions` on the SLO ARN.
59 */
60export const makeSloIdHttpBinding = <
61 I extends { Id: string },
62 A,
63 E,
64 R,
65>(options: {
66 /** Fully-qualified binding tag. */
67 tag: string;
68 /** The distilled operation; `Id` is injected from the SLO. */
69 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
70 /** IAM actions granted on the SLO ARN. */
71 actions: readonly string[];
72}) =>
73 Effect.gen(function* () {
74 const op = yield* options.operation;
75
76 return Effect.fn(function* (slo: ServiceLevelObjective) {
77 const SloArn = yield* slo.sloArn;
78 if (!globalThis.__ALCHEMY_RUNTIME__) {
79 const host = yield* Binding.Host;
80 if (isBindingHost(host)) {
81 yield* host.bind`Allow(${host}, ${options.tag}(${slo}))`({
82 policyStatements: [
83 {
84 Effect: "Allow",
85 Action: [...options.actions],
86 Resource: [Output.interpolate`${slo.sloArn}`],
87 },
88 ],
89 });
90 }
91 }
92 return Effect.fn(`${options.tag}(${slo.LogicalId})`)(function* (
93 request?: Omit<I, "Id">,
94 ) {
95 return yield* op({
96 ...request,
97 Id: yield* SloArn,
98 } as I);
99 });
100 });
101 });
102
103/**
104 * Build the impl Effect for a batch SLO operation taking `SloIds`
105 * (`BatchGetServiceLevelObjectiveBudgetReport`,
106 * `BatchUpdateExclusionWindows`), narrowed to the single bound SLO: the
107 * runtime callable injects `SloIds: [sloArn]` and the deploy-time half
108 * grants `actions` on the SLO ARN.
109 */
110export const makeSloBatchHttpBinding = <
111 I extends { SloIds: string[] },
112 A,
113 E,
114 R,
115>(options: {
116 /** Fully-qualified binding tag. */
117 tag: string;
118 /** The distilled operation; `SloIds` is injected from the SLO. */
119 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
120 /** IAM actions granted on the SLO ARN. */
121 actions: readonly string[];
122 /**
123 * Additional IAM actions granted on `*` — dependent permissions the
124 * operation exercises with the caller's credentials (e.g. budget reports
125 * read the SLI metric via `cloudwatch:GetMetricData`).
126 */
127 accountActions?: readonly string[];
128}) =>
129 Effect.gen(function* () {
130 const op = yield* options.operation;
131
132 return Effect.fn(function* (slo: ServiceLevelObjective) {
133 const SloArn = yield* slo.sloArn;
134 if (!globalThis.__ALCHEMY_RUNTIME__) {
135 const host = yield* Binding.Host;
136 if (isBindingHost(host)) {
137 yield* host.bind`Allow(${host}, ${options.tag}(${slo}))`({
138 policyStatements: [
139 {
140 Effect: "Allow",
141 Action: [...options.actions],
142 Resource: [Output.interpolate`${slo.sloArn}`],
143 },
144 ...(options.accountActions?.length
145 ? [
146 {
147 Effect: "Allow" as const,
148 Action: [...options.accountActions],
149 Resource: ["*"],
150 },
151 ]
152 : []),
153 ],
154 });
155 }
156 }
157 return Effect.fn(`${options.tag}(${slo.LogicalId})`)(function* (
158 request: Omit<I, "SloIds">,
159 ) {
160 return yield* op({
161 ...request,
162 SloIds: [yield* SloArn],
163 } as I);
164 });
165 });
166 });