Repository: typedorm/typedorm (ts)

Issue:
Update type definitions for attribute aliasing supports on primary key
The primary key now supports specifying attribute aliases to help in scenarios where the type of primary key (partition key or sort key) is required to be of non-string type.

However, the type definitions of it still refer to the previous implementation of primary key structure and prevent users from being able to specify attribute aliases.

- [x] Update type definition of the primary key to supporting attribute aliasing
- [x] Add primary key attribute aliasing example in the guide 

Pull request:
[#194] Update type definition for attribute aliasing
closes #194 

Patch:
diff --git a/docs/how-to-recipes.md b/docs/how-to-recipes.md
index abc89cf..a21ef2a 100644
--- a/docs/how-to-recipes.md
+++ b/docs/how-to-recipes.md
@@ -5,7 +5,8 @@ This page will walk you through some of the unique recipes to enhance your devel
 - [How to recipes](#how-to-recipes)
   - [Entity configuration](#entity-configuration)
     - [Define a simple entity](#define-a-simple-entity)
-    - [Define an entity with index](#define-an-entity-with-index)
+    - [Define an entity with attribute aliases in primary key](#define-an-entity-with-attribute-aliases-in-primary-key)
+    - [Define an entity with attribute aliases in index](#define-an-entity-with-attribute-aliases-in-index)
   - [Attribute Transformation](#attribute-transformation)
     - [Using TransformToDynamo](#using-transformtodynamo)
     - [Using TransformFromDynamo](#using-transformfromdynamo)
@@ -58,7 +59,30 @@ class User {
 }
 ```
 
-### Define an entity with index
+### Define an entity with attribute aliases in primary key
+
+```Typescript
+@Entity<User>(
+  name: 'user', // name of the entity that will be added to each item as an attribute
+  // primary key
+  primaryKey: {
+    partitionKey: 'USER#{{id}}',
+    sortKey: {
+      alias: 'age' // <- this tells TypeDORM to auto infer "type" and "value" for partition key from age attribute.
+    },
+  }
+)
+class User {
+  @Attribute()
+  id: string  // <- this attribute is required as it is referenced by primary key
+
+  @Attribute()
+  age: number
+
+}
+```
+
+### Define an entity with attribute aliases in index
 
 ```Typescript
 @Entity<User>(
diff --git a/packages/common/src/decorators/attribute.decorator.ts b/packages/common/src/decorators/attribute.decorator.ts
index e350907..2033ef9 100644
--- a/packages/common/src/decorators/attribute.decorator.ts
+++ b/packages/common/src/decorators/attribute.decorator.ts
@@ -7,7 +7,9 @@ import {
 } from '../metadata/metadata-storage';
 import {MissingReflectMetadataError} from '../error';
 
-export type AttributeOptionsUniqueType = boolean | PrimaryKey;
+export type AttributeOptionsUniqueType<Entity = any> =
+  | boolean
+  | PrimaryKey<Entity>;
 
 export interface AttributeOptions<Entity> {
   /**
@@ -16,7 +18,7 @@ export interface AttributeOptions<Entity> {
    * @default false
    *
    */
-  unique?: AttributeOptionsUniqueType;
+  unique?: AttributeOptionsUniqueType<Entity>;
   /**
    * Mark property as enum
    * @required when property of type enum is referenced in key
diff --git a/packages/common/src/metadata/metadata-storage.ts b/packages/common/src/metadata/metadata-storage.ts
index 6960e5f..32e4454 100644
--- a/packages/common/src/metadata/metadata-storage.ts
+++ b/packages/common/src/metadata/metadata-storage.ts
@@ -2,20 +2,23 @@ import {AUTO_GENERATE_ATTRIBUTE_STRATEGY, EntityTarget} from '@typedorm/common';
 import {Table} from '../table';
 import {AttributeOptionsUniqueType} from '../decorators/attribute.decorator';
 import {ScalarType} from '../helpers/scalar-type';
-import {IndexOptionsWithAlias} from '../index-options';
+import {EntityAliasOrString, IndexOptionsWithAlias} from '../index-options';
 
 export const IsAutoGenerateAttributeRawMetadataOptions = (
   attr: any
 ): attr is AutoGenerateAttributeRawMetadataOptions => !!attr.strategy;
 
-export type PrimaryKey = SimplePrimaryKey | CompositePrimaryKey;
-export type SimplePrimaryKey = {
-  partitionKey: string;
+export type PrimaryKey<Entity = any> =
+  | SimplePrimaryKey<Entity>
+  | CompositePrimaryKey<Entity>;
+
+export type SimplePrimaryKey<Entity = any> = {
+  partitionKey: EntityAliasOrString<Entity>;
 };
 
-export type CompositePrimaryKey = {
-  partitionKey: string;
-  sortKey: string;
+export type CompositePrimaryKey<Entity = any> = {
+  partitionKey: EntityAliasOrString<Entity>;
+  sortKey: EntityAliasOrString<Entity>;
 };
 
 export type Indexes<Entity = any> = {
@@ -25,7 +28,7 @@ export type Indexes<Entity = any> = {
 export interface EntityRawMetadataOptions<Entity = any> {
   name: string;
   target: EntityTarget<Entity>;
-  primaryKey: PrimaryKey;
+  primaryKey: PrimaryKey<Entity>;
   indexes?: Indexes<Entity>;
   table?: Table;
 }
diff --git a/packages/core/src/helpers/build-primary-key-schema.ts b/packages/core/src/helpers/build-primary-key-schema.ts
index c32c768..f5cb622 100644
--- a/packages/core/src/helpers/build-primary-key-schema.ts
+++ b/packages/core/src/helpers/build-primary-key-schema.ts
@@ -1,4 +1,9 @@
-import {Table, PrimaryKey, IsCompositePrimaryKey} from '@typedorm/common';
+import {
+  Table,
+  PrimaryKey,
+  IsCompositePrimaryKey,
+  EntityAliasOrString,
+} from '@typedorm/common';
 import {getInterpolatedKeys} from './get-interpolated-keys';
 import {validateKey} from './validate-key';
 
@@ -11,7 +16,7 @@ export function buildPrimaryKeySchema({
   primaryKey: PrimaryKey;
   attributes: {[key: string]: string};
 }): {
-  attributes: {[key: string]: string};
+  attributes: {[key: string]: EntityAliasOrString<any>};
   metadata: {
     _interpolations: {[key: string]: any};
   };


Test patch:
diff --git a/__test__/github-issues/194/issue-194.spec.ts b/__test__/github-issues/194/issue-194.spec.ts
new file mode 100644
index 0000000..c538812
--- /dev/null
+++ b/__test__/github-issues/194/issue-194.spec.ts
@@ -0,0 +1,88 @@
+import 'reflect-metadata';
+
+import {createTestConnection, resetTestConnection} from '@typedorm/testing';
+import {EntityManager} from '@typedorm/core';
+import {Tenant} from './tenant-entity';
+
+let entityManager: EntityManager;
+
+const dcMock = {
+  update: jest.fn(),
+  put: jest.fn(),
+  query: jest.fn(),
+};
+beforeEach(() => {
+  const connection = createTestConnection({
+    entities: [Tenant],
+    documentClient: dcMock,
+  });
+  entityManager = new EntityManager(connection);
+});
+
+afterEach(() => {
+  resetTestConnection();
+});
+
+test('creates tenant and preserves attribute types for primary key attributes', async () => {
+  dcMock.put.mockReturnValue({promise: () => ({})});
+
+  const ten = new Tenant();
+  ten.id = 1;
+  ten.active = true;
+  ten.tenant = 1234;
+
+  const updateResponse = await entityManager.create(ten);
+
+  expect(dcMock.put).toHaveBeenCalledWith({
+    ConditionExpression:
+      '(attribute_not_exists(#CE_PK)) AND (attribute_not_exists(#CE_SK))',
+    ExpressionAttributeNames: {
+      '#CE_PK': 'PK',
+      '#CE_SK': 'SK',
+    },
+    Item: {
+      PK: 1,
+      SK: true,
+      __en: 'tenant',
+      active: true,
+      id: 1,
+      tenant: 1234,
+    },
+    ReturnConsumedCapacity: undefined,
+    TableName: 'test-table',
+  });
+  expect(updateResponse).toBeDefined();
+});
+
+test('allows updating attributes', async () => {
+  dcMock.update.mockReturnValue({promise: () => ({})});
+
+  const updateResponse = await entityManager.update(
+    Tenant,
+    {
+      id: 1,
+      active: true,
+    },
+    {
+      tenant: 4444,
+    }
+  );
+
+  expect(dcMock.update).toHaveBeenCalledWith({
+    ExpressionAttributeNames: {
+      '#UE_tenant': 'tenant',
+    },
+    ExpressionAttributeValues: {
+      ':UE_tenant': 4444,
+    },
+    Key: {
+      PK: 1,
+      SK: true,
+    },
+    ReturnConsumedCapacity: undefined,
+    ReturnValues: 'ALL_NEW',
+    TableName: 'test-table',
+    UpdateExpression: 'SET #UE_tenant = :UE_tenant',
+  });
+  expect(updateResponse).toBeDefined();
+});
diff --git a/__test__/github-issues/194/tenant-entity.ts b/__test__/github-issues/194/tenant-entity.ts
new file mode 100644
index 0000000..ae869a6
--- /dev/null
+++ b/__test__/github-issues/194/tenant-entity.ts
@@ -0,0 +1,25 @@
+import {Attribute, Entity} from '@typedorm/common';
+import {testTable} from '../../__mocks__/test-table';
+
+@Entity<Tenant>({
+  table: testTable,
+  name: 'tenant',
+  primaryKey: {
+    partitionKey: {
+      alias: 'id',
+    },
+    sortKey: {
+      alias: 'active',
+    },
+  },
+})
+export class Tenant {
+  @Attribute()
+  id: number;
+
+  @Attribute()
+  tenant: number;
+
+  @Attribute()
+  active: boolean;
+}