1import type * as aas from "@distilled.cloud/aws/application-auto-scaling";
2import * as Effect from "effect/Effect";
3import * as Binding from "../../Binding.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { ScalableTarget } from "./ScalableTarget.ts";
6import type { ScalingPolicy } from "./ScalingPolicy.ts";
7
8/**
9 * Shared scaffolding for Application Auto Scaling 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 and the IAM action list is
14 * boilerplate: the runtime callable injects the scalable target's identity
15 * triple (`ServiceNamespace`/`ResourceId`/`ScalableDimension`) — plus
16 * `PolicyName` for policy-scoped operations — from the bound resource.
17 *
18 * All grants use `Resource: ["*"]` because Application Auto Scaling's
19 * describe/get actions do not support resource-level permissions (only the
20 * tagging actions accept the `scalable-target/…` ARN).
21 */
22
23/** The request fields identifying a scalable target, injected from the bound resource. */
24interface TargetTriple {
25 ServiceNamespace: aas.ServiceNamespace;
26 ResourceId: string;
27 ScalableDimension: aas.ScalableDimension;
28}
29
30/**
31 * Build the impl Effect for a scalable-target-scoped operation: the runtime
32 * callable injects the bound {@link ScalableTarget}'s identity triple and the
33 * deploy-time half grants `actions` on `*`.
34 */
35export const makeTargetScopedHttpBinding = <
36 I extends TargetTriple,
37 A,
38 E,
39 R,
40>(options: {
41 /** Fully-qualified binding tag, e.g. `AWS.ApplicationAutoScaling.DescribeScalingActivities`. */
42 tag: string;
43 /** The distilled operation; the identity triple is injected from the target. */
44 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
45 /** IAM actions granted on `*` (no resource-level permission support). */
46 actions: readonly string[];
47}) =>
48 Effect.gen(function* () {
49 const op = yield* options.operation;
50
51 return Effect.fn(function* (target: ScalableTarget) {
52 const ServiceNamespace = yield* target.serviceNamespace;
53 const ResourceId = yield* target.resourceId;
54 const ScalableDimension = yield* target.scalableDimension;
55 if (!globalThis.__ALCHEMY_RUNTIME__) {
56 const host = yield* Binding.Host;
57 if (isBindingHost(host)) {
58 yield* host.bind`Allow(${host}, ${options.tag}(${target}))`({
59 policyStatements: [
60 {
61 Effect: "Allow",
62 Action: [...options.actions],
63 Resource: ["*"],
64 },
65 ],
66 });
67 }
68 }
69 return Effect.fn(`${options.tag}(${target.LogicalId})`)(function* (
70 request?: Omit<I, keyof TargetTriple>,
71 ) {
72 return yield* op({
73 ...request,
74 ServiceNamespace: yield* ServiceNamespace,
75 ResourceId: yield* ResourceId,
76 ScalableDimension: yield* ScalableDimension,
77 } as I);
78 });
79 });
80 });
81
82/**
83 * Build the impl Effect for a scaling-policy-scoped operation: the runtime
84 * callable injects the bound {@link ScalingPolicy}'s identity triple and
85 * `PolicyName`, and the deploy-time half grants `actions` on `*`.
86 */
87export const makePolicyScopedHttpBinding = <
88 I extends TargetTriple & { PolicyName: string },
89 A,
90 E,
91 R,
92>(options: {
93 /** Fully-qualified binding tag, e.g. `AWS.ApplicationAutoScaling.GetPredictiveScalingForecast`. */
94 tag: string;
95 /** The distilled operation; the triple and `PolicyName` are injected from the policy. */
96 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
97 /** IAM actions granted on `*` (no resource-level permission support). */
98 actions: readonly string[];
99}) =>
100 Effect.gen(function* () {
101 const op = yield* options.operation;
102
103 return Effect.fn(function* (policy: ScalingPolicy) {
104 const ServiceNamespace = yield* policy.serviceNamespace;
105 const ResourceId = yield* policy.resourceId;
106 const ScalableDimension = yield* policy.scalableDimension;
107 const PolicyName = yield* policy.policyName;
108 if (!globalThis.__ALCHEMY_RUNTIME__) {
109 const host = yield* Binding.Host;
110 if (isBindingHost(host)) {
111 yield* host.bind`Allow(${host}, ${options.tag}(${policy}))`({
112 policyStatements: [
113 {
114 Effect: "Allow",
115 Action: [...options.actions],
116 Resource: ["*"],
117 },
118 ],
119 });
120 }
121 }
122 return Effect.fn(`${options.tag}(${policy.LogicalId})`)(function* (
123 request: Omit<I, keyof TargetTriple | "PolicyName">,
124 ) {
125 return yield* op({
126 ...request,
127 ServiceNamespace: yield* ServiceNamespace,
128 ResourceId: yield* ResourceId,
129 ScalableDimension: yield* ScalableDimension,
130 PolicyName: yield* PolicyName,
131 } as I);
132 });
133 });
134 });