Repository: typedorm/typedorm (ts)

Issue:
Attributes referenced in secondary indexes and primary key are not correctly updated 
The class definition
```
import 'reflect-metadata';
import {
    Attribute,
    AutoGenerateAttribute,
    AUTO_GENERATE_ATTRIBUTE_STRATEGY,
    Entity,
    INDEX_TYPE } from '@typedorm/common';
import { ENTITY_STATE, IEntityState } from './EntityState';

export enum ARTICLE_STAGE {
    INITIALIZED = 1001,
    INBOUND = 1002,
    PREPARE_FOR_PHOTOSHOOT = 1003,
    PHOTOSHOOTING = 1004,
    OUTBOUND = 1005,
    NONE = 1006
}

/**
 * KSUID: string
 */
export interface IArticlePrimaryKey {
    KSUID: string;
}

export interface IArticleGSI1 {
    ContainerId: string;
    KSUID?: string;
}

export interface IArticleGSI2 {
    ContainerId: string;
    KSUID?: string;
}

export interface IArticleGSI3 {
    TransportUnitId: string;
    KSUID?: string;
}

export interface IArticleGSI4 {
    IsoTimeBased: string;
    KSUID?: string;
}

/**
 * Gtin: string
 */
export interface IArticleGSI5 {
    Gtin: string;
}

export interface IArticleLSI1 {
    UpdatedAt: string;
}

export interface IArticleLSI2 {
    Stage: ARTICLE_STAGE;
}

export interface IArticleLSI3 {
    TotalAuxImage: number;
}

export interface IArticleLSI4 {
    TotalRequiredImage: number;
}

@Entity({
    name: 'article',
    primaryKey: {
      partitionKey: 'ARTICLE#ARTICLE_KSUID#{{KSUID}}',
      sortKey: 'METADATA#ARTICLE#{{KSUID}}',
    },
    indexes: {
        GSI1: {
            type: INDEX_TYPE.GSI,
            partitionKey: 'CONTAINER#CONTAINER_KSUID#{{ContainerId}}',// test with gsi1 add {{KSUID}}
            sortKey: 'ARTICLE#{{KSUID}}',
        },
        GSI2: {
            type: INDEX_TYPE.GSI,
            partitionKey: 'TRANSPORT_UNIT#TRANSPORT_UNIT_KSUID#{{TransportUnitId}}',
            sortKey: 'ARTICLE#{{KSUID}}',
        },
        GSI3: {
            type: INDEX_TYPE.GSI,
            partitionKey: 'CONTAINER#{{ContainerId}}#ARTICLE#{{KSUID}}',
            sortKey: 'ARTICLE#{{KSUID}}',
        },
        GSI4: {
            type: INDEX_TYPE.GSI,
            partitionKey: 'ARTICLE#{{KSUID}}',
            sortKey: 'TIME_BASED#ARTICLE#{{IsoTimeBased}}'
        },
        GSI5: {
            type: INDEX_TYPE.GSI,
            partitionKey: 'ARTICLE#GTIN',
            sortKey: 'ARTICLE#GTIN#{{Gtin}}'
        },
        LSI1: {
            sortKey: 'ARTICLE#UPDATED_AT#{{UpdatedAt}}',
            type: INDEX_TYPE.LSI
        },
        LSI2: {
            sortKey: 'ARTICLE#STAGE#{{Stage}}',
            type: INDEX_TYPE.LSI
        },
        LSI3:  {
            sortKey: 'ARTICLE#TOTAL_AUX_IMAGE#{{TotalAuxImage}}',
            type: INDEX_TYPE.LSI
        },
        LSI4: {
            sortKey: 'ARTICLE#TOTAL_REQUIRED_IMAGE#{{TotalRequiredImage}}',
            type: INDEX_TYPE.LSI
        }
    }
})
export class Article implements IArticlePrimaryKey, IArticleGSI1, IArticleGSI2,
    IArticleGSI3, IArticleGSI4, IArticleGSI5, IArticleLSI1, IArticleLSI2, IArticleLSI3, IArticleLSI4, IEntityState{
    /**
     * Article's ID, Autogenerated with KSUID strategy
     */
    @AutoGenerateAttribute({ strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY.KSUID })
    KSUID: string;

    @AutoGenerateAttribute({
      strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY.ISO_DATE,
      // This tells TypeDORM to auto update this attribute for each update operation on entity
      autoUpdate: true
    })
    UpdatedAt: string;

    @AutoGenerateAttribute({ strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY.ISO_DATE })
    CreatedAt: string;

    @Attribute()
    TransportUnitId: string;

    @Attribute()
    ContainerId: string;

    @Attribute()
    TotalAuxImage: number;

    @Attribute()
    TotalRequiredImage: number;

    @Attribute({ isEnum: true, default: ARTICLE_STAGE.INITIALIZED })
    Stage: ARTICLE_STAGE;

    @Attribute()
    Gtin: string;

    @Attribute()
    Channel: string;

    @Attribute()
    ScanAt: string;

    @Attribute()
    IsRequiredAuxImage: boolean;

    @Attribute()
    Tags: string;

    @Attribute()
    Description: string;

    @Attribute()
    IsoTimeBased: string; // format datetime, eg 2020-11-12T20:04:00 for versioning feature

    @Attribute()
    IssuedDate: string;

    @Attribute()
    Color: string;

    @Attribute()
    Size: string;

    @Attribute({ isEnum: true, default: ENTITY_STATE.ACTIVE })
    State: ENTITY_STATE;

    @Attribute()
    Name: string;

    @Attribute()
    BrandName: string;

    @Attribute()
    ConsumptionTopic: number;
}
```
- Step 1: Create new article without ContainerId => OK
- Step 2: Assign article to specific container by update article with ContainerId => got error: "KSUID" was referenced in CONTAINER#{{ContainerId}}#ARTICLE#{{KSUID}} but it's value could not be resolved.

```Update script
await getEntityManager().update<Article, IArticlePrimaryKey>(Article, { KSUID: articleKSUID }, {
            ContainerId: containerKSUID
        });
```

Pull request:
fix(core): correctly update primary key and secondary indexes
closes #195

Patch:
diff --git a/packages/core/src/classes/transformer/base-transformer.ts b/packages/core/src/classes/transformer/base-transformer.ts
index f782960..7aaafa1 100644
--- a/packages/core/src/classes/transformer/base-transformer.ts
+++ b/packages/core/src/classes/transformer/base-transformer.ts
@@ -210,7 +210,10 @@ export abstract class BaseTransformer {
     entityClass: EntityTarget<Entity>,
     attributes: Record<string, any>,
     attributesTypeMetadata: Record<string, 'static' | 'dynamic'>,
-    options?: {nestedKeySeparator: string}
+    options?: {
+      nestedKeySeparator?: string;
+      additionalAttributesDict?: Record<string, any>;
+    }
   ) {
     const nestedKeySeparator = options?.nestedKeySeparator ?? '.';
     const {
@@ -259,7 +262,7 @@ export abstract class BaseTransformer {
               try {
                 const parsedIndex = parseKey(
                   currIndex.attributes[interpolationKey],
-                  attributes
+                  {...options?.additionalAttributesDict, ...attributes}
                 );
                 acc[interpolationKey] = parsedIndex;
               } catch (err) {
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..53e82fd 100644
--- a/packages/core/src/classes/transformer/document-client-request-transformer.ts
+++ b/packages/core/src/classes/transformer/document-client-request-transformer.ts
@@ -383,6 +383,12 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
     });
     const tableName = metadata.table.name;
 
+    // FIXME: correctly apply decorated transformations on the primary key attributes
+    // apply class transformation on attributes before further processing
+    // primaryKeyAttributes = this.applyClassTransformerFormations(
+    //   primaryKeyAttributes
+    // ) as PrimaryKey;
+
     const parsedPrimaryKey = this.getParsedPrimaryKey(
       metadata.table,
       metadata.schema.primaryKey,
@@ -532,6 +538,9 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
       staticOrDynamicUpdateAttributesWithMetadata.typeMetadata,
       {
         nestedKeySeparator,
+        additionalAttributesDict: {
+          ...primaryKeyAttributes,
+        },
       }
     );
 


Test patch:
diff --git a/__test__/github-issues/195/issue-195.spec.ts b/__test__/github-issues/195/issue-195.spec.ts
new file mode 100644
index 0000000..c549453
--- /dev/null
+++ b/__test__/github-issues/195/issue-195.spec.ts
@@ -0,0 +1,66 @@
+import {Article, IArticlePrimaryKey} from './specific-event-entity';
+import 'reflect-metadata';
+
+import {createTestConnection, resetTestConnection} from '@typedorm/testing';
+import {EntityManager} from '@typedorm/core';
+
+jest.useFakeTimers('modern').setSystemTime(new Date('2020-10-10'));
+
+let entityManager: EntityManager;
+
+const dcMock = {
+  update: jest.fn(),
+};
+beforeEach(() => {
+  const connection = createTestConnection({
+    entities: [Article],
+    documentClient: dcMock,
+  });
+  entityManager = new EntityManager(connection);
+});
+
+afterEach(() => {
+  resetTestConnection();
+});
+
+test('allows updating article with primary key', async () => {
+  dcMock.update.mockReturnValue({
+    promise: () => ({}),
+  });
+
+  await entityManager.update<Article, IArticlePrimaryKey>(
+    Article,
+    {
+      KSUID: '12341',
+    },
+    {
+      ContainerId: 'container-1',
+    }
+  );
+
+  // batch write is called with correctly transformed dynamo entity
+  expect(dcMock.update).toHaveBeenCalledWith({
+    ExpressionAttributeNames: {
+      '#UE_ContainerId': 'ContainerId',
+      '#UE_GSI1PK': 'GSI1PK',
+      '#UE_GSI3PK': 'GSI3PK',
+      '#UE_LSI1SK': 'LSI1SK',
+      '#UE_UpdatedAt': 'UpdatedAt',
+    },
+    ExpressionAttributeValues: {
+      ':UE_ContainerId': 'container-1',
+      ':UE_GSI1PK': 'CONTAINER#CONTAINER_KSUID#container-1',
+      ':UE_GSI3PK': 'CONTAINER#container-1#ARTICLE#12341',
+      ':UE_LSI1SK': 'ARTICLE#UPDATED_AT#2020-10-10T00:00:00.000Z',
+      ':UE_UpdatedAt': '2020-10-10T00:00:00.000Z',
+    },
+    Key: {
+      PK: 'ARTICLE#ARTICLE_KSUID#12341',
+      SK: 'METADATA#ARTICLE#12341',
+    },
+    ReturnValues: 'ALL_NEW',
+    TableName: 'article',
+    UpdateExpression:
+      'SET #UE_ContainerId = :UE_ContainerId, #UE_UpdatedAt = :UE_UpdatedAt, #UE_GSI1PK = :UE_GSI1PK, #UE_GSI3PK = :UE_GSI3PK, #UE_LSI1SK = :UE_LSI1SK',
+  });
+});
diff --git a/__test__/github-issues/195/specific-event-entity.ts b/__test__/github-issues/195/specific-event-entity.ts
new file mode 100644
index 0000000..4c9c920
--- /dev/null
+++ b/__test__/github-issues/195/specific-event-entity.ts
@@ -0,0 +1,209 @@
+import {testTable} from './test-table';
+import {
+  Attribute,
+  AutoGenerateAttribute,
+  AUTO_GENERATE_ATTRIBUTE_STRATEGY,
+  Entity,
+  INDEX_TYPE,
+} from '@typedorm/common';
+
+enum ENTITY_STATE {
+  ACTIVE = 1,
+}
+
+export enum ARTICLE_STAGE {
+  INITIALIZED = 1001,
+  INBOUND = 1002,
+  PREPARE_FOR_PHOTOSHOOT = 1003,
+  PHOTOSHOOTING = 1004,
+  OUTBOUND = 1005,
+  NONE = 1006,
+}
+
+/**
+ * KSUID: string
+ */
+export interface IArticlePrimaryKey {
+  KSUID: string;
+}
+
+export interface IArticleGSI1 {
+  ContainerId: string;
+  KSUID?: string;
+}
+
+export interface IArticleGSI2 {
+  ContainerId: string;
+  KSUID?: string;
+}
+
+export interface IArticleGSI3 {
+  TransportUnitId: string;
+  KSUID?: string;
+}
+
+export interface IArticleGSI4 {
+  IsoTimeBased: string;
+  KSUID?: string;
+}
+
+/**
+ * Gtin: string
+ */
+export interface IArticleGSI5 {
+  Gtin: string;
+}
+
+export interface IArticleLSI1 {
+  UpdatedAt: string;
+}
+
+export interface IArticleLSI2 {
+  Stage: ARTICLE_STAGE;
+}
+
+export interface IArticleLSI3 {
+  TotalAuxImage: number;
+}
+
+export interface IArticleLSI4 {
+  TotalRequiredImage: number;
+}
+
+@Entity({
+  name: 'article',
+  table: testTable,
+  primaryKey: {
+    partitionKey: 'ARTICLE#ARTICLE_KSUID#{{KSUID}}',
+    sortKey: 'METADATA#ARTICLE#{{KSUID}}',
+  },
+  indexes: {
+    GSI1: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'CONTAINER#CONTAINER_KSUID#{{ContainerId}}', // test with gsi1 add {{KSUID}}
+      sortKey: 'ARTICLE#{{KSUID}}',
+    },
+    GSI2: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'TRANSPORT_UNIT#TRANSPORT_UNIT_KSUID#{{TransportUnitId}}',
+      sortKey: 'ARTICLE#{{KSUID}}',
+    },
+    GSI3: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'CONTAINER#{{ContainerId}}#ARTICLE#{{KSUID}}',
+      sortKey: 'ARTICLE#{{KSUID}}',
+      isSparse: true,
+    },
+    GSI4: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'ARTICLE#{{KSUID}}',
+      sortKey: 'TIME_BASED#ARTICLE#{{IsoTimeBased}}',
+    },
+    GSI5: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'ARTICLE#GTIN',
+      sortKey: 'ARTICLE#GTIN#{{Gtin}}',
+    },
+    LSI1: {
+      sortKey: 'ARTICLE#UPDATED_AT#{{UpdatedAt}}',
+      type: INDEX_TYPE.LSI,
+    },
+    LSI2: {
+      sortKey: 'ARTICLE#STAGE#{{Stage}}',
+      type: INDEX_TYPE.LSI,
+    },
+    LSI3: {
+      sortKey: 'ARTICLE#TOTAL_AUX_IMAGE#{{TotalAuxImage}}',
+      type: INDEX_TYPE.LSI,
+    },
+    LSI4: {
+      sortKey: 'ARTICLE#TOTAL_REQUIRED_IMAGE#{{TotalRequiredImage}}',
+      type: INDEX_TYPE.LSI,
+    },
+  },
+})
+export class Article
+  implements
+    IArticlePrimaryKey,
+    IArticleGSI1,
+    IArticleGSI2,
+    IArticleGSI3,
+    IArticleGSI4,
+    IArticleGSI5,
+    IArticleLSI1,
+    IArticleLSI2,
+    IArticleLSI3,
+    IArticleLSI4 {
+  /**
+   * Article's ID, Autogenerated with KSUID strategy
+   */
+  @AutoGenerateAttribute({strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY.KSUID})
+  KSUID: string;
+
+  @AutoGenerateAttribute({
+    strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY.ISO_DATE,
+    // This tells TypeDORM to auto update this attribute for each update operation on entity
+    autoUpdate: true,
+  })
+  UpdatedAt: string;
+
+  @AutoGenerateAttribute({strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY.ISO_DATE})
+  CreatedAt: string;
+
+  @Attribute()
+  TransportUnitId: string;
+
+  @Attribute()
+  ContainerId: string;
+
+  @Attribute()
+  TotalAuxImage: number;
+
+  @Attribute()
+  TotalRequiredImage: number;
+
+  @Attribute({isEnum: true, default: ARTICLE_STAGE.INITIALIZED})
+  Stage: ARTICLE_STAGE;
+
+  @Attribute()
+  Gtin: string;
+
+  @Attribute()
+  Channel: string;
+
+  @Attribute()
+  ScanAt: string;
+
+  @Attribute()
+  IsRequiredAuxImage: boolean;
+
+  @Attribute()
+  Tags: string;
+
+  @Attribute()
+  Description: string;
+
+  @Attribute()
+  IsoTimeBased: string; // format datetime, eg 2020-11-12T20:04:00 for versioning feature
+
+  @Attribute()
+  IssuedDate: string;
+
+  @Attribute()
+  Color: string;
+
+  @Attribute()
+  Size: string;
+
+  @Attribute({isEnum: true, default: ENTITY_STATE.ACTIVE})
+  State: ENTITY_STATE;
+
+  @Attribute()
+  Name: string;
+
+  @Attribute()
+  BrandName: string;
+
+  @Attribute()
+  ConsumptionTopic: number;
+}
diff --git a/__test__/github-issues/195/test-table.ts b/__test__/github-issues/195/test-table.ts
new file mode 100644
index 0000000..486c94c
--- /dev/null
+++ b/__test__/github-issues/195/test-table.ts
@@ -0,0 +1,50 @@
+import {Table, INDEX_TYPE} from '@typedorm/common';
+
+export const testTable = new Table({
+  name: 'article',
+  partitionKey: 'PK',
+  sortKey: 'SK',
+  indexes: {
+    GSI1: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'GSI1PK',
+      sortKey: 'GSI1SK',
+    },
+    GSI2: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'GSI2PK',
+      sortKey: 'GSI2SK',
+    },
+    GSI3: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'GSI3PK',
+      sortKey: 'GSI3SK',
+    },
+    GSI4: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'GSI4PK',
+      sortKey: 'GSI4SK',
+    },
+    GSI5: {
+      type: INDEX_TYPE.GSI,
+      partitionKey: 'GSI5PK',
+      sortKey: 'GSI5SK',
+    },
+    LSI1: {
+      type: INDEX_TYPE.LSI,
+      sortKey: 'LSI1SK',
+    },
+    LSI2: {
+      type: INDEX_TYPE.LSI,
+      sortKey: 'LSI2SK',
+    },
+    LSI3: {
+      type: INDEX_TYPE.LSI,
+      sortKey: 'LSI3SK',
+    },
+    LSI4: {
+      type: INDEX_TYPE.LSI,
+      sortKey: 'LSI4SK',
+    },
+  },
+});