diff --git a/lib/keyValueRepository.ts b/lib/keyValueRepository.ts index d783a103..7f660dca 100644 --- a/lib/keyValueRepository.ts +++ b/lib/keyValueRepository.ts @@ -10,6 +10,7 @@ import { ScanCommandOutput, UpdateCommand, UpdateCommandInput, + UpdateCommandOutput, } from '@aws-sdk/lib-dynamodb'; import { ConstructorArgs, IdOptions } from './types'; @@ -158,29 +159,12 @@ class KeyValueRepository { return itemToSave; } - async updatePartial(item: any) { + async updatePartial(item: any): Promise> { validateHashKeyPropertyExists({ item, keyName: this.keyName }); - const { revision: previousRevision } = item; - const itemToSave = setRepositoryModifiedProperties(item); - const key = createDynamoDbKey({ keyName: this.keyName, keyValue: itemToSave[this.keyName] }); - const updateInput: UpdateCommandInput = { - TableName: this.tableName, - Key: key, - ConditionExpression: 'attribute_exists(#key) AND #revision = :prevRev', - UpdateExpression: this.updateExpressionsBuilder.buildUpdateExpression(itemToSave), - ExpressionAttributeNames: { - '#key': this.keyName, - '#revision': 'revision', - ...this.updateExpressionsBuilder.buildExpressionNames(itemToSave), - }, - ExpressionAttributeValues: { - ':prevRev': previousRevision, - ...this.updateExpressionsBuilder.buildExpressionValues(itemToSave), - }, - ReturnValuesOnConditionCheckFailure: 'ALL_OLD', - }; + const updateInput = this.buildUpdateCommandInput(item); + let result: UpdateCommandOutput; try { - await this.docClient.send(new UpdateCommand(updateInput)); + result = await this.docClient.send(new UpdateCommand(updateInput)); } catch (err: any) { if (err.name === 'ConditionalCheckFailedException') { const { Item } = err; @@ -189,19 +173,43 @@ class KeyValueRepository { } if ( isRevisionConflict({ - expectedRevision: previousRevision, + expectedRevision: item.revision, actualRevision: Item?.revision?.N, }) ) { throw Conflict( - `Conflict: Item in DB has revision [${Item?.revision?.N}]. You are using revision [${previousRevision}]`, + `Conflict: Item in DB has revision [${Item?.revision?.N}]. You are using revision [${item.revision}]`, ); } } throw err; } + return result.Attributes || {}; + } - return itemToSave; + private buildUpdateCommandInput(item: any): UpdateCommandInput { + const { revision: previousRevision } = item; + const itemToSave = setRepositoryModifiedPropertiesForPartialUpdate(item); + const key = createDynamoDbKey({ keyName: this.keyName, keyValue: itemToSave[this.keyName] }); + const updateInput: UpdateCommandInput = { + TableName: this.tableName, + Key: key, + ConditionExpression: 'attribute_exists(#key) AND #revision = :prevRev', + UpdateExpression: this.updateExpressionsBuilder.buildUpdateExpression(itemToSave), + ExpressionAttributeNames: { + '#key': this.keyName, + '#revision': 'revision', + ...this.updateExpressionsBuilder.buildExpressionNames(itemToSave), + }, + ExpressionAttributeValues: { + ':prevRev': previousRevision, + ...this.updateExpressionsBuilder.buildExpressionValues(itemToSave), + }, + ReturnValues: 'ALL_NEW', + ReturnValuesOnConditionCheckFailure: 'ALL_OLD', + }; + + return updateInput; } } @@ -228,4 +236,13 @@ const setRepositoryModifiedProperties = (item: any) => { return returnItem; }; +const setRepositoryModifiedPropertiesForPartialUpdate = (item: any) => { + const returnItem = { ...item }; + delete returnItem.createdAt; + returnItem.updatedAt = new Date().toISOString(); + returnItem.revision = item.revision + 1; + + return returnItem; +}; + export default KeyValueRepository; diff --git a/test/updatePartial.int.test.ts b/test/updatePartial.int.test.ts index 439ee41b..e6fafd69 100644 --- a/test/updatePartial.int.test.ts +++ b/test/updatePartial.int.test.ts @@ -72,7 +72,7 @@ describe('When updating an item', () => { expect(result.field1).toEqual(newField1); }); - it.skip('should maintain original createdAt value', async () => { + it('should maintain original createdAt value', async () => { // ARRANGE const item = createTestKeyValueItem(); const originalCreatedAt = item.createdAt;