Repository: jeremydaly/dynamodb-toolbox
Pull request #302 (2022-07-27 23:07:37)

[BREAKING] Make entities extend default entity type
Fixes https://github.com/jeremydaly/dynamodb-toolbox/issues/246

This is breaking as `Name` generic type now becomes the first generic type. Developers using `Overlays` have to update their code to follow the change.

It was needed to detect if we are using an instance (`Name` is a narrow string like `"MyEntityName"`) or the general `Entity` type (`Name` is simply `string`) in the rest of the type computations.

I updated the documentation and tests as well.

The first commit is a simple formatting by prettier, no logic has been modified.

There are 2 small regressions, that were needed otherwise TS was going crazy:
- `Aliases` are not prevented from attribute keys anymore (`{ entity: { type: "string" } }` doesn't raise a type error)
- `myEntity.sortKey` is not hardly typed (it returns `null | string`)

Otherwise, it was smoother than I expected.


Patch:
diff --git a/docs/docs/type-inference/index.md b/docs/docs/type-inference/index.md
index a1239d75..e3f582f3 100644
--- a/docs/docs/type-inference/index.md
+++ b/docs/docs/type-inference/index.md
@@ -51,7 +51,8 @@ const { Item } = await MyEntity.get<CustomItem, CustomCompositeKey>({
 Overlaying at the Entity level is also possible. The overlay is passed down to every method, and type inference is fully deactivated:
 
 ```typescript
-const MyEntity =  new Entity<CustomItem, CustomCompositeKey, typeof table>({
+const MyEntity =  new Entity<"MyEntityName", CustomItem, CustomCompositeKey, typeof table>({
+  name: "MyEntityName",
   ...,
   table,
 } as const)
diff --git a/src/classes/Entity/Entity.ts b/src/classes/Entity/Entity.ts
index 9d50f308..a7bbf610 100644
--- a/src/classes/Entity/Entity.ts
+++ b/src/classes/Entity/Entity.ts
@@ -14,7 +14,7 @@ import formatItem from '../../lib/formatItem'
 import getKey from '../../lib/getKey'
 import parseConditions from '../../lib/expressionBuilder'
 import parseProjections from '../../lib/projectionBuilder'
-import { error, transformAttr, isEmpty, If, PreventKeys, FirstDefined } from '../../lib/utils'
+import { error, transformAttr, isEmpty, If, FirstDefined } from '../../lib/utils'
 import {
   ATTRIBUTE_VALUES_LIST_DEFAULT_KEY,
   ATTRIBUTE_VALUES_LIST_DEFAULT_VALUE
@@ -48,47 +48,60 @@ import type {
 } from './types'
 
 class Entity<
-  EntityItemOverlay extends Overlay = undefined,
-  EntityCompositeKeyOverlay extends Overlay = EntityItemOverlay,
-  EntityTable extends TableDef | undefined = undefined,
   Name extends string = string,
-  AutoExecute extends boolean = true,
-  AutoParse extends boolean = true,
-  Timestamps extends boolean = true,
-  CreatedAlias extends string = 'created',
-  ModifiedAlias extends string = 'modified',
-  TypeAlias extends string = 'entity',
-  ReadonlyAttributeDefinitions extends PreventKeys<
-    AttributeDefinitions | Readonly<AttributeDefinitions>,
-    CreatedAlias | ModifiedAlias | TypeAlias
-  > = PreventKeys<AttributeDefinitions, CreatedAlias | ModifiedAlias | TypeAlias>,
+  // Name is used to detect Entity instances (new Entity(...)) vs Entity type (const e: Entity = ...)
+  // "string extends Name" means that Name is not defined, which only happens when using the Entity type
+  // In this case, broader types should be applied
+  EntityItemOverlay extends Overlay = string extends Name ? Overlay : undefined,
+  EntityCompositeKeyOverlay extends Overlay = string extends Name ? Overlay : EntityItemOverlay,
+  EntityTable extends TableDef | undefined = string extends Name ? TableDef | undefined : undefined,
+  AutoExecute extends boolean = string extends Name ? boolean : true,
+  AutoParse extends boolean = string extends Name ? boolean : true,
+  Timestamps extends boolean = string extends Name ? boolean : true,
+  CreatedAlias extends string = string extends Name ? string : 'created',
+  ModifiedAlias extends string = string extends Name ? string : 'modified',
+  TypeAlias extends string = string extends Name ? string : 'entity',
+  ReadonlyAttributeDefinitions extends Readonly<AttributeDefinitions> = Readonly<AttributeDefinitions>,
   WritableAttributeDefinitions extends AttributeDefinitions = Writable<ReadonlyAttributeDefinitions>,
-  Attributes extends ParsedAttributes = If<
-    A.Equals<EntityItemOverlay, undefined>,
-    // 🔨 TOIMPROVE: Use EntityTable in attributes parsing
-    ParseAttributes<
-      WritableAttributeDefinitions,
-      Timestamps,
-      CreatedAlias,
-      ModifiedAlias,
-      TypeAlias
-    >,
-    ParsedAttributes<keyof EntityItemOverlay>
-  >,
-  $Item extends any = If<
-    A.Equals<EntityItemOverlay, undefined>,
-    // 🔨 TOIMPROVE: Use EntityTable in item infering
-    InferItem<WritableAttributeDefinitions, Attributes>,
-    EntityItemOverlay
-  >,
+  Attributes extends ParsedAttributes = string extends Name
+    ? ParsedAttributes
+    : If<
+        A.Equals<EntityItemOverlay, undefined>,
+        // 🔨 TOIMPROVE: Use EntityTable in attributes parsing
+        ParseAttributes<
+          WritableAttributeDefinitions,
+          Timestamps,
+          CreatedAlias,
+          ModifiedAlias,
+          TypeAlias
+        >,
+        ParsedAttributes<keyof EntityItemOverlay>
+      >,
+  $Item extends any = string extends Name
+    ? any
+    : If<
+        A.Equals<EntityItemOverlay, undefined>,
+        // 🔨 TOIMPROVE: Use EntityTable in item infering
+        InferItem<WritableAttributeDefinitions, Attributes>,
+        EntityItemOverlay
+      >,
   // Necessary to cast in a second step to prevent infinite loop during type check
-  Item extends O.Object = A.Cast<$Item, O.Object>,
-  CompositePrimaryKey extends O.Object = If<
-    A.Equals<EntityItemOverlay, undefined>,
-    InferCompositePrimaryKey<Item, Attributes>,
-    O.Object
-  >
+  Item extends O.Object = string extends Name ? O.Object : A.Cast<$Item, O.Object>,
+  CompositePrimaryKey extends O.Object = string extends Name
+    ? O.Object
+    : If<
+        A.Equals<EntityItemOverlay, undefined>,
+        InferCompositePrimaryKey<Item, Attributes>,
+        O.Object
+      >
 > {
+  // @ts-ignore
+  public _typesOnly: {
+    _entityItemOverlay: EntityItemOverlay
+    _attributes: Attributes
+    _compositePrimaryKey: CompositePrimaryKey
+    _item: Item
+  }
   private _table?: EntityTable
   private _execute?: boolean
   private _parse?: boolean
@@ -98,8 +111,6 @@ class Entity<
   public defaults: any
   public linked: any
   public required: any
-  // @ts-ignore
-  public _typesOnly: { _entityItemOverlay: EntityItemOverlay }
   public attributes: ReadonlyAttributeDefinitions
   public timestamps: Timestamps
   public createdAlias: CreatedAlias
@@ -233,11 +244,8 @@ class Entity<
       : error(`No partitionKey defined`)
   }
 
-  get sortKey(): If<
-    A.Equals<Attributes['key']['sortKey']['pure'], never>,
-    null,
-    Attributes['key']['sortKey']['pure']
-  > {
+  // 🔨 TOIMPROVE: We could hardly type sortKey here
+  get sortKey(): string | null {
     return this.schema.keys.sortKey ? this.attribute(this.schema.keys.sortKey) : null
   }
 
@@ -381,10 +389,10 @@ class Entity<
     options: { attributes?: ResponseAttributes[] } = {}
   ): {
     Entity: Entity<
+      Name,
       EntityItemOverlay,
       EntityCompositeKeyOverlay,
       EntityTable,
-      Name,
       AutoExecute,
       AutoParse,
       Timestamps,
@@ -1138,10 +1146,7 @@ class Entity<
           values[`:${ATTRIBUTE_VALUES_LIST_DEFAULT_KEY}`] = ATTRIBUTE_VALUES_LIST_DEFAULT_VALUE
 
           // if a list and updating by index
-        } else if (
-          mapping.type === 'list' &&
-          data[field]?.constructor === Object
-        ) {
+        } else if (mapping.type === 'list' && data[field]?.constructor === Object) {
           Object.keys(data[field]).forEach(i => {
             if (String(parseInt(i)) !== i) {
               error(`Properties must be numeric to update specific list items in '${field}'`)
diff --git a/src/classes/Entity/types.ts b/src/classes/Entity/types.ts
index 61f761e7..ed72437f 100644
--- a/src/classes/Entity/types.ts
+++ b/src/classes/Entity/types.ts
@@ -1,11 +1,12 @@
-import type { DocumentClient } from 'aws-sdk/clients/dynamodb';
-import type { A, B, O } from 'ts-toolbelt';
+import type { DocumentClient } from 'aws-sdk/clients/dynamodb'
+import type { A, B, O, F } from 'ts-toolbelt'
 
-import type { FirstDefined, If, PreventKeys } from '../../lib/utils';
-import type { DynamoDBKeyTypes, DynamoDBTypes, $QueryOptions, TableDef } from '../Table';
+import type { FirstDefined, If } from '../../lib/utils'
+import type { DynamoDBKeyTypes, DynamoDBTypes, $QueryOptions, TableDef } from '../Table'
+import Entity from './Entity'
 
-
-export interface EntityConstructor<EntityTable extends TableDef | undefined = undefined,
+export interface EntityConstructor<
+  EntityTable extends TableDef | undefined = undefined,
   Name extends string = string,
   AutoExecute extends boolean = true,
   AutoParse extends boolean = true,
@@ -13,19 +14,19 @@ export interface EntityConstructor<EntityTable extends TableDef | undefined = un
   CreatedAlias extends string = 'created',
   ModifiedAlias extends string = 'modified',
   TypeAlias extends string = 'entity',
-  ReadonlyAttributeDefinitions extends PreventKeys<AttributeDefinitions | O.Readonly<AttributeDefinitions, A.Key, 'deep'>,
-    CreatedAlias | ModifiedAlias | TypeAlias> = PreventKeys<AttributeDefinitions, CreatedAlias | ModifiedAlias | TypeAlias>> {
-  table?: EntityTable;
-  name: Name;
-  timestamps?: Timestamps;
-  created?: string;
-  modified?: string;
-  createdAlias?: CreatedAlias;
-  modifiedAlias?: ModifiedAlias;
-  typeAlias?: TypeAlias;
-  attributes: ReadonlyAttributeDefinitions;
-  autoExecute?: AutoExecute;
-  autoParse?: AutoParse;
+  ReadonlyAttributeDefinitions extends Readonly<AttributeDefinitions> = Readonly<AttributeDefinitions>
+> {
+  table?: EntityTable
+  name: Name
+  timestamps?: Timestamps
+  created?: string
+  modified?: string
+  createdAlias?: CreatedAlias
+  modifiedAlias?: ModifiedAlias
+  typeAlias?: TypeAlias
+  attributes: ReadonlyAttributeDefinitions
+  autoExecute?: AutoExecute
+  autoParse?: AutoParse
 }
 
 export type KeyAttributeDefinition = {
@@ -106,43 +107,48 @@ export type AttributeDefinition =
 
 export type AttributeDefinitions = Record<A.Key, AttributeDefinition>
 
-export type InferKeyAttribute<Definitions extends AttributeDefinitions,
-  KeyType extends 'partitionKey' | 'sortKey'> = O.SelectKeys<Definitions, Record<KeyType, true>>
+export type InferKeyAttribute<
+  Definitions extends AttributeDefinitions,
+  KeyType extends 'partitionKey' | 'sortKey'
+> = O.SelectKeys<Definitions, Record<KeyType, true>>
 
-export type InferMappedAttributes<Definitions extends AttributeDefinitions,
-  AttributeName extends A.Key> = O.SelectKeys<Definitions, [AttributeName, any, any?]>
+export type InferMappedAttributes<
+  Definitions extends AttributeDefinitions,
+  AttributeName extends A.Key
+> = O.SelectKeys<Definitions, [AttributeName, any, any?]>
 
 export interface ParsedAttributes<Attributes extends A.Key = A.Key> {
-  aliases: Attributes;
-  all: Attributes;
-  default: Attributes;
+  aliases: Attributes
+  all: Attributes
+  default: Attributes
   key: {
     partitionKey: { pure: Attributes; dependsOn: Attributes; mapped: Attributes; all: Attributes }
     sortKey: { pure: Attributes; dependsOn: Attributes; mapped: Attributes; all: Attributes }
     all: Attributes
-  };
-  always: { all: Attributes; default: Attributes; input: Attributes };
-  required: { all: Attributes; default: Attributes; input: Attributes };
-  optional: Attributes;
-  shown: Attributes;
+  }
+  always: { all: Attributes; default: Attributes; input: Attributes }
+  required: { all: Attributes; default: Attributes; input: Attributes }
+  optional: Attributes
+  shown: Attributes
 }
 
 export type GetDependsOnAttributes<A extends AttributeDefinition> = A extends { dependsOn: A.Key }
   ? A['dependsOn']
   : A extends { dependsOn: A.Key[] }
-    ? A['dependsOn'][number]
-    : never
+  ? A['dependsOn'][number]
+  : never
 
-export type ParseAttributes<Definitions extends AttributeDefinitions,
+export type ParseAttributes<
+  Definitions extends AttributeDefinitions,
   Timestamps extends boolean,
   CreatedAlias extends string,
   ModifiedAlias extends string,
   TypeAlias extends string,
   Aliases extends string =
-      | (Timestamps extends true ? CreatedAlias | ModifiedAlias : never)
+    | (Timestamps extends true ? CreatedAlias | ModifiedAlias : never)
     | TypeAlias,
   Default extends A.Key =
-      | O.SelectKeys<Definitions, { default: any } | [any, any, { default: any }]>
+    | O.SelectKeys<Definitions, { default: any } | [any, any, { default: any }]>
     | Aliases,
   PK extends A.Key = InferKeyAttribute<Definitions, 'partitionKey'>,
   PKDependsOn extends A.Key = GetDependsOnAttributes<Definitions[PK]>,
@@ -151,16 +157,21 @@ export type ParseAttributes<Definitions extends AttributeDefinitions,
   SKDependsOn extends A.Key = GetDependsOnAttributes<Definitions[SK]>,
   SKMappedAttribute extends A.Key = InferMappedAttributes<Definitions, SK>,
   KeyAttributes extends A.Key = PK | PKMappedAttribute | SK | SKMappedAttribute,
-  AlwaysAttributes extends A.Key = Exclude<| O.SelectKeys<Definitions, { required: 'always' } | [any, any, { required: 'always' }]>
+  AlwaysAttributes extends A.Key = Exclude<
+    | O.SelectKeys<Definitions, { required: 'always' } | [any, any, { required: 'always' }]>
     | (Timestamps extends true ? ModifiedAlias : never),
-    KeyAttributes>,
-  RequiredAttributes extends A.Key = Exclude<| O.SelectKeys<Definitions, { required: true } | [any, any, { required: true }]>
+    KeyAttributes
+  >,
+  RequiredAttributes extends A.Key = Exclude<
+    | O.SelectKeys<Definitions, { required: true } | [any, any, { required: true }]>
     | (Timestamps extends true ? CreatedAlias : never)
     | TypeAlias,
-    KeyAttributes>,
+    KeyAttributes
+  >,
   // 🔨 TOIMPROVE: Use EntityTable to infer extra attributes
   Attribute extends A.Key = keyof Definitions | Aliases,
-  Hidden extends A.Key = O.SelectKeys<Definitions, { hidden: true } | [any, any, { hidden: true }]>> = {
+  Hidden extends A.Key = O.SelectKeys<Definitions, { hidden: true } | [any, any, { hidden: true }]>
+> = {
   aliases: Aliases
   all: Attribute
   default: Default
@@ -203,11 +214,14 @@ export type FromDynamoData<T extends DynamoDBTypes> = {
   set: any[]
 }[T]
 
-export type InferItemAttributeValue<Definitions extends AttributeDefinitions,
+export type InferItemAttributeValue<
+  Definitions extends AttributeDefinitions,
   AttributeName extends keyof Definitions,
-  Definition = Definitions[AttributeName]> = {
+  Definition = Definitions[AttributeName]
+> = {
   dynamoDbType: Definition extends DynamoDBTypes ? FromDynamoData<Definition> : never
-  pure: Definition extends | PartitionKeyDefinition
+  pure: Definition extends
+    | PartitionKeyDefinition
     | GSIPartitionKeyDefinition
     | SortKeyDefinition
     | GSISortKeyDefinition
@@ -223,48 +237,65 @@ export type InferItemAttributeValue<Definitions extends AttributeDefinitions,
     : never
 }[Definition extends DynamoDBTypes
   ? 'dynamoDbType'
-  : Definition extends | PartitionKeyDefinition
-    | GSIPartitionKeyDefinition
-    | SortKeyDefinition
-    | GSISortKeyDefinition
-    | PureAttributeDefinition
-    ? 'pure'
-    : Definition extends CompositeAttributeDefinition
-      ? 'composite'
-      : never]
-
-export type InferItem<Definitions extends AttributeDefinitions,
-  Attributes extends ParsedAttributes> = O.Optional<{
-  [K in Attributes['all']]: K extends keyof Definitions
-    ? InferItemAttributeValue<Definitions, K>
-    : K extends Attributes['aliases']
+  : Definition extends
+      | PartitionKeyDefinition
+      | GSIPartitionKeyDefinition
+      | SortKeyDefinition
+      | GSISortKeyDefinition
+      | PureAttributeDefinition
+  ? 'pure'
+  : Definition extends CompositeAttributeDefinition
+  ? 'composite'
+  : never]
+
+export type InferItem<
+  Definitions extends AttributeDefinitions,
+  Attributes extends ParsedAttributes
+> = O.Optional<
+  {
+    [K in Attributes['all']]: K extends keyof Definitions
+      ? InferItemAttributeValue<Definitions, K>
+      : K extends Attributes['aliases']
       ? string
       : never
-},
-  Attributes['optional']>
+  },
+  Attributes['optional']
+>
 
-export type CompositePrimaryKeyPart<Item extends O.Object,
+export type CompositePrimaryKeyPart<
+  Item extends O.Object,
   Attributes extends ParsedAttributes,
   KeyType extends 'partitionKey' | 'sortKey',
   KeyPureAttribute extends A.Key = Attributes['key'][KeyType]['pure'],
   KeyDependsOnAttributes extends A.Key = Attributes['key'][KeyType]['dependsOn'],
-  KeyCompositeAttributes extends A.Key = Attributes['key'][KeyType]['mapped']> = If<A.Equals<KeyPureAttribute, never>,
+  KeyCompositeAttributes extends A.Key = Attributes['key'][KeyType]['mapped']
+> = If<
+  A.Equals<KeyPureAttribute, never>,
   Record<never, unknown>,
-  O.Optional<| O.Pick<Item, KeyPureAttribute>
+  O.Optional<
+    | O.Pick<Item, KeyPureAttribute>
     | If<A.Equals<KeyDependsOnAttributes, never>, never, O.Pick<Item, KeyDependsOnAttributes>>
     | If<A.Equals<KeyCompositeAttributes, never>, never, O.Pick<Item, KeyCompositeAttributes>>,
-    If<A.Equals<KeyDependsOnAttributes, never>,
+    If<
+      A.Equals<KeyDependsOnAttributes, never>,
       // If primary key part doesn't have "dependsOn" attribute, either it has "default" attribute and is optional,
       // either it doesn't and is required
       Attributes['default'],
       // If primary key part has "dependsOn" attribute, "default" should be a function using other attributes. We want
       // either: - O.Pick<Item, KeyDependsOnAttributes> which should not contain KeyPureAttribute - O.Pick<Item,
       // KeyPureAttribute> with KeyPureAttribute NOT optional this time
-      Exclude<Attributes['default'], KeyPureAttribute>>>>
+      Exclude<Attributes['default'], KeyPureAttribute>
+    >
+  >
+>
 
-export type InferCompositePrimaryKey<Item extends O.Object,
-  Attributes extends ParsedAttributes> = A.Compute<CompositePrimaryKeyPart<Item, Attributes, 'partitionKey'> &
-  CompositePrimaryKeyPart<Item, Attributes, 'sortKey'>>
+export type InferCompositePrimaryKey<
+  Item extends O.Object,
+  Attributes extends ParsedAttributes
+> = A.Compute<
+  CompositePrimaryKeyPart<Item, Attributes, 'partitionKey'> &
+    CompositePrimaryKeyPart<Item, Attributes, 'sortKey'>
+>
 
 // Options
 
@@ -273,7 +304,7 @@ export type Overlay = undefined | O.Object
 export type ConditionOrFilter<Attributes extends A.Key = A.Key> = (
   | { attr: Attributes }
   | { size: string }
-  ) &
+) &
   O.Partial<{
     contains: string
     exists: boolean
@@ -297,33 +328,45 @@ export type ConditionsOrFilters<Attributes extends A.Key = A.Key> =
   | ConditionOrFilter<Attributes>
   | ConditionsOrFilters<Attributes>[]
 
-export type BaseOptions<Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = {
+export type BaseOptions<
+  Execute extends boolean | undefined = undefined,
+  Parse extends boolean | undefined = undefined
+> = {
   capacity: DocumentClient.ReturnConsumedCapacity
   execute: Execute
   parse: Parse
 }
 
-export type $ReadOptions<Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = BaseOptions<Execute, Parse> & {
+export type $ReadOptions<
+  Execute extends boolean | undefined = undefined,
+  Parse extends boolean | undefined = undefined
+> = BaseOptions<Execute, Parse> & {
   consistent: boolean
 }
 
-export type $GetOptions<Attributes extends A.Key = A.Key,
+export type $GetOptions<
+  Attributes extends A.Key = A.Key,
   Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = O.Partial<$ReadOptions<Execute, Parse> & { attributes: Attributes[]; include: string[] }>
+  Parse extends boolean | undefined = undefined
+> = O.Partial<$ReadOptions<Execute, Parse> & { attributes: Attributes[]; include: string[] }>
 
-export type EntityQueryOptions<Attributes extends A.Key = A.Key,
+export type EntityQueryOptions<
+  Attributes extends A.Key = A.Key,
   FiltersAttributes extends A.Key = Attributes,
   Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = O.Partial<$QueryOptions<Execute, Parse> & {
-  attributes: Attributes[]
-  filters: ConditionsOrFilters<FiltersAttributes>
-}>
+  Parse extends boolean | undefined = undefined
+> = O.Partial<
+  $QueryOptions<Execute, Parse> & {
+    attributes: Attributes[]
+    filters: ConditionsOrFilters<FiltersAttributes>
+  }
+>
 
-export type $WriteOptions<Attributes extends A.Key = A.Key,
+export type $WriteOptions<
+  Attributes extends A.Key = A.Key,
   Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = BaseOptions<Execute, Parse> & {
+  Parse extends boolean | undefined = undefined
+> = BaseOptions<Execute, Parse> & {
   conditions: ConditionsOrFilters<Attributes>
   metrics: DocumentClient.ReturnItemCollectionMetrics
   include: string[]
@@ -331,136 +374,183 @@ export type $WriteOptions<Attributes extends A.Key = A.Key,
 
 export type PutOptionsReturnValues = 'NONE' | 'ALL_OLD'
 
-export type $PutOptions<Attributes extends A.Key = A.Key,
+export type $PutOptions<
+  Attributes extends A.Key = A.Key,
   ReturnValues extends PutOptionsReturnValues = PutOptionsReturnValues,
   Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = O.Partial<$WriteOptions<Attributes, Execute, Parse> & { returnValues: ReturnValues }>
+  Parse extends boolean | undefined = undefined
+> = O.Partial<$WriteOptions<Attributes, Execute, Parse> & { returnValues: ReturnValues }>
 
-export type PutItem<MethodItemOverlay extends Overlay,
+export type PutItem<
+  MethodItemOverlay extends Overlay,
   EntityItemOverlay extends Overlay,
   CompositePrimaryKey extends O.Object,
   Item extends O.Object,
-  Attributes extends ParsedAttributes> = FirstDefined<[
-  MethodItemOverlay,
-  EntityItemOverlay,
-  A.Compute<CompositePrimaryKey &
-    O.Pick<Item, Attributes['always']['input'] | Attributes['required']['input']> &
-    O.Partial<O.Pick<Item,
-    | Attributes['always']['default']
-    | Attributes['required']['default']> & 
-     O.Update<Item,  Attributes['optional'], A.x | null>>>
-]>
-
-export type UpdateOptionsReturnValues = 'NONE' | 'UPDATED_OLD' | 'UPDATED_NEW' | 'ALL_OLD' | 'ALL_NEW'
-
-export type $UpdateOptions<Attributes extends A.Key = A.Key,
+  Attributes extends ParsedAttributes
+> = FirstDefined<
+  [
+    MethodItemOverlay,
+    EntityItemOverlay,
+    A.Compute<
+      CompositePrimaryKey &
+        O.Pick<Item, Attributes['always']['input'] | Attributes['required']['input']> &
+        O.Partial<
+          O.Pick<Item, Attributes['always']['default'] | Attributes['required']['default']> &
+            O.Update<Item, Attributes['optional'], A.x | null>
+        >
+    >
+  ]
+>
+
+export type UpdateOptionsReturnValues =
+  | 'NONE'
+  | 'UPDATED_OLD'
+  | 'UPDATED_NEW'
+  | 'ALL_OLD'
+  | 'ALL_NEW'
+
+export type $UpdateOptions<
+  Attributes extends A.Key = A.Key,
   ReturnValues extends UpdateOptionsReturnValues = UpdateOptionsReturnValues,
   Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = O.Partial<$WriteOptions<Attributes, Execute, Parse> & { returnValues: ReturnValues }>
+  Parse extends boolean | undefined = undefined
+> = O.Partial<$WriteOptions<Attributes, Execute, Parse> & { returnValues: ReturnValues }>
 
 export interface UpdateCustomParameters {
-  SET: string[];
-  REMOVE: string[];
-  ADD: string[];
-  DELETE: string[];
+  SET: string[]
+  REMOVE: string[]
+  ADD: string[]
+  DELETE: string[]
 }
 
 export type UpdateCustomParams = O.Partial<UpdateCustomParameters & DocumentClient.UpdateItemInput>
 
-export type UpdateItem<MethodItemOverlay extends Overlay,
+export type UpdateItem<
+  MethodItemOverlay extends Overlay,
   EntityItemOverlay extends Overlay,
   CompositePrimaryKey extends O.Object,
   Item extends O.Object,
-  Attributes extends ParsedAttributes> = FirstDefined<[
-  MethodItemOverlay,
-  EntityItemOverlay,
-  A.Compute<CompositePrimaryKey &
-    {
-      [inputAttr in Attributes['always']['input']]:
-      | Item[A.Cast<inputAttr, keyof Item>]
-      | { $delete?: string[]; $add?: any; $prepend?: any[]; $append?: any[]; }
-    } &
-    {
-      [optAttr in Attributes['required']['all'] | Attributes['always']['default']]?:
-      | Item[A.Cast<optAttr, keyof Item>]
-      | { $delete?: string[]; $add?: any; $prepend?: any[]; $append?: any[]; }
-    } &
-    {
-      [attr in Attributes['optional']]?:
-      | null
-      | Item[A.Cast<attr, keyof Item>]
-      | { $delete?: string[]; $add?: any; $append?: any[]; $prepend?: any[] }
-    } & { $remove?: Attributes['optional'] | Attributes['optional'][] }
-  >
-]>
+  Attributes extends ParsedAttributes
+> = FirstDefined<
+  [
+    MethodItemOverlay,
+    EntityItemOverlay,
+    A.Compute<
+      CompositePrimaryKey &
+        {
+          [inputAttr in Attributes['always']['input']]:
+            | Item[A.Cast<inputAttr, keyof Item>]
+            | { $delete?: string[]; $add?: any; $prepend?: any[]; $append?: any[] }
+        } &
+        {
+          [optAttr in Attributes['required']['all'] | Attributes['always']['default']]?:
+            | Item[A.Cast<optAttr, keyof Item>]
+            | { $delete?: string[]; $add?: any; $prepend?: any[]; $append?: any[] }
+        } &
+        {
+          [attr in Attributes['optional']]?:
+            | null
+            | Item[A.Cast<attr, keyof Item>]
+            | { $delete?: string[]; $add?: any; $append?: any[]; $prepend?: any[] }
+        } & { $remove?: Attributes['optional'] | Attributes['optional'][] }
+    >
+  ]
+>
 
 export type DeleteOptionsReturnValues = 'NONE' | 'ALL_OLD'
 
-export type RawDeleteOptions<Attributes extends A.Key = A.Key,
+export type RawDeleteOptions<
+  Attributes extends A.Key = A.Key,
   ReturnValues extends DeleteOptionsReturnValues = DeleteOptionsReturnValues,
   Execute extends boolean | undefined = undefined,
-  Parse extends boolean | undefined = undefined> = O.Partial<$WriteOptions<Attributes, Execute, Parse> & { returnValues: ReturnValues }>
+  Parse extends boolean | undefined = undefined
+> = O.Partial<$WriteOptions<Attributes, Execute, Parse> & { returnValues: ReturnValues }>
 
 export type TransactionOptionsReturnValues = 'NONE' | 'ALL_OLD'
 
 export interface TransactionOptions<Attributes extends A.Key = A.Key> {
-  conditions?: ConditionsOrFilters<Attributes>;
-  returnValues?: TransactionOptionsReturnValues;
+  conditions?: ConditionsOrFilters<Attributes>
+  returnValues?: TransactionOptionsReturnValues
 }
 
-export type ShouldExecute<Execute extends boolean | undefined, AutoExecute extends boolean> = B.Or<A.Equals<Execute, true>,
-  B.And<A.Equals<Execute, undefined>, A.Equals<AutoExecute, true>>>
-
-export type ShouldParse<Parse extends boolean | undefined, AutoParse extends boolean> = B.Or<A.Equals<Parse, true>,
-  B.And<A.Equals<Parse, undefined>, A.Equals<AutoParse, true>>>
-
-export type Readonly<T> = T extends O.Object ? { readonly [P in keyof T]: Readonly<T[P]> } : T
-export type Writable<T> = { -readonly [P in keyof T]: Writable<T[P]> }
-
-export type EntityDef = {
-  _typesOnly: { _entityItemOverlay: Overlay }
-  timestamps: boolean
-  createdAlias: string
-  modifiedAlias: string
-  typeAlias: string
-  attributes: AttributeDefinitions | O.Readonly<AttributeDefinitions, A.Key, 'deep'>
-}
-
-export type InferEntityItem<E extends EntityDef,
-  WritableAttributeDefinitions extends AttributeDefinitions = A.Cast<O.Writable<E['attributes'], A.Key, 'deep'>,
-    AttributeDefinitions>,
-  Attributes extends ParsedAttributes = ParseAttributes<WritableAttributeDefinitions,
+export type ShouldExecute<Execute extends boolean | undefined, AutoExecute extends boolean> = B.Or<
+  A.Equals<Execute, true>,
+  B.And<A.Equals<Execute, undefined>, A.Equals<AutoExecute, true>>
+>
+
+export type ShouldParse<Parse extends boolean | undefined, AutoParse extends boolean> = B.Or<
+  A.Equals<Parse, true>,
+  B.And<A.Equals<Parse, undefined>, A.Equals<AutoParse, true>>
+>
+
+export type Readonly<T> = T extends F.Function | undefined
+  ? T
+  : T extends O.Object
+  ? { readonly [P in keyof T]: Readonly<T[P]> }
+  : T
+
+export type Writable<T> = T extends F.Function | undefined
+  ? T
+  : T extends O.Object
+  ? { -readonly [P in keyof T]: Writable<T[P]> }
+  : T
+
+export type InferEntityItem<
+  E extends Entity,
+  WritableAttributeDefinitions extends AttributeDefinitions = A.Cast<
+    O.Writable<E['attributes'], A.Key, 'deep'>,
+    AttributeDefinitions
+  >,
+  Attributes extends ParsedAttributes = ParseAttributes<
+    WritableAttributeDefinitions,
     E['timestamps'],
     E['createdAlias'],
     E['modifiedAlias'],
-    E['typeAlias']>,
-  Item = InferItem<WritableAttributeDefinitions, Attributes>> = Pick<Item, Extract<Attributes['shown'], keyof Item>>
-
-export type EntityItem<E extends EntityDef> = E['_typesOnly']['_entityItemOverlay'] extends Record<A.Key,
-    any>
+    E['typeAlias']
+  >,
+  Item = InferItem<WritableAttributeDefinitions, Attributes>
+> = Pick<Item, Extract<Attributes['shown'], keyof Item>>
+
+export type EntityItem<E extends Entity> = E['_typesOnly']['_entityItemOverlay'] extends Record<
+  A.Key,
+  any
+>
   ? E['_typesOnly']['_entityItemOverlay']
   : InferEntityItem<E>
 
-export type ExtractAttributes<E extends EntityDef> = E['_typesOnly']['_entityItemOverlay'] extends Record<A.Key,
-    any>
+export type ExtractAttributes<
+  E extends Entity
+> = E['_typesOnly']['_entityItemOverlay'] extends Record<A.Key, any>
   ? ParsedAttributes<keyof E['_typesOnly']['_entityItemOverlay']>
-  : ParseAttributes<A.Cast<O.Writable<E['attributes'], A.Key, 'deep'>, AttributeDefinitions>,
-    E['timestamps'],
-    E['createdAlias'],
-    E['modifiedAlias'],
-    E['typeAlias']>
-
-export type GetOptions<E extends EntityDef,
-  A extends ParsedAttributes = ExtractAttributes<E>> = $GetOptions<A['shown'], boolean | undefined, boolean | undefined>
-
-export type QueryOptions<E extends EntityDef,
-  A extends ParsedAttributes = ExtractAttributes<E>> = EntityQueryOptions<A['shown'], A['all'], boolean | undefined, boolean | undefined>
-
-export type PutOptions<E extends EntityDef,
-  A extends ParsedAttributes = ExtractAttributes<E>> = $PutOptions<A['all'], PutOptionsReturnValues, boolean | undefined, boolean | undefined>
-
-export type DeleteOptions<E extends EntityDef,
-  A extends ParsedAttributes = ExtractAttributes<E>> = RawDeleteOptions<A['all'], DeleteOptionsReturnValues, boolean | undefined, boolean | undefined>
-
-export type UpdateOptions<E extends EntityDef,
-  A extends ParsedAttributes = ExtractAttributes<E>> = $UpdateOptions<A['all'], UpdateOptionsReturnValues, boolean | undefined, boolean | undefined>
+  : ParseAttributes<
+      A.Cast<O.Writable<E['attributes'], A.Key, 'deep'>, AttributeDefinitions>,
+      E['timestamps'],
+      E['createdAlias'],
+      E['modifiedAlias'],
+      E['typeAlias']
+    >
+
+export type GetOptions<
+  E extends Entity,
+  A extends ParsedAttributes = ExtractAttributes<E>
+> = $GetOptions<A['shown'], boolean | undefined, boolean | undefined>
+
+export type QueryOptions<
+  E extends Entity,
+  A extends ParsedAttributes = ExtractAttributes<E>
+> = EntityQueryOptions<A['shown'], A['all'], boolean | undefined, boolean | undefined>
+
+export type PutOptions<
+  E extends Entity,
+  A extends ParsedAttributes = ExtractAttributes<E>
+> = $PutOptions<A['all'], PutOptionsReturnValues, boolean | undefined, boolean | undefined>
+
+export type DeleteOptions<
+  E extends Entity,
+  A extends ParsedAttributes = ExtractAttributes<E>
+> = RawDeleteOptions<A['all'], DeleteOptionsReturnValues, boolean | undefined, boolean | undefined>
+
+export type UpdateOptions<
+  E extends Entity,
+  A extends ParsedAttributes = ExtractAttributes<E>
+> = $UpdateOptions<A['all'], UpdateOptionsReturnValues, boolean | undefined, boolean | undefined>
diff --git a/src/lib/parseEntity.ts b/src/lib/parseEntity.ts
index b0b58b17..e48c3a40 100644
--- a/src/lib/parseEntity.ts
+++ b/src/lib/parseEntity.ts
@@ -5,12 +5,10 @@
  */
 
 // Import libraries & types
-import { A, O } from 'ts-toolbelt'
-
 import parseEntityAttributes from './parseEntityAttributes'
 import { TableDef } from '../classes/Table'
-import { AttributeDefinitions, EntityConstructor } from '../classes/Entity'
-import { error, PreventKeys } from './utils'
+import { AttributeDefinitions, EntityConstructor, Readonly } from '../classes/Entity'
+import { error } from './utils'
 
 export interface TrackingInfo {
   fields: string[]
@@ -41,10 +39,7 @@ export function parseEntity<
   CreatedAlias extends string,
   ModifiedAlias extends string,
   TypeAlias extends string,
-  ReadonlyAttributeDefinitions extends PreventKeys<
-    AttributeDefinitions | O.Readonly<AttributeDefinitions, A.Key, 'deep'>,
-    CreatedAlias | ModifiedAlias | TypeAlias
-  >
+  ReadonlyAttributeDefinitions extends Readonly<AttributeDefinitions> = Readonly<AttributeDefinitions>
 >(
   entity: EntityConstructor<
     EntityTable,
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 5a0dd9ae..370eab47 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -93,6 +93,3 @@ export type FirstDefined<List extends L.List> = {
   If<A.Equals<List, []>, 'stopNone', If<A.Equals<L.Head<List>, undefined>, 'continue', 'stopOne'>>,
   'stopNone' | 'stopOne' | 'continue'
 >]
-
-export type PreventKeys<O extends Record<A.Key, any>, K extends A.Key> = O &
-  O.Partial<Record<K, never> & O.Readonly<Record<K, never>>>


Test patch:
diff --git a/src/__tests__/type-infering.unit.test.ts b/src/__tests__/type-infering.unit.test.ts
index debc795d..4289a5ab 100644
--- a/src/__tests__/type-infering.unit.test.ts
+++ b/src/__tests__/type-infering.unit.test.ts
@@ -138,7 +138,8 @@ describe('Entity', () => {
       } as const
 
       expect(() => {
-        // @ts-expect-error
+        // 🔨 TOIMPROVE: we could raise error here by preventing Aliases from attributes keys but it wreaks havoc with Readonly / Writable
+        // @ts-NOT-expect-error
         new Entity({
           name: entityName,
           attributes: { ...ck, created: 'string' },
@@ -147,7 +148,8 @@ describe('Entity', () => {
       }).toThrow()
 
       expect(() => {
-        // @ts-expect-error
+        // 🔨 TOIMPROVE: we could raise error here by preventing Aliases from attributes keys but it wreaks havoc with Readonly / Writable
+        // @ts-NOT-expect-error
         new Entity({
           name: entityName,
           createdAlias: 'cr',
@@ -157,7 +159,8 @@ describe('Entity', () => {
       }).toThrow()
 
       expect(() => {
-        // @ts-expect-error
+        // 🔨 TOIMPROVE: we could raise error here by preventing Aliases from attributes keys but it wreaks havoc with Readonly / Writable
+        // @ts-NOT-expect-error
         new Entity({
           name: entityName,
           attributes: { ...ck, modified: 'string' },
@@ -166,7 +169,8 @@ describe('Entity', () => {
       }).toThrow()
 
       expect(() => {
-        // @ts-expect-error
+        // 🔨 TOIMPROVE: we could raise error here by preventing Aliases from attributes keys but it wreaks havoc with Readonly / Writable
+        // @ts-NOT-expect-error
         new Entity({
           name: entityName,
           modifiedAlias: 'mod',
@@ -176,7 +180,8 @@ describe('Entity', () => {
       }).toThrow()
 
       // 🔨 TOIMPROVE: Not sure this is expected behavior: overriding typeAlias doesn't throw
-      // @ts-expect-error
+      // 🔨 TOIMPROVE: we could raise error here by preventing Aliases from attributes keys but it wreaks havoc with Readonly / Writable
+      // @ts-NOT-expect-error
       new Entity({
         name: entityName,
         typeAlias: 'en',
@@ -185,7 +190,8 @@ describe('Entity', () => {
       } as const)
 
       // 🔨 TOIMPROVE: Not sure this is expected behavior: overriding typeAlias doesn't throw
-      // @ts-expect-error
+      // 🔨 TOIMPROVE: we could raise error here by preventing Aliases from attributes keys but it wreaks havoc with Readonly / Writable
+      // @ts-NOT-expect-error
       new Entity({
         name: entityName,
         typeAlias: 'en',
@@ -219,6 +225,10 @@ describe('Entity', () => {
       table: tableWithoutSK
     } as const)
 
+    type TestExtends = A.Equals<typeof ent extends Entity ? true : false, true>
+    const testExtends: TestExtends = 1
+    testExtends
+
     const entNoExecute = new Entity({
       name: entityName,
       autoExecute: false,
@@ -967,6 +977,10 @@ describe('Entity', () => {
       table
     } as const)
 
+    type TestExtends = A.Equals<typeof ent extends Entity ? true : false, true>
+    const testExtends: TestExtends = 1
+    testExtends
+
     type ExpectedItem = {
       cr: string
       mod: string
@@ -1603,6 +1617,10 @@ describe('Entity', () => {
       table
     } as const)
 
+    type TestExtends = A.Equals<typeof ent extends Entity ? true : false, true>
+    const testExtends: TestExtends = 1
+    testExtends
+
     type ExpectedItem = {
       created: string
       modified: string
@@ -1762,6 +1780,10 @@ describe('Entity', () => {
       table
     } as const)
 
+    type TestExtends = A.Equals<typeof ent extends Entity ? true : false, true>
+    const testExtends: TestExtends = 1
+    testExtends
+
     type ExpectedItem = {
       created: string
       modified: string
@@ -1824,6 +1846,10 @@ describe('Entity', () => {
       table
     } as const)
 
+    type TestExtends = A.Equals<typeof ent extends Entity ? true : false, true>
+    const testExtends: TestExtends = 1
+    testExtends
+
     it('get method', () => {
       ent.getParams({ pk })
       ent.getParams(ck2)
@@ -1872,6 +1898,10 @@ describe('Entity', () => {
       table
     } as const)
 
+    type TestExtends = A.Equals<typeof ent extends Entity ? true : false, true>
+    const testExtends: TestExtends = 1
+    testExtends
+
     describe('get method', () => {
       describe('MethodItemOverlay', () => {
         it('composite key should match infered composite key', () => {
@@ -2117,7 +2147,12 @@ describe('Entity', () => {
     }
     type EntityCompositeKeyOverlay = { pk0: string; sk0: string }
 
-    const ent = new Entity<EntityItemOverlay, EntityCompositeKeyOverlay, typeof table>({
+    const ent = new Entity<
+      'TestEntity',
+      EntityItemOverlay,
+      EntityCompositeKeyOverlay,
+      typeof table
+    >({
       name: 'TestEntity',
       attributes: {
         pk: { type: 'string', partitionKey: true },
@@ -2126,6 +2161,10 @@ describe('Entity', () => {
       table
     } as const)
 
+    type TestExtends = A.Equals<typeof ent extends Entity ? true : false, true>
+    const testExtends: TestExtends = 1
+    testExtends
+
     type MethodItemOverlay = {
       pk1: string
       sk1: string