From 684bd51b556380bdc8bb4b7c8d7c8abf00521eed Mon Sep 17 00:00:00 2001 From: Nithiwat Kampanya Date: Wed, 10 Jul 2024 14:33:50 -0400 Subject: [PATCH] Added .returning() This MR allows callers to get the original object as it was before an update is made or the new object as it is after the update. Since TransactWriteItems does not support this feature, additional logic has been added to nodenamo to support this addition. Returning an old object does not incur additional cost. Returning a new object, however, involves an additional get query. The returned object, if any, is strongly consistent. --- package.json | 2 +- spec/acceptance/customNameTest.spec.ts | 31 ++- spec/acceptance/globalTableTest.spec.ts | 4 +- spec/acceptance/hashRangePairTest.spec.ts | 45 +++- spec/acceptance/hashRangeTest.spec.ts | 45 +++- spec/acceptance/hashTest.spec.ts | 45 +++- spec/acceptance/idTest.spec.ts | 30 ++- spec/acceptance/indexConsistentTest.spec.ts | 1 - ...valuatedKeyWithFilterAndPagingTest.spec.ts | 1 - spec/acceptance/marshallTest.spec.ts | 4 +- .../multiValuesHashRangeTest.spec.ts | 5 +- spec/acceptance/multiValuesHashTest.spec.ts | 31 ++- spec/acceptance/multiValuesRangeTest.spec.ts | 45 +++- spec/acceptance/tableVersioningTest.spec.ts | 5 +- .../urlSafeFirstAndLastEvaluatedKeys.spec.ts | 1 - spec/acceptance/versionTest.spec.ts | 5 +- spec/unit/dynamodbManagerUpdate.spec.ts | 67 +++++- spec/unit/queryUpdate.spec.ts | 215 ++++++++++++++++++ src/dbColumn.ts | 4 +- src/interfaces/iDynamodbManager.ts | 3 +- src/interfaces/returnValue.ts | 6 + src/managers/dynamodbManager.ts | 15 +- src/managers/validatedDynamodbManager.ts | 4 +- src/queries/update/execute.ts | 5 +- src/queries/update/from.ts | 9 +- src/queries/update/returning.ts | 31 +++ src/queries/update/where.ts | 11 +- src/queries/update/withVersionCheck.ts | 17 +- 28 files changed, 644 insertions(+), 43 deletions(-) create mode 100644 src/interfaces/returnValue.ts create mode 100644 src/queries/update/returning.ts diff --git a/package.json b/package.json index bbf8a2a..34d186a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nodenamo", - "version": "2.1.0", + "version": "2.2.0", "description": "A powerful ORM for DynamoDb", "main": "dist/index.js", "typings": "dist/index", diff --git a/spec/acceptance/customNameTest.spec.ts b/spec/acceptance/customNameTest.spec.ts index 3594e0e..8bce5da 100644 --- a/spec/acceptance/customNameTest.spec.ts +++ b/spec/acceptance/customNameTest.spec.ts @@ -2,7 +2,7 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable({name:'nodenamo_acceptance_targetNameTest'}) class User @@ -261,7 +261,9 @@ describe('Custom-name tests', function () user.name = 'This Three'; user['extra'] = 'invalid'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, { id: 3, name: 'This Three', account: 2000, created: 2018, department: 'development', enabled: false }); @@ -300,6 +302,31 @@ describe('Custom-name tests', function () assert.deepEqual(user, { id: 6, name: '', account: 3000, created: 2020, department: 'hr', enabled: true }); }); + it('Update an item - return AllOld', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.AllOld).execute(); + + assert.deepEqual(result, originalUser); + }); + + it('Update an item - return AllNew', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newest Two'}).from(User).returning(ReturnValue.AllNew).execute(); + + assert.deepEqual(result, {...originalUser, name: 'Newest Two'}); + }); + + it('Update an item - return None', async () => + { + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.None).execute(); + + assert.isUndefined(result); + }); + it('On item', async () => { let user = await nodenamo.get(6).from(User).execute(); diff --git a/spec/acceptance/globalTableTest.spec.ts b/spec/acceptance/globalTableTest.spec.ts index 5df1587..8615211 100644 --- a/spec/acceptance/globalTableTest.spec.ts +++ b/spec/acceptance/globalTableTest.spec.ts @@ -209,7 +209,9 @@ describe('Global table tests', function () assert.deepEqual(book, { id: 2, title: 'Another Book' }); user.name = 'This Two'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(2).from(User).execute(); assert.deepEqual(user, { id: 2, name: 'This Two' }); diff --git a/spec/acceptance/hashRangePairTest.spec.ts b/spec/acceptance/hashRangePairTest.spec.ts index 6e294a6..216a5b3 100644 --- a/spec/acceptance/hashRangePairTest.spec.ts +++ b/spec/acceptance/hashRangePairTest.spec.ts @@ -2,7 +2,7 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable({name:'nodenamo_acceptance_hashRangePairTest'}) class User @@ -283,7 +283,9 @@ describe('Hash-range pair tests', function () user.name = 'This Three'; user['extra'] = 'invalid'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, { id: 3, name: 'This Three', account: 2000, created: 2018, parentId: 300 }); @@ -361,6 +363,45 @@ describe('Hash-range pair tests', function () assert.deepEqual(user, { id: 6, name: '', account: 3000, created: 2020, parentId: 600 }); }); + it('Update an item - return AllOld', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.AllOld).execute(); + + assert.deepEqual(result, originalUser); + }); + + it('Update an item - return AllNew', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newest Two'}).from(User).returning(ReturnValue.AllNew).execute(); + + assert.deepEqual(result, {...originalUser, name: 'Newest Two'}); + }); + + it('Update an item - return None', async () => + { + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.None).execute(); + + assert.isUndefined(result); + }); + + it('Update an item - with all combinations', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Another Two'}) + .from(User) + .where('#account=:account', {'#account': 'account'}, {':account': originalUser.account}) + .returning(ReturnValue.AllOld) + .withVersionCheck() + .execute(); + + assert.deepEqual(result, originalUser); + }); + it('On item', async () => { let user = await nodenamo.get(6).from(User).execute(); diff --git a/spec/acceptance/hashRangeTest.spec.ts b/spec/acceptance/hashRangeTest.spec.ts index da50c9a..c2180b0 100644 --- a/spec/acceptance/hashRangeTest.spec.ts +++ b/spec/acceptance/hashRangeTest.spec.ts @@ -2,7 +2,7 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable({name:'nodenamo_acceptance_hashRangeTest'}) class User @@ -361,7 +361,9 @@ describe('Hash-range tests', function () user.name = 'This Three'; user['extra'] = 'invalid'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, { id: 3, name: 'This Three', account: 2000, created: 2018 }); @@ -440,6 +442,45 @@ describe('Hash-range tests', function () assert.deepEqual(user, { id: 6, name: '', account: 3000, created: 2020 }); }); + it('Update an item - return AllOld', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.AllOld).execute(); + + assert.deepEqual(result, originalUser); + }); + + it('Update an item - return AllNew', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newest Two'}).from(User).returning(ReturnValue.AllNew).execute(); + + assert.deepEqual(result, {...originalUser, name: 'Newest Two'}); + }); + + it('Update an item - return None', async () => + { + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.None).execute(); + + assert.isUndefined(result); + }); + + it('Update an item - with all combinations', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Another Two'}) + .from(User) + .where('#account=:account', {'#account': 'account'}, {':account': originalUser.account}) + .returning(ReturnValue.AllOld) + .withVersionCheck() + .execute(); + + assert.deepEqual(result, originalUser); + }); + it('On item', async () => { let user = await nodenamo.get(6).from(User).execute(); diff --git a/spec/acceptance/hashTest.spec.ts b/spec/acceptance/hashTest.spec.ts index 4229174..525a496 100644 --- a/spec/acceptance/hashTest.spec.ts +++ b/spec/acceptance/hashTest.spec.ts @@ -2,7 +2,7 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable({name:'nodenamo_acceptance_hashTest'}) class User @@ -288,7 +288,9 @@ describe('Hash tests', function () user.name = 'This Three'; user['extra'] = 'invalid'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, { id: 3, name: 'This Three', account: 3000 }); @@ -367,6 +369,45 @@ describe('Hash tests', function () assert.deepEqual((await nodenamo.list().from(User).execute()).items.length, 6); }); + it('Update an item - return AllOld', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.AllOld).execute(); + + assert.deepEqual(result, originalUser); + }); + + it('Update an item - return AllNew', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newest Two'}).from(User).returning(ReturnValue.AllNew).execute(); + + assert.deepEqual(result, {...originalUser, name: 'Newest Two'}); + }); + + it('Update an item - return None', async () => + { + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.None).execute(); + + assert.isUndefined(result); + }); + + it('Update an item - with all combinations', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Another Two'}) + .from(User) + .where('#account=:account', {'#account': 'account'}, {':account': originalUser.account}) + .returning(ReturnValue.AllOld) + .withVersionCheck() + .execute(); + + assert.deepEqual(result, originalUser); + }); + it('On item', async () => { let user = await nodenamo.get(6).from(User).execute(); diff --git a/spec/acceptance/idTest.spec.ts b/spec/acceptance/idTest.spec.ts index fd047ec..b4bb2fa 100644 --- a/spec/acceptance/idTest.spec.ts +++ b/spec/acceptance/idTest.spec.ts @@ -2,6 +2,7 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable({name:'nodenamo_acceptance_idTest'}) class User @@ -232,8 +233,10 @@ describe('ID tests', function () user3.name = 'This Three'; user3['extra'] = 'invalid'; - await nodenamo.update(user3).from(User).execute(); + let result = await nodenamo.update(user3).from(User).execute(); + assert.isUndefined(result); + user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, {id:3, name: 'This Three', description: 'Description 3', secret: undefined, obj:{array:[], bool:true, empty:'', num:1, obj:{n:1,e:''}, str:'string'}}); }); @@ -272,6 +275,31 @@ describe('ID tests', function () assert.deepEqual(user, {id:4, name: 'Some Four', description: '', secret: undefined, obj:undefined}); }); + it('Update an item - return AllOld', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'New Two'}).from(User).returning(ReturnValue.AllOld).execute(); + + assert.deepEqual(result, originalUser); + }); + + it('Update an item - return AllNew', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newest Two'}).from(User).returning(ReturnValue.AllNew).execute(); + + assert.deepEqual(result, {...originalUser, name: 'Newest Two'}); + }); + + it('Update an item - return None', async () => + { + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.None).execute(); + + assert.isUndefined(result); + }); + it('On item', async () => { let user = await nodenamo.get(4).from(User).execute(); diff --git a/spec/acceptance/indexConsistentTest.spec.ts b/spec/acceptance/indexConsistentTest.spec.ts index e5bc305..104410b 100644 --- a/spec/acceptance/indexConsistentTest.spec.ts +++ b/spec/acceptance/indexConsistentTest.spec.ts @@ -2,7 +2,6 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { Const } from '../../src/const'; @DBTable({name:'nodenamo_acceptance_indexConsistentTest'}) diff --git a/spec/acceptance/lastEvaluatedKeyWithFilterAndPagingTest.spec.ts b/spec/acceptance/lastEvaluatedKeyWithFilterAndPagingTest.spec.ts index 3a1db43..ec81297 100644 --- a/spec/acceptance/lastEvaluatedKeyWithFilterAndPagingTest.spec.ts +++ b/spec/acceptance/lastEvaluatedKeyWithFilterAndPagingTest.spec.ts @@ -2,7 +2,6 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; @DBTable({name:'nodenamo_acceptance_lastEvaluatedKeyWithFilterAndPagingTest'}) class User diff --git a/spec/acceptance/marshallTest.spec.ts b/spec/acceptance/marshallTest.spec.ts index f4a475a..d5ec348 100644 --- a/spec/acceptance/marshallTest.spec.ts +++ b/spec/acceptance/marshallTest.spec.ts @@ -97,7 +97,9 @@ describe('Marshall tests', function () person.children.push(person2); - await nodenamo.update(person).from(Person).execute(); + let result = await nodenamo.update(person).from(Person).execute(); + + assert.isUndefined(result); person = await nodenamo.get(0).from(Person).execute(); assert.equal(person.children.length, 2); diff --git a/spec/acceptance/multiValuesHashRangeTest.spec.ts b/spec/acceptance/multiValuesHashRangeTest.spec.ts index 89c9e6b..a7cac57 100644 --- a/spec/acceptance/multiValuesHashRangeTest.spec.ts +++ b/spec/acceptance/multiValuesHashRangeTest.spec.ts @@ -2,7 +2,6 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; @DBTable({name:'nodenamo_acceptance_multiValuesHashRangeTest'}) class User @@ -270,7 +269,9 @@ describe('Multi-values Hash/Range tests', function () user.name = 'This Three'; user['extra'] = 'invalid'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, new User({ id: 3, name: 'This Three', roles: ['user', 'admin'], departments: ['IT', 'HR'], createdTimestamp: 2014 })); diff --git a/spec/acceptance/multiValuesHashTest.spec.ts b/spec/acceptance/multiValuesHashTest.spec.ts index 7656754..fcd98e7 100644 --- a/spec/acceptance/multiValuesHashTest.spec.ts +++ b/spec/acceptance/multiValuesHashTest.spec.ts @@ -2,7 +2,7 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable({name:'nodenamo_acceptance_multiValuesHashTest'}) class User @@ -315,7 +315,9 @@ describe('Multi-values Hash tests', function () user.name = 'This Three'; user['extra'] = 'invalid'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, new User({ id: 3, name: 'This Three', roles: ['user', 'admin'] })); @@ -369,6 +371,31 @@ describe('Multi-values Hash tests', function () assert.deepEqual((await nodenamo.list().from(User).execute()).items.length, 6); }); + it('Update an item - return AllOld', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.AllOld).execute(); + + assert.deepEqual(result, originalUser); + }); + + it('Update an item - return AllNew', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newest Two'}).from(User).returning(ReturnValue.AllNew).execute(); + + assert.deepEqual(result, {...originalUser, name: 'Newest Two'}); + }); + + it('Update an item - return None', async () => + { + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.None).execute(); + + assert.isUndefined(result); + }); + it('On item', async () => { let user = await nodenamo.get(6).from(User).execute(); diff --git a/spec/acceptance/multiValuesRangeTest.spec.ts b/spec/acceptance/multiValuesRangeTest.spec.ts index c67f308..244eda4 100644 --- a/spec/acceptance/multiValuesRangeTest.spec.ts +++ b/spec/acceptance/multiValuesRangeTest.spec.ts @@ -2,7 +2,7 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable({name:'nodenamo_acceptance_multiValuesRangeTest'}) class User @@ -190,7 +190,9 @@ describe('Multi-values range tests', function () user.name = 'This Three'; user3['extra'] = 'invalid'; - await nodenamo.update(user).from(User).execute(); + let result = await nodenamo.update(user).from(User).execute(); + + assert.isUndefined(result); user = await nodenamo.get(3).from(User).execute(); assert.deepEqual(user, { id: 3, name: 'This Three', account: 2000, ranges: ['2016#3', 'true#3', 'Some Three#3'] }); @@ -229,6 +231,45 @@ describe('Multi-values range tests', function () assert.deepEqual(user, { id: 6, name: '', account: 3000, ranges: ['2020#6', 'true#6', 'Some Six#6'] }); }); + it('Update an item - return AllOld', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.AllOld).execute(); + + assert.deepEqual(result, originalUser); + }); + + it('Update an item - return AllNew', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Newest Two'}).from(User).returning(ReturnValue.AllNew).execute(); + + assert.deepEqual(result, {...originalUser, name: 'Newest Two'}); + }); + + it('Update an item - return None', async () => + { + let result = await nodenamo.update({id: 2, name: 'Newer Two'}).from(User).returning(ReturnValue.None).execute(); + + assert.isUndefined(result); + }); + + it('Update an item - with all combinations', async () => + { + let originalUser = await nodenamo.get(2).from(User).execute(); + + let result = await nodenamo.update({id: 2, name: 'Another Two'}) + .from(User) + .where('#account=:account', {'#account': 'account'}, {':account': originalUser.account}) + .returning(ReturnValue.AllOld) + .withVersionCheck() + .execute(); + + assert.deepEqual(result, originalUser); + }); + it('On item', async () => { let user = await nodenamo.get(6).from(User).execute(); diff --git a/spec/acceptance/tableVersioningTest.spec.ts b/spec/acceptance/tableVersioningTest.spec.ts index deff4cd..66d0631 100644 --- a/spec/acceptance/tableVersioningTest.spec.ts +++ b/spec/acceptance/tableVersioningTest.spec.ts @@ -2,7 +2,6 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { Reflector } from '../../src/reflector'; import { VersionError } from '../../src/errors/versionError'; @@ -85,7 +84,9 @@ describe('Table versioning tests', function () assert.deepEqual(user2, { id: 1, name: 'Some One', age: 16 }); assert.equal(Reflector.getObjectVersion(user2), 1); - await nodenamo.update({id: 1, name: 'I am first'}).from(User).execute(); + let result = await nodenamo.update({id: 1, name: 'I am first'}).from(User).execute(); + + assert.isUndefined(result); let user3 = await nodenamo.get(1).from(User).execute(); assert.deepEqual(user3, { id: 1, name: 'I am first', age: 16 }); diff --git a/spec/acceptance/urlSafeFirstAndLastEvaluatedKeys.spec.ts b/spec/acceptance/urlSafeFirstAndLastEvaluatedKeys.spec.ts index c17bd5b..c6173a3 100644 --- a/spec/acceptance/urlSafeFirstAndLastEvaluatedKeys.spec.ts +++ b/spec/acceptance/urlSafeFirstAndLastEvaluatedKeys.spec.ts @@ -2,7 +2,6 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; @DBTable({name:'nodenamo_acceptance_hashRangeWithUnicodeTest'}) class User diff --git a/spec/acceptance/versionTest.spec.ts b/spec/acceptance/versionTest.spec.ts index 046bd7e..4620be5 100644 --- a/spec/acceptance/versionTest.spec.ts +++ b/spec/acceptance/versionTest.spec.ts @@ -2,7 +2,6 @@ import {assert as assert} from 'chai'; import { DBTable, DBColumn } from '../../src'; import { NodeNamo } from '../../src/nodeNamo'; import Config from './config'; -import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; import { Reflector } from '../../src/reflector'; import { VersionError } from '../../src/errors/versionError'; @@ -73,7 +72,9 @@ describe('Version tests', function () assert.deepEqual(user2, { id: 1, name: 'Some One', age: 16 }); assert.equal(Reflector.getObjectVersion(user2), 1); - await nodenamo.update({id: 1, name: 'I am first', age: undefined}).from(User).withVersionCheck().execute(); + let result = await nodenamo.update({id: 1, name: 'I am first', age: undefined}).from(User).withVersionCheck().execute(); + + assert.isUndefined(result); let user3 = await nodenamo.get(1).from(User).execute(); assert.deepEqual(user3, { id: 1, name: 'I am first', age: 16 }); diff --git a/spec/unit/dynamodbManagerUpdate.spec.ts b/spec/unit/dynamodbManagerUpdate.spec.ts index 417cd17..834ec76 100644 --- a/spec/unit/dynamodbManagerUpdate.spec.ts +++ b/spec/unit/dynamodbManagerUpdate.spec.ts @@ -7,6 +7,7 @@ import {Const} from '../../src/const'; import { VersionError } from '../../src/errors/versionError'; import AggregateError from 'aggregate-error'; import { DynamoDBDocumentClient, GetCommand, GetCommandOutput, QueryCommand, QueryCommandOutput } from '@aws-sdk/lib-dynamodb'; +import { ReturnValue } from '../../src/interfaces/returnValue'; describe('DynamoDbManager.Update()', function () @@ -635,18 +636,74 @@ describe('DynamoDbManager.Update()', function () assert.isTrue(desiredObjectCreatedFromStronglyConsistentRead); }); - function setupStronglyConsistentRead(expectedItem:object) + [ + { + updatePayload: {name: 'New Two', created: "New Created"}, + returnValue: undefined, + expectedReturn: undefined, + }, + { + updatePayload: {name: 'New Two', created: "New Created"}, + returnValue: ReturnValue.None, + expectedReturn: undefined, + }, + { + updatePayload: {name: 'New Two', created: "New Created"}, + returnValue: ReturnValue.AllNew, + expectedReturn: {id:1, name:'New Two', created:'New Created', order:'original order'} + }, + { + updatePayload: {name: 'New Two', created: "New Created"}, + returnValue: ReturnValue.AllOld, + expectedReturn: {id:1, name:'original name', created:'original created', order:'original order'} + }, + ] + .forEach(test => + { + it(`update() - returning ${test.returnValue}`, async () => + { + @DBTable() + class Entity + { + @DBColumn({hash:true}) + id:number; + + @DBColumn() + name:string; + + @DBColumn({range:true}) + created:string; + + @DBColumn({range:true}) + order:string; + }; + + let consistentReadPayload = [{hash: 'entity#1', range: 'created', id:1, name:'original name', created:'original created', order:'original order'}]; + consistentReadPayload.push({...consistentReadPayload[0], ...test.updatePayload}); + setupStronglyConsistentRead(consistentReadPayload); + + let findResponse = getMockedQueryResponse({Items: [{hash: 'entity#1', range: 'created', id:1, name:'original name', created:'original created', order:'original order'}, {hash: 'entity#1', range: 'original order', id:1, name:'original name', created:'original created', order:'original order'}]}); + mockedClient.setup(q => q.send(It.is((p:QueryCommand) => !!p.input.TableName && p.input.IndexName === Const.IdIndexName && p.input.KeyConditionExpression === '#objid = :objid' && p.input.ExpressionAttributeNames?.['#objid'] === Const.IdColumn && p.input.ExpressionAttributeValues?.[':objid'] === 'entity#1'))).callback(()=>called=true).returns(()=>findResponse); + + let manager = new DynamoDbManager(mockedClient.object); + let result = await manager.update(Entity, 1, test.updatePayload, {returnValue: test.returnValue}, mockedTransaction.object); + + assert.isTrue(called); + assert.isTrue(desiredObjectCreatedFromStronglyConsistentRead); + + assert.deepEqual(result, test.expectedReturn); + }); + }); + + function setupStronglyConsistentRead(expectedItem:object|object[]) { - let stronglyConsistentResponse = getMockedGetResponse( - {Item:expectedItem} - ); mockedClient.setup(q => q.send(It.is((p:GetCommand) => !!p.input.TableName && p.input.Key?.[Const.HashColumn] === 'entity#1' && p.input.Key?.[Const.RangeColumn] === 'nodenamo' && p.input.ConsistentRead === true))) .callback(()=>desiredObjectCreatedFromStronglyConsistentRead=true) - .returns(()=>stronglyConsistentResponse); + .returns(()=>Array.isArray(expectedItem) ? getMockedGetResponse({Item:expectedItem.shift()}) : getMockedGetResponse({Item: expectedItem})); } }); diff --git a/spec/unit/queryUpdate.spec.ts b/spec/unit/queryUpdate.spec.ts index b477222..01ad34a 100644 --- a/spec/unit/queryUpdate.spec.ts +++ b/spec/unit/queryUpdate.spec.ts @@ -4,6 +4,7 @@ import { IMock, Mock } from 'typemoq'; import { DBTable } from '../../src/dbTable'; import { DBColumn } from '../../src/dbColumn'; import { Update } from '../../src/queries/update/update'; +import { ReturnValue } from '../../src/interfaces/returnValue'; @DBTable() class Entity { @@ -56,6 +57,80 @@ describe('Query.Update', function () assert.isTrue(called); }); + it('withVersionCheck() - with where', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + versionCheck: true + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .withVersionCheck(true) + .where('condition', {'name':'n'}, {'value':'v'}); + await update.execute(); + + assert.isTrue(called); + }); + + it('withVersionCheck() - with where and returning', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + versionCheck: true, + returnValue: ReturnValue.None + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .withVersionCheck(true) + .where('condition', {'name':'n'}, {'value':'v'}) + .returning(ReturnValue.None); + await update.execute(); + + assert.isTrue(called); + }); + + it('withVersionCheck() - with returning', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + versionCheck: true, + returnValue: ReturnValue.None + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .withVersionCheck(true) + .returning(ReturnValue.None); + await update.execute(); + + assert.isTrue(called); + }); + + it('withVersionCheck() - with returning and where', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + versionCheck: true, + returnValue: ReturnValue.None + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .withVersionCheck(true) + .returning(ReturnValue.None) + .where('condition', {'name':'n'}, {'value':'v'}); + await update.execute(); + + assert.isTrue(called); + }); + it('where()', async ()=> { mockedManager.setup(m => m.update(Entity, 1, {id:1}, { @@ -107,4 +182,144 @@ describe('Query.Update', function () assert.isTrue(called); }); + + it('where() - with a version check and returning', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + versionCheck: true, + returnValue: ReturnValue.AllNew + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .where('condition', {'name':'n'}, {'value':'v'}) + .withVersionCheck(true) + .returning(ReturnValue.AllNew); + await update.execute(); + + assert.isTrue(called); + }); + + it('where() - with returning', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + returnValue: ReturnValue.AllNew + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .where('condition', {'name':'n'}, {'value':'v'}) + .returning(ReturnValue.AllNew); + await update.execute(); + + assert.isTrue(called); + }); + it('where() - with returning and a version check', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + versionCheck: true, + returnValue: ReturnValue.AllNew + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .where('condition', {'name':'n'}, {'value':'v'}) + .returning(ReturnValue.AllNew) + .withVersionCheck(true); + await update.execute(); + + assert.isTrue(called); + }); + + it('returning()', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, {returnValue:ReturnValue.AllOld}, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .returning(ReturnValue.AllOld); + await update.execute(); + + assert.isTrue(called); + }); + + it('returning() - with version check', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, {returnValue:ReturnValue.AllOld, versionCheck: true}, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .returning(ReturnValue.AllOld) + .withVersionCheck(); + await update.execute(); + + assert.isTrue(called); + }); + + it('returning() - with version check and where', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + returnValue:ReturnValue.AllOld, + versionCheck: true, + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .returning(ReturnValue.AllOld) + .withVersionCheck() + .where('condition', {'name':'n'}, {'value':'v'}); + await update.execute(); + + assert.isTrue(called); + }); + + it('returning() - with where', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + returnValue:ReturnValue.AllOld, + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .returning(ReturnValue.AllOld) + .where('condition', {'name':'n'}, {'value':'v'}); + await update.execute(); + + assert.isTrue(called); + }); + + it('returning() - with where and version check', async ()=> + { + mockedManager.setup(m => m.update(Entity, 1, {id:1}, { + returnValue:ReturnValue.AllOld, + versionCheck: true, + conditionExpression:'condition', + expressionAttributeNames: {name: 'n'}, + expressionAttributeValues: {value: 'v'}, + }, undefined, true)).callback(()=>called=true); + + let update = new Update(mockedManager.object, {id:1}) + .from(Entity) + .returning(ReturnValue.AllOld) + .where('condition', {'name':'n'}, {'value':'v'}) + .withVersionCheck(); + await update.execute(); + + assert.isTrue(called); + }); }); \ No newline at end of file diff --git a/src/dbColumn.ts b/src/dbColumn.ts index 9ea7061..b08b1a6 100644 --- a/src/dbColumn.ts +++ b/src/dbColumn.ts @@ -1,8 +1,8 @@ import { Reflector } from "./reflector"; -export function DBColumn(params:{id?:boolean, name?:string, hash?:boolean|string, range?:boolean|string} = {}) +export function DBColumn(params:{id?:boolean, name?:string, hash?:boolean|string, range?:boolean|string} = {}): any { - return function(target: Object, propertyName: string): void + return function(target: Object, propertyName: string) { let value = params.name || propertyName; diff --git a/src/interfaces/iDynamodbManager.ts b/src/interfaces/iDynamodbManager.ts index 4af9661..4602a4e 100644 --- a/src/interfaces/iDynamodbManager.ts +++ b/src/interfaces/iDynamodbManager.ts @@ -1,5 +1,6 @@ import { DynamoDbTransaction } from '../managers/dynamodbTransaction'; import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'; +import { ReturnValue } from './returnValue'; export interface IDynamoDbManager { @@ -15,7 +16,7 @@ export interface IDynamoDbManager params?:{limit?:number, fetchSize?:number, indexName?:string, order?:number, exclusiveStartKey?:string, projections?:string[], stronglyConsistent?:boolean}) : Promise<{items:T[], lastEvaluatedKey: string, firstEvaluatedKey: string}>; - update(type:{new(...args: any[]):T}, id:string|number, obj:object, params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, transaction?:DynamoDbTransaction, autoCommit?:boolean): Promise; + update(type:{new(...args: any[]):T}, id:string|number, obj:object, params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean, returnValue?:ReturnValue}, transaction?:DynamoDbTransaction, autoCommit?:boolean): Promise; apply(type:{new(...args: any[]):T}, id:string|number, params:{updateExpression:{set?:string[], remove?:string[], add?:string[], delete?:string[]}, conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, transaction?:DynamoDbTransaction, autoCommit?:boolean): Promise; diff --git a/src/interfaces/returnValue.ts b/src/interfaces/returnValue.ts new file mode 100644 index 0000000..8ee9f04 --- /dev/null +++ b/src/interfaces/returnValue.ts @@ -0,0 +1,6 @@ +export enum ReturnValue +{ + None = "NONE", + AllOld = "ALL_OLD", + AllNew = "ALL_NEW", +} \ No newline at end of file diff --git a/src/managers/dynamodbManager.ts b/src/managers/dynamodbManager.ts index e89007b..a82de87 100644 --- a/src/managers/dynamodbManager.ts +++ b/src/managers/dynamodbManager.ts @@ -11,6 +11,7 @@ import { Key } from '../Key'; import AggregateError from 'aggregate-error'; import base64url from "base64url"; import { DynamoDBDocumentClient, QueryCommand, GetCommand } from '@aws-sdk/lib-dynamodb'; +import { ReturnValue } from '../interfaces/returnValue'; export class DynamoDbManager implements IDynamoDbManager { @@ -266,12 +267,13 @@ export class DynamoDbManager implements IDynamoDbManager } } - async update(type:{new(...args: any[]):T}, id:string|number, obj:object, params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, transaction?:DynamoDbTransaction, autoCommit:boolean = true) + async update(type:{new(...args: any[]):T}, id:string|number, obj:object, params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean, returnValue?:ReturnValue}, transaction?:DynamoDbTransaction, autoCommit:boolean = true): Promise { let instance = new type(); let tableName = Reflector.getTableName(instance); let tableVersioning = Reflector.getTableVersioning(instance); let versioningRequired = tableVersioning || (params && params.versionCheck); + let returnValue:T = undefined; //Calculate new representations let [rows, stronglyConsistentRow ] = await Promise.all([this.getById(id, type), this.getOneRepresendationById(type,id,{ stronglyConsistent:true})]); @@ -444,6 +446,17 @@ export class DynamoDbManager implements IDynamoDbManager throw e; } + + //Return + switch(params?.returnValue) + { + case ReturnValue.AllOld: + return EntityFactory.create(type, stronglyConsistentRow); + case ReturnValue.AllNew: + return await this.getOne(type, id, {stronglyConsistent:true}); + default: + return undefined; + } } async apply(type:{new(...args: any[]):T}, id:string|number, params:{updateExpression:{set?:string[], remove?:string[], add?:string[], delete?:string[]}, conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, transaction?:DynamoDbTransaction, autoCommit:boolean = true) diff --git a/src/managers/validatedDynamodbManager.ts b/src/managers/validatedDynamodbManager.ts index bfca5e0..ecee0ff 100644 --- a/src/managers/validatedDynamodbManager.ts +++ b/src/managers/validatedDynamodbManager.ts @@ -52,7 +52,7 @@ export class ValidatedDynamoDbManager implements IDynamoDbManager return await this.manager.find(type, keyParams, filterParams, params); } - async update(type:{new(...args: any[]):T}, id:string|number, obj:object, params?:{conditionExpression:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, transaction?:DynamoDbTransaction, autoCommit:boolean = true) + async update(type:{new(...args: any[]):T}, id:string|number, obj:object, params?:{conditionExpression:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, transaction?:DynamoDbTransaction, autoCommit:boolean = true): Promise { validateType(type); validateObject(type, obj); @@ -60,7 +60,7 @@ export class ValidatedDynamoDbManager implements IDynamoDbManager validateKeyConditionExpression(type, params); validateVersioning(type, params); - await this.manager.update(type, id, obj, params, transaction, autoCommit); + return await this.manager.update(type, id, obj, params, transaction, autoCommit); } async apply(type:{new(...args: any[]):T}, id:string|number, params:{updateExpression:{set?:string[], remove?:string[], add?:string[], delete?:string[]}, conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, transaction?:DynamoDbTransaction, autoCommit:boolean = true) diff --git a/src/queries/update/execute.ts b/src/queries/update/execute.ts index 5dfb63a..0502a20 100644 --- a/src/queries/update/execute.ts +++ b/src/queries/update/execute.ts @@ -3,15 +3,16 @@ import { Reflector } from "../../reflector"; import { Key } from '../../Key'; import { DynamoDbTransaction } from '../../managers/dynamodbTransaction'; import { Reexecutable } from '../Reexecutable'; +import { ReturnValue } from '../../interfaces/returnValue'; export class Execute extends Reexecutable { - constructor(private manager:IDynamoDbManager, private type:{new(...args: any[])}, private obj:object, private params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean}, private transaction?:DynamoDbTransaction) + constructor(private manager:IDynamoDbManager, private type:{new(...args: any[])}, private obj:object, private params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, versionCheck?:boolean, returnValue?:ReturnValue}, private transaction?:DynamoDbTransaction) { super() } - async execute(): Promise + async execute(): Promise { return await super.execute(async ()=> { diff --git a/src/queries/update/from.ts b/src/queries/update/from.ts index 55edb08..e98f8ab 100644 --- a/src/queries/update/from.ts +++ b/src/queries/update/from.ts @@ -4,6 +4,8 @@ import { Where } from "./where"; import { WithVersionCheck } from './withVersionCheck'; import ITransactionable from '../../interfaces/iTransactionable'; import { DynamoDbTransaction } from '../../managers/dynamodbTransaction'; +import { ReturnValue } from '../../interfaces/returnValue'; +import { Returning } from './returning'; export class From implements ITransactionable { @@ -22,7 +24,12 @@ export class From implements ITransactionable return new Where(this.manager, this.type, this.obj, {conditionExpression, expressionAttributeNames, expressionAttributeValues}) } - async execute(transaction?:DynamoDbTransaction): Promise + returning(returnValue:ReturnValue): Returning + { + return new Returning(this.manager, this.type, this.obj, undefined, returnValue) + } + + async execute(transaction?:DynamoDbTransaction): Promise { return await new Execute(this.manager, this.type, this.obj, undefined, transaction).execute(); } diff --git a/src/queries/update/returning.ts b/src/queries/update/returning.ts new file mode 100644 index 0000000..364fabb --- /dev/null +++ b/src/queries/update/returning.ts @@ -0,0 +1,31 @@ +import { IDynamoDbManager } from '../../interfaces/iDynamodbManager'; +import { Execute } from "./execute"; +import { WithVersionCheck } from './withVersionCheck'; +import ITransactionable from '../../interfaces/iTransactionable'; +import { DynamoDbTransaction } from '../../managers/dynamodbTransaction'; +import { ReturnValue } from '../../interfaces/returnValue'; +import { Where } from './where'; + +export class Returning implements ITransactionable +{ + constructor(private manager:IDynamoDbManager, private type:{new(...args: any[])}, private obj:object, private params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, returnValue?:ReturnValue, versionCheck?:boolean}, private returnValue?:ReturnValue) + { + this.params = this.params || {}; + this.params['returnValue'] = this.returnValue; + } + + where(conditionExpression:string, expressionAttributeNames?:object, expressionAttributeValues?:object): Where + { + return new Where(this.manager, this.type, this.obj, {...this.params, ...{conditionExpression, expressionAttributeNames, expressionAttributeValues}}) + } + + withVersionCheck(versionCheck:boolean = true): WithVersionCheck + { + return new WithVersionCheck(this.manager, this.type, this.obj, this.params, versionCheck); + } + + async execute(transaction?:DynamoDbTransaction): Promise + { + return await new Execute(this.manager, this.type, this.obj, this.params, transaction).execute(); + } +} \ No newline at end of file diff --git a/src/queries/update/where.ts b/src/queries/update/where.ts index f61705b..53d07aa 100644 --- a/src/queries/update/where.ts +++ b/src/queries/update/where.ts @@ -3,20 +3,27 @@ import { Execute } from "./execute"; import { WithVersionCheck } from './withVersionCheck'; import ITransactionable from '../../interfaces/iTransactionable'; import { DynamoDbTransaction } from '../../managers/dynamodbTransaction'; +import { ReturnValue } from '../../interfaces/returnValue'; +import { Returning } from './returning'; export class Where implements ITransactionable { - constructor(private manager:IDynamoDbManager, private type:{new(...args: any[])}, private obj:object, private params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object}) + constructor(private manager:IDynamoDbManager, private type:{new(...args: any[])}, private obj:object, private params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, returnValue?:ReturnValue, versionCheck?:boolean}) { } + returning(returnValue:ReturnValue): Returning + { + return new Returning(this.manager, this.type, this.obj, this.params, returnValue); + } + withVersionCheck(versionCheck:boolean = true): WithVersionCheck { return new WithVersionCheck(this.manager, this.type, this.obj, this.params, versionCheck); } - async execute(transaction?:DynamoDbTransaction): Promise + async execute(transaction?:DynamoDbTransaction): Promise { return await new Execute(this.manager, this.type, this.obj, this.params, transaction).execute(); } diff --git a/src/queries/update/withVersionCheck.ts b/src/queries/update/withVersionCheck.ts index 0f2a45c..56010e6 100644 --- a/src/queries/update/withVersionCheck.ts +++ b/src/queries/update/withVersionCheck.ts @@ -2,16 +2,29 @@ import { IDynamoDbManager } from '../../interfaces/iDynamodbManager'; import { Execute } from "./execute"; import ITransactionable from '../../interfaces/iTransactionable'; import { DynamoDbTransaction } from '../../managers/dynamodbTransaction'; +import { ReturnValue } from '../../interfaces/returnValue'; +import { Returning } from './returning'; +import { Where } from './where'; export class WithVersionCheck implements ITransactionable { - constructor(private manager:IDynamoDbManager, private type:{new(...args: any[])}, private obj:object, private params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object}, versionCheck?:boolean) + constructor(private manager:IDynamoDbManager, private type:{new(...args: any[])}, private obj:object, private params?:{conditionExpression?:string, expressionAttributeValues?:object, expressionAttributeNames?:object, returnValue?:ReturnValue, versionCheck?:boolean}, versionCheck?:boolean) { this.params = this.params || {}; this.params['versionCheck'] = versionCheck; } - async execute(transaction?:DynamoDbTransaction): Promise + returning(returnValue:ReturnValue): Returning + { + return new Returning(this.manager, this.type, this.obj, this.params, returnValue); + } + + where(conditionExpression:string, expressionAttributeNames?:object, expressionAttributeValues?:object): Where + { + return new Where(this.manager, this.type, this.obj, {...this.params, ...{conditionExpression, expressionAttributeNames, expressionAttributeValues}}) + } + + async execute(transaction?:DynamoDbTransaction): Promise { return await new Execute(this.manager, this.type, this.obj, this.params, transaction).execute(); }