1import * as Lambda from "@/AWS/Lambda";
2import * as Omics from "@/AWS/Omics";
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, "bindings-handler.ts");
10
11// A syntactically-plausible but nonexistent read-set / run id.
12const BOGUS_ID = "0000000000";
13
14export class OmicsBindingsFunction extends Lambda.Function<Lambda.Function>()(
15 "OmicsBindingsFunction",
16) {}
17
18export default OmicsBindingsFunction.make(
19 {
20 main,
21 functionUrl: true,
22 },
23 Effect.gen(function* () {
24 const sequenceStore = yield* Omics.SequenceStore("BindingsSequenceStore");
25 const referenceStore = yield* Omics.ReferenceStore(
26 "BindingsReferenceStore",
27 );
28
29 // Resource-scoped bindings — the store id is injected automatically.
30 const listReadSets = yield* Omics.ListReadSets(sequenceStore);
31 const getReadSetMetadata = yield* Omics.GetReadSetMetadata(sequenceStore);
32 const startReadSetImportJob =
33 yield* Omics.StartReadSetImportJob(sequenceStore);
34 const batchDeleteReadSet = yield* Omics.BatchDeleteReadSet(sequenceStore);
35 const listReferences = yield* Omics.ListReferences(referenceStore);
36 const getReferenceMetadata =
37 yield* Omics.GetReferenceMetadata(referenceStore);
38
39 // Account-level run-control bindings.
40 const listRuns = yield* Omics.ListRuns();
41 const getRun = yield* Omics.GetRun();
42
43 const bound = {
44 listReadSets,
45 getReadSetMetadata,
46 startReadSetImportJob,
47 batchDeleteReadSet,
48 listReferences,
49 getReferenceMetadata,
50 listRuns,
51 getRun,
52 };
53
54 return {
55 fetch: Effect.gen(function* () {
56 const request = yield* HttpServerRequest;
57 const url = new URL(request.originalUrl);
58 const pathname = url.pathname;
59
60 if (request.method === "GET" && pathname === "/bindings") {
61 return yield* HttpServerResponse.json({ bound: Object.keys(bound) });
62 }
63
64 if (request.method === "GET" && pathname === "/readsets") {
65 // sequenceStoreId injection scopes the list to the bound store.
66 const response = yield* listReadSets();
67 return yield* HttpServerResponse.json({
68 count: (response.readSets ?? []).length,
69 });
70 }
71
72 if (request.method === "GET" && pathname === "/references") {
73 const response = yield* listReferences();
74 return yield* HttpServerResponse.json({
75 count: (response.references ?? []).length,
76 });
77 }
78
79 if (request.method === "GET" && pathname === "/runs") {
80 const response = yield* listRuns();
81 return yield* HttpServerResponse.json({
82 count: (response.items ?? []).length,
83 });
84 }
85
86 // The typed-not-found routes prove the grant + injection end-to-end:
87 // an IAM gap would surface AccessDeniedException (a 500), while the
88 // typed ResourceNotFoundException proves the request reached the API.
89 if (
90 request.method === "GET" &&
91 pathname === "/readset/typed-not-found"
92 ) {
93 const typed = yield* getReadSetMetadata({ id: BOGUS_ID }).pipe(
94 Effect.map(() => false),
95 Effect.catchTag(
96 ["ResourceNotFoundException", "ValidationException"],
97 () => Effect.succeed(true),
98 ),
99 );
100 return yield* HttpServerResponse.json({ typed });
101 }
102
103 if (request.method === "GET" && pathname === "/run/typed-not-found") {
104 const typed = yield* getRun({ id: BOGUS_ID }).pipe(
105 Effect.map(() => false),
106 Effect.catchTag(
107 ["ResourceNotFoundException", "ValidationException"],
108 () => Effect.succeed(true),
109 ),
110 );
111 return yield* HttpServerResponse.json({ typed });
112 }
113
114 return yield* HttpServerResponse.json(
115 { error: "Not found", method: request.method, pathname },
116 { status: 404 },
117 );
118 }).pipe(Effect.orDie),
119 };
120 }).pipe(
121 Effect.provide(
122 Layer.mergeAll(
123 Omics.ListReadSetsHttp,
124 Omics.GetReadSetMetadataHttp,
125 Omics.StartReadSetImportJobHttp,
126 Omics.BatchDeleteReadSetHttp,
127 Omics.ListReferencesHttp,
128 Omics.GetReferenceMetadataHttp,
129 Omics.ListRunsHttp,
130 Omics.GetRunHttp,
131 ),
132 ),
133 ),
134);