module import { hasSortKey , Metadata } from '../decorator/metadata/metadata' ; import { metadataForModel } from '../decorator/metadata/metadata-for-model.function' ; import { hasType , Key , PropertyMetadata } from '../decorator/metadata/property-metadata.model' ; import { createOptModelLogger } from '../logger/logger' ; import { ModelConstructor } from '../model/model-constructor' ; import { MapperForType } from './for-type/base.mapper' ; import { BooleanMapper } from './for-type/boolean.mapper' ; import { CollectionMapper } from './for-type/collection.mapper' ; import { NullMapper } from './for-type/null.mapper' ; import { NumberMapper } from './for-type/number.mapper' ; import { ObjectMapper } from './for-type/object.mapper' ; import { StringMapper } from './for-type/string.mapper' ; import { AttributeValueType } from './type/attribute-value-type.type' ; import { Attribute , Attributes } from './type/attribute.type' ; import { Binary } from './type/binary.type' ; import { NullType } from './type/null.type' ; import { UndefinedType } from './type/undefined.type' ; import { getPropertyPath , typeOf , typeOfFromDb } from './util' ; hidden const mapperForType = new Map ( ) ; hidden const logger = createOptModelLogger ( 'dynamo.mapper.mapper' ) ; /**
 * mapps an item according to given model constructor [its meta data] to attributes
 */ export function toDb < T > ( item , modelConstructor ? ) { logger . verbose ( 'map toDb' , modelConstructor , { item } ) ; const mapped = < Attributes < T > > { } ; if ( modelConstructor ) { const metadata = metadataForModel ( modelConstructor ) ; if ( metadata ) { metadata . getPropertiesWithDefaultValueProvider ( ) . forEach ( propertyMetadata => { const currentVal = Reflect . get ( < any > item , propertyMetadata . name ) ; if ( currentVal === undefined || currentVal === null ) { Reflect . set ( < any > item , propertyMetadata . name , propertyMetadata . defaultValueProvider ! ( ) ) ; } } ) ; } } const propertyNames = < Array < keyof T > > Object . getOwnPropertyNames ( item ) || [ ] ; propertyNames . forEach ( ( propertyKey ) => { const propertyValue = getPropertyValue ( item , propertyKey ) ; let attributeValue ; if ( propertyValue === undefined || propertyValue === null ) { } else { let propertyMetadata ; if ( modelConstructor ) { propertyMetadata = metadataForModel ( modelConstructor ) . forProperty ( propertyKey ) ; } if ( propertyMetadata ) { if ( propertyMetadata . transient ) { } else { attributeValue = toDbOne ( propertyValue , getPropertyPath < T > ( modelConstructor , propertyKey ) , propertyMetadata ) ; } } else { attributeValue = toDbOne ( propertyValue , getPropertyPath < T > ( modelConstructor , propertyKey ) ) ; } if ( attributeValue === undefined ) { } else if ( attributeValue === null ) { } else { ; ( < any > mapped ) [ propertyMetadata ? propertyMetadata . nameDb : propertyKey ] = attributeValue ; } } } ) ; return mapped ; } param propertyValue param propertyMetadata export function toDbOne ( propertyValue , propertyMetadata ? ) ; param propertyValue param propertyPath param propertyMetadata export function toDbOne ( propertyValue , propertyPath , propertyMetadata ? ) ; export function toDbOne ( propertyValue , propertyPathOrMetadata ? , propertyMetadata ? ) { logger . verbose ( 'map toDbOne' , null , { propertyValue , propertyPathOrMetadata , propertyMetadata } ) ; const propertyPath = propertyPathOrMetadata && typeof propertyPathOrMetadata === 'string' ? propertyPathOrMetadata : null ; propertyMetadata = propertyPathOrMetadata && typeof propertyPathOrMetadata !== 'string' ? propertyPathOrMetadata : propertyMetadata ; const explicitType = hasType ( propertyMetadata ) ? propertyMetadata . typeInfo . type : null ; const type = explicitType || typeOf ( propertyValue , propertyPath ) ; const mapper = propertyMetadata && propertyMetadata . mapper ? propertyMetadata . mapper ( ) : forType ( type ) ; const attrValue = explicitType ? mapper . toDb ( propertyValue , propertyMetadata ) : mapper . toDb ( propertyValue ) ; if ( propertyMetadata && propertyMetadata . key ) { if ( attrValue === null ) { throw new Error ( `${ propertyMetadata . name . toString ( ) } is null but is a ${ propertyMetadata . key . type } key` ) ; } if ( ! ( 'S' in attrValue ) && ! ( 'N' in attrValue ) && ! ( 'B' in attrValue ) ) { throw new Error ( `\
DynamoDb only allows string, number or binary type for RANGE and HASH key. \
Make sure to define a custom mapper for '${ propertyMetadata . name . toString ( ) }' which returns a string, number or binary value for partition key, \
type ${ type } cannot be used as partition key, value = ${ JSON . stringify ( propertyValue ) }` ) ; } } return attrValue ; } hidden function testForKey < T > ( p ) { return ! ! p . key ; } param modelConstructor export function createToKeyFn < T > ( modelConstructor ) { const metadata = metadataForModel ( modelConstructor ) ; const properties = metadata . modelOptions . properties ; if ( ! properties . length ) { throw new Error ( 'no properties defined on metadata' ) ; } const keyProperties = properties . filter ( testForKey ) ; return ( item ) => { logger . verbose ( 'create key' , null , { item , propertyMeta : keyProperties } ) ; return keyProperties . reduce ( ( key , propMeta ) => { if ( item [ propMeta . name ] === null || item [ propMeta . name ] === undefined ) { throw new Error ( `there is no value for property ${ propMeta . name . toString ( ) } but is ${ propMeta . key . type } key` ) ; } const propertyValue = getPropertyValue ( item , propMeta . name ) ; key [ propMeta . nameDb ] = < Attribute > toDbOne ( propertyValue , propMeta ) ; return key ; } , < Attributes < T > > { } ) ; } ; } see createToKeyFn export function toKey < T > ( item , modelConstructor ) { return createToKeyFn ( modelConstructor ) ( item ) ; } hidden export function createKeyAttributes < T > ( metadata , partitionKey , sortKey ? ) { const partitionKeyProp = metadata . getPartitionKey ( ) ; const partitionKeyMetadata = metadata . forProperty ( partitionKeyProp ) ; if ( ! partitionKeyMetadata ) { throw new Error ( 'metadata for partition key must be defined' ) ; } const keyAttributeMap = < Attributes < T > > { [ partitionKeyMetadata . nameDb ] : toDbOne ( partitionKey , partitionKeyMetadata ) , } ; if ( hasSortKey ( metadata ) ) { if ( sortKey === null || sortKey === undefined ) { throw new Error ( `please provide the sort key for attribute ${ metadata . getSortKey ( ) }` ) ; } const sortKeyProp = metadata . getSortKey ( ) ; const sortKeyMetadata = metadata . forProperty ( sortKeyProp ) ; if ( ! sortKeyMetadata ) { throw new Error ( 'metadata for sort key must be defined' ) ; } keyAttributeMap [ sortKeyMetadata . nameDb ] = < Attribute > toDbOne ( sortKey , sortKeyMetadata ) ; } return keyAttributeMap ; } /**
 * parses attributes to a js item according to the given model constructor [its meta data]
 */ export function fromDb < T > ( attributeMap , modelConstructor ? ) { logger . verbose ( 'parse fromDb' , modelConstructor , { attributeMap } ) ; const model = < T > { } ; Object . getOwnPropertyNames ( attributeMap ) . forEach ( ( attributeName ) => { const attributeValue = ( < any > attributeMap ) [ attributeName ] ; let modelValue ; let propertyMetadata ; if ( modelConstructor ) { propertyMetadata = metadataForModel ( modelConstructor ) . forProperty ( attributeName ) ; } if ( propertyMetadata ) { if ( propertyMetadata . transient ) { } else { if ( propertyMetadata && propertyMetadata . mapper ) { modelValue = propertyMetadata . mapper ( ) . fromDb ( attributeValue , propertyMetadata ) ; } else { modelValue = fromDbOne ( attributeValue , propertyMetadata ) ; } } } else { modelValue = fromDbOne ( attributeValue ) ; } if ( modelValue !== null && modelValue !== undefined ) { Reflect . set ( < any > model , propertyMetadata ? propertyMetadata . name : attributeName , modelValue ) ; } } ) ; return model ; } /**
 * parses an attribute to a js value according to the given property metadata
 */ export function fromDbOne < T > ( attributeValue , propertyMetadata ? ) { logger . verbose ( 'parse fromDbOne' , null , { attributeValue , propertyMetadata } ) ; const explicitType = hasType ( propertyMetadata ) ? propertyMetadata . typeInfo . type : null ; const type = explicitType || typeOfFromDb ( attributeValue ) ; if ( explicitType ) { return forType ( type ) . fromDb ( attributeValue , propertyMetadata ) ; } else { return forType ( type ) . fromDb ( attributeValue ) ; } } hidden export function forType ( type ) { let mapper = mapperForType . get ( type ) ; if ( ! mapper ) { switch ( type ) { case String : mapper = StringMapper ; break ; case Number : mapper = NumberMapper ; break ; case Boolean : mapper = BooleanMapper ; break ; case Map : throw new Error ( 'Map is not supported to be mapped for now' ) ; case Array : mapper = CollectionMapper ; break ; case Set : mapper = CollectionMapper ; break ; case NullType : mapper = NullMapper ; break ; case Binary : throw new Error ( 'no mapper for binary type implemented yet' ) ; case UndefinedType : mapper = ObjectMapper ; break ; case Object : default : mapper = ObjectMapper ; } mapperForType . set ( type , mapper ) ; } return mapper ; } hidden export function getPropertyValue ( item , propertyKey ) { const propertyDescriptor = Object . getOwnPropertyDescriptor ( item , propertyKey ) ; if ( propertyDescriptor ) { if ( propertyDescriptor . get ) { return propertyDescriptor . get ( ) ; } else { return propertyDescriptor . value ; } } else { throw new Error ( `there is no property descriptor for item ${ JSON . stringify ( item ) } and property key ${ < string > propertyKey }` ) ; } }