Repository: typedorm/typedorm
Pull request #50 (2021-01-14 10:48:46)

Merge Main to beta


Patch:
diff --git a/README.md b/README.md
index ffa140c..4aba00b 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ TypeDORM borrows decorator based syntax from TypeORM and provides fully type saf
 
 ![Stability](https://img.shields.io/badge/Stability-Unstable-orange?style=for-the-badge)
 
-TypeDORM is currency in it's very early stage of development so breaking changes are likely. Consider this before using it in production.
+TypeDORM is currently in it's very early stage of development so breaking changes are likely. Consider this before using it in production.
 
 Please submit an issue for any bugs or suggestions [here](https://github.com/typedorm/typedorm/issues), or you can reach out to me on twitter [@whimzy_live](https://mobile.twitter.com/whimzy_live).
 
@@ -30,9 +30,9 @@ Please submit an issue for any bugs or suggestions [here](https://github.com/typ
 - Declarative relational schema
 - Entity manager
 - Transaction manager
-- Multiple connections support
+- Multiple connections support (Partial)
 - Code follows all possible best practices when modeling for dynamodb
-- Additional higher level features like, ability to declare non key attribute as unique(beta)
+- Additional higher level features like, ability to declare non key attribute a unique
 - Auto Generated values fro attributes
 - Auto Update values on update
 - Complex update, key condition and condition expression all made easy to work with (Partial)
@@ -221,6 +221,5 @@ To find out more about how the data looks like when it is stored in dynamo have
 
 ## Current Limitations
 
-- TypDORM currently is not able to connect with dynamodb table using aws credentials and only works within environment like lambda/ec2 that already has access to dynamo table, but this will be resolved very soon.
 - To keep things simple TypeDORM, at the moment WILL NOT create/update table configuration and must be done separately.
 - There is very minimal support for providing different arguments to queries, and will be resolved soon.
diff --git a/docs/api.md b/docs/api.md
index 59ed3f5..496ebc7 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -2,7 +2,7 @@
 
 This section contains information on all the public apis that are available within TypeDORM, this is likely to change to a live generated documentation, but until that is done, this can be used as a starting point.
 
-_Note: In an event of inconstancy between actual API and this document, API should looked as the source of truth, and also if possible, file an issue in this repository or submit a PR._
+_Note: In an event of inconstancy between actual API and this document, API should looked as the source of truth, and also file an issue in this repository or submit a PR._
 
 ## On this page
 
@@ -12,7 +12,6 @@ _Note: In an event of inconstancy between actual API and this document, API shou
 - [Entity](#entity)
 - [Attribute](#attribute)
 - [AutoGeneratedAttribute](#autogeneratedattribute)
-- [EntityManager](#entitymanager)
 
 ## Table
 
@@ -20,17 +19,17 @@ Declares table to be used to store entity, can be per entity based on single tab
 
 ```Typescript
 Table({
-  // Name of the table, must match exactly to what is provisioned on aws
+  // name of the table, must match exactly to what is provisioned on aws
   name
 
-  // Partition key of table
+  // partition key of table
   partitionKey // identifier of partition key
 
-  // Sort key of table
+  // sort key of table
   // @optional
   sortKey
 
-  // Indexes if there are any
+  // indexes if there are any
   indexes
 })
 ```
@@ -43,23 +42,23 @@ Declare an entity to be registered in TypeDORM domain
 
 ```Typescript
 @Entity({
-  // Name of the entity that will be saved as __en attribute on each record
+  // name of the entity that will be saved as __en attribute on each record
   name,
 
   // table where the entity be saved
   // @optional
-  // Required when no global table exits in connection
+  // required when no global table exits in connection
   table
 
   // primary key of entity, must adhere to table primary key
   primaryKey: {
     partitionKey
     // @optional
-    // Required if table uses composite primary key
+    // required if table uses composite primary key
     sortKey
   }
 
-  // Additional indexes to add to entity
+  // additional indexes to add to entity
   // @optional
   indexes
 })
@@ -73,13 +72,13 @@ Declare Attribute on an entity
 @Attribute({
 
   // @optional
-  // Can either be of type boolean or with primary key
-  // When primary key is set to unique attribute, it can only reference it self
+  // can either be of type boolean or with primary key
+  // when primary key is set to unique attribute, it can only reference it self
   // i.e `USER#{{email}}` on unique attribute named `email` is considered valid but not on `id`
   unique
 
   // @optional
-  // Required when attribute type is enum and referenced in key schema
+  // required when attribute type is enum and referenced in key schema
   isEnum
 })
 ```
@@ -91,142 +90,13 @@ Declare Auto generated attribute on an entity
 ```Typescript
 @AutoGenerateAttribute({
 
-  // Strategy to use when auto generating attribute
-  // Valid values are of enum `AUTO_GENERATE_ATTRIBUTE_STRATEGY`
+  // strategy to use when auto generating attribute
+  // valid values are of enum `AUTO_GENERATE_ATTRIBUTE_STRATEGY`
   strategy
 
   // @optional
-  // When true, attribute will be auto updated on write request with specified strategy
-  // Useful for defining attributes like `updatedAt`
+  // when true, attribute will be auto updated on write request with specified strategy
+  // useful for defining attributes like `updatedAt`
   autoUpdate
 })
 ```
-
-## EntityManager
-
-To Manage entity with ease.
-
-### EntityManager.create
-
-Create entity with given params.
-
-```Typescript
-create(
-  // Entity to put into db
-  // Item will be created to table configured on @Entity or Connection
-  entity
-)
-```
-
-### EntityManager.findOne
-
-Finds single item matching given primary key or returns undefined
-
-```Typescript
-findOne(
-  // Entity class to resolve schema against
-  entityClass
-
-  // All attributes referenced in primary key
-  primaryKeyAttributes
-)
-```
-
-### EntityManager.exists
-
-Checks existence of item
-
-```Typescript
-exists(
-  // Entity class to resolve schema against
-  entityClass
-
-  // Primary key attributes or unique attributes referenced in schemas
-  attributes
-)
-```
-
-### EntityManager.update
-
-Update item with magically generated set operation
-
-```Typescript
-exists(
-  // Entity class to resolve schema against
-  entityClass
-
-  // Primary key attributes referenced in schemas
-  primaryKeyAttributes
-
-  // Attributes to update, if doesn't already exist,it will be created
-  body
-
-  // @Optional;
-  // Additional options
-  options: {
-    // @optional
-    // Nested key separator to use, (i.e when updating user:{name},
-    // body can include 'user$name' set to new value)
-    // @default it '.'
-    nestedKeySeparator
-  }
-
-)
-```
-
-### EntityManager.delete
-
-Delete item by key
-
-```Typescript
-delete(
-  // Entity class to resolve schema against
-  entityClass
-
-  // Primary key attributes or unique attributes referenced in schemas
-  primaryKeyAttributes
-)
-```
-
-### EntityManager.query
-
-Query items from db
-
-```Typescript
-find(
-  // Entity class to resolve schema against
-  entityClass
-
-  // all attributes referenced in partition key
-  partitionKeyAttributes: {
-    // @optional
-    // Name of the index if querying against GSI/LSI
-    // @default - query will be run against main table
-    queryIndex
-  }
-
-  // @optional
-  // Entity manager query options
-  // @default none - if non specified, query will be made using only partition key
-  queryOptions: {
-    // @optional
-    // Cursor to start querying from
-    // @default - none
-    cursor
-
-    // Key condition to use when querying items
-    // i.e this could be `{BEGINS_WITH: 'ORDER#'}`
-    keyCondition
-
-    // @optional
-    // Total number of items to return
-    // @default max limit configured in connection
-    limit
-
-    // @optional
-    // Order in which to perform query
-    // @default ASC
-    orderBy
-  }
-)
-```
diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts
index 51f4712..f8a4de7 100644
--- a/packages/common/src/constants.ts
+++ b/packages/common/src/constants.ts
@@ -1,5 +1,5 @@
 /**
- * Current transaction write items limit set by dynamodb itself
+ * Current transaction write items limit
  */
 export const TRANSACTION_WRITE_ITEMS_LIMIT = 25;
 
diff --git a/packages/common/src/enums.ts b/packages/common/src/enums.ts
index 44483e2..113d863 100644
--- a/packages/common/src/enums.ts
+++ b/packages/common/src/enums.ts
@@ -3,7 +3,6 @@ export enum RETURN_VALUES {
   ALL_NEW = 'ALL_NEW',
   UPDATED_OLD = 'UPDATED_OLD',
   UPDATED_NEW = 'UPDATED_NEW',
-  NONE = 'NONE',
 }
 
 export enum INDEX_TYPE {
diff --git a/packages/common/src/metadata/metadata-storage.ts b/packages/common/src/metadata/metadata-storage.ts
index 73a297f..26fdc7f 100644
--- a/packages/common/src/metadata/metadata-storage.ts
+++ b/packages/common/src/metadata/metadata-storage.ts
@@ -2,10 +2,6 @@ import {AUTO_GENERATE_ATTRIBUTE_STRATEGY, EntityTarget} from '@typedorm/common';
 import {IndexOptions, Table} from '../table';
 import {AttributeOptionsUniqueType} from '../decorators/attribute.decorator';
 
-export const IsAutoGenerateAttributeRawMetadataOptions = (
-  attr: any
-): attr is AutoGenerateAttributeRawMetadataOptions => !!attr.strategy;
-
 export type PrimaryKey = SimplePrimaryKey | CompositePrimaryKey;
 export type SimplePrimaryKey = {
   partitionKey: string;
@@ -28,25 +24,22 @@ export interface EntityRawMetadataOptions {
   table?: Table;
 }
 
-interface BaseAttributeRawMetadataOptions {
+export interface AttributeRawMetadataOptions {
   name: string;
   type: any;
-}
-export interface AttributeRawMetadataOptions
-  extends BaseAttributeRawMetadataOptions {
   unique?: AttributeOptionsUniqueType;
 }
 
 export interface AutoGenerateAttributeRawMetadataOptions
-  extends BaseAttributeRawMetadataOptions {
+  extends AttributeRawMetadataOptions {
   strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY;
   autoUpdate?: boolean;
 }
 
 export class MetadataStorage {
-  private _entities: Map<EntityTarget<any>, EntityRawMetadataOptions>;
+  private _entities: Map<string, EntityRawMetadataOptions>;
   private _attributes: Map<
-    EntityTarget<any>,
+    string,
     Map<
       string,
       AttributeRawMetadataOptions | AutoGenerateAttributeRawMetadataOptions
@@ -58,12 +51,12 @@ export class MetadataStorage {
     this._attributes = new Map();
   }
 
-  hasKnownEntity<Entity>(entityClass: EntityTarget<Entity>) {
-    return this._entities.has(entityClass);
+  hasKnownEntityByName(name: string) {
+    return this._entities.has(name);
   }
 
   getRawAttributesForEntity<Entity>(entityClass: EntityTarget<Entity>) {
-    const attributes = this._attributes.get(entityClass)?.values();
+    const attributes = this._attributes.get(entityClass.name)?.values();
 
     if (!attributes) {
       throw new Error(`No entity with name "${entityClass.name}" could be resolved, 
@@ -73,7 +66,7 @@ export class MetadataStorage {
   }
 
   getRawEntityByTarget<Entity>(entityClass: EntityTarget<Entity>) {
-    const entity = this._entities.get(entityClass);
+    const entity = this._entities.get(entityClass.name);
 
     if (!entity) {
       throw new Error(`No entity with name "${entityClass.name}" could be resolved, 
@@ -86,18 +79,18 @@ export class MetadataStorage {
     entityClass: EntityTarget<Entity>,
     attribute: AttributeRawMetadataOptions
   ) {
-    let attributesForEntity = this._attributes.get(entityClass);
+    let attributesForEntity = this._attributes.get(entityClass.name);
 
     if (!attributesForEntity) {
       attributesForEntity = new Map();
     }
 
     attributesForEntity.set(attribute.name, attribute);
-    this._attributes.set(entityClass, attributesForEntity);
+    this._attributes.set(entityClass.name, attributesForEntity);
   }
 
   addRawEntity(entity: EntityRawMetadataOptions) {
-    this._entities.set(entity.target, entity);
+    this._entities.set(entity.target.name, entity);
   }
 
   get entities() {
diff --git a/packages/core/public-api.ts b/packages/core/public-api.ts
index 31d44ca..6884e68 100644
--- a/packages/core/public-api.ts
+++ b/packages/core/public-api.ts
@@ -14,7 +14,16 @@ export * from './src/classes/manager/transaction-manager';
 
 export function createConnection(options: ConnectionOptions) {
   const connection = connectionManger().create(options);
-  return connection.connect();
+
+  const connected = connection.connect();
+
+  if (!connected) {
+    throw new Error(
+      `Failed to create connection with options: ${JSON.stringify(options)}`
+    );
+  }
+
+  return connected;
 }
 
 export function createConnections(optionsList: ConnectionOptions[]) {
diff --git a/packages/core/src/classes/condition/base-condition.ts b/packages/core/src/classes/condition/base-condition.ts
index bd72052..f8aec69 100644
--- a/packages/core/src/classes/condition/base-condition.ts
+++ b/packages/core/src/classes/condition/base-condition.ts
@@ -123,12 +123,12 @@ export abstract class BaseCondition {
     return this;
   }
 
-  and(): this {
+  and(): BaseCondition {
     this.appendToExpression('AND');
     return this;
   }
 
-  or(): this {
+  or(): BaseCondition {
     this.appendToExpression('OR');
     return this;
   }
diff --git a/packages/core/src/classes/connection/attribute-metadata-builder.ts b/packages/core/src/classes/connection/attribute-metadata-builder.ts
index 8321082..96eed6b 100644
--- a/packages/core/src/classes/connection/attribute-metadata-builder.ts
+++ b/packages/core/src/classes/connection/attribute-metadata-builder.ts
@@ -1,82 +1,31 @@
-import {
-  AutoGenerateAttributeRawMetadataOptions,
-  EntityRawMetadataOptions,
-  IsAutoGenerateAttributeRawMetadataOptions,
-  MetadataManager,
-  Table,
-} from '@typedorm/common';
-import {isUsedForPrimaryKey} from '../../helpers/is-used-for-primary-key';
+import {MetadataManager, Table} from '@typedorm/common';
 import {AttributeMetadata} from '../metadata/attribute-metadata';
-import {AutoGeneratedAttributeMetadata} from '../metadata/auto-generated-attribute-metadata';
+import {
+  AutoGeneratedAttributeMetadata,
+  IsAutoGeneratedAttributeMetadata,
+} from '../metadata/auto-generated-attribute-metadata';
 
 export class AttributesMetadataBuilder {
   constructor() {}
 
-  build(
-    table: Table,
-    attributeTargetEntityClass: Function,
-    decoratedEntityClass: Function
-  ) {
+  build(table: Table, entityClass: Function, decoratedClass?: Function) {
     const getRawAttributesForEntity = MetadataManager.metadataStorage.getRawAttributesForEntity(
-      attributeTargetEntityClass
+      entityClass
     );
 
     return getRawAttributesForEntity.map(attr => {
-      if (IsAutoGenerateAttributeRawMetadataOptions(attr)) {
-        const entityMetadata = this.tryGetEntityMetadata(
-          attributeTargetEntityClass,
-          decoratedEntityClass
-        );
-
-        return this.validateAndBuildAutoGeneratedAttributeMetadata(
-          attr,
-          entityMetadata
-        );
+      if (IsAutoGeneratedAttributeMetadata(attr)) {
+        return new AutoGeneratedAttributeMetadata({
+          ...attr,
+        });
       }
 
       return new AttributeMetadata({
         table,
         // when working with entity with multiple inheritance, use class with @Entity() if available
-        entityClass: decoratedEntityClass ?? attributeTargetEntityClass,
+        entityClass: decoratedClass ?? entityClass,
         ...attr,
       });
     });
   }
-
-  private validateAndBuildAutoGeneratedAttributeMetadata(
-    attr: AutoGenerateAttributeRawMetadataOptions,
-    entityMetadata: EntityRawMetadataOptions
-  ) {
-    if (attr.autoUpdate) {
-      if (isUsedForPrimaryKey(entityMetadata.primaryKey, attr.name)) {
-        throw new Error(
-          `Failed to build metadata for "${attr.name}", attributes referenced in primary key cannot be auto updated.`
-        );
-      }
-    }
-    return new AutoGeneratedAttributeMetadata({
-      ...attr,
-    });
-  }
-
-  private tryGetEntityMetadata(
-    attributeTargetEntityClass: Function,
-    decoratedEntityClass: Function
-  ) {
-    let entityMetadata: EntityRawMetadataOptions;
-
-    try {
-      // check if attribute target entity has metadata
-      entityMetadata = MetadataManager.metadataStorage.getRawEntityByTarget(
-        attributeTargetEntityClass
-      );
-    } catch (err) {
-      // fall back to using derived class metadata
-      entityMetadata = MetadataManager.metadataStorage.getRawEntityByTarget(
-        decoratedEntityClass
-      );
-    }
-
-    return entityMetadata;
-  }
 }
diff --git a/packages/core/src/classes/connection/connection-manager.ts b/packages/core/src/classes/connection/connection-manager.ts
index 95718be..3f6daf4 100644
--- a/packages/core/src/classes/connection/connection-manager.ts
+++ b/packages/core/src/classes/connection/connection-manager.ts
@@ -23,7 +23,13 @@ export class ConnectionManager {
         `There is already an existing connection with name "${name}".`
       );
     }
-    this.connections.set(name, new Connection(options));
+    this.connections.set(
+      name,
+      new Connection(
+        options,
+        this.clearByName.bind(this) // bind Connection manager ctx
+      )
+    );
 
     const createdConnection = this.connections.get(name);
     if (!createdConnection) {
@@ -37,4 +43,8 @@ export class ConnectionManager {
   clear() {
     this.connections = new Map<string, Connection>();
   }
+
+  clearByName(name: string) {
+    this.connections.delete(name);
+  }
 }
diff --git a/packages/core/src/classes/connection/connection-metadata-builder.ts b/packages/core/src/classes/connection/connection-metadata-builder.ts
index 54f7ae9..6812ad4 100644
--- a/packages/core/src/classes/connection/connection-metadata-builder.ts
+++ b/packages/core/src/classes/connection/connection-metadata-builder.ts
@@ -18,7 +18,7 @@ export class ConnectionMetadataBuilder {
 
     // filter all entities that are not marked with `@Entity` decorator
     const entitiesToBuild = possibleEntitiesToBuild.filter(entity =>
-      MetadataManager.metadataStorage.hasKnownEntity(entity)
+      MetadataManager.metadataStorage.hasKnownEntityByName(entity.name)
     );
 
     return new EntityMetadataBuilder(this.connection).build(entitiesToBuild);
diff --git a/packages/core/src/classes/connection/connection.ts b/packages/core/src/classes/connection/connection.ts
index ea53578..3f6f3d6 100644
--- a/packages/core/src/classes/connection/connection.ts
+++ b/packages/core/src/classes/connection/connection.ts
@@ -1,11 +1,9 @@
 import {
   DYNAMO_QUERY_ITEMS_IMPLICIT_LIMIT,
   EntityTarget,
-  Replace,
   Table,
 } from '@typedorm/common';
 import {DynamoDB} from 'aws-sdk';
-import {isUsedForPrimaryKey} from '../../helpers/is-used-for-primary-key';
 import {EntityManager} from '../manager/entity-manager';
 import {TransactionManager} from '../manager/transaction-manager';
 import {AttributeMetadata} from '../metadata/attribute-metadata';
@@ -28,7 +26,10 @@ export class Connection {
   private _entityMetadatas: Map<string, EntityMetadata>;
   private isConnected: boolean;
 
-  constructor(private options: ConnectionOptions) {
+  constructor(
+    private options: ConnectionOptions,
+    private destroySelf: (name: string) => void
+  ) {
     const {table, name = 'default'} = options;
     if (table) {
       this.table = table;
@@ -60,15 +61,21 @@ export class Connection {
       );
     }
 
-    this._entityMetadatas = new Map(
-      this.buildMetadatas().map(entityMeta => [
-        entityMeta.target.name,
-        entityMeta,
-      ])
-    );
+    try {
+      this._entityMetadatas = new Map(
+        this.buildMetadatas().map(entityMeta => [
+          entityMeta.target.name,
+          entityMeta,
+        ])
+      );
 
-    this.isConnected = true;
-    return this;
+      this.isConnected = true;
+      return this;
+    } catch (err) {
+      // Failed to connect to connection, clear self from connection manager
+      this.destroySelf(this.name);
+      return;
+    }
   }
 
   get entityMetadatas() {
@@ -111,13 +118,9 @@ export class Connection {
       // only attributes that are not part of primary key should be included
       return (
         (attr as AttributeMetadata)?.unique &&
-        !isUsedForPrimaryKey(entityMetadata.schema.primaryKey, attr.name)
+        !this.isUsedForPrimaryKey(entityMetadata.schema.primaryKey, attr.name)
       );
-    }) as Replace<
-      AttributeMetadata,
-      'unique',
-      {unique: DynamoEntitySchemaPrimaryKey}
-    >[];
+    }) as AttributeMetadata[];
   }
 
   getEntityByTarget<Entity>(entityClass: EntityTarget<Entity>) {
@@ -136,7 +139,18 @@ export class Connection {
     ) as AutoGeneratedAttributeMetadata[];
   }
 
-  private buildMetadatas() {
+  isUsedForPrimaryKey(
+    primaryKey: DynamoEntitySchemaPrimaryKey,
+    attributeName: string
+  ) {
+    const primaryKeyInterpolations = primaryKey._interpolations ?? {};
+    return Object.keys(primaryKeyInterpolations).some(key => {
+      const currInterpolation = primaryKeyInterpolations[key];
+      return currInterpolation.includes(attributeName);
+    });
+  }
+
+  buildMetadatas() {
     return new ConnectionMetadataBuilder(this).buildEntityMetadatas(
       this.options.entities
     );
diff --git a/packages/core/src/classes/connection/entity-metadata-builder.ts b/packages/core/src/classes/connection/entity-metadata-builder.ts
index 3044394..d59d7d7 100644
--- a/packages/core/src/classes/connection/entity-metadata-builder.ts
+++ b/packages/core/src/classes/connection/entity-metadata-builder.ts
@@ -13,16 +13,14 @@ export class EntityMetadataBuilder {
     this.attributesMetadataBuilder = new AttributesMetadataBuilder();
   }
   build(entityClasses: Function[]): EntityMetadata[] {
-    return entityClasses.map(decoratedEntityClass => {
+    return entityClasses.map(entityClass => {
       const {
         target,
         table,
         name,
         primaryKey,
         indexes,
-      } = MetadataManager.metadataStorage.getRawEntityByTarget(
-        decoratedEntityClass
-      );
+      } = MetadataManager.metadataStorage.getRawEntityByTarget(entityClass);
 
       if (table) {
         // if no entity level table is defined fallback to global connection table
@@ -31,17 +29,15 @@ export class EntityMetadataBuilder {
         this.table = this.connection.table;
       }
 
-      const inheritedClasses = this.recursiveGetInheritanceTree(
-        decoratedEntityClass
-      );
+      const inheritedClasses = this.recursiveGetInheritanceTree(entityClass);
 
       // metadata are sorted by [very base class] -> [very derived class]
       const inheritedEntitiesAttributesMetadata = inheritedClasses
-        .map(derivedClass =>
+        .map(inheritedClass =>
           this.attributesMetadataBuilder.build(
             this.table,
-            derivedClass,
-            decoratedEntityClass
+            inheritedClass,
+            entityClass
           )
         )
         .reverse();
diff --git a/packages/core/src/classes/expression-builder.ts b/packages/core/src/classes/expression-builder.ts
index 57cc8d0..5867ff4 100644
--- a/packages/core/src/classes/expression-builder.ts
+++ b/packages/core/src/classes/expression-builder.ts
@@ -1,25 +1,9 @@
-import {Table} from '@typedorm/common';
 import {isEmptyObject} from '../helpers/is-empty-object';
 import {isObject} from '../helpers/is-object';
 import {Condition} from './condition/condition';
 import {KeyCondition} from './condition/key-condition';
 
 export class ExpressionBuilder {
-  /**
-   * Higher level function to build unique record condition expression
-   * @param table table to build unique record expression for
-   */
-  buildUniqueRecordConditionExpression(table: Table) {
-    const uniqueRecordCondition = table.usesCompositeKey()
-      ? new Condition()
-          .attributeNotExist(table.partitionKey)
-          .and()
-          .attributeNotExist(table.sortKey)
-      : new Condition().attributeNotExist(table.partitionKey);
-
-    return this.buildConditionExpression(uniqueRecordCondition);
-  }
-
   buildConditionExpression(condition: Condition) {
     if (!condition.expression) {
       return {ConditionExpression: ''};
@@ -75,7 +59,7 @@ export class ExpressionBuilder {
 
         const expValKey = `:val${index}`;
         acc.UpdateExpression += ` ${expAttrKey} = ${expValKey}`;
-        acc.ExpressionAttributeValues[expValKey] = item[key];
+        acc.ExpressionAttributeValues[expValKey] = item[key] ?? null;
         return acc;
       },
       {
diff --git a/packages/core/src/classes/manager/entity-manager.ts b/packages/core/src/classes/manager/entity-manager.ts
index 5408178..b7c1fbd 100644
--- a/packages/core/src/classes/manager/entity-manager.ts
+++ b/packages/core/src/classes/manager/entity-manager.ts
@@ -3,6 +3,7 @@ import {
   EntityAttributes,
   EntityTarget,
   PrimaryKeyAttributes,
+  RETURN_VALUES,
   UpdateAttributes,
 } from '@typedorm/common';
 import {getDynamoQueryItemsLimit} from '../../helpers/get-dynamo-query-items-limit';
@@ -15,15 +16,17 @@ import {
 } from '../transformer/document-client-request-transformer';
 import {EntityTransformer} from '../transformer/entity-transformer';
 import {getConstructorForInstance} from '../../helpers/get-constructor-for-instance';
-import {isUsedForPrimaryKey} from '../../helpers/is-used-for-primary-key';
-import {isWriteTransactionItemList} from '../transaction/type-guards';
-import {isLazyTransactionWriteItemListLoader} from '../transformer/is-lazy-transaction-write-item-list-loder';
 
 export interface EntityManagerUpdateOptions {
   /**
    * @default '.'
    */
   nestedKeySeparator?: string;
+
+  /**
+   * @default ALL_NEW
+   */
+  returnValues?: RETURN_VALUES;
 }
 
 export interface EntityManagerQueryOptions
@@ -48,10 +51,8 @@ export class EntityManager {
     const dynamoPutItemInput = this._dcReqTransformer.toDynamoPutItem(entity);
     const entityClass = getConstructorForInstance(entity);
 
-    if (!isWriteTransactionItemList(dynamoPutItemInput)) {
+    if (!Array.isArray(dynamoPutItemInput)) {
       await this.connection.documentClient.put(dynamoPutItemInput).promise();
-
-      // by default dynamodb does not return attributes on create operation, so return one
       const itemToReturn = this._entityTransformer.fromDynamoEntity<Entity>(
         entityClass,
         dynamoPutItemInput.Item
@@ -60,19 +61,18 @@ export class EntityManager {
       return itemToReturn;
     }
 
-    // dynamoPutItemInput is a transact item list, meaning that it contains one or more unique attributes, which also
-    // needs to be created along with original item
-
+    // when put item is set to array, one or more attributes are marked as unique
+    // to maintain all records consistency, all items must be put into db as a single transaction
     const transaction = new WriteTransaction(
       this.connection,
-      dynamoPutItemInput
+      dynamoPutItemInput.map(putItem => ({Put: putItem}))
     );
     await this.connection.transactionManger.write(transaction);
 
     const itemToReturn = this._entityTransformer.fromDynamoEntity<Entity>(
       entityClass,
-      // if create operation contains multiple items, first one will the original item
-      dynamoPutItemInput[0]?.Put?.Item ?? {}
+      // if create operation contains multiple items, first one will the original requested item
+      dynamoPutItemInput[0].Item
     );
 
     return itemToReturn;
@@ -131,7 +131,12 @@ export class EntityManager {
       attributes
     ).reduce(
       (acc, [attrKey, value]) => {
-        if (isUsedForPrimaryKey(metadata.schema.primaryKey, attrKey)) {
+        if (
+          this.connection.isUsedForPrimaryKey(
+            metadata.schema.primaryKey,
+            attrKey
+          )
+        ) {
           acc.primaryKeyAttributes[attrKey] = value;
         } else if (uniqueAttributeNames.includes(attrKey)) {
           acc.uniqueAttributes[attrKey] = value;
@@ -201,103 +206,47 @@ export class EntityManager {
   /**
    *
    * @param entityClass Entity class to update
-   * @param primaryKeyAttributes Primary key
+   * @param primaryKey Primary key
    * @param body Attributes to update
    * @param options update options
    */
   async update<PrimaryKey, Entity>(
     entityClass: EntityTarget<Entity>,
-    primaryKeyAttributes: PrimaryKeyAttributes<PrimaryKey, any>,
+    primaryKey: PrimaryKeyAttributes<PrimaryKey, any>,
     body: UpdateAttributes<PrimaryKey, Entity>,
     options?: EntityManagerUpdateOptions
   ): Promise<Entity> {
     const dynamoUpdateItem = this._dcReqTransformer.toDynamoUpdateItem<
       PrimaryKey,
       Entity
-    >(entityClass, primaryKeyAttributes, body, options);
+    >(entityClass, primaryKey, body, options);
 
-    if (!isLazyTransactionWriteItemListLoader(dynamoUpdateItem)) {
-      const response = await this.connection.documentClient
-        .update(dynamoUpdateItem)
-        .promise();
-
-      return this._entityTransformer.fromDynamoEntity<Entity>(
-        entityClass,
-        // return all new attributes
-        response.Attributes ?? {}
-      );
-    }
+    const response = await this.connection.documentClient
+      .update(dynamoUpdateItem)
+      .promise();
 
-    // first get existing item, so that we can safely clear previous unique attributes
-    const existingItem = await this.findOne<PrimaryKey, Entity>(
+    return this._entityTransformer.fromDynamoEntity<Entity>(
       entityClass,
-      primaryKeyAttributes
-    );
-
-    if (!existingItem) {
-      throw new Error(
-        `Failed to update entity, could not find entity with primary key "${JSON.stringify(
-          primaryKeyAttributes
-        )}"`
-      );
-    }
-
-    const updateItemList = dynamoUpdateItem.lazyLoadTransactionWriteItems(
-      existingItem
+      response.Attributes ?? {}
     );
-
-    const transaction = new WriteTransaction(this.connection, updateItemList);
-    // since write transaction does not return any, we will need to get the latest one from dynamo
-    await this.connection.transactionManger.write(transaction);
-    const updatedItem = (await this.findOne<PrimaryKey, Entity>(
-      entityClass,
-      primaryKeyAttributes
-    )) as Entity;
-
-    return updatedItem;
   }
 
   /**
    * Deletes an entity by primary key
    * @param entityClass Entity Class to delete
-   * @param primaryKeyAttributes Entity Primary key
+   * @param primaryKey Entity Primary key
    */
-  async delete<PrimaryKeyAttributes, Entity>(
+  async delete<PrimaryKey, Entity>(
     entityClass: EntityTarget<Entity>,
-    primaryKeyAttributes: PrimaryKeyAttributes
+    primaryKey: PrimaryKey
   ) {
     const dynamoDeleteItem = this._dcReqTransformer.toDynamoDeleteItem<
-      PrimaryKeyAttributes,
+      PrimaryKey,
       Entity
-    >(entityClass, primaryKeyAttributes);
+    >(entityClass, primaryKey);
 
-    if (!isLazyTransactionWriteItemListLoader(dynamoDeleteItem)) {
-      await this.connection.documentClient.delete(dynamoDeleteItem).promise();
-      return {
-        success: true,
-      };
-    }
+    await this.connection.documentClient.delete(dynamoDeleteItem).promise();
 
-    // first get existing item, so that we can safely clear previous unique attributes
-    const existingItem = await this.findOne<PrimaryKeyAttributes, Entity>(
-      entityClass,
-      primaryKeyAttributes
-    );
-
-    if (!existingItem) {
-      throw new Error(
-        `Failed to update entity, could not find entity with primary key "${JSON.stringify(
-          primaryKeyAttributes
-        )}"`
-      );
-    }
-
-    const deleteItemList = dynamoDeleteItem.lazyLoadTransactionWriteItems(
-      existingItem
-    );
-    const transaction = new WriteTransaction(this.connection, deleteItemList);
-    // delete main item and all it's unique attributes as part of single transaction
-    await this.connection.transactionManger.write(transaction);
     return {
       success: true,
     };
diff --git a/packages/core/src/classes/manager/transaction-manager.ts b/packages/core/src/classes/manager/transaction-manager.ts
index 0fb5497..94485a8 100644
--- a/packages/core/src/classes/manager/transaction-manager.ts
+++ b/packages/core/src/classes/manager/transaction-manager.ts
@@ -2,7 +2,6 @@ import {DynamoDB} from 'aws-sdk';
 import {TRANSACTION_WRITE_ITEMS_LIMIT} from '@typedorm/common';
 import {WriteTransaction} from '../transaction/write-transaction';
 import {Connection} from '../connection/connection';
-import {isLazyTransactionWriteItemListLoader} from '../transformer/is-lazy-transaction-write-item-list-loder';
 
 /**
  * Performs transactions using document client's writeTransaction
@@ -11,42 +10,14 @@ export class TransactionManager {
   constructor(private connection: Connection) {}
 
   async write(transaction: WriteTransaction) {
-    const transactionItems = (
-      await Promise.all(
-        transaction.items.map(async item => {
-          if (!isLazyTransactionWriteItemListLoader(item)) {
-            return item;
-          }
-
-          // if updating/removing unique attribute in transaction, get previous value of attributes
-          const existingItem = await this.connection.entityManager.findOne(
-            item.entityClass,
-            item.primaryKeyAttributes
-          );
-
-          if (!existingItem) {
-            throw new Error(
-              `Failed to process entity "${
-                item.entityClass.name
-              }", could not find entity with primary key "${JSON.stringify(
-                item.primaryKeyAttributes
-              )}"`
-            );
-          }
-
-          return item.lazyLoadTransactionWriteItems(existingItem);
-        })
-      )
-    ).flat();
-
-    if (transactionItems.length > TRANSACTION_WRITE_ITEMS_LIMIT) {
+    if (transaction.items.length > TRANSACTION_WRITE_ITEMS_LIMIT) {
       throw new Error(
         `Transaction write limit exceeded, current transaction write limit is "${TRANSACTION_WRITE_ITEMS_LIMIT}"`
       );
     }
 
     const transactionInput: DynamoDB.DocumentClient.TransactWriteItemsInput = {
-      TransactItems: transactionItems,
+      TransactItems: transaction.items,
     };
 
     // FIXME: current promise implementation of document client transact write does not provide a way
@@ -84,9 +55,8 @@ export class TransactionManager {
           );
           return reject(transactionError);
         }
-
         return resolve(response);
       });
-    }) as Promise<DynamoDB.DocumentClient.TransactWriteItemsOutput>;
+    });
   }
 }
diff --git a/packages/core/src/classes/metadata/entity-metadata.ts b/packages/core/src/classes/metadata/entity-metadata.ts
index a201ca2..07b88c9 100644
--- a/packages/core/src/classes/metadata/entity-metadata.ts
+++ b/packages/core/src/classes/metadata/entity-metadata.ts
@@ -9,7 +9,6 @@ import {
 } from '@typedorm/common';
 import {buildPrimaryKeySchema} from '../../helpers/build-primary-key-schema';
 import {getInterpolatedKeys} from '../../helpers/get-interpolated-keys';
-import {validateKey} from '../../helpers/validate-key';
 import {Connection} from '../connection/connection';
 import {AttributeMetadata} from './attribute-metadata';
 import {AutoGeneratedAttributeMetadata} from './auto-generated-attribute-metadata';
@@ -131,9 +130,11 @@ export class EntityMetadata extends BaseMetadata {
 
       const currentIndex = indexes[key];
 
-      validateKey(currentIndex.sortKey, attributes);
       // validates and gets and fill set indexes interpolations of sort key
-      const sortKeyInterpolations = getInterpolatedKeys(currentIndex.sortKey);
+      const sortKeyInterpolations = getInterpolatedKeys(
+        currentIndex.sortKey,
+        attributes
+      );
 
       if (tableIndexSignature.type === INDEX_TYPE.LSI) {
         if (currentIndex.type !== INDEX_TYPE.LSI) {
@@ -152,10 +153,10 @@ export class EntityMetadata extends BaseMetadata {
         if (currentIndex.type !== INDEX_TYPE.GSI) {
           throw new Error('Index signature mismatch.');
         }
-        validateKey(currentIndex.partitionKey, attributes);
         // validates and gets and fill set indexes interpolations of partition key
         const partitionKeyInterpolations = getInterpolatedKeys(
-          currentIndex.partitionKey
+          currentIndex.partitionKey,
+          attributes
         );
 
         acc[key] = {
diff --git a/packages/core/src/classes/transaction/transaction.ts b/packages/core/src/classes/transaction/transaction.ts
index 210179d..5e19837 100644
--- a/packages/core/src/classes/transaction/transaction.ts
+++ b/packages/core/src/classes/transaction/transaction.ts
@@ -7,13 +7,12 @@ import {
 import {Connection} from '../connection/connection';
 import {EntityManagerUpdateOptions} from '../manager/entity-manager';
 import {DocumentClientRequestTransformer} from '../transformer/document-client-request-transformer';
-import {LazyTransactionWriteItemListLoader} from '../transformer/is-lazy-transaction-write-item-list-loder';
 
 // transaction interfaces
-export interface WriteTransactionCreate<Entity> {
+interface WriteTransactionCreate<Entity> {
   create: {item: Entity};
 }
-export interface WriteTransactionUpdate<PrimaryKey, Entity> {
+interface WriteTransactionUpdate<PrimaryKey, Entity> {
   update: {
     item: EntityTarget<Entity>;
     primaryKey: PrimaryKeyAttributes<PrimaryKey, any>;
@@ -21,26 +20,32 @@ export interface WriteTransactionUpdate<PrimaryKey, Entity> {
     options?: EntityManagerUpdateOptions;
   };
 }
-export interface WriteTransactionRemove<PrimaryKey, Entity> {
-  delete: {
-    item: EntityTarget<Entity>;
-    primaryKey: PrimaryKeyAttributes<PrimaryKey, any>;
-  };
-}
 export type WriteTransactionChainItem<PrimaryKey, Entity> =
   | WriteTransactionCreate<Entity>
-  | WriteTransactionUpdate<PrimaryKey, Entity>
-  | WriteTransactionRemove<PrimaryKey, Entity>;
+  | WriteTransactionUpdate<PrimaryKey, Entity>;
+
+// custom type guards
+export function isCreateTransaction<Entity>(
+  item: any
+): item is WriteTransactionCreate<Entity> {
+  return (item as WriteTransactionCreate<Entity>).create !== undefined;
+}
+
+export function isUpdateTransaction<PrimaryKey, Entity>(
+  item: any
+): item is WriteTransactionUpdate<PrimaryKey, Entity> {
+  return (
+    (item as WriteTransactionUpdate<PrimaryKey, Entity>).update !== undefined
+  );
+}
 
 /**
  * Base Transaction
  */
 export abstract class Transaction {
-  protected _items: (
-    | DynamoDB.DocumentClient.TransactWriteItem
-    | LazyTransactionWriteItemListLoader
-    | DynamoDB.DocumentClient.TransactGetItem
-  )[];
+  protected _items:
+    | DynamoDB.DocumentClient.TransactWriteItemList
+    | DynamoDB.DocumentClient.TransactGetItemList;
 
   protected _dcReqTransformer: DocumentClientRequestTransformer;
 
diff --git a/packages/core/src/classes/transaction/type-guards.ts b/packages/core/src/classes/transaction/type-guards.ts
deleted file mode 100644
index 61cde95..0000000
--- a/packages/core/src/classes/transaction/type-guards.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import {DynamoDB} from 'aws-sdk';
-import {
-  WriteTransactionCreate,
-  WriteTransactionRemove,
-  WriteTransactionUpdate,
-} from './transaction';
-
-export function isCreateTransaction<Entity>(
-  item: any
-): item is WriteTransactionCreate<Entity> {
-  return (item as WriteTransactionCreate<Entity>).create !== undefined;
-}
-
-export function isUpdateTransaction<PrimaryKey, Entity>(
-  item: any
-): item is WriteTransactionUpdate<PrimaryKey, Entity> {
-  return (
-    (item as WriteTransactionUpdate<PrimaryKey, Entity>).update !== undefined
-  );
-}
-
-export function isRemoveTransaction<PrimaryKey, Entity>(
-  item: any
-): item is WriteTransactionRemove<PrimaryKey, Entity> {
-  return (
-    (item as WriteTransactionRemove<PrimaryKey, Entity>).delete !== undefined
-  );
-}
-
-export const isWriteTransactionItemList = (
-  item: any
-): item is DynamoDB.DocumentClient.TransactWriteItemList =>
-  !!(item as DynamoDB.DocumentClient.TransactWriteItemList).length &&
-  !!(
-    (item as DynamoDB.DocumentClient.TransactWriteItemList)[0]
-      ?.ConditionCheck ||
-    (item as DynamoDB.DocumentClient.TransactWriteItemList)[0]?.Delete ||
-    (item as DynamoDB.DocumentClient.TransactWriteItemList)[0]?.Put ||
-    (item as DynamoDB.DocumentClient.TransactWriteItemList)[0]?.Update
-  );
diff --git a/packages/core/src/classes/transaction/write-transaction.ts b/packages/core/src/classes/transaction/write-transaction.ts
index b392d12..315ee5f 100644
--- a/packages/core/src/classes/transaction/write-transaction.ts
+++ b/packages/core/src/classes/transaction/write-transaction.ts
@@ -1,27 +1,14 @@
 import {DynamoDB} from 'aws-sdk';
-import {dropProp} from '../../helpers/drop-prop';
 import {Connection} from '../connection/connection';
 import {
-  isLazyTransactionWriteItemListLoader,
-  LazyTransactionWriteItemListLoader,
-} from '../transformer/is-lazy-transaction-write-item-list-loder';
-import {
+  isCreateTransaction,
+  isUpdateTransaction,
   Transaction,
   WriteTransactionChainItem,
-  WriteTransactionCreate,
 } from './transaction';
-import {
-  isCreateTransaction,
-  isRemoveTransaction,
-  isUpdateTransaction,
-  isWriteTransactionItemList,
-} from './type-guards';
 
 export class WriteTransaction extends Transaction {
-  protected _items: (
-    | LazyTransactionWriteItemListLoader
-    | DynamoDB.DocumentClient.TransactWriteItem
-  )[];
+  protected _items: DynamoDB.DocumentClient.TransactWriteItemList;
 
   constructor(
     connection: Connection,
@@ -38,77 +25,41 @@ export class WriteTransaction extends Transaction {
   chian<PrimaryKey, Entity>(
     chainedItem: WriteTransactionChainItem<PrimaryKey, Entity>
   ): WriteTransaction {
-    // create
     if (isCreateTransaction(chainedItem)) {
-      this.items = this.chainCreateTransaction(chainedItem);
-      // update
-    } else if (isUpdateTransaction(chainedItem)) {
-      const {item, body, primaryKey, options} = chainedItem.update;
+      const {
+        create: {item},
+      } = chainedItem;
 
-      const itemToUpdate = this._dcReqTransformer.toDynamoUpdateItem<
-        PrimaryKey,
-        Entity
-      >(item, primaryKey, body, options);
-      if (!isLazyTransactionWriteItemListLoader(itemToUpdate)) {
-        this.items.push({
-          Update: dropProp(itemToUpdate, 'ReturnValues') as DynamoDB.Update,
-        });
-      } else {
-        this.items.push(itemToUpdate);
-      }
-      // remove
-    } else if (isRemoveTransaction(chainedItem)) {
-      const {item, primaryKey} = chainedItem.delete;
+      const dynamoPutItemInput = this._dcReqTransformer.toDynamoPutItem<Entity>(
+        item
+      );
 
-      const itemToRemove = this._dcReqTransformer.toDynamoDeleteItem<
-        PrimaryKey,
-        Entity
-      >(item, primaryKey);
-      if (!isLazyTransactionWriteItemListLoader(itemToRemove)) {
-        this.items.push({
-          Delete: itemToRemove,
+      // when put item is set to array, one or more attributes are marked as unique
+      // to maintain all records consistency, all items must be put into db as a single transaction
+      if (!Array.isArray(dynamoPutItemInput)) {
+        this._items.push({
+          Put: dynamoPutItemInput,
         });
       } else {
-        this.items.push(itemToRemove);
+        this._items.push(
+          ...dynamoPutItemInput.map(puttItem => ({Put: puttItem}))
+        );
       }
-    } else {
-      return this;
+    } else if (isUpdateTransaction(chainedItem)) {
+      const {item, body, primaryKey, options} = chainedItem.update;
+      this._items.push({
+        Update: this._dcReqTransformer.toDynamoUpdateItem<PrimaryKey, Entity>(
+          item,
+          primaryKey,
+          body,
+          options
+        ) as DynamoDB.Update,
+      });
     }
     return this;
   }
 
-  private chainCreateTransaction<Entity>(
-    chainedItem: WriteTransactionCreate<Entity>
-  ) {
-    const {
-      create: {item},
-    } = chainedItem;
-
-    const dynamoPutItemInput = this._dcReqTransformer.toDynamoPutItem<Entity>(
-      item
-    );
-
-    if (!isWriteTransactionItemList(dynamoPutItemInput)) {
-      return [
-        {
-          Put: dynamoPutItemInput,
-        },
-      ] as DynamoDB.DocumentClient.TransactWriteItemList;
-    }
-
-    return [...dynamoPutItemInput];
-  }
-
   get items() {
     return this._items;
   }
-
-  set items(
-    items: (
-      | DynamoDB.DocumentClient.TransactWriteItem
-      | LazyTransactionWriteItemListLoader
-    )[]
-  ) {
-    this._items = [...this.items, ...items];
-  }
 }
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 d18fb80..d7c9bec 100644
--- a/packages/core/src/classes/transformer/document-client-request-transformer.ts
+++ b/packages/core/src/classes/transformer/document-client-request-transformer.ts
@@ -7,33 +7,29 @@ import {
   INDEX_TYPE,
   PrimaryKeyAttributes,
   QUERY_ORDER,
-  Replace,
   RequireOnlyOne,
   RETURN_VALUES,
   ScalarType,
-  Table,
   UpdateAttributes,
 } from '@typedorm/common';
 import {DynamoDB} from 'aws-sdk';
-import {dropProp} from '../../helpers/drop-prop';
 import {getConstructorForInstance} from '../../helpers/get-constructor-for-instance';
 import {isEmptyObject} from '../../helpers/is-empty-object';
 import {parseKey} from '../../helpers/parse-key';
+import {Condition} from '../condition/condition';
 import {KeyCondition} from '../condition/key-condition';
 import {Connection} from '../connection/connection';
 import {ExpressionBuilder} from '../expression-builder';
 import {EntityManagerUpdateOptions} from '../manager/entity-manager';
 import {AttributeMetadata} from '../metadata/attribute-metadata';
-import {DynamoEntitySchemaPrimaryKey} from '../metadata/entity-metadata';
 import {BaseTransformer} from './base-transformer';
-import {LazyTransactionWriteItemListLoader} from './is-lazy-transaction-write-item-list-loder';
 
 export interface ManagerToDynamoQueryItemsOptions {
   /**
    * Sort key condition
    * @default none - no sort key condition is applied
    */
-  keyCondition: RequireOnlyOne<
+  keyCondition?: RequireOnlyOne<
     {
       [key in FindKeyScalarOperator]: ScalarType;
     } &
@@ -101,7 +97,7 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
     options?: ManagerToDynamoPutItemOptions
   ):
     | DynamoDB.DocumentClient.PutItemInput
-    | DynamoDB.DocumentClient.TransactWriteItemList {
+    | DynamoDB.DocumentClient.PutItemInput[] {
     const entityClass = getConstructorForInstance(entity);
     const {table, internalAttributes} = this.connection.getEntityByTarget(
       entityClass
@@ -118,33 +114,12 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
       return acc;
     }, {} as DynamoDB.DocumentClient.PutItemInputAttributeMap);
 
-    let dynamoPutItem = {
-      Item: {
-        ...entityInternalAttributes,
-        ...dynamoEntity,
-      },
-      TableName: table.name,
-    } as DynamoDB.DocumentClient.PutItemInput;
-
+    let uniqueAttributePutItems = [] as DynamoDB.DocumentClient.PutItemInput[];
     // apply attribute not exist condition when creating unique
-    const uniqueRecordConditionExpression = new ExpressionBuilder().buildUniqueRecordConditionExpression(
-      table
+    const uniqueRecordConditionExpression = this._expressionBuilder.buildConditionExpression(
+      new Condition().attributeNotExist(table.partitionKey)
     );
 
-    // always prevent overwriting data until explicitly told to do otherwise
-    if (!options?.overwriteIfExists) {
-      dynamoPutItem = {
-        ...dynamoPutItem,
-        ...uniqueRecordConditionExpression,
-      };
-    }
-
-    if (!uniqueAttributes.length) {
-      return dynamoPutItem;
-    }
-
-    // if there are unique attributes, return transaction list item
-    let uniqueAttributePutItems: DynamoDB.DocumentClient.TransactWriteItemList = [];
     if (uniqueAttributes.length) {
       uniqueAttributePutItems = uniqueAttributes.map(attr => {
         const attributeValue = (entity as any)[attr.name];
@@ -168,28 +143,41 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
         );
 
         return {
-          Put: {
-            Item: uniqueItemPrimaryKey,
-            TableName: table.name,
-            ...uniqueRecordConditionExpression,
-          },
-        };
+          Item: uniqueItemPrimaryKey,
+          TableName: table.name,
+          ...uniqueRecordConditionExpression,
+        } as DynamoDB.DocumentClient.PutItemInput;
       });
     }
 
-    return [{Put: dynamoPutItem}, ...uniqueAttributePutItems];
+    let dynamoPutItem = {
+      Item: {
+        ...entityInternalAttributes,
+        ...dynamoEntity,
+      },
+      TableName: table.name,
+    } as DynamoDB.DocumentClient.PutItemInput;
+
+    // always prevent overwriting data until explicitly told to do otherwise
+    if (!options?.overwriteIfExists) {
+      dynamoPutItem = {...dynamoPutItem, ...uniqueRecordConditionExpression};
+    }
+
+    if (uniqueAttributePutItems.length) {
+      return [dynamoPutItem, ...uniqueAttributePutItems];
+    }
+    return dynamoPutItem;
   }
 
   toDynamoUpdateItem<PrimaryKey, Entity>(
     entityClass: EntityTarget<Entity>,
-    primaryKeyAttributes: PrimaryKeyAttributes<PrimaryKey, any>,
+    primaryKey: PrimaryKeyAttributes<PrimaryKey, any>,
     body: UpdateAttributes<PrimaryKey, Entity>,
-    options: EntityManagerUpdateOptions = {}
-  ):
-    | DynamoDB.DocumentClient.UpdateItemInput
-    | LazyTransactionWriteItemListLoader {
+    options?: EntityManagerUpdateOptions
+  ): DynamoDB.DocumentClient.UpdateItemInput {
     // default values
-    const {nestedKeySeparator = '.'} = options;
+    const {nestedKeySeparator = '.', returnValues = RETURN_VALUES.ALL_NEW} =
+      options ?? {};
 
     if (!this.connection.hasMetadata(entityClass)) {
       throw new Error(`No metadata found for class "${entityClass.name}".`);
@@ -202,13 +190,21 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
     const parsedPrimaryKey = this.getParsedPrimaryKey(
       metadata.table,
       metadata.schema.primaryKey,
-      primaryKeyAttributes
+      primaryKey
     );
 
     if (isEmptyObject(parsedPrimaryKey)) {
       throw new Error('Primary could not be resolved');
     }
 
+    // TODO: add support for updating unique attributes
+    this.connection.getUniqueAttributesForEntity(entityClass).forEach(attr => {
+      // key that is marked as unique, can not be updated
+      if (body[attr.name]) {
+        throw new Error('');
+      }
+    });
+
     // get all the attributes for entity that are marked as to be auto update
     const autoUpdateAttributes = this.connection.getAutoUpdateAttributes(
       entityClass
@@ -217,6 +213,11 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
     // check if auto update attributes are not referenced by primary key
     const formattedAutoUpdateAttributes = autoUpdateAttributes.reduce(
       (acc, attr) => {
+        if ((primaryKey as any)[attr.name]) {
+          throw new Error(
+            `Failed to build update expression, key "${attr.name}" is marked as to up auto updated but is also referenced by primary key`
+          );
+        }
         acc[attr.name] = attr.autoGenerateValue(attr.strategy);
         return acc;
       },
@@ -242,51 +243,22 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
       ...affectedIndexes,
     });
 
-    const uniqueAttributesToUpdate = this.connection
-      .getUniqueAttributesForEntity(entityClass)
-      .filter(attr => !!body[attr.name]);
-
-    const itemToUpdate: DynamoDB.DocumentClient.UpdateItemInput = {
+    return {
       TableName: tableName,
       Key: {
         ...parsedPrimaryKey,
       },
       UpdateExpression,
-      // request all new attributes
-      ReturnValues: RETURN_VALUES.ALL_NEW,
       ExpressionAttributeNames,
       ExpressionAttributeValues,
-    };
-
-    // when item does not have any unique attributes to update, return putItemInput
-    if (!uniqueAttributesToUpdate.length) {
-      return itemToUpdate;
-    }
-
-    // if there are unique attributes, return a lazy loader, which will return write item list
-    const lazyLoadTransactionWriteItems = this.lazyToDynamoUpdateItemFactory<
-      PrimaryKey,
-      Entity
-    >(
-      metadata.table,
-      uniqueAttributesToUpdate,
-      dropProp(itemToUpdate, 'ReturnValues'),
-      body
-    );
-
-    return {
-      primaryKeyAttributes,
-      entityClass,
-      lazyLoadTransactionWriteItems,
+      ReturnValues: returnValues,
     };
   }
 
   toDynamoDeleteItem<PrimaryKey, Entity>(
     entityClass: EntityTarget<Entity>,
     primaryKey: PrimaryKey
-  ):
-    | DynamoDB.DocumentClient.DeleteItemInput
-    | LazyTransactionWriteItemListLoader {
+  ): DynamoDB.DocumentClient.DeleteItemInput {
     const metadata = this.connection.getEntityByTarget(entityClass);
 
     const tableName = metadata.table.name;
@@ -301,33 +273,12 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
       throw new Error('Primary could not be resolved');
     }
 
-    const uniqueAttributesToRemove = this.connection.getUniqueAttributesForEntity(
-      entityClass
-    );
-
-    const mainItemToRemove = {
+    return {
       TableName: tableName,
       Key: {
         ...parsedPrimaryKey,
       },
     };
-    // if item does not have any unique attributes return it as is
-    if (!uniqueAttributesToRemove?.length) {
-      return mainItemToRemove;
-    }
-
-    // or return lazy resolver
-    const lazyLoadTransactionWriteItems = this.lazyToDynamoRemoveItemFactory(
-      metadata.table,
-      uniqueAttributesToRemove,
-      mainItemToRemove
-    );
-
-    return {
-      primaryKeyAttributes: primaryKey,
-      entityClass,
-      lazyLoadTransactionWriteItems,
-    };
   }
 
   toDynamoQueryItem<PartitionKeyAttributes, Entity>(
@@ -459,116 +410,4 @@ export class DocumentClientRequestTransformer extends BaseTransformer {
       ...queryInputParams,
     };
   }
-
-  /**
-   * Lazy build update item input
-   * This is helpful in cases where we don't you have all the attributes to build item input, and the caller will need to
-   * to perform some sort of async call in order to fetch attributes and proceed with build
-   *
-   */
-  private lazyToDynamoUpdateItemFactory<PrimaryKey, Entity>(
-    table: Table,
-    uniqueAttributesToUpdate: Replace<
-      AttributeMetadata,
-      'unique',
-      {
-        unique: DynamoEntitySchemaPrimaryKey;
-      }
-    >[],
-    mainItem: DynamoDB.DocumentClient.UpdateItemInput,
-    newBody: UpdateAttributes<PrimaryKey, Entity>
-  ) {
-    // returns transact write item list
-    return (previousItemBody: any) => {
-      // updating unique attributes also require checking if new value exists
-      const uniqueRecordConditionExpression = new ExpressionBuilder().buildUniqueRecordConditionExpression(
-        table
-      );
-
-      // map all unique attributes to [put, delete] item tuple
-      const uniqueAttributeInputs: DynamoDB.DocumentClient.TransactWriteItemList = uniqueAttributesToUpdate.flatMap(
-        attr => {
-          const uniqueAttributeWriteItems: DynamoDB.DocumentClient.TransactWriteItemList = [
-            {
-              Put: {
-                TableName: table.name,
-                Item: {
-                  ...this.getParsedPrimaryKey(table, attr.unique, newBody),
-                },
-                ...uniqueRecordConditionExpression,
-              },
-            },
-          ];
-
-          // if unique attribute previously existed, remove it as part of the same transaction
-          if (previousItemBody[attr.name]) {
-            uniqueAttributeWriteItems.push({
-              Delete: {
-                TableName: table.name,
-                Key: {
-                  ...this.getParsedPrimaryKey(
-                    table,
-                    attr.unique,
-                    previousItemBody
-                  ),
-                },
-              },
-            });
-          }
-
-          return uniqueAttributeWriteItems;
-        }
-      );
-
-      // in order for update express to succeed, all listed must succeed in a transaction
-      return [
-        {Update: mainItem},
-        ...uniqueAttributeInputs,
-      ] as DynamoDB.DocumentClient.TransactWriteItem[];
-    };
-  }
-
-  /**
-   * lazily resolve all unique attribute items to remove
-   * @param table
-   * @param uniqueAttributesToRemove
-   * @param mainItem
-   */
-  private lazyToDynamoRemoveItemFactory(
-    table: Table,
-    uniqueAttributesToRemove: Replace<
-      AttributeMetadata,
-      'unique',
-      {
-        unique: DynamoEntitySchemaPrimaryKey;
-      }
-    >[],
-    mainItem: DynamoDB.DocumentClient.DeleteItemInput
-  ) {
-    return (existingItemBody: any) => {
-      const uniqueAttributeInputs: DynamoDB.DocumentClient.TransactWriteItemList = uniqueAttributesToRemove.map(
-        attr => {
-          return {
-            Delete: {
-              TableName: table.name,
-              Key: {
-                ...this.getParsedPrimaryKey(
-                  table,
-                  attr.unique,
-                  existingItemBody
-                ),
-              },
-            },
-          };
-        }
-      );
-
-      return [
-        {
-          Delete: mainItem,
-        },
-        ...uniqueAttributeInputs,
-      ] as DynamoDB.DocumentClient.TransactWriteItem[];
-    };
-  }
 }
diff --git a/packages/core/src/classes/transformer/is-lazy-transaction-write-item-list-loder.ts b/packages/core/src/classes/transformer/is-lazy-transaction-write-item-list-loder.ts
deleted file mode 100644
index fc6ba01..0000000
--- a/packages/core/src/classes/transformer/is-lazy-transaction-write-item-list-loder.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import {EntityTarget} from '@typedorm/common';
-import {DynamoDB} from 'aws-sdk';
-
-export type LazyTransactionWriteItemListLoader = {
-  lazyLoadTransactionWriteItems: (
-    previousItemBody: any
-  ) => DynamoDB.DocumentClient.TransactWriteItem[];
-  entityClass: EntityTarget<any>;
-  primaryKeyAttributes: any;
-};
-
-export const isLazyTransactionWriteItemListLoader = (
-  response: any
-): response is LazyTransactionWriteItemListLoader =>
-  !!(
-    (response as LazyTransactionWriteItemListLoader)
-      .lazyLoadTransactionWriteItems &&
-    typeof (response as LazyTransactionWriteItemListLoader)
-      .lazyLoadTransactionWriteItems === 'function'
-  );
diff --git a/packages/core/src/helpers/build-primary-key-schema.ts b/packages/core/src/helpers/build-primary-key-schema.ts
index 9ce73a8..e100572 100644
--- a/packages/core/src/helpers/build-primary-key-schema.ts
+++ b/packages/core/src/helpers/build-primary-key-schema.ts
@@ -12,7 +12,8 @@ export function buildPrimaryKeySchema({
   attributes: {[key: string]: string};
 }) {
   const partitionKeyInterpolations = getInterpolatedKeys(
-    primaryKey.partitionKey
+    primaryKey.partitionKey,
+    attributes
   );
 
   const tablePartitionKeyName = table.partitionKey;
@@ -29,7 +30,10 @@ export function buildPrimaryKeySchema({
 
     // build primary key
     const tableSortKeyName = table.sortKey ?? '';
-    const sortKeyInterpolations = getInterpolatedKeys(primaryKey.sortKey);
+    const sortKeyInterpolations = getInterpolatedKeys(
+      primaryKey.sortKey,
+      attributes
+    );
     return {
       [tablePartitionKeyName]: primaryKey.partitionKey,
       [tableSortKeyName]: primaryKey.sortKey,
diff --git a/packages/core/src/helpers/drop-prop.ts b/packages/core/src/helpers/drop-prop.ts
deleted file mode 100644
index b92431f..0000000
--- a/packages/core/src/helpers/drop-prop.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export function dropProp<T, K extends keyof T>(object: T, prop: K) {
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  const {[prop]: propToDrop, ...updatedObj} = object;
-  return updatedObj;
-}
diff --git a/packages/core/src/helpers/get-interpolated-keys.ts b/packages/core/src/helpers/get-interpolated-keys.ts
index 4521c55..cc6d1af 100644
--- a/packages/core/src/helpers/get-interpolated-keys.ts
+++ b/packages/core/src/helpers/get-interpolated-keys.ts
@@ -1,9 +1,12 @@
 import {regexInterpolatedWord} from './constants';
 
-export function getInterpolatedKeys(key: string) {
+export function getInterpolatedKeys(
+  key: string,
+  dict: {[key: string]: string}
+) {
   const matchIterator = key.matchAll(regexInterpolatedWord);
   const interpolatedKeys = [] as string[];
-  recursiveFindInterpolatedKeys(key, matchIterator, interpolatedKeys);
+  recursiveFindInterpolatedKeys(key, matchIterator, dict, interpolatedKeys);
 
   // there shouldn't be any duplicates, still to be double sure remove duplicates if there are any
   return [...new Set(interpolatedKeys)];
@@ -12,6 +15,7 @@ export function getInterpolatedKeys(key: string) {
 function recursiveFindInterpolatedKeys(
   key: string,
   iterator: IterableIterator<RegExpMatchArray>,
+  dict: {[key: string]: string},
   interpolatedKeys: string[]
 ) {
   const next = iterator.next();
@@ -21,8 +25,25 @@ function recursiveFindInterpolatedKeys(
 
   // regex match returns [match, key]
   const variable = next.value[1];
+  const variableType = dict[variable];
+
+  if (!variableType) {
+    throw new Error(
+      `key "${key}" references variable "${variable}" but it could not be resolved`
+    );
+  }
+
+  if (
+    variableType !== 'String' &&
+    variableType !== 'Number' &&
+    variableType !== 'Boolean'
+  ) {
+    throw new Error(
+      `"${variable}" is used in key ${key}, thus it's type must be a "Number" or a "String"`
+    );
+  }
 
   // push interpolated word to array
   interpolatedKeys.push(variable);
-  recursiveFindInterpolatedKeys(key, iterator, interpolatedKeys);
+  recursiveFindInterpolatedKeys(key, iterator, dict, interpolatedKeys);
 }
diff --git a/packages/core/src/helpers/is-used-for-primary-key.ts b/packages/core/src/helpers/is-used-for-primary-key.ts
deleted file mode 100644
index 3498808..0000000
--- a/packages/core/src/helpers/is-used-for-primary-key.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import {IsCompositePrimaryKey, PrimaryKey} from '@typedorm/common';
-import {DynamoEntitySchemaPrimaryKey} from '../classes/metadata/entity-metadata';
-import {getInterpolatedKeys} from './get-interpolated-keys';
-
-const IsRawPrimaryKey = (key: unknown): key is PrimaryKey => {
-  return !!(key as PrimaryKey).partitionKey;
-};
-
-export function isUsedForPrimaryKey(
-  primaryKey: DynamoEntitySchemaPrimaryKey | PrimaryKey,
-  attributeName: string
-) {
-  let primaryKeyInterpolations = [] as string[];
-
-  // if raw primary key was provided, resolve all the interpolations for it
-  if (IsRawPrimaryKey(primaryKey)) {
-    primaryKeyInterpolations = [
-      ...primaryKeyInterpolations,
-      ...getInterpolatedKeys(primaryKey.partitionKey),
-    ];
-
-    if (IsCompositePrimaryKey(primaryKey)) {
-      primaryKeyInterpolations = [
-        ...primaryKeyInterpolations,
-        ...getInterpolatedKeys(primaryKey.sortKey),
-      ];
-    }
-    // when parsed primary key is provided, flatten all interpolations to an array
-  } else {
-    const interpolationsToFlatten = primaryKey._interpolations ?? {};
-    primaryKeyInterpolations = [
-      ...primaryKeyInterpolations,
-      ...Object.keys(interpolationsToFlatten).flatMap(key => {
-        const currInterpolation = interpolationsToFlatten[key];
-        return currInterpolation;
-      }),
-    ];
-  }
-
-  // check if there is a matching attribute name
-  return primaryKeyInterpolations.includes(attributeName);
-}
diff --git a/packages/core/src/helpers/validate-key.ts b/packages/core/src/helpers/validate-key.ts
index a2568a5..63af1f4 100644
--- a/packages/core/src/helpers/validate-key.ts
+++ b/packages/core/src/helpers/validate-key.ts
@@ -29,13 +29,9 @@ function validateMatch(
     );
   }
 
-  if (
-    variableType !== 'String' &&
-    variableType !== 'Number' &&
-    variableType !== 'Boolean'
-  ) {
+  if (variableType !== 'String' && variableType !== 'Number') {
     throw new Error(
-      `"${variable}" is used in key ${key}, thus it's type must be or scalar type, if attribute type is Enum, please set "isEnum" to true in attribute decorator.`
+      `"${variable}" is used in key ${key}, thus it's type must be a "Number" or a "String"`
     );
   }
 


Test patch:
diff --git a/packages/core/src/classes/__test__/expression-builder.spec.ts b/packages/core/src/classes/__test__/expression-builder.spec.ts
index f81e323..38635f2 100644
--- a/packages/core/src/classes/__test__/expression-builder.spec.ts
+++ b/packages/core/src/classes/__test__/expression-builder.spec.ts
@@ -87,3 +87,43 @@ test('builds update expression for complex nested object', () => {
     },
   });
 });
+
+/**
+ * Issue 41
+ */
+test('allows updating attribute with undefined value', () => {
+  const item = {
+    'user.name': undefined,
+  };
+  const expression = expressionBuilder.buildUpdateExpression(item);
+  expect(expression).toEqual({
+    UpdateExpression: 'SET #attr0_inner0.#attr0_inner1 = :val0',
+    ExpressionAttributeNames: {
+      '#attr0_inner0': 'user',
+      '#attr0_inner1': 'name',
+    },
+    ExpressionAttributeValues: {
+      ':val0': null,
+    },
+  });
+});
+
+/**
+ * Issue 41
+ */
+test('allows updating attribute with empty string', () => {
+  const item = {
+    'user.name': '',
+  };
+  const expression = expressionBuilder.buildUpdateExpression(item);
+  expect(expression).toEqual({
+    UpdateExpression: 'SET #attr0_inner0.#attr0_inner1 = :val0',
+    ExpressionAttributeNames: {
+      '#attr0_inner0': 'user',
+      '#attr0_inner1': 'name',
+    },
+    ExpressionAttributeValues: {
+      ':val0': '',
+    },
+  });
+});
diff --git a/packages/core/src/classes/connection/__test__/attributes-metadata-builder.spec.ts b/packages/core/src/classes/connection/__test__/attributes-metadata-builder.spec.ts
index cc05aee..ef63d32 100644
--- a/packages/core/src/classes/connection/__test__/attributes-metadata-builder.spec.ts
+++ b/packages/core/src/classes/connection/__test__/attributes-metadata-builder.spec.ts
@@ -1,12 +1,6 @@
 import {UserUniqueEmail} from './../../../../__mocks__/user-unique-email';
 import {table} from './../../../../__mocks__/table';
-import {
-  Entity,
-  Table,
-  Attribute,
-  AutoGenerateAttribute,
-  AUTO_GENERATE_ATTRIBUTE_STRATEGY,
-} from '@typedorm/common';
+import {Entity, Table, Attribute} from '@typedorm/common';
 import {UserAutoGenerateAttributes} from '../../../../__mocks__/user-auto-generate-attributes';
 import {User} from '../../../../__mocks__/user';
 import {AttributesMetadataBuilder} from '../attribute-metadata-builder';
@@ -20,7 +14,7 @@ beforeEach(() => {
 
 test('builds simple attribute metadata', () => {
   const metadata = attributesMetadataBuilder
-    .build(table, User, User)
+    .build(table, User)
     .map(obj => Object.assign({}, obj));
 
   expect(metadata).toEqual([
@@ -52,13 +46,6 @@ test('builds simple attribute metadata', () => {
 });
 
 test('builds attribute metadata for inherited entity', () => {
-  @Entity({
-    name: 'admin',
-    primaryKey: {
-      partitionKey: 'ADMIN#{{id}}',
-      sortKey: 'ADMIN#{{id}}',
-    },
-  })
   class Admin extends User {}
 
   const metadata = attributesMetadataBuilder
@@ -95,7 +82,7 @@ test('builds attribute metadata for inherited entity', () => {
 
 test('builds multi type attribute metadata', () => {
   const metadata = attributesMetadataBuilder
-    .build(table, UserAutoGenerateAttributes, UserAutoGenerateAttributes)
+    .build(table, UserAutoGenerateAttributes)
     .map(obj => Object.assign({}, obj));
 
   expect(metadata).toEqual([
@@ -137,7 +124,7 @@ test('builds metadata for attribute with explicit entity', () => {
   }
 
   const metadata = attributesMetadataBuilder
-    .build(table, Admin, Admin)
+    .build(table, Admin)
     .map(obj => Object.assign({}, obj));
 
   expect(metadata).toEqual([
@@ -160,7 +147,7 @@ test('builds metadata for attribute with explicit entity', () => {
 
 test('builds metadata with implicit primary key for unique attribute', () => {
   const metadata = attributesMetadataBuilder
-    .build(table, UserUniqueEmail, UserUniqueEmail)
+    .build(table, UserUniqueEmail)
     .map(obj => Object.assign({}, obj));
 
   expect(metadata).toEqual([
@@ -198,30 +185,3 @@ test('builds metadata with implicit primary key for unique attribute', () => {
     },
   ]);
 });
-
-test('throws an error when attribute referenced in primary key is also marked as "autoUpdate"', () => {
-  @Entity({
-    name: 'admin',
-    table,
-    primaryKey: {
-      partitionKey: 'ADMIN#{{adminId}}',
-      sortKey: 'ADMIN#{{adminId}}',
-    },
-  })
-  class Admin extends User {
-    @AutoGenerateAttribute({
-      strategy: AUTO_GENERATE_ATTRIBUTE_STRATEGY.UUID4,
-      autoUpdate: true,
-    })
-    adminId: string;
-  }
-
-  const metadata = () =>
-    attributesMetadataBuilder
-      .build(table, Admin, User)
-      .map(obj => Object.assign({}, obj));
-
-  expect(metadata).toThrow(
-    'Failed to build metadata for "adminId", attributes referenced in primary key cannot be auto updated.'
-  );
-});
diff --git a/packages/core/src/classes/connection/__test__/connection-manager.spec.ts b/packages/core/src/classes/connection/__test__/connection-manager.spec.ts
index d09b6e6..3104275 100644
--- a/packages/core/src/classes/connection/__test__/connection-manager.spec.ts
+++ b/packages/core/src/classes/connection/__test__/connection-manager.spec.ts
@@ -3,6 +3,7 @@ import {getConnection} from '@typedorm/core';
 import {User} from '../../../../__mocks__/user';
 import {createTestConnection, resetTestConnection} from '@typedorm/testing';
 import path from 'path';
+import {ConnectionManager} from '../connection-manager';
 jest.useFakeTimers('modern').setSystemTime(new Date('2020-01-01'));
 
 beforeEach(() => {
@@ -97,3 +98,23 @@ test('Connection manager should properly configure global table instance for sin
 
   expect(userMetadata.table).toEqual(globalTable);
 });
+
+/**
+ * Issue: 38
+ */
+test('Auto removes connection when failed to connect', () => {
+  const connectionManager = new ConnectionManager();
+  const createdConnection = connectionManager.create({
+    entities: [User],
+  });
+
+  createdConnection.buildMetadatas = jest.fn().mockImplementation(() => {
+    throw new Error('Failed to build metadata');
+  });
+
+  createdConnection.connect();
+
+  expect(() => connectionManager.get()).toThrow(
+    'No such connection with name "default" exists'
+  );
+});
diff --git a/packages/core/src/classes/connection/__test__/entity-metadata-builder.spec.ts b/packages/core/src/classes/connection/__test__/entity-metadata-builder.spec.ts
index ec9fe4f..0868578 100644
--- a/packages/core/src/classes/connection/__test__/entity-metadata-builder.spec.ts
+++ b/packages/core/src/classes/connection/__test__/entity-metadata-builder.spec.ts
@@ -169,6 +169,12 @@ test('overrides property of base class if it is defined again on derived class w
 
   const [entityMetadata] = metadataBuilder.build([Customer]);
   expect(entityMetadata.attributes.map(obj => Object.assign({}, obj))).toEqual([
+    {
+      name: 'email',
+      type: 'String',
+      table,
+      entityClass: Customer,
+    },
     {
       name: 'password',
       type: 'String',
diff --git a/packages/core/src/classes/manager/__test__/entity-manager.spec.ts b/packages/core/src/classes/manager/__test__/entity-manager.spec.ts
index a1908a6..ef594e8 100644
--- a/packages/core/src/classes/manager/__test__/entity-manager.spec.ts
+++ b/packages/core/src/classes/manager/__test__/entity-manager.spec.ts
@@ -11,20 +11,17 @@ import {
   UserAutoGenerateAttributesPrimaryKey,
   UserAutoGenerateAttributes,
 } from '../../../../__mocks__/user-auto-generate-attributes';
-import {Connection} from '../../connection/connection';
 
 let manager: EntityManager;
-let connection: Connection;
 const dcMock = {
   put: jest.fn(),
   get: jest.fn(),
   update: jest.fn(),
   delete: jest.fn(),
   query: jest.fn(),
-  transactWrite: jest.fn(),
 };
 beforeEach(() => {
-  connection = createTestConnection({
+  const connection = createTestConnection({
     entities: [User, UserUniqueEmail, UserAutoGenerateAttributes],
     documentClient: dcMock,
   });
@@ -63,11 +60,9 @@ test('creates entity', async () => {
       status: 'active',
     },
     TableName: 'test-table',
-    ConditionExpression:
-      'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+    ConditionExpression: 'attribute_not_exists(#CE_PK)',
     ExpressionAttributeNames: {
       '#CE_PK': 'PK',
-      '#CE_SK': 'SK',
     },
   });
   expect(userEntity).toEqual({
@@ -101,11 +96,9 @@ test('creates entity and returns all attributes, including auto generated ones',
       updatedAt: 1606896235,
     },
     TableName: 'test-table',
-    ConditionExpression:
-      'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+    ConditionExpression: 'attribute_not_exists(#CE_PK)',
     ExpressionAttributeNames: {
       '#CE_PK': 'PK',
-      '#CE_SK': 'SK',
     },
   });
   expect(userEntity).toEqual({
@@ -231,7 +224,7 @@ test('throws an error if trying to perform exists check with partial primary key
 /**
  * @group update
  */
-test('updates item and return all new attributes', async () => {
+test('updates item', async () => {
   dcMock.update.mockReturnValue({
     promise: () => ({
       Attributes: {
@@ -240,7 +233,7 @@ test('updates item and return all new attributes', async () => {
         GSI1PK: 'USER#STATUS#active',
         GSI1SK: 'USER#Me',
         id: '1',
-        name: 'user',
+        name: 'Me',
         status: 'active',
       },
     }),
@@ -276,7 +269,7 @@ test('updates item and return all new attributes', async () => {
     UpdateExpression:
       'SET #attr0 = :val0, #attr1 = :val1, #attr2 = :val2, #attr3 = :val3',
   });
-  expect(updatedItem).toEqual({id: '1', name: 'user', status: 'active'});
+  expect(updatedItem).toEqual({id: '1', name: 'Me', status: 'active'});
 });
 
 test('updates item and attributes marked to be autoUpdated', async () => {
@@ -328,116 +321,6 @@ test('updates item and attributes marked to be autoUpdated', async () => {
   expect(updatedItem).toEqual({id: '1', name: 'Me', status: 'active'});
 });
 
-test('updates item with unique attributes and returns all updated attributes', async () => {
-  manager.findOne = jest
-    .fn()
-    // mock first call to return existing item, this will be called before update is performed
-    .mockImplementationOnce(() => ({
-      id: '1',
-      email: 'old@email.com',
-      status: 'active',
-    }))
-    // mock send call to return new updated item, this will be called after update is performed
-    .mockImplementationOnce(() => ({
-      id: '1',
-      email: 'new@email.com',
-      status: 'active',
-    }));
-
-  const updateOperationSpy = dcMock.transactWrite.mockReturnValue({
-    on: jest.fn(),
-    send: jest.fn().mockImplementation(cb => {
-      cb(null, {
-        ConsumedCapacity: [{}],
-        ItemCollectionMetrics: [{}],
-      });
-    }),
-  });
-
-  const updatedItem = await manager.update<
-    UserUniqueEmailPrimaryKey,
-    UserUniqueEmail
-  >(
-    UserUniqueEmail,
-    {
-      id: '1',
-    },
-    {
-      email: 'new@examil.com',
-    }
-  );
-
-  expect(updateOperationSpy).toHaveBeenCalledTimes(1);
-  expect(updateOperationSpy).toHaveBeenCalledWith({
-    TransactItems: [
-      {
-        Update: {
-          ExpressionAttributeNames: {
-            '#attr0': 'email',
-          },
-          ExpressionAttributeValues: {
-            ':val0': 'new@examil.com',
-          },
-          Key: {
-            PK: 'USER#1',
-            SK: 'USER#1',
-          },
-          TableName: 'test-table',
-          UpdateExpression: 'SET #attr0 = :val0',
-        },
-      },
-      {
-        Put: {
-          ConditionExpression:
-            'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-          ExpressionAttributeNames: {
-            '#CE_PK': 'PK',
-            '#CE_SK': 'SK',
-          },
-          Item: {
-            PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@examil.com',
-            SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@examil.com',
-          },
-          TableName: 'test-table',
-        },
-      },
-      {
-        Delete: {
-          Key: {
-            PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#old@email.com',
-            SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#old@email.com',
-          },
-          TableName: 'test-table',
-        },
-      },
-    ],
-  });
-  expect(updatedItem).toEqual({
-    id: '1',
-    email: 'new@email.com',
-    status: 'active',
-  });
-});
-
-test('does not update an item when failed to get item by key', async () => {
-  manager.findOne = jest.fn();
-
-  const updatedItem = async () =>
-    await manager.update<UserUniqueEmailPrimaryKey, UserUniqueEmail>(
-      UserUniqueEmail,
-      {
-        id: '1',
-      },
-      {
-        email: 'new@examil.com',
-      }
-    );
-
-  await expect(updatedItem).rejects.toThrow(
-    'Failed to update entity, could not find entity with primary key "{"id":"1"}"'
-  );
-});
-
 /**
  * @group delete
  */
@@ -472,58 +355,6 @@ test('throws an error when trying to delete item by non primary key attributes',
   ).rejects.toThrowError('Could not resolve "id" from given dictionary');
 });
 
-test('deletes an item with unique attributes', async () => {
-  manager.findOne = jest.fn().mockReturnValue({
-    id: '1',
-    email: 'old@email.com',
-    status: 'active',
-  });
-
-  const deleteItemOperation = dcMock.transactWrite.mockReturnValue({
-    on: jest.fn(),
-    send: jest.fn().mockImplementation(cb => {
-      cb(null, {
-        ConsumedCapacity: [{}],
-        ItemCollectionMetrics: [{}],
-      });
-    }),
-  });
-
-  const deletedResponse = await manager.delete<
-    UserUniqueEmailPrimaryKey,
-    UserUniqueEmail
-  >(UserUniqueEmail, {
-    id: '1',
-  });
-
-  expect(deleteItemOperation).toHaveBeenCalledTimes(1);
-  expect(deleteItemOperation).toHaveBeenCalledWith({
-    TransactItems: [
-      {
-        Delete: {
-          Key: {
-            PK: 'USER#1',
-            SK: 'USER#1',
-          },
-          TableName: 'test-table',
-        },
-      },
-      {
-        Delete: {
-          Key: {
-            PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#old@email.com',
-            SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#old@email.com',
-          },
-          TableName: 'test-table',
-        },
-      },
-    ],
-  });
-  expect(deletedResponse).toEqual({
-    success: true,
-  });
-});
-
 /**
  * @group find
  */
diff --git a/packages/core/src/classes/manager/__test__/transaction-manager.spec.ts b/packages/core/src/classes/manager/__test__/transaction-manager.spec.ts
index 589bbcb..b661bf9 100644
--- a/packages/core/src/classes/manager/__test__/transaction-manager.spec.ts
+++ b/packages/core/src/classes/manager/__test__/transaction-manager.spec.ts
@@ -4,10 +4,6 @@ import {Connection} from '../../connection/connection';
 import {WriteTransaction} from '../../transaction/write-transaction';
 import {TransactionManager} from '../transaction-manager';
 import {User} from '../../../../__mocks__/user';
-import {
-  UserUniqueEmail,
-  UserUniqueEmailPrimaryKey,
-} from '@typedorm/core/__mocks__/user-unique-email';
 
 let manager: TransactionManager;
 const dcMock = {
@@ -17,7 +13,7 @@ const dcMock = {
 let connection: Connection;
 beforeEach(() => {
   connection = createTestConnection({
-    entities: [User, UserUniqueEmail],
+    entities: [User],
     documentClient: dcMock,
   });
   manager = new TransactionManager(connection);
@@ -30,7 +26,7 @@ afterEach(() => {
 /**
  * @group write
  */
-test('performs write transactions for simple writes', async () => {
+test('performs write transactions', async () => {
   dcMock.transactWrite.mockReturnValue({
     promise: jest.fn().mockReturnValue({
       ConsumedCapacity: [{}],
@@ -96,11 +92,9 @@ test('performs write transactions for simple writes', async () => {
             status: 'inactive',
           },
           TableName: 'test-table',
-          ConditionExpression:
-            'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+          ConditionExpression: 'attribute_not_exists(#CE_PK)',
           ExpressionAttributeNames: {
             '#CE_PK': 'PK',
-            '#CE_SK': 'SK',
           },
         },
       },
@@ -118,6 +112,7 @@ test('performs write transactions for simple writes', async () => {
             PK: 'USER#1',
             SK: 'USER#1',
           },
+          ReturnValues: 'ALL_NEW',
           TableName: 'test-table',
           UpdateExpression: 'SET #attr0 = :val0, #attr1 = :val1',
         },
@@ -135,11 +130,9 @@ test('performs write transactions for simple writes', async () => {
             status: 'inactive',
           },
           TableName: 'test-table',
-          ConditionExpression:
-            'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+          ConditionExpression: 'attribute_not_exists(#CE_PK)',
           ExpressionAttributeNames: {
             '#CE_PK': 'PK',
-            '#CE_SK': 'SK',
           },
         },
       },
@@ -150,248 +143,3 @@ test('performs write transactions for simple writes', async () => {
     ItemCollectionMetrics: [{}],
   });
 });
-
-test('performs write transactions for entities with unique attributes ', async () => {
-  connection.entityManager.findOne = jest.fn().mockReturnValue({
-    id: '1',
-    email: 'olduser@example.com',
-  });
-
-  dcMock.transactWrite.mockReturnValue({
-    on: jest.fn(),
-    send: jest.fn().mockImplementation(cb => {
-      cb(null, {
-        ConsumedCapacity: [{}],
-        ItemCollectionMetrics: [{}],
-      });
-    }),
-  });
-
-  const transaction = new WriteTransaction(connection).chian<
-    UserUniqueEmailPrimaryKey,
-    UserUniqueEmail
-  >({
-    update: {
-      item: UserUniqueEmail,
-      primaryKey: {
-        id: '1',
-      },
-      body: {
-        email: 'new@example.com',
-        status: 'active',
-      },
-    },
-  });
-
-  await manager.write(transaction);
-
-  expect(dcMock.transactWrite).toHaveBeenCalledTimes(1);
-  expect(dcMock.transactWrite).toHaveBeenCalledWith({
-    TransactItems: [
-      {
-        Update: {
-          ExpressionAttributeNames: {
-            '#attr0': 'email',
-            '#attr1': 'status',
-            '#attr2': 'GSI1PK',
-          },
-          ExpressionAttributeValues: {
-            ':val0': 'new@example.com',
-            ':val1': 'active',
-            ':val2': 'USER#STATUS#active',
-          },
-          Key: {
-            PK: 'USER#1',
-            SK: 'USER#1',
-          },
-          TableName: 'test-table',
-          UpdateExpression:
-            'SET #attr0 = :val0, #attr1 = :val1, #attr2 = :val2',
-        },
-      },
-      {
-        Put: {
-          ConditionExpression:
-            'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-          ExpressionAttributeNames: {
-            '#CE_PK': 'PK',
-            '#CE_SK': 'SK',
-          },
-          Item: {
-            PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@example.com',
-            SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@example.com',
-          },
-          TableName: 'test-table',
-        },
-      },
-      {
-        Delete: {
-          Key: {
-            PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#olduser@example.com',
-            SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#olduser@example.com',
-          },
-          TableName: 'test-table',
-        },
-      },
-    ],
-  });
-});
-
-test('performs write transactions for entities with non existing unique attributes ', async () => {
-  connection.entityManager.findOne = jest.fn().mockReturnValue({
-    id: '1',
-    status: 'inactive',
-  });
-
-  dcMock.transactWrite.mockReturnValue({
-    on: jest.fn(),
-    send: jest.fn().mockImplementation(cb => {
-      cb(null, {
-        ConsumedCapacity: [{}],
-        ItemCollectionMetrics: [{}],
-      });
-    }),
-  });
-
-  const transaction = new WriteTransaction(connection).chian<
-    UserUniqueEmailPrimaryKey,
-    UserUniqueEmail
-  >({
-    update: {
-      item: UserUniqueEmail,
-      primaryKey: {
-        id: '1',
-      },
-      body: {
-        email: 'new@example.com',
-        status: 'active',
-      },
-    },
-  });
-
-  await manager.write(transaction);
-
-  expect(dcMock.transactWrite).toHaveBeenCalledTimes(1);
-  expect(dcMock.transactWrite).toHaveBeenCalledWith({
-    TransactItems: [
-      {
-        Update: {
-          ExpressionAttributeNames: {
-            '#attr0': 'email',
-            '#attr1': 'status',
-            '#attr2': 'GSI1PK',
-          },
-          ExpressionAttributeValues: {
-            ':val0': 'new@example.com',
-            ':val1': 'active',
-            ':val2': 'USER#STATUS#active',
-          },
-          Key: {
-            PK: 'USER#1',
-            SK: 'USER#1',
-          },
-          TableName: 'test-table',
-          UpdateExpression:
-            'SET #attr0 = :val0, #attr1 = :val1, #attr2 = :val2',
-        },
-      },
-      {
-        Put: {
-          ConditionExpression:
-            'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-          ExpressionAttributeNames: {
-            '#CE_PK': 'PK',
-            '#CE_SK': 'SK',
-          },
-          Item: {
-            PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@example.com',
-            SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@example.com',
-          },
-          TableName: 'test-table',
-        },
-      },
-    ],
-  });
-});
-
-test('throws an error if no existing item found', async () => {
-  connection.entityManager.findOne = jest.fn();
-
-  const transaction = new WriteTransaction(connection).chian<
-    UserUniqueEmailPrimaryKey,
-    UserUniqueEmail
-  >({
-    update: {
-      item: UserUniqueEmail,
-      primaryKey: {
-        id: '1',
-      },
-      body: {
-        email: 'new@example.com',
-        status: 'active',
-      },
-    },
-  });
-
-  const update = () => manager.write(transaction);
-
-  expect(dcMock.transactWrite).not.toHaveBeenCalled();
-  await expect(update).rejects.toThrow(
-    'Failed to process entity "UserUniqueEmail", could not find entity with primary key "{"id":"1"}"'
-  );
-});
-
-test('performs write transactions when removing entities with unique attributes ', async () => {
-  connection.entityManager.findOne = jest.fn().mockReturnValue({
-    id: '1',
-    email: 'olduser@example.com',
-  });
-
-  dcMock.transactWrite.mockReturnValue({
-    on: jest.fn(),
-    send: jest.fn().mockImplementation(cb => {
-      cb(null, {
-        ConsumedCapacity: [{}],
-        ItemCollectionMetrics: [{}],
-      });
-    }),
-  });
-
-  const transaction = new WriteTransaction(connection).chian<
-    UserUniqueEmailPrimaryKey,
-    UserUniqueEmail
-  >({
-    delete: {
-      item: UserUniqueEmail,
-      primaryKey: {
-        id: '1',
-      },
-    },
-  });
-
-  await manager.write(transaction);
-
-  expect(dcMock.transactWrite).toHaveBeenCalledTimes(1);
-  expect(dcMock.transactWrite).toHaveBeenCalledWith({
-    TransactItems: [
-      {
-        Delete: {
-          Key: {
-            PK: 'USER#1',
-            SK: 'USER#1',
-          },
-          TableName: 'test-table',
-        },
-      },
-      {
-        Delete: {
-          Key: {
-            PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#olduser@example.com',
-            SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#olduser@example.com',
-          },
-          TableName: 'test-table',
-        },
-      },
-    ],
-  });
-});
diff --git a/packages/core/src/classes/transaction/__test__/write-transaction.spec.ts b/packages/core/src/classes/transaction/__test__/write-transaction.spec.ts
index b1f4699..f0d8164 100644
--- a/packages/core/src/classes/transaction/__test__/write-transaction.spec.ts
+++ b/packages/core/src/classes/transaction/__test__/write-transaction.spec.ts
@@ -75,11 +75,9 @@ test('chains simple transaction requests', () => {
           status: 'active',
         },
         TableName: 'test-table',
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+        ConditionExpression: 'attribute_not_exists(#CE_PK)',
         ExpressionAttributeNames: {
           '#CE_PK': 'PK',
-          '#CE_SK': 'SK',
         },
       },
     },
@@ -91,6 +89,7 @@ test('chains simple transaction requests', () => {
           ':val1': 'USER#new name',
         },
         Key: {PK: 'USER#123', SK: 'USER#123'},
+        ReturnValues: 'ALL_NEW',
         TableName: 'test-table',
         UpdateExpression: 'SET #attr0 = :val0, #attr1 = :val1',
       },
@@ -111,9 +110,9 @@ test('chains complex transaction requests', () => {
         item: user,
       },
     })
-    .chian<UserPrimaryKey, UserUniqueEmail>({
+    .chian<UserPrimaryKey, User>({
       update: {
-        item: UserUniqueEmail,
+        item: User,
         primaryKey: {
           id: '123',
         },
@@ -123,14 +122,12 @@ test('chains complex transaction requests', () => {
       },
     });
 
-  expect(transaction.items).toMatchObject([
+  expect(transaction.items).toEqual([
     {
       Put: {
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+        ConditionExpression: 'attribute_not_exists(#CE_PK)',
         ExpressionAttributeNames: {
           '#CE_PK': 'PK',
-          '#CE_SK': 'SK',
         },
         Item: {
           PK: 'USER#1',
@@ -148,11 +145,9 @@ test('chains complex transaction requests', () => {
     },
     {
       Put: {
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+        ConditionExpression: 'attribute_not_exists(#CE_PK)',
         ExpressionAttributeNames: {
           '#CE_PK': 'PK',
-          '#CE_SK': 'SK',
         },
         Item: {
           PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#user@example.com',
@@ -162,9 +157,20 @@ test('chains complex transaction requests', () => {
       },
     },
     {
-      entityClass: UserUniqueEmail,
-      primaryKeyAttributes: {
-        id: '123',
+      Update: {
+        ExpressionAttributeNames: {
+          '#attr0': 'email',
+        },
+        ExpressionAttributeValues: {
+          ':val0': 'example@email.com',
+        },
+        Key: {
+          PK: 'USER#123',
+          SK: 'USER#123',
+        },
+        ReturnValues: 'ALL_NEW',
+        TableName: 'test-table',
+        UpdateExpression: 'SET #attr0 = :val0',
       },
     },
   ]);
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 17120e6..617990d 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
@@ -5,15 +5,11 @@ import {User, UserGSI1} from '../../../../__mocks__/user';
 import {createTestConnection, resetTestConnection} from '@typedorm/testing';
 import {UserPrimaryKey} from '../../../../__mocks__/user';
 import {DocumentClientRequestTransformer} from '../document-client-request-transformer';
-import {
-  UserUniqueEmail,
-  UserUniqueEmailPrimaryKey,
-} from '../../../../__mocks__/user-unique-email';
 
 let transformer: DocumentClientRequestTransformer;
 beforeEach(async () => {
   const connection = createTestConnection({
-    entities: [User, Customer, UserUniqueEmail],
+    entities: [User, Customer],
   });
   transformer = new DocumentClientRequestTransformer(connection);
 });
@@ -73,11 +69,9 @@ test('transforms put item requests', () => {
       __en: 'user',
       status: 'active',
     },
-    ConditionExpression:
-      'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
+    ConditionExpression: 'attribute_not_exists(#CE_PK)',
     ExpressionAttributeNames: {
       '#CE_PK': 'PK',
-      '#CE_SK': 'SK',
     },
     TableName: 'test-table',
   });
@@ -104,43 +98,37 @@ test('transforms put item request with unique attributes', () => {
     email: string;
   }
 
-  const newConnection = createTestConnection({
+  const connection = createTestConnection({
     entities: [UserUniqueEmail],
   });
-  const newTransformer = new DocumentClientRequestTransformer(newConnection);
+  transformer = new DocumentClientRequestTransformer(connection);
 
   const user = new UserUniqueEmail();
   user.id = '1';
   user.email = 'user@example.com';
 
-  const putItem = newTransformer.toDynamoPutItem(user);
+  const putItem = transformer.toDynamoPutItem(user);
   expect(putItem).toEqual([
     {
-      Put: {
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-        ExpressionAttributeNames: {'#CE_PK': 'PK', '#CE_SK': 'SK'},
-        Item: {
-          PK: 'USER#1',
-          SK: 'USER#1',
-          email: 'user@example.com',
-          id: '1',
-          __en: 'user',
-        },
-        TableName: 'test-table',
+      ConditionExpression: 'attribute_not_exists(#CE_PK)',
+      ExpressionAttributeNames: {'#CE_PK': 'PK'},
+      Item: {
+        PK: 'USER#1',
+        SK: 'USER#1',
+        email: 'user@example.com',
+        id: '1',
+        __en: 'user',
       },
+      TableName: 'test-table',
     },
     {
-      Put: {
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-        ExpressionAttributeNames: {'#CE_PK': 'PK', '#CE_SK': 'SK'},
-        Item: {
-          PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#user@example.com',
-          SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#user@example.com',
-        },
-        TableName: 'test-table',
+      ConditionExpression: 'attribute_not_exists(#CE_PK)',
+      ExpressionAttributeNames: {'#CE_PK': 'PK'},
+      Item: {
+        PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#user@example.com',
+        SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#user@example.com',
       },
+      TableName: 'test-table',
     },
   ]);
 });
@@ -169,43 +157,37 @@ test('transforms put item request consisting unique attributes with provided pri
     email: string;
   }
 
-  const newConnection = createTestConnection({
+  const connection = createTestConnection({
     entities: [UserUniqueEmail],
   });
-  const newTransformer = new DocumentClientRequestTransformer(newConnection);
+  transformer = new DocumentClientRequestTransformer(connection);
 
   const user = new UserUniqueEmail();
   user.id = '1';
   user.email = 'user@example.com';
 
-  const putItem = newTransformer.toDynamoPutItem(user);
+  const putItem = transformer.toDynamoPutItem(user);
   expect(putItem).toEqual([
     {
-      Put: {
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-        ExpressionAttributeNames: {'#CE_PK': 'PK', '#CE_SK': 'SK'},
-        Item: {
-          PK: 'USER#1',
-          SK: 'USER#1',
-          email: 'user@example.com',
-          id: '1',
-          __en: 'user',
-        },
-        TableName: 'test-table',
+      ConditionExpression: 'attribute_not_exists(#CE_PK)',
+      ExpressionAttributeNames: {'#CE_PK': 'PK'},
+      Item: {
+        PK: 'USER#1',
+        SK: 'USER#1',
+        email: 'user@example.com',
+        id: '1',
+        __en: 'user',
       },
+      TableName: 'test-table',
     },
     {
-      Put: {
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-        ExpressionAttributeNames: {'#CE_PK': 'PK', '#CE_SK': 'SK'},
-        Item: {
-          PK: 'CUSTOM#user@example.com',
-          SK: 'CUSTOM#user@example.com',
-        },
-        TableName: 'test-table',
+      ConditionExpression: 'attribute_not_exists(#CE_PK)',
+      ExpressionAttributeNames: {'#CE_PK': 'PK'},
+      Item: {
+        PK: 'CUSTOM#user@example.com',
+        SK: 'CUSTOM#user@example.com',
       },
+      TableName: 'test-table',
     },
   ]);
 });
@@ -242,71 +224,6 @@ test('transforms update item request', () => {
   });
 });
 
-test('transforms update item record with unique attributes', () => {
-  const updatedItem = transformer.toDynamoUpdateItem<
-    UserPrimaryKey,
-    UserUniqueEmail
-  >(
-    UserUniqueEmail,
-    {
-      id: '1',
-    },
-    {
-      name: 'new name',
-      email: 'new@email.com',
-    }
-  );
-
-  const lazyWriteItemListLoader = (updatedItem as any)
-    .lazyLoadTransactionWriteItems;
-  expect(typeof lazyWriteItemListLoader).toEqual('function');
-
-  const writeItemList = lazyWriteItemListLoader({
-    name: 'new name',
-    email: 'old@email.com',
-  });
-  expect(writeItemList).toEqual([
-    {
-      Update: {
-        ExpressionAttributeNames: {
-          '#attr0': 'name',
-          '#attr1': 'email',
-          '#attr2': 'GSI1SK',
-        },
-        ExpressionAttributeValues: {
-          ':val0': 'new name',
-          ':val1': 'new@email.com',
-          ':val2': 'USER#new name',
-        },
-        Key: {PK: 'USER#1', SK: 'USER#1'},
-        TableName: 'test-table',
-        UpdateExpression: 'SET #attr0 = :val0, #attr1 = :val1, #attr2 = :val2',
-      },
-    },
-    {
-      Put: {
-        ConditionExpression:
-          'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-        ExpressionAttributeNames: {'#CE_PK': 'PK', '#CE_SK': 'SK'},
-        Item: {
-          PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@email.com',
-          SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#new@email.com',
-        },
-        TableName: 'test-table',
-      },
-    },
-    {
-      Delete: {
-        Key: {
-          PK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#old@email.com',
-          SK: 'DRM_GEN_USERUNIQUEEMAIL.EMAIL#old@email.com',
-        },
-        TableName: 'test-table',
-      },
-    },
-  ]);
-});
-
 /**
  * @group toDynamoDeleteItem
  */
@@ -326,21 +243,6 @@ test('transforms delete item request', () => {
   });
 });
 
-test('transforms delete item request with unique attributes', () => {
-  const deleteItemInput = transformer.toDynamoDeleteItem<
-    UserUniqueEmailPrimaryKey,
-    UserUniqueEmail
-  >(UserUniqueEmail, {
-    id: '1',
-  });
-  expect(deleteItemInput).toMatchObject({
-    entityClass: UserUniqueEmail,
-    primaryKeyAttributes: {
-      id: '1',
-    },
-  });
-});
-
 /**
  * @group toDynamoQueryItem
  */
diff --git a/packages/core/src/classes/transformer/__test__/expression-builder.spec.ts b/packages/core/src/classes/transformer/__test__/expression-builder.spec.ts
deleted file mode 100644
index 469ef53..0000000
--- a/packages/core/src/classes/transformer/__test__/expression-builder.spec.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import {Table} from '@typedorm/common';
-import {ExpressionBuilder} from '../../expression-builder';
-
-test('builds unique record expression for table with simple key', () => {
-  const simpleTable = new Table({
-    name: 'simple-primary-key-table',
-    partitionKey: 'PK',
-  });
-  const expression = new ExpressionBuilder().buildUniqueRecordConditionExpression(
-    simpleTable
-  );
-  expect(expression).toEqual({
-    ConditionExpression: 'attribute_not_exists(#CE_PK)',
-    ExpressionAttributeNames: {
-      '#CE_PK': 'PK',
-    },
-  });
-});
-
-test('builds unique record expression for table with composite key', () => {
-  const simpleTable = new Table({
-    name: 'simple-primary-key-table',
-    partitionKey: 'PK',
-    sortKey: 'SK',
-  });
-  const expression = new ExpressionBuilder().buildUniqueRecordConditionExpression(
-    simpleTable
-  );
-  expect(expression).toEqual({
-    ConditionExpression:
-      'attribute_not_exists(#CE_PK) AND attribute_not_exists(#CE_SK)',
-    ExpressionAttributeNames: {
-      '#CE_PK': 'PK',
-      '#CE_SK': 'SK',
-    },
-  });
-});
diff --git a/packages/core/src/helpers/__test__/drop-prop.spec.ts b/packages/core/src/helpers/__test__/drop-prop.spec.ts
deleted file mode 100644
index df80ddc..0000000
--- a/packages/core/src/helpers/__test__/drop-prop.spec.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import {dropProp} from '../drop-prop';
-
-test('drops given prop from object', () => {
-  const obj = {
-    a: '1',
-    b: '2',
-    c: '3',
-  };
-
-  const updated = dropProp(obj, 'a');
-  expect(updated).toEqual({
-    b: '2',
-    c: '3',
-  });
-});
-
-test('does drops any prop from object if no matches', () => {
-  const obj = {
-    a: '1',
-    b: '2',
-    c: '3',
-  };
-
-  const updated = dropProp(obj, 'd' as any);
-  expect(updated).toEqual({
-    a: '1',
-    b: '2',
-    c: '3',
-  });
-});
diff --git a/packages/core/src/helpers/__test__/is-used-for-primary-key.spec.ts b/packages/core/src/helpers/__test__/is-used-for-primary-key.spec.ts
deleted file mode 100644
index 39b0fb8..0000000
--- a/packages/core/src/helpers/__test__/is-used-for-primary-key.spec.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import {isUsedForPrimaryKey} from '../is-used-for-primary-key';
-
-test('checks if given attribute is referenced in raw primary key', () => {
-  const isNotUsed = isUsedForPrimaryKey(
-    {
-      partitionKey: 'USER#{{id}}#NAME#{{name}}:something',
-    },
-    'role'
-  );
-  expect(isNotUsed).toBeFalsy();
-
-  const isUsed = isUsedForPrimaryKey(
-    {
-      partitionKey: 'USER#{{id}}#NAME#{{name}}:something',
-    },
-    'name'
-  );
-  expect(isUsed).toBeTruthy();
-});
-
-test('checks if given attribute is referenced in formatted primary key', () => {
-  const isNotUsed = isUsedForPrimaryKey(
-    {
-      PK: 'USER#{{id}}#NAME#{{name}}:something',
-      _interpolations: {
-        PK: ['id', 'name'],
-      },
-    },
-    'role'
-  );
-  expect(isNotUsed).toBeFalsy();
-
-  const isUsed = isUsedForPrimaryKey(
-    {
-      PK: 'USER#{{id}}#NAME#{{name}}:something',
-      SK: 'USER#{{id}}#ROLE#{{role}}',
-      _interpolations: {
-        PK: ['id', 'name'],
-        SK: ['id', 'role'],
-      },
-    },
-    'role'
-  );
-  expect(isUsed).toBeTruthy();
-});
diff --git a/packages/core/src/helpers/__test__/validate-key.spec.ts b/packages/core/src/helpers/__test__/validate-key.spec.ts
index c88dd75..c77b122 100644
--- a/packages/core/src/helpers/__test__/validate-key.spec.ts
+++ b/packages/core/src/helpers/__test__/validate-key.spec.ts
@@ -25,7 +25,7 @@ describe('validateKey()', () => {
         status: 'Object',
       })
     ).toThrowError(
-      '"status" is used in key USER#{{id}}#STATUS#{{status}}, thus it\'s type must be or scalar type, if attribute type is Enum, please set "isEnum" to true in attribute decorator.'
+      '"status" is used in key USER#{{id}}#STATUS#{{status}}, thus it\'s type must be a "Number" or a "String"'
     );
   });
 });