1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type { Output } from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { ConfigRule } from "./ConfigRule.ts";
6import type { DeliveryChannel } from "./DeliveryChannel.ts";
7
8/**
9 * Shared scaffolding for AWS Config HTTP bindings.
10 *
11 * NOT exported from `index.ts` — every single-operation `{Op}Http.ts` in
12 * this service is a thin `Layer.effect(Cap, make…HttpBinding({ … }))` over
13 * one of the two builders below. Everything except the operation, the IAM
14 * action, and the injected identifier is boilerplate.
15 *
16 * AWS Config actions do not support resource-level permissions (the service
17 * authorization reference lists no resource types for the data-plane
18 * read/evaluate actions), so the deploy-time half always grants `actions`
19 * on `*`. Resource-scoped bindings still scope the *API call* to the bound
20 * resource by injecting its physical identifier into every request.
21 */
22
23/** Config resources a resource-scoped binding can be bound to. */
24export type BindableConfigResource = ConfigRule | DeliveryChannel;
25
26/**
27 * Build the impl Effect for an account-level AWS Config operation. The
28 * runtime callable passes the caller's request through unchanged; the
29 * deploy-time half grants `actions` on `*`.
30 */
31export const makeConfigAccountHttpBinding = <I, A, E, R>(options: {
32 /** Fully-qualified binding tag, e.g. `AWS.Config.SelectResourceConfig`. */
33 tag: string;
34 /** The distilled operation, invoked with the caller's request as-is. */
35 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
36 /** IAM actions granted on `*`. */
37 actions: readonly string[];
38}) =>
39 Effect.gen(function* () {
40 const op = yield* options.operation;
41
42 return Effect.fn(function* () {
43 if (!globalThis.__ALCHEMY_RUNTIME__) {
44 const host = yield* Binding.Host;
45 if (isBindingHost(host)) {
46 yield* host.bind`Allow(${host}, ${options.tag}())`({
47 policyStatements: [
48 {
49 Effect: "Allow",
50 Action: [...options.actions],
51 Resource: ["*"],
52 },
53 ],
54 });
55 }
56 }
57 return Effect.fn(options.tag)(function* (request?: I) {
58 return yield* op((request ?? {}) as I);
59 });
60 });
61 });
62
63/**
64 * Build the impl Effect for an operation scoped to a single bound Config
65 * resource ({@link ConfigRule} or {@link DeliveryChannel}). The runtime
66 * callable injects the resolved `identifier` under `requestKey` (wrapped in
67 * a single-element array when `asList` is set, e.g.
68 * `StartConfigRulesEvaluation`'s `ConfigRuleNames`); the deploy-time half
69 * grants `actions` on `*` (Config actions are not resource-scoped in IAM).
70 */
71export const makeConfigResourceHttpBinding = <
72 Res extends BindableConfigResource,
73 K extends string,
74 I,
75 A,
76 E,
77 R,
78>(options: {
79 /** Fully-qualified binding tag, e.g. `AWS.Config.GetComplianceDetailsByConfigRule`. */
80 tag: string;
81 /** The distilled operation; the identifier is injected from the resource. */
82 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
83 /** IAM actions granted on `*`. */
84 actions: readonly string[];
85 /** Request field the resolved identifier is injected under. */
86 requestKey: K;
87 /** Wrap the injected identifier in a single-element array. */
88 asList?: boolean;
89 /** Resolve the injected identifier from the bound resource. */
90 identifier: (resource: Res) => Output<string, never>;
91}) =>
92 Effect.gen(function* () {
93 const op = yield* options.operation;
94
95 return Effect.fn(function* (resource: Res) {
96 const identifier = yield* options.identifier(resource);
97 if (!globalThis.__ALCHEMY_RUNTIME__) {
98 const host = yield* Binding.Host;
99 if (isBindingHost(host)) {
100 yield* host.bind`Allow(${host}, ${options.tag}(${resource}))`({
101 policyStatements: [
102 {
103 Effect: "Allow",
104 Action: [...options.actions],
105 Resource: ["*"],
106 },
107 ],
108 });
109 }
110 }
111 return Effect.fn(`${options.tag}(${resource.LogicalId})`)(function* (
112 request?: Omit<I, K>,
113 ) {
114 const input: Record<string, unknown> = { ...request };
115 const id = yield* identifier;
116 input[options.requestKey] = options.asList ? [id] : id;
117 return yield* op(input as unknown as I);
118 });
119 });
120 });