Repository: typedorm/typedorm (ts)

Issue:
Support updating non-key and primary key attributes in the same request
Updating the primary key is done by removing the old entity and creating a new one, this is done via the document client's 
`TransactWrite` API, whereas the updates on the non-key attributes are performed using the document client's `Update API`.  

Because of these reasons, TypeDORM currently doesn't allow updating non-key and primary key attributes in a single request,  the goal of this issue is to track the progress on supporting key,non-key attribute updates.

Pull request description:
fix(core): allow updating non-key attributes with primary key attributes
closes #153, #167

Patch:
diff --git a/packages/common/src/error/index.ts b/packages/common/src/error/index.ts
index de762d3..5b3cab0 100644
--- a/packages/common/src/error/index.ts
+++ b/packages/common/src/error/index.ts
@@ -17,6 +17,5 @@ export * from './no-such-entity-exists-error';
 export * from './missing-required-entity-physical-name-error';
 export * from './duplicate-entity-physical-name-error';
 export * from './invalid-unique-attribute-update-error';
-export * from './invalid-primary-key-attributes-update-error';
 export * from './invalid-parallel-scan-limit-option-error';
 export * from './invalid-dynamic-update-attribute-value';
diff --git a/packages/common/src/error/invalid-primary-key-attributes-update-error.ts b/packages/common/src/error/invalid-primary-key-attributes-update-error.ts
deleted file mode 100644
index 97ce9cb..0000000
--- a/packages/common/src/error/invalid-primary-key-attributes-update-error.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export class InvalidPrimaryKeyAttributesUpdateError extends Error {
-  name = 'InvalidPrimaryKeyAttributesUpdateError';
-
-  constructor(
-    affectedPrimaryKeyAttributes: Record<string, string>,
-    nonKeyAttributes: string[]
-  ) {
-    super();
-    this.message = `Primary key attribute and non primary key attributes can not be
-    updated together. When updating attributes that references a primary key, it can not have
-    additional non key attributes.
-    \n Affected Primary key attributes: ${JSON.stringify(
-      affectedPrimaryKeyAttributes
-    )}
-    \n Tried to update non-key attributes: ${JSON.stringify(nonKeyAttributes)}`;
-  }
-}
diff --git a/packages/core/src/classes/transformer/document-client-request-transformer.ts b/packages/core/src/classes/transformer/document-client-request-transformer.ts
index c62c5e5..370cf0c 100644
--- a/packages/core/src/classes/transformer/document-client-request-transformer.ts
+++ b/packages/core/src/classes/transformer/document-client-request-transformer.ts
@@ -12,7 +12,6 @@ import {
   InvalidFilterInputError,
   InvalidSelectInputError,
   InvalidUniqueAttributeUpdateError,
-  InvalidPrimaryKeyAttributesUpdateError,
   InvalidDynamicUpdateAttributeValueError,
 } from '@typedorm/common';
 import {DynamoDB} from 'aws-sdk';
@@ -499,13 +498,6 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
 
     // validate primary key attributes
     if (!isEmptyObject(affectedPrimaryKeyAttributes)) {
-      const primaryKeyReferencedAttributes = this.connection.getPrimaryKeyAttributeInterpolationsForEntity(
-        entityClass
-      );
-      const nonKeyAttributesToUpdate = Object.keys(body).filter(
-        attr => !primaryKeyReferencedAttributes.includes(attr)
-      );
-
       // updates are not allowed for attributes that unique and also references primary key.
       if (uniqueAttributesToUpdate.length) {
         throw new InvalidUniqueAttributeUpdateError(
@@ -513,14 +505,6 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
           uniqueAttributesToUpdate.map(attr => attr.name)
         );
       }
-
-      // primary key and non key attributes can not be updated together
-      if (nonKeyAttributesToUpdate.length) {
-        throw new InvalidPrimaryKeyAttributesUpdateError(
-          affectedPrimaryKeyAttributes!,
-          nonKeyAttributesToUpdate
-        );
-      }
     }
 
     /**


Test patch:
diff --git a/__test__/github-issues/152/issue-152.spec.ts b/__test__/github-issues/152/issue-152.spec.ts
index 0fad928..f388574 100644
--- a/__test__/github-issues/152/issue-152.spec.ts
+++ b/__test__/github-issues/152/issue-152.spec.ts
@@ -8,6 +8,8 @@ let entityManager: EntityManager;
 
 const dcMock = {
   update: jest.fn(),
+  get: jest.fn(),
+  transactWrite: jest.fn(),
 };
 beforeEach(() => {
   const connection = createTestConnection({
@@ -52,3 +54,50 @@ test('allows updating non-key attributes', async () => {
   });
   expect(updateResponse).toBeDefined();
 });
+
+test('allows updating key only attributes', async () => {
+  dcMock.update.mockReturnValue({promise: () => ({})});
+  dcMock.get.mockReturnValue({promise: () => ({})});
+  dcMock.transactWrite.mockReturnValue({
+    on: jest.fn(),
+    send: jest.fn().mockImplementation(cb => {
+      cb(null, {
+        ConsumedCapacity: [{}],
+        ItemCollectionMetrics: [{}],
+      });
+    }),
+  });
+
+  await entityManager.update(
+    TestEntity,
+    {
+      id: '1',
+      tenant: 'NEW_TENANT',
+    },
+    {
+      id: '2',
+      tenant: 'NEW_TENANT',
+      status: true,
+    }
+  );
+
+  expect(dcMock.transactWrite).toHaveBeenCalledWith({
+    TransactItems: [
+      {
+        Put: {
+          Item: {
+            GSI1PK: 'USER#TENANT#NEW_TENANT#STATUS#true',
+            GSI1SK: 'USER#TENANT#NEW_TENANT#STATUS#true',
+            PK: 'USER#2#TENANT#NEW_TENANT',
+            SK: 'USER#2#TENANT#NEW_TENANT',
+            id: '2',
+            status: true,
+            tenant: 'NEW_TENANT',
+          },
+          ReturnValues: 'ALL_NEW',
+          TableName: 'test-table',
+        },
+      },
+    ],
+  });
+});
diff --git a/packages/core/src/classes/transformer/__test__/dc-request-transformer.spec.ts b/packages/core/src/classes/transformer/__test__/dc-request-transformer.spec.ts
index 262d474..3cb8b5f 100644
--- a/packages/core/src/classes/transformer/__test__/dc-request-transformer.spec.ts
+++ b/packages/core/src/classes/transformer/__test__/dc-request-transformer.spec.ts
@@ -1,7 +1,7 @@
+import {LazyTransactionWriteItemListLoader} from './../is-lazy-transaction-write-item-list-loader';
 import {
   Attribute,
   Entity,
-  InvalidPrimaryKeyAttributesUpdateError,
   InvalidUniqueAttributeUpdateError,
   QUERY_ORDER,
 } from '@typedorm/common';
@@ -935,21 +935,63 @@ test('throws when trying to update primary key attribute and unique attribute in
   expect(updateItem).toThrow(InvalidUniqueAttributeUpdateError);
 });
 
-test('throws when trying to update primary key attribute and non key attribute in the same request', () => {
-  const updateItem = () =>
-    transformer.toDynamoUpdateItem<Photo, PhotoPrimaryKey>(
-      Photo,
+test('allows updating primary key attribute and non key attribute in the same request', () => {
+  const updateItem = transformer.toDynamoUpdateItem<Photo, PhotoPrimaryKey>(
+    Photo,
+    {
+      category: CATEGORY.PETS,
+      id: 1,
+    },
+    {
+      id: 2,
+      createdAt: moment(),
+      name: 'new name',
+    }
+  );
+
+  expect(updateItem).toEqual({
+    entityClass: Photo,
+    lazyLoadTransactionWriteItems: expect.any(Function),
+    primaryKeyAttributes: {
+      category: 'PETS',
+      id: 1,
+    },
+  });
+
+  expect(
+    (updateItem as LazyTransactionWriteItemListLoader).lazyLoadTransactionWriteItems(
       {
-        category: CATEGORY.PETS,
         id: 1,
-      },
-      {
-        id: 2,
-        createdAt: moment(),
+        category: CATEGORY.KIDS,
+        name: 'old name',
       }
-    );
-
-  expect(updateItem).toThrow(InvalidPrimaryKeyAttributesUpdateError);
+    )
+  ).toEqual([
+    {
+      Put: {
+        Item: {
+          GSI1PK: 'PHOTO#2',
+          SK: 'PHOTO#2',
+          category: 'KIDS',
+          createdAt: '2021-06-01',
+          id: 2,
+          name: 'new name',
+          updatedAt: '1622530750',
+        },
+        ReturnValues: 'ALL_NEW',
+        TableName: 'test-table',
+      },
+    },
+    {
+      Delete: {
+        Key: {
+          PK: 'PHOTO#KIDS',
+          SK: 'PHOTO#1',
+        },
+        TableName: 'test-table',
+      },
+    },
+  ]);
 });
 
 test('transforms update item request with complex condition input', () => {