1import * as Lambda from "@/AWS/Lambda";
2import * as NeptuneGraph from "@/AWS/NeptuneGraph";
3import * as Context from "effect/Context";
4import * as Duration from "effect/Duration";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import * as Stream from "effect/Stream";
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, "handler.ts");
13
14export class NeptuneGraphTestFunction extends Lambda.Function<Lambda.Function>()(
15 "NeptuneGraphTestFunction",
16) {}
17
18export class FixtureGraph extends Context.Service<
19 FixtureGraph,
20 {
21 graph: NeptuneGraph.Graph;
22 }
23>()("FixtureGraph") {}
24
25export const FixtureGraphLive = Layer.effect(
26 FixtureGraph,
27 Effect.gen(function* () {
28 // 16 m-NCUs is the smallest provisioned size; zero replicas and public
29 // connectivity keep the fixture as cheap and simple as possible (data
30 // plane is still IAM-authenticated).
31 const graph = yield* NeptuneGraph.Graph("FixtureGraph", {
32 provisionedMemory: 16,
33 publicConnectivity: true,
34 replicaCount: 0,
35 deletionProtection: false,
36 tags: { fixture: "neptune-graph" },
37 });
38 return { graph };
39 }),
40);
41
42/**
43 * Lambda fixture hosting the Neptune Analytics runtime bindings.
44 *
45 * Routes:
46 * - `GET /graph` — the graph id/endpoint the fixture is bound to.
47 * - `POST /query` — runs the openCypher query in the JSON body
48 * (`{ query, parameters? }`) and returns the parsed result document
49 * (ExecuteQuery binding).
50 * - `GET /summary` — the graph's data summary (GetGraphSummary binding).
51 * - `GET /queries` — the graph's active queries (ListQueries binding).
52 * - `GET /snapshots` — the graph's snapshots (ListGraphSnapshots binding).
53 */
54export const NeptuneGraphTestFunctionLive = NeptuneGraphTestFunction.make(
55 {
56 main,
57 functionUrl: true,
58 timeout: Duration.seconds(30),
59 },
60 Effect.gen(function* () {
61 const { graph } = yield* FixtureGraph;
62 const executeQuery = yield* NeptuneGraph.ExecuteQuery(graph);
63 const getGraphSummary = yield* NeptuneGraph.GetGraphSummary(graph);
64 const listQueries = yield* NeptuneGraph.ListQueries(graph);
65 const listGraphSnapshots = yield* NeptuneGraph.ListGraphSnapshots(graph);
66 const GraphId = yield* graph.graphId;
67 const GraphEndpoint = yield* graph.endpoint;
68
69 return {
70 fetch: Effect.gen(function* () {
71 const request = yield* HttpServerRequest;
72 const url = new URL(request.originalUrl);
73 const pathname = url.pathname;
74
75 if (request.method === "GET" && pathname === "/graph") {
76 const graphId = yield* GraphId;
77 const endpoint = yield* GraphEndpoint;
78 return yield* HttpServerResponse.json({ graphId, endpoint });
79 }
80
81 if (request.method === "GET" && pathname === "/summary") {
82 const summary = yield* getGraphSummary({ mode: "BASIC" });
83 return yield* HttpServerResponse.json(summary);
84 }
85
86 if (request.method === "GET" && pathname === "/queries") {
87 const queries = yield* listQueries({ maxResults: 100 });
88 return yield* HttpServerResponse.json(queries);
89 }
90
91 if (request.method === "GET" && pathname === "/snapshots") {
92 const snapshots = yield* listGraphSnapshots();
93 return yield* HttpServerResponse.json(snapshots);
94 }
95
96 if (request.method === "POST" && pathname === "/query") {
97 const body = (yield* request.json) as unknown as {
98 query: string;
99 parameters?: Record<string, unknown>;
100 };
101 const response = yield* executeQuery({
102 queryString: body.query,
103 language: "OPEN_CYPHER",
104 parameters: body.parameters,
105 });
106 const payload = yield* response.payload.pipe(
107 Stream.decodeText,
108 Stream.mkString,
109 );
110 return yield* HttpServerResponse.json(JSON.parse(payload));
111 }
112
113 return yield* HttpServerResponse.json(
114 { error: "Not found", method: request.method, pathname },
115 { status: 404 },
116 );
117 }).pipe(Effect.orDie),
118 };
119 }).pipe(
120 Effect.provide(
121 Layer.mergeAll(
122 NeptuneGraph.ExecuteQueryHttp,
123 NeptuneGraph.GetGraphSummaryHttp,
124 NeptuneGraph.ListQueriesHttp,
125 NeptuneGraph.ListGraphSnapshotsHttp,
126 FixtureGraphLive,
127 ),
128 ),
129 ),
130 // Re-merge so the deploying Stack can `yield* FixtureGraph` and expose the
131 // graph attributes as deploy-time outputs. Reusing the same
132 // `FixtureGraphLive` reference keeps it a single shared graph.
133).pipe(Layer.provideMerge(FixtureGraphLive));
134
135export default NeptuneGraphTestFunctionLive;