1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { Table } from "./Table.ts";
6import type { TableBucket } from "./TableBucket.ts";
7
8/**
9 * Shared scaffolding for the S3 Tables runtime bindings.
10 *
11 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
12 * thin `Layer.effect(Cap, makeS3Tables…HttpBinding({ … }))` over one of the
13 * two builders below. Everything except the operation, the IAM action list,
14 * and the injected identifiers is boilerplate.
15 */
16
17/**
18 * Build the impl Effect for a table-bucket-scoped S3 Tables operation: the
19 * runtime callable injects the bound {@link TableBucket}'s ARN as
20 * `tableBucketARN` and the deploy-time half grants `actions` on the bucket
21 * ARN (and everything under it, for operations like `ListTables` whose IAM
22 * resource is the namespace/table).
23 */
24export const makeS3TablesTableBucketHttpBinding = <
25 I extends { tableBucketARN?: string },
26 A,
27 E,
28 R,
29>(options: {
30 /** Fully-qualified binding tag, e.g. `AWS.S3Tables.ListNamespaces`. */
31 tag: string;
32 /** The distilled operation; `tableBucketARN` is injected from the bucket. */
33 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
34 /** IAM actions granted on the bucket ARN and its children. */
35 actions: readonly string[];
36}) =>
37 Effect.gen(function* () {
38 const op = yield* options.operation;
39
40 return Effect.fn(function* (tableBucket: TableBucket) {
41 const tableBucketARN = yield* tableBucket.tableBucketArn;
42 if (!globalThis.__ALCHEMY_RUNTIME__) {
43 const host = yield* Binding.Host;
44 if (isBindingHost(host)) {
45 yield* host.bind`Allow(${host}, ${options.tag}(${tableBucket}))`({
46 policyStatements: [
47 {
48 Effect: "Allow",
49 Action: [...options.actions],
50 Resource: [
51 Output.interpolate`${tableBucket.tableBucketArn}`,
52 // List operations authorize against the namespaces/tables
53 // under the bucket, e.g. `…:bucket/name/table/*`.
54 Output.interpolate`${tableBucket.tableBucketArn}/*`,
55 ],
56 },
57 ],
58 });
59 }
60 }
61 return Effect.fn(`${options.tag}(${tableBucket.LogicalId})`)(function* (
62 request?: Omit<I, "tableBucketARN">,
63 ) {
64 return yield* op({
65 ...request,
66 tableBucketARN: yield* tableBucketARN,
67 } as I);
68 });
69 });
70 });
71
72/**
73 * Build the impl Effect for a table-scoped S3 Tables operation: the runtime
74 * callable injects the bound {@link Table}'s `tableBucketARN`, `namespace`,
75 * and `name`, and the deploy-time half grants `actions` on the table's ARN.
76 */
77export const makeS3TablesTableHttpBinding = <
78 I extends { tableBucketARN?: string; namespace?: string; name?: string },
79 A,
80 E,
81 R,
82>(options: {
83 /** Fully-qualified binding tag, e.g. `AWS.S3Tables.GetTableMetadataLocation`. */
84 tag: string;
85 /**
86 * The distilled operation; `tableBucketARN`, `namespace`, and `name` are
87 * injected from the table.
88 */
89 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
90 /** IAM actions granted on the table ARN. */
91 actions: readonly string[];
92}) =>
93 Effect.gen(function* () {
94 const op = yield* options.operation;
95
96 return Effect.fn(function* (table: Table) {
97 const tableBucketARN = yield* table.tableBucketArn;
98 const namespace = yield* table.namespace;
99 const name = yield* table.name;
100 if (!globalThis.__ALCHEMY_RUNTIME__) {
101 const host = yield* Binding.Host;
102 if (isBindingHost(host)) {
103 yield* host.bind`Allow(${host}, ${options.tag}(${table}))`({
104 policyStatements: [
105 {
106 Effect: "Allow",
107 Action: [...options.actions],
108 Resource: [Output.interpolate`${table.tableArn}`],
109 },
110 ],
111 });
112 }
113 }
114 return Effect.fn(`${options.tag}(${table.LogicalId})`)(function* (
115 request?: Omit<I, "tableBucketARN" | "namespace" | "name">,
116 ) {
117 return yield* op({
118 ...request,
119 tableBucketARN: yield* tableBucketARN,
120 namespace: yield* namespace,
121 name: yield* name,
122 } as I);
123 });
124 });
125 });