1import * as Effect from "effect/Effect";
2import * as Layer from "effect/Layer";
3import * as Binding from "../../Binding.ts";
4import * as Output from "../../Output.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6import type { Cluster } from "./Cluster.ts";
7import { Connect, connectEnvPrefix, type ConnectOptions } from "./Connect.ts";
8
9/**
10 * Implementation of {@link Connect}. At deploy time it publishes the cluster
11 * endpoint as `MEMORYDB_{LOGICAL_ID}_{HOST,PORT,TLS}` environment variables
12 * on the host Function and grants `memorydb:Connect` on the cluster ARN plus
13 * any IAM-auth users' ARNs; at runtime it resolves the same values into a
14 * typed connection descriptor. The valkey/redis data plane itself is reached
15 * over the VPC network — password auth needs no IAM, while IAM auth
16 * additionally requires the `memorydb:Connect` grant this binding attaches.
17 */
18export const ConnectHttp = Layer.effect(
19 Connect,
20 Effect.gen(function* () {
21 return Effect.fn(function* (cluster: Cluster, options?: ConnectOptions) {
22 const Host = yield* cluster.endpointAddress;
23 const Port = yield* cluster.endpointPort;
24 const Tls = yield* cluster.tlsEnabled;
25
26 if (!globalThis.__ALCHEMY_RUNTIME__) {
27 const host = yield* Binding.Host;
28 if (isBindingHost(host)) {
29 // MemoryDB is VPC-only — request the host's VPC attachment
30 // declaratively through the `vpc` binding channel.
31 const vpc =
32 options?.subnetIds !== undefined ||
33 options?.securityGroupIds !== undefined
34 ? {
35 vpc: {
36 subnetIds: options?.subnetIds ?? [],
37 securityGroupIds: options?.securityGroupIds ?? [],
38 },
39 }
40 : {};
41 const prefix = connectEnvPrefix(cluster.LogicalId);
42 yield* host.bind`Allow(${host}, AWS.MemoryDB.Connect(${cluster}))`({
43 env: {
44 [`${prefix}_HOST`]: cluster.endpointAddress,
45 // Lambda environment variables are strings — stringify the port.
46 [`${prefix}_PORT`]: Output.interpolate`${cluster.endpointPort}`,
47 [`${prefix}_TLS`]: Output.map(cluster.tlsEnabled, (tls) =>
48 String(tls ?? true),
49 ),
50 },
51 policyStatements: [
52 {
53 Effect: "Allow",
54 Action: ["memorydb:Connect"],
55 // IAM auth authorizes against both the cluster and the user.
56 Resource: [
57 Output.interpolate`${cluster.clusterArn}`,
58 ...(options?.users ?? []).map(
59 (user) => Output.interpolate`${user.userArn}`,
60 ),
61 ],
62 },
63 ],
64 ...vpc,
65 });
66 }
67 }
68
69 return Effect.gen(function* () {
70 const host = yield* Host;
71 const port = yield* Port;
72 const tls = yield* Tls;
73 if (!host || port === undefined) {
74 return yield* Effect.die(
75 `MemoryDB endpoint for '${cluster.LogicalId}' is not available yet`,
76 );
77 }
78 return {
79 host,
80 port,
81 tls: tls ?? true,
82 };
83 });
84 });
85 }),
86);