1import * as DocDB from "@/AWS/DocDB";
2import type { SecurityGroupId } from "@/AWS/EC2/SecurityGroup.ts";
3import type { SubnetId } from "@/AWS/EC2/Subnet.ts";
4import * as Lambda from "@/AWS/Lambda";
5import * as Duration from "effect/Duration";
6import * as Effect from "effect/Effect";
7import * as Layer from "effect/Layer";
8import { HttpServerRequest } from "effect/http/HttpServerRequest";
9import * as HttpServerResponse from "effect/http/HttpServerResponse";
10import path from "pathe";
11
12const main = path.resolve(import.meta.dirname, "slow-handler.ts");
13
14/**
15 * The gated test resolves the default VPC's subnets/security group at
16 * runtime (a lookup this fixture module cannot perform) and passes them via
17 * env before deploying. The fixture's init Effect runs in the same process,
18 * so the values are visible here at deploy time.
19 */
20const subnetIds = () =>
21 (process.env.DOCDB_TEST_SUBNET_IDS ?? "")
22 .split(",")
23 .filter((id): id is SubnetId => id.startsWith("subnet-"));
24const securityGroupIds = () =>
25 (process.env.DOCDB_TEST_SG_IDS ?? "")
26 .split(",")
27 .filter((id): id is SecurityGroupId => id.startsWith("sg-"));
28
29// The mongodb driver's optional native/companion packages are `require`d in
30// try/catch fallbacks — leave them external so the bundle builds and the
31// driver's graceful degradation kicks in at runtime.
32const MONGODB_OPTIONAL_DEPS = [
33 "kerberos",
34 "@mongodb-js/zstd",
35 "@aws-sdk/credential-providers",
36 "gcp-metadata",
37 "snappy",
38 "socks",
39 "aws4",
40 "mongodb-client-encryption",
41];
42
43export class DocDBSlowTestFunction extends Lambda.Function<Lambda.Function>()(
44 "DocDBSlowTestFunction",
45) {}
46
47/**
48 * Data-plane fixture: deploys a real DocumentDB cluster + instance
49 * (~10-15 minutes, billed per instance-hour — gated behind AWS_TEST_SLOW)
50 * and a VPC-attached Lambda bound with Connect + the mongo Effect client.
51 */
52export default DocDBSlowTestFunction.make(
53 {
54 main,
55 functionUrl: true,
56 timeout: Duration.seconds(60),
57 build: { external: MONGODB_OPTIONAL_DEPS },
58 },
59 Effect.gen(function* () {
60 const subnetGroup = yield* DocDB.DBSubnetGroup("Subnets", {
61 description: "alchemy docdb bindings test subnets",
62 subnetIds: subnetIds(),
63 });
64 const cluster = yield* DocDB.DBCluster("Docs", {
65 dbSubnetGroupName: subnetGroup.dbSubnetGroupName,
66 vpcSecurityGroupIds: securityGroupIds(),
67 masterUsername: "alchemy",
68 manageMasterUserPassword: true,
69 backupRetentionPeriod: "1 day",
70 deletionProtection: false,
71 tags: { fixture: "docdb-bindings" },
72 });
73 yield* DocDB.DBInstance("Writer", {
74 dbClusterIdentifier: cluster.dbClusterIdentifier,
75 dbInstanceClass: "db.t3.medium",
76 tags: { fixture: "docdb-bindings" },
77 });
78
79 // init — bind the managed master secret + attach the Lambda to the
80 // cluster's VPC so it can open a MongoDB-protocol socket.
81 const connect = yield* DocDB.Connect(cluster, {
82 database: "alchemy_test",
83 subnetIds: subnetIds(),
84 securityGroupIds: securityGroupIds(),
85 });
86 const db = yield* DocDB.mongo(connect);
87
88 return {
89 fetch: Effect.gen(function* () {
90 const request = yield* HttpServerRequest;
91 const url = new URL(request.originalUrl);
92 const pathname = url.pathname;
93
94 if (request.method === "GET" && pathname === "/info") {
95 const info = yield* connect;
96 const prefix = DocDB.connectEnvPrefix("Docs");
97 const env = yield* Effect.sync(() => ({
98 host: process.env[`${prefix}_HOST`],
99 port: process.env[`${prefix}_PORT`],
100 }));
101 return yield* HttpServerResponse.json({
102 host: info.host,
103 port: info.port,
104 database: info.database,
105 username: info.username,
106 tls: info.tls,
107 hasPassword: info.password !== undefined,
108 env,
109 });
110 }
111
112 if (request.method === "GET" && pathname === "/ping") {
113 const { use } = yield* db;
114 const pong = yield* use((_db, client) =>
115 client.db("admin").command({ ping: 1 }),
116 );
117 return yield* HttpServerResponse.json({ ok: pong.ok });
118 }
119
120 if (request.method === "GET" && pathname === "/crud") {
121 const { use } = yield* db;
122 const value = url.searchParams.get("value") ?? "roundtrip";
123 const found = yield* use(async (db) => {
124 const orders = db.collection("orders");
125 await orders.insertOne({ marker: value, at: new Date() });
126 return orders.findOne({ marker: value });
127 });
128 return yield* HttpServerResponse.json({
129 marker: (found as { marker?: string } | null)?.marker,
130 });
131 }
132
133 return yield* HttpServerResponse.json(
134 { error: "Not found", method: request.method, pathname },
135 { status: 404 },
136 );
137 }).pipe(Effect.orDie),
138 };
139 }).pipe(Effect.provide(Layer.mergeAll(DocDB.ConnectHttp))),
140);