diff --git a/src/nodes/TransactItemNode.ts b/src/nodes/TransactItemNode.ts index bee2064..d8fce78 100644 --- a/src/nodes/TransactItemNode.ts +++ b/src/nodes/TransactItemNode.ts @@ -1,8 +1,10 @@ import { DeleteNode } from "./deleteNode"; import { PutNode } from "./putNode"; +import { UpdateNode } from "./updateNode"; export type TransactItemNode = { readonly kind: "TransactItemNode"; readonly Put?: PutNode; readonly Delete?: DeleteNode; + readonly Update?: UpdateNode; }; diff --git a/src/queryBuilders/__snapshots__/transactionBuilder.integration.test.ts.snap b/src/queryBuilders/__snapshots__/transactionBuilder.integration.test.ts.snap index 3463d1e..2dee0bd 100644 --- a/src/queryBuilders/__snapshots__/transactionBuilder.integration.test.ts.snap +++ b/src/queryBuilders/__snapshots__/transactionBuilder.integration.test.ts.snap @@ -12,3 +12,35 @@ exports[`TransactionBuilder > handles puts 1`] = ` }, ] `; + +exports[`TransactionBuilder > handles transaction with puts 1`] = ` +[ + { + "dataTimestamp": 1, + "userId": "9999", + }, + { + "dataTimestamp": 2, + "userId": "9999", + }, +] +`; + +exports[`TransactionBuilder > handles transaction with updates 1`] = ` +[ + { + "dataTimestamp": 1, + "someBoolean": true, + "userId": "9999", + }, + { + "dataTimestamp": 2, + "tags": [ + "a", + "b", + "c", + ], + "userId": "9999", + }, +] +`; diff --git a/src/queryBuilders/transactionBuilder.integration.test.ts b/src/queryBuilders/transactionBuilder.integration.test.ts index 0bb04eb..506a418 100644 --- a/src/queryBuilders/transactionBuilder.integration.test.ts +++ b/src/queryBuilders/transactionBuilder.integration.test.ts @@ -16,7 +16,7 @@ describe("TransactionBuilder", () => { }); }); - it("handles puts", async () => { + it("handles transaction with puts", async () => { const trx = tsynamoClient.createTransaction(); trx.addItem({ @@ -41,7 +41,7 @@ describe("TransactionBuilder", () => { expect(result).toMatchSnapshot(); }); - it("handles deletes", async () => { + it("handles transaction with deletes", async () => { await tsynamoClient .putItem("myTable") .item({ userId: "9999", dataTimestamp: 1 }) @@ -110,4 +110,41 @@ describe("TransactionBuilder", () => { expect(foundItem).toBeUndefined(); }); + + it("handles transaction with updates", async () => { + await tsynamoClient + .putItem("myTable") + .item({ userId: "1", dataTimestamp: 1 }) + .execute(); + + await tsynamoClient + .putItem("myTable") + .item({ userId: "1", dataTimestamp: 2 }) + .execute(); + + const trx = tsynamoClient.createTransaction(); + + trx.addItem({ + Update: tsynamoClient + .updateItem("myTable") + .keys({ userId: "9999", dataTimestamp: 1 }) + .set("someBoolean", "=", true), + }); + + trx.addItem({ + Update: tsynamoClient + .updateItem("myTable") + .keys({ userId: "9999", dataTimestamp: 2 }) + .set("tags", "=", ["a", "b", "c"]), + }); + + await trx.execute(); + + const result = await tsynamoClient + .query("myTable") + .keyCondition("userId", "=", "9999") + .execute(); + + expect(result).toMatchSnapshot(); + }); }); diff --git a/src/queryBuilders/transactionBuilder.ts b/src/queryBuilders/transactionBuilder.ts index 22cb9d7..b11823b 100644 --- a/src/queryBuilders/transactionBuilder.ts +++ b/src/queryBuilders/transactionBuilder.ts @@ -3,14 +3,16 @@ import { TransactionNode } from "../nodes/transactionNode"; import { QueryCompiler } from "../queryCompiler"; import { PutItemQueryBuilder } from "./putItemQueryBuilder"; import { DeleteItemQueryBuilder } from "./deleteItemQueryBuilder"; +import { UpdateItemQueryBuilder } from "./updateItemQueryBuilder"; export interface TransactionBuilderInterface { /** - * TODO: Update, ConditionCheck + * TODO: ConditionCheck */ addItem(item: { Put?: PutItemQueryBuilder; Delete?: DeleteItemQueryBuilder; + Update?: UpdateItemQueryBuilder; }): void; execute(): Promise; @@ -28,11 +30,13 @@ export class TransactionBuilder addItem(item: { Put?: PutItemQueryBuilder; Delete?: DeleteItemQueryBuilder; + Update?: UpdateItemQueryBuilder; }) { this.#props.node.transactItems.push({ kind: "TransactItemNode", Put: item.Put?.node, Delete: item.Delete?.node, + Update: item.Update?.node, }); } diff --git a/src/queryBuilders/updateItemQueryBuilder.ts b/src/queryBuilders/updateItemQueryBuilder.ts index 6579aa7..bd64e3a 100644 --- a/src/queryBuilders/updateItemQueryBuilder.ts +++ b/src/queryBuilders/updateItemQueryBuilder.ts @@ -31,71 +31,71 @@ import { SetUpdateExpressionFunctionQueryBuilder } from "./setUpdateExpressionFu export interface UpdateItemQueryBuilderInterface< DDB, Table extends keyof DDB, - O + O extends DDB[Table] > { // conditionExpression conditionExpression>( ...args: ComparatorExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; conditionExpression>( ...args: AttributeFuncExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; conditionExpression>( ...args: AttributeBeginsWithExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; conditionExpression>( ...args: AttributeContainsExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; conditionExpression>( ...args: AttributeBetweenExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; conditionExpression>( ...args: NotExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; conditionExpression>( ...args: BuilderExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; // orConditionExpression orConditionExpression>( ...args: ComparatorExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; orConditionExpression>( ...args: AttributeFuncExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; orConditionExpression>( ...args: AttributeBeginsWithExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; orConditionExpression>( ...args: AttributeContainsExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; orConditionExpression>( ...args: AttributeBetweenExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; orConditionExpression>( ...args: NotExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; orConditionExpression>( ...args: BuilderExprArg - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; set>>( key: Key, operand: UpdateExpressionOperands, value: StripKeys> - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; set>>( key: Key, @@ -103,7 +103,7 @@ export interface UpdateItemQueryBuilderInterface< value: ( builder: SetUpdateExpressionFunctionQueryBuilder ) => SetUpdateExpressionFunction - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; set>>( key: Key, @@ -111,16 +111,16 @@ export interface UpdateItemQueryBuilderInterface< value: ( builder: SetUpdateExpressionFunctionQueryBuilder ) => [SetUpdateExpressionFunction, number] - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; keys & PickSkRequired>( pk: Keys - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; // TODO: Make it possible to delete a whole object, and not just nested keys remove>>( attribute: Key - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; add< Key extends ObjectKeyPaths< @@ -129,7 +129,7 @@ export interface UpdateItemQueryBuilderInterface< >( attribute: Key, value: StripKeys> - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; delete< Key extends ObjectKeyPaths< @@ -138,11 +138,11 @@ export interface UpdateItemQueryBuilderInterface< >( attribute: Key, value: StripKeys> - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; returnValues( option: ReturnValuesOptions - ): UpdateItemQueryBuilderInterface; + ): UpdateItemQueryBuilder; compile(): UpdateCommand; execute(): Promise[] | undefined>; @@ -162,7 +162,7 @@ export class UpdateItemQueryBuilder< conditionExpression>( ...args: ExprArgs - ): UpdateItemQueryBuilderInterface { + ): UpdateItemQueryBuilder { const eB = new ExpressionBuilder({ node: { ...this.#props.node.conditionExpression }, }); @@ -180,7 +180,7 @@ export class UpdateItemQueryBuilder< orConditionExpression>( ...args: ExprArgs - ): UpdateItemQueryBuilderInterface { + ): UpdateItemQueryBuilder { const eB = new ExpressionBuilder({ node: { ...this.#props.node.conditionExpression }, }); @@ -225,7 +225,7 @@ export class UpdateItemQueryBuilder< > ) => [SetUpdateExpressionFunction, number] ] - ): UpdateItemQueryBuilderInterface { + ): UpdateItemQueryBuilder { const [key, operand, right] = args; if (typeof right === "function") { @@ -312,7 +312,7 @@ export class UpdateItemQueryBuilder< remove>>( attribute: Key - ): UpdateItemQueryBuilderInterface { + ): UpdateItemQueryBuilder { return new UpdateItemQueryBuilder({ ...this.#props, node: { @@ -332,7 +332,7 @@ export class UpdateItemQueryBuilder< add>>( attribute: Key, value: StripKeys> - ): UpdateItemQueryBuilderInterface { + ): UpdateItemQueryBuilder { return new UpdateItemQueryBuilder({ ...this.#props, node: { @@ -355,7 +355,7 @@ export class UpdateItemQueryBuilder< >( attribute: Key, value: StripKeys> - ): UpdateItemQueryBuilderInterface { + ): UpdateItemQueryBuilder { return new UpdateItemQueryBuilder({ ...this.#props, node: { @@ -375,7 +375,7 @@ export class UpdateItemQueryBuilder< returnValues( option: ReturnValuesOptions - ): UpdateItemQueryBuilderInterface { + ): UpdateItemQueryBuilder { return new UpdateItemQueryBuilder({ ...this.#props, node: { @@ -412,6 +412,10 @@ export class UpdateItemQueryBuilder< const data = await this.#props.ddbClient.send(putCommand); return data.Attributes as any; }; + + public get node() { + return this.#props.node; + } } preventAwait( diff --git a/src/queryCompiler/queryCompiler.ts b/src/queryCompiler/queryCompiler.ts index 56ee044..7355a48 100644 --- a/src/queryCompiler/queryCompiler.ts +++ b/src/queryCompiler/queryCompiler.ts @@ -1,4 +1,4 @@ -import { TransactWriteItem } from "@aws-sdk/client-dynamodb"; +import { TransactWriteItem, Update } from "@aws-sdk/client-dynamodb"; import { DeleteCommand, DeleteCommandInput, @@ -228,6 +228,10 @@ export class QueryCompiler { } compileUpdateNode(updateNode: UpdateNode) { + return new UpdateCommand(this.compileUpdateCmdInput(updateNode)); + } + + compileUpdateCmdInput(updateNode: UpdateNode) { const { table: tableNode, conditionExpression: conditionExpressionNode, @@ -251,7 +255,7 @@ export class QueryCompiler { attributeNames ); - return new UpdateCommand({ + return { TableName: tableNode.table, Key: keysNode?.keys, ReturnValues: returnValuesNode?.option, @@ -273,7 +277,7 @@ export class QueryCompiler { ...Object.fromEntries(attributeNames), } : undefined, - }); + }; } compileTransactionNode(transactionNode: TransactionNode) { @@ -288,6 +292,12 @@ export class QueryCompiler { compiledTransactItem.Delete = this.compileDeleteCmdInput(item.Delete); } + if (item.Update) { + compiledTransactItem.Update = this.compileUpdateCmdInput( + item.Update + ) as Update; + } + return compiledTransactItem; });