1import * as AMP from "@/AWS/AMP";
2import * as Lambda from "@/AWS/Lambda";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import { HttpServerRequest } from "effect/http/HttpServerRequest";
6import * as HttpServerResponse from "effect/http/HttpServerResponse";
7import path from "pathe";
8
9const main = path.resolve(import.meta.dirname, "handler.ts");
10
11export class AmpTestFunction extends Lambda.Function<Lambda.Function>()(
12 "AmpTestFunction",
13) {}
14
15export default AmpTestFunction.make(
16 {
17 main,
18 functionUrl: true,
19 },
20 Effect.gen(function* () {
21 const workspace = yield* AMP.Workspace("BindingsWorkspace", {
22 alias: "alchemy-test-amp-bindings",
23 tags: { Environment: "test" },
24 });
25
26 const remoteWrite = yield* AMP.RemoteWrite(workspace);
27 const metrics = yield* AMP.QueryMetrics(workspace);
28 const labels = yield* AMP.GetLabels(workspace);
29 const getSeries = yield* AMP.GetSeries(workspace);
30 const getMetricMetadata = yield* AMP.GetMetricMetadata(workspace);
31 const describeWorkspace = yield* AMP.DescribeWorkspace(workspace);
32 const listWorkspaces = yield* AMP.ListWorkspaces();
33 const getDefaultScraperConfiguration =
34 yield* AMP.GetDefaultScraperConfiguration();
35
36 return {
37 fetch: Effect.gen(function* () {
38 const request = yield* HttpServerRequest;
39 const url = new URL(request.originalUrl);
40 const pathname = url.pathname;
41
42 if (request.method === "GET" && pathname === "/health") {
43 return HttpServerResponse.text("ok");
44 }
45
46 if (request.method === "POST" && pathname === "/remote-write") {
47 const body = (yield* request.json) as unknown as {
48 name: string;
49 labels?: Record<string, string>;
50 value: number;
51 timestamp?: number;
52 };
53 yield* remoteWrite({
54 timeseries: [
55 {
56 name: body.name,
57 labels: body.labels,
58 samples: [{ value: body.value, timestamp: body.timestamp }],
59 },
60 ],
61 });
62 return yield* HttpServerResponse.json({ success: true });
63 }
64
65 if (request.method === "GET" && pathname === "/query") {
66 const query = url.searchParams.get("query");
67 if (!query) {
68 return HttpServerResponse.text("Missing query", { status: 400 });
69 }
70 const result = yield* metrics.query({
71 query,
72 timeout: "30 seconds",
73 });
74 return yield* HttpServerResponse.json({ result });
75 }
76
77 if (request.method === "GET" && pathname === "/query-range") {
78 const query = url.searchParams.get("query");
79 if (!query) {
80 return HttpServerResponse.text("Missing query", { status: 400 });
81 }
82 const now = yield* Effect.sync(() => Date.now());
83 const result = yield* metrics.queryRange({
84 query,
85 start: new Date(now - 15 * 60_000),
86 end: new Date(now),
87 step: "30 seconds",
88 });
89 return yield* HttpServerResponse.json({ result });
90 }
91
92 if (request.method === "GET" && pathname === "/labels") {
93 const names = yield* labels.labelNames();
94 return yield* HttpServerResponse.json({ names });
95 }
96
97 if (request.method === "GET" && pathname === "/label-values") {
98 const label = url.searchParams.get("label");
99 if (!label) {
100 return HttpServerResponse.text("Missing label", { status: 400 });
101 }
102 const values = yield* labels.labelValues({ label });
103 return yield* HttpServerResponse.json({ values });
104 }
105
106 if (request.method === "GET" && pathname === "/series") {
107 const match = url.searchParams.getAll("match");
108 if (match.length === 0) {
109 return HttpServerResponse.text("Missing match", { status: 400 });
110 }
111 const series = yield* getSeries({ match });
112 return yield* HttpServerResponse.json({ series });
113 }
114
115 if (request.method === "GET" && pathname === "/describe-workspace") {
116 const response = yield* describeWorkspace();
117 return yield* HttpServerResponse.json({
118 workspaceId: response.workspace.workspaceId,
119 status: response.workspace.status.statusCode,
120 });
121 }
122
123 if (request.method === "GET" && pathname === "/list-workspaces") {
124 const response = yield* listWorkspaces();
125 return yield* HttpServerResponse.json({
126 workspaceIds: response.workspaces.map((w) => w.workspaceId),
127 });
128 }
129
130 if (
131 request.method === "GET" &&
132 pathname === "/default-scraper-config"
133 ) {
134 const configuration = yield* getDefaultScraperConfiguration();
135 return yield* HttpServerResponse.json({ configuration });
136 }
137
138 if (request.method === "GET" && pathname === "/metadata") {
139 const metadata = yield* getMetricMetadata({
140 metric: url.searchParams.get("metric") ?? undefined,
141 });
142 return yield* HttpServerResponse.json({ metadata });
143 }
144
145 return yield* HttpServerResponse.json(
146 { error: "Not found", method: request.method, pathname },
147 { status: 404 },
148 );
149 }).pipe(Effect.orDie),
150 };
151 }).pipe(
152 Effect.provide(
153 Layer.mergeAll(
154 AMP.RemoteWriteHttp,
155 AMP.QueryMetricsHttp,
156 AMP.GetLabelsHttp,
157 AMP.GetSeriesHttp,
158 AMP.GetMetricMetadataHttp,
159 AMP.DescribeWorkspaceHttp,
160 AMP.ListWorkspacesHttp,
161 AMP.GetDefaultScraperConfigurationHttp,
162 ),
163 ),
164 ),
165);