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 { Cluster } from "./Cluster.ts";
6
7/**
8 * Shared scaffolding for Amazon MemoryDB 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. The env-only side of `ConnectHttp` stays
14 * bespoke — it attaches environment variables in addition to IAM policy.
15 */
16
17/**
18 * MemoryDB snapshot ARNs embed the snapshot name, which is runtime data for
19 * every snapshot-addressed operation, so snapshot-scoped grants use this
20 * wildcard.
21 */
22export const SNAPSHOT_ARN_WILDCARD = "arn:aws:memorydb:*:*:snapshot/*";
23
24/**
25 * Build the impl Effect for an account-level operation (snapshot management,
26 * cluster/event monitoring). The deploy-time half grants `actions` on
27 * `resources` (default `*`) — these operations address clusters and
28 * snapshots by names that are runtime data.
29 */
30export const makeMemoryDBAccountHttpBinding = <I, A, E, R>(options: {
31 /** Fully-qualified binding tag, e.g. `AWS.MemoryDB.DescribeEvents`. */
32 tag: string;
33 /** The distilled operation, invoked with the caller's request as-is. */
34 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
35 /** IAM actions granted. */
36 actions: readonly string[];
37 /**
38 * IAM resources the actions are granted on.
39 * @default ["*"]
40 */
41 resources?: readonly string[];
42}) =>
43 Effect.gen(function* () {
44 const op = yield* options.operation;
45
46 return Effect.fn(function* () {
47 if (!globalThis.__ALCHEMY_RUNTIME__) {
48 const host = yield* Binding.Host;
49 if (isBindingHost(host)) {
50 yield* host.bind`Allow(${host}, ${options.tag}())`({
51 policyStatements: [
52 {
53 Effect: "Allow",
54 Action: [...options.actions],
55 Resource: [...(options.resources ?? ["*"])],
56 },
57 ],
58 });
59 }
60 }
61 return Effect.fn(options.tag)(function* (request?: I) {
62 return yield* op((request ?? {}) as I);
63 });
64 });
65 });
66
67/**
68 * Build the impl Effect for a cluster-scoped operation: the runtime callable
69 * injects the bound {@link Cluster}'s name as `ClusterName` and the
70 * deploy-time half grants `actions` on the cluster ARN (plus any
71 * `extraResources`, e.g. the snapshot ARN wildcard for snapshot creation).
72 */
73export const makeMemoryDBClusterHttpBinding = <
74 I extends { ClusterName: string },
75 A,
76 E,
77 R,
78>(options: {
79 /** Fully-qualified binding tag, e.g. `AWS.MemoryDB.FailoverShard`. */
80 tag: string;
81 /** The distilled operation; `ClusterName` is injected from the cluster. */
82 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
83 /** IAM actions granted on the cluster ARN. */
84 actions: readonly string[];
85 /** Static IAM resources granted in addition to the cluster ARN. */
86 extraResources?: readonly string[];
87}) =>
88 Effect.gen(function* () {
89 const op = yield* options.operation;
90
91 return Effect.fn(function* (cluster: Cluster) {
92 const ClusterName = yield* cluster.clusterName;
93 if (!globalThis.__ALCHEMY_RUNTIME__) {
94 const host = yield* Binding.Host;
95 if (isBindingHost(host)) {
96 yield* host.bind`Allow(${host}, ${options.tag}(${cluster}))`({
97 policyStatements: [
98 {
99 Effect: "Allow",
100 Action: [...options.actions],
101 Resource: [
102 Output.interpolate`${cluster.clusterArn}`,
103 ...(options.extraResources ?? []),
104 ],
105 },
106 ],
107 });
108 }
109 }
110 return Effect.fn(`${options.tag}(${cluster.LogicalId})`)(function* (
111 request?: Omit<I, "ClusterName">,
112 ) {
113 return yield* op({
114 ...request,
115 ClusterName: yield* ClusterName,
116 } as I);
117 });
118 });
119 });