Skip to content
Source
Sign inSign up
Open History to see the last commit

BindingHttp.ts

156 lines5.5 KB

1import * as Output from "alchemy/Output";
2import * as Effect from "effect/Effect";
3import * as AWSBindings from "../AWSBindings.ts";
4import type { DBCluster } from "./DBCluster.ts";
5import type { DBInstance } from "./DBInstance.ts";
6​
7/**
8 * Shared scaffolding for Amazon RDS 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 identifier resolver, and the
13 * IAM action list is boilerplate.
14 */
15​
16/**
17 * Build the impl Effect for an account-level operation (cluster/instance
18 * discovery, event history, snapshot administration). The deploy-time half
19 * grants `actions` on `*` — these operations span every database in the
20 * account and the identifiers they filter on are runtime data.
21 */
22export const makeRdsAccountHttpBinding = <I, A, E, R>(options: {
23 /** Fully-qualified binding tag, e.g. `AWS.RDS.DescribeEvents`. */
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 bind = yield* AWSBindings.Bind;
32 const op = yield* AWSBindings.withCredentials(options.operation);
33​
34 return Effect.fn(function* () {
35 if (!globalThis.__ALCHEMY_RUNTIME__) {
36 yield* bind`${options.tag}()`({
37 policyStatements: [
38 {
39 Effect: "Allow",
40 Action: [...options.actions],
41 Resource: ["*"],
42 },
43 ],
44 });
45 }
46 return Effect.fn(options.tag)(function* (request?: I) {
47 return yield* op((request ?? {}) as I);
48 });
49 });
50 });
51​
52/**
53 * Build the impl Effect for a cluster-scoped operation: the runtime callable
54 * injects the bound {@link DBCluster}'s identifier as `DBClusterIdentifier`
55 * and the deploy-time half grants `actions` on the cluster ARN (plus any
56 * `extraResources`, e.g. the `cluster-snapshot` ARN pattern for snapshot
57 * creation).
58 */
59export const makeRdsClusterHttpBinding = <
60 I extends { DBClusterIdentifier?: string },
61 A,
62 E,
63 R,
64>(options: {
65 /** Fully-qualified binding tag, e.g. `AWS.RDS.FailoverDBCluster`. */
66 tag: string;
67 /** The distilled operation; `DBClusterIdentifier` is injected. */
68 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
69 /** IAM actions granted on the cluster ARN. */
70 actions: readonly string[];
71 /** Additional IAM resource ARNs derived from the cluster ARN. */
72 extraResources?: (clusterArn: string) => string[];
73}) =>
74 Effect.gen(function* () {
75 const bind = yield* AWSBindings.Bind;
76 const op = yield* AWSBindings.withCredentials(options.operation);
77​
78 return Effect.fn(function* (cluster: DBCluster) {
79 const Identifier = yield* cluster.dbClusterIdentifier;
80 if (!globalThis.__ALCHEMY_RUNTIME__) {
81 const resources = options.extraResources;
82 yield* bind`${options.tag}(${cluster})`({
83 policyStatements: [
84 {
85 Effect: "Allow",
86 Action: [...options.actions],
87 Resource: resources
88 ? Output.map(cluster.dbClusterArn, (arn) => [arn, ...resources(arn)])
89 : [Output.interpolate`${cluster.dbClusterArn}`],
90 },
91 ],
92 });
93 }
94 return Effect.fn(`${options.tag}(${cluster.LogicalId})`)(function* (
95 request?: Omit<I, "DBClusterIdentifier">,
96 ) {
97 return yield* op({
98 ...request,
99 DBClusterIdentifier: yield* Identifier,
100 } as I);
101 });
102 });
103 });
104​
105/**
106 * Build the impl Effect for an instance-scoped operation: the runtime
107 * callable injects the bound {@link DBInstance}'s identifier as
108 * `DBInstanceIdentifier` and the deploy-time half grants `actions` on the
109 * instance ARN (plus any `extraResources`, e.g. the `snapshot` ARN pattern
110 * for snapshot creation).
111 */
112export const makeRdsInstanceHttpBinding = <
113 I extends { DBInstanceIdentifier?: string },
114 A,
115 E,
116 R,
117>(options: {
118 /** Fully-qualified binding tag, e.g. `AWS.RDS.RebootDBInstance`. */
119 tag: string;
120 /** The distilled operation; `DBInstanceIdentifier` is injected. */
121 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
122 /** IAM actions granted on the instance ARN. */
123 actions: readonly string[];
124 /** Additional IAM resource ARNs derived from the instance ARN. */
125 extraResources?: (instanceArn: string) => string[];
126}) =>
127 Effect.gen(function* () {
128 const bind = yield* AWSBindings.Bind;
129 const op = yield* AWSBindings.withCredentials(options.operation);
130​
131 return Effect.fn(function* (instance: DBInstance) {
132 const Identifier = yield* instance.dbInstanceIdentifier;
133 if (!globalThis.__ALCHEMY_RUNTIME__) {
134 const resources = options.extraResources;
135 yield* bind`${options.tag}(${instance})`({
136 policyStatements: [
137 {
138 Effect: "Allow",
139 Action: [...options.actions],
140 Resource: resources
141 ? Output.map(instance.dbInstanceArn, (arn) => [arn, ...resources(arn)])
142 : [Output.interpolate`${instance.dbInstanceArn}`],
143 },
144 ],
145 });
146 }
147 return Effect.fn(`${options.tag}(${instance.LogicalId})`)(function* (
148 request?: Omit<I, "DBInstanceIdentifier">,
149 ) {
150 return yield* op({
151 ...request,
152 DBInstanceIdentifier: yield* Identifier,
153 } as I);
154 });
155 });
156 });