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