1/**
2 * Shared scaffolding for the S3 Vectors data-plane HTTP bindings.
3 *
4 * NOT exported from `index.ts` — the per-level `Vectors{Read,Write,}Http.ts`
5 * layers are thin compositions over this builder. Everything except the
6 * distilled operations and the IAM action set is boilerplate:
7 *
8 * - the deploy-time half registers `Allow(host, AWS.S3Vectors.{name}(index))`
9 * with the requested actions on exactly the bound index's ARN;
10 * - the runtime client methods inject the resolved `indexArn` into each
11 * distilled request.
12 */
13import * as s3vectors from "@distilled.cloud/aws/s3vectors";
14import * as Effect from "effect/Effect";
15import * as Binding from "../../Binding.ts";
16import { isBindingHost } from "../Lambda/Function.ts";
17import type { Index } from "./VectorIndex.ts";
18import type {
19 GetVectorsRequest,
20 ListVectorsRequest,
21 QueryVectorsRequest,
22 ReadVectorsClient,
23} from "./VectorsRead.ts";
24import type {
25 DeleteVectorsRequest,
26 PutVectorsRequest,
27 WriteVectorsClient,
28} from "./VectorsWrite.ts";
29
30/** IAM actions granted by the read-level binding. */
31export const readVectorsActions = [
32 "s3vectors:QueryVectors",
33 "s3vectors:GetVectors",
34 "s3vectors:ListVectors",
35] as const;
36
37/** IAM actions granted by the write-level binding. */
38export const writeVectorsActions = [
39 "s3vectors:PutVectors",
40 "s3vectors:DeleteVectors",
41] as const;
42
43/**
44 * Build the shared body of an S3 Vectors data-plane `*Http` binding layer:
45 * resolve the distilled operations once at layer construction (via
46 * `makeClient`), register the least-privilege IAM statement on the host
47 * Function at deploy time, and hand the per-index `indexArn` resolver to the
48 * capability-specific client builder.
49 */
50export const makeVectorsHttpBinding = <Client, R>(options: {
51 /** Capability name in the binding SID + trace spans, e.g. `"VectorsRead"`. */
52 name: string;
53 /** IAM actions granted on the bound index's ARN. */
54 actions: readonly string[];
55 /** Layer-scoped builder of the per-index typed runtime client. */
56 makeClient: Effect.Effect<
57 (indexArn: Effect.Effect<string>, label: string) => Client,
58 never,
59 R
60 >;
61}) =>
62 Effect.gen(function* () {
63 const makeClient = yield* options.makeClient;
64
65 return Effect.fn(function* <I extends Index>(index: I) {
66 const IndexArn = yield* index.indexArn;
67 if (!globalThis.__ALCHEMY_RUNTIME__) {
68 const host = yield* Binding.Host;
69 if (isBindingHost(host)) {
70 yield* host.bind`Allow(${host}, AWS.S3Vectors.${options.name}(${index}))`(
71 {
72 policyStatements: [
73 {
74 Effect: "Allow",
75 Action: [...options.actions],
76 Resource: [index.indexArn],
77 },
78 ],
79 },
80 );
81 }
82 }
83 return makeClient(
84 IndexArn,
85 `AWS.S3Vectors.${options.name}(${index.LogicalId})`,
86 );
87 });
88 });
89
90/** Layer-scoped builder of the read-level {@link ReadVectorsClient}. */
91export const makeReadVectorsClient = Effect.gen(function* () {
92 const queryVectors = yield* s3vectors.queryVectors;
93 const getVectors = yield* s3vectors.getVectors;
94 const listVectors = yield* s3vectors.listVectors;
95
96 return (
97 IndexArn: Effect.Effect<string>,
98 label: string,
99 ): ReadVectorsClient => ({
100 query: Effect.fn(`${label}.query`)(function* (
101 request: QueryVectorsRequest,
102 ) {
103 const indexArn = yield* IndexArn;
104 return yield* queryVectors({ ...request, indexArn });
105 }),
106 get: Effect.fn(`${label}.get`)(function* (request: GetVectorsRequest) {
107 const indexArn = yield* IndexArn;
108 return yield* getVectors({ ...request, indexArn });
109 }),
110 list: Effect.fn(`${label}.list`)(function* (request: ListVectorsRequest) {
111 const indexArn = yield* IndexArn;
112 return yield* listVectors({ ...request, indexArn });
113 }),
114 });
115});
116
117/** Layer-scoped builder of the write-level {@link WriteVectorsClient}. */
118export const makeWriteVectorsClient = Effect.gen(function* () {
119 const putVectors = yield* s3vectors.putVectors;
120 const deleteVectors = yield* s3vectors.deleteVectors;
121
122 return (
123 IndexArn: Effect.Effect<string>,
124 label: string,
125 ): WriteVectorsClient => ({
126 put: Effect.fn(`${label}.put`)(function* (request: PutVectorsRequest) {
127 const indexArn = yield* IndexArn;
128 return yield* putVectors({ ...request, indexArn });
129 }),
130 delete: Effect.fn(`${label}.delete`)(function* (
131 request: DeleteVectorsRequest,
132 ) {
133 const indexArn = yield* IndexArn;
134 return yield* deleteVectors({ ...request, indexArn });
135 }),
136 });
137});