1import * as DynamoDB from "@distilled.cloud/aws/dynamodb";
2import * as Effect from "effect/Effect";
3import * as Binding from "../../Binding.ts";
4import * as Layer from "effect/Layer";
5import { isBindingHost } from "../Lambda/Function.ts";
6import {
7 TransactWriteItems,
8 type TransactWriteItemsRequest,
9 type TransactWriteItemsTables,
10} from "./TransactWriteItems.ts";
11
12export const TransactWriteItemsHttp = Layer.effect(
13 TransactWriteItems,
14 Effect.gen(function* () {
15 const transactWriteItems = yield* DynamoDB.transactWriteItems;
16
17 return Effect.fn(function* (...tables: TransactWriteItemsTables) {
18 const sortedTables = sortTables(tables);
19 const tableNames = new Map(
20 yield* Effect.forEach(sortedTables, (table) =>
21 Effect.gen(function* () {
22 return [table.LogicalId, yield* table.tableName] as const;
23 }),
24 ),
25 );
26
27 const getTableName = Effect.fn(function* (tableId: string) {
28 const TableName = tableNames.get(tableId);
29 if (!TableName) {
30 return yield* Effect.die(
31 new Error(
32 `TransactWriteItems request references unbound table '${tableId}'`,
33 ),
34 );
35 }
36 return yield* TableName;
37 });
38
39 if (!globalThis.__ALCHEMY_RUNTIME__) {
40 const host = yield* Binding.Host;
41 if (isBindingHost(host)) {
42 yield* host.bind`Allow(${host}, AWS.DynamoDB.TransactWriteItems(${sortedTables}))`(
43 {
44 policyStatements: [
45 {
46 Effect: "Allow",
47 Action: [
48 "dynamodb:ConditionCheckItem",
49 "dynamodb:DeleteItem",
50 "dynamodb:PutItem",
51 "dynamodb:UpdateItem",
52 ],
53 Resource: sortedTables.map((table) => table.tableArn),
54 },
55 ],
56 },
57 );
58 }
59 }
60
61 return Effect.fn(`AWS.DynamoDB.TransactWriteItems(${sortedTables})`)(
62 function* (request: TransactWriteItemsRequest) {
63 const transactItems = yield* Effect.forEach(
64 request.TransactItems,
65 (item) =>
66 Effect.gen(function* () {
67 if (item.ConditionCheck) {
68 return {
69 ConditionCheck: {
70 ...item.ConditionCheck,
71 TableName: yield* getTableName(item.ConditionCheck.Table),
72 },
73 };
74 }
75 if (item.Delete) {
76 return {
77 Delete: {
78 ...item.Delete,
79 TableName: yield* getTableName(item.Delete.Table),
80 },
81 };
82 }
83 if (item.Put) {
84 return {
85 Put: {
86 ...item.Put,
87 TableName: yield* getTableName(item.Put.Table),
88 },
89 };
90 }
91 if (item.Update) {
92 return {
93 Update: {
94 ...item.Update,
95 TableName: yield* getTableName(item.Update.Table),
96 },
97 };
98 }
99 return yield* Effect.die(
100 new Error(
101 "TransactWriteItems request item must include one DynamoDB operation",
102 ),
103 );
104 }),
105 );
106
107 return yield* transactWriteItems({
108 ...request,
109 TransactItems: transactItems,
110 });
111 },
112 );
113 });
114 }),
115);
116
117const sortTables = (tables: TransactWriteItemsTables) =>
118 [
119 ...new Map(
120 tables.map((table) => [table.LogicalId, table] as const),
121 ).values(),
122 ].sort((a, b) =>
123 a.LogicalId.localeCompare(b.LogicalId),
124 ) as TransactWriteItemsTables;