1import * as secretsmanager from "@distilled.cloud/aws/secrets-manager";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as Redacted from "effect/Redacted";
5import * as Binding from "../../Binding.ts";
6import * as Output from "../../Output.ts";
7import { isBindingHost } from "../Lambda/Function.ts";
8import type { DBCluster } from "./DBCluster.ts";
9import {
10 Connect,
11 connectEnvPrefix,
12 type ConnectOptions,
13 formatMongoConnectionUrl,
14} from "./Connect.ts";
15
16export const ConnectHttp = Layer.effect(
17 Connect,
18 Effect.gen(function* () {
19 const getSecretValue = yield* secretsmanager.getSecretValue;
20
21 return Effect.fn(function* (cluster: DBCluster, options?: ConnectOptions) {
22 const Host = yield* cluster.endpoint;
23 const Port = yield* cluster.port;
24 // Default credential source: the cluster's managed master user secret
25 // (`manageMasterUserPassword: true`). An explicit Secrets Manager
26 // secret overrides it.
27 const SecretArn = options?.secret
28 ? yield* options.secret.secretArn
29 : yield* cluster.masterUserSecretArn;
30
31 if (!globalThis.__ALCHEMY_RUNTIME__) {
32 const host = yield* Binding.Host;
33 if (isBindingHost(host)) {
34 // DocumentDB is VPC-only — request the host's VPC attachment
35 // declaratively through the `vpc` binding channel.
36 const vpc =
37 options?.subnetIds !== undefined ||
38 options?.securityGroupIds !== undefined
39 ? {
40 vpc: {
41 subnetIds: options?.subnetIds ?? [],
42 securityGroupIds: options?.securityGroupIds ?? [],
43 },
44 }
45 : {};
46 const secretResource = options?.secret
47 ? options.secret.secretArn
48 : Output.map(cluster.masterUserSecretArn, (arn) => {
49 if (arn === undefined) {
50 throw new Error(
51 `AWS.DocDB.Connect(${cluster.LogicalId}): the cluster has no managed master user secret — ` +
52 `set manageMasterUserPassword: true on the cluster or pass options.secret`,
53 );
54 }
55 return arn;
56 });
57 const prefix = connectEnvPrefix(cluster.LogicalId);
58 yield* host.bind`Allow(${host}, AWS.DocDB.Connect(${cluster}))`({
59 env: {
60 [`${prefix}_HOST`]: cluster.endpoint,
61 // Lambda environment variables are strings — stringify the port.
62 [`${prefix}_PORT`]: Output.interpolate`${cluster.port}`,
63 },
64 policyStatements: [
65 {
66 Effect: "Allow",
67 Action: [
68 "secretsmanager:GetSecretValue",
69 "secretsmanager:DescribeSecret",
70 ],
71 Resource: [secretResource],
72 },
73 ],
74 ...vpc,
75 });
76 }
77 }
78
79 return Effect.gen(function* () {
80 const host = yield* Host;
81 const port = yield* Port;
82 const secretArn = yield* SecretArn;
83
84 if (!host) {
85 return yield* Effect.die(
86 `DocumentDB endpoint for '${cluster.LogicalId}' is not available yet`,
87 );
88 }
89 if (!secretArn) {
90 return yield* Effect.die(
91 `AWS.DocDB.Connect(${cluster.LogicalId}): the cluster has no managed master user secret — ` +
92 `set manageMasterUserPassword: true on the cluster or pass options.secret`,
93 );
94 }
95
96 const value = yield* getSecretValue({ SecretId: secretArn });
97 const secretString = value.SecretString
98 ? typeof value.SecretString === "string"
99 ? value.SecretString
100 : Redacted.value(value.SecretString)
101 : "{}";
102 const secret = JSON.parse(secretString) as {
103 username?: string;
104 password?: string;
105 };
106
107 const resolvedPort = options?.port ?? port ?? 27017;
108 const tls = options?.tls ?? true;
109 const password =
110 secret.password !== undefined
111 ? Redacted.make(secret.password)
112 : undefined;
113
114 return {
115 host,
116 port: resolvedPort,
117 database: options?.database,
118 username: secret.username,
119 password,
120 tls,
121 url: formatMongoConnectionUrl({
122 host,
123 port: resolvedPort,
124 database: options?.database,
125 username: secret.username,
126 password,
127 tls,
128 }),
129 };
130 });
131 });
132 }),
133);