author Jeremy Daly <jeremy@jeremydaly.com>
 *  license import Table , { DynamoDBSetTypes , DynamoDBTypes , queryOptions , scanOptions } from './Table' ; import { DocumentClient } from 'aws-sdk/clients/dynamodb' ; import parseEntity from '../lib/parseEntity' ; import validateTypes from '../lib/validateTypes' ; import normalizeData from '../lib/normalizeData' ; import formatItem from '../lib/formatItem' ; import getKey from '../lib/getKey' ; import parseConditions , { FilterExpressions } from '../lib/expressionBuilder' ; import parseProjections , { ProjectionAttributes } from '../lib/projectionBuilder' ; import { error , transformAttr , isEmpty } from '../lib/utils' ; import { Document } from 'aws-sdk/clients/textract' ; export type SchemaType = string | number | boolean | null | { [ key ] : SchemaType ; } | SchemaType [ ] ; export interface EntityConstructor { name : string ; timestamps ? : boolean ; created ? : string ; modified ? : string ; createdAlias ? : string ; modifiedAlias ? : string ; typeAlias ? : string ; attributes : EntityAttributes ; autoExecute ? : boolean ; autoParse ? : boolean ; table ? : Table ; } export interface EntityAttributeConfig { type ? : DynamoDBTypes ; default ? : any | ( ( data : object ) => any ) ; dependsOn ? : string | string [ ] ; transform ? : ( value : any , data : { } ) => any ; coerce ? : boolean ; save ? : boolean ; onUpdate ? : boolean ; hidden ? : boolean ; required ? : boolean | 'always' ; alias ? : string ; map ? : string ; setType ? : DynamoDBSetTypes ; partitionKey ? : boolean | string | ( string | boolean ) [ ] ; delimiter ? : string ; sortKey ? : boolean | string ; prefix ? : string ; suffix ? : string ; } export type EntityCompositeAttributes = [ ( string ) , number , ( string | EntityAttributeConfig ) ? ] ; export interface EntityAttributes { [ attr ] : DynamoDBTypes | EntityAttributeConfig | EntityCompositeAttributes ; } export type EntityAttributeConfiguration = EntityAttributeConfig & { link ? : string ; } ; interface getOptions { consistent ? : boolean ; capacity ? : DocumentClient . ReturnConsumedCapacity ; attributes ? : ProjectionAttributes ; include ? : string [ ] ; execute ? : boolean ; parse ? : boolean ; } interface deleteOptions { conditions ? : FilterExpressions ; capacity ? : DocumentClient . ReturnConsumedCapacity ; metrics ? : DocumentClient . ReturnItemCollectionMetrics ; returnValues ? : DocumentClient . ReturnValue ; include ? : string [ ] ; execute ? : boolean ; parse ? : boolean ; } interface transactionOptions { conditions ? : FilterExpressions ; returnValues ? : DocumentClient . ReturnValuesOnConditionCheckFailure ; } interface putOptions { conditions ? : FilterExpressions ; capacity ? : DocumentClient . ReturnConsumedCapacity ; metrics ? : DocumentClient . ReturnItemCollectionMetrics ; returnValues ? : DocumentClient . ReturnValue ; include ? : string [ ] ; execute ? : boolean ; parse ? : boolean ; } interface updateOptions { conditions ? : FilterExpressions ; capacity ? : DocumentClient . ReturnConsumedCapacity ; metrics ? : DocumentClient . ReturnItemCollectionMetrics ; returnValues ? : DocumentClient . ReturnValue ; include ? : string [ ] ; execute ? : boolean ; parse ? : boolean ; } interface updateCustomParameters { SET ? : string [ ] ; REMOVE ? : string [ ] ; ADD ? : string [ ] ; DELETE ? : string [ ] ; } type updateCustomParams = updateCustomParameters & Partial < DocumentClient . UpdateItemInput > ; class Entity < Schema extends { [ key in keyof Schema ] : SchemaType ; } > { private _table ? : Table ; private _execute ? : boolean ; private _parse ? : boolean ; public name ! : string ; public schema : any ; public _etAlias ! : string ; public defaults : any ; public linked : any ; public required : any ; constructor ( entity ) { if ( typeof entity !== 'object' || Array . isArray ( entity ) ) error ( 'Please provide a valid entity definition' ) ; Object . assign ( this , parseEntity ( entity ) ) ; } set table ( table ) { if ( table . Table && table . Table . attributes ) { if ( this . _table ) { error ( `This entity is already assigned a Table (${ this . _table . name })` ) ; } else if ( ! table . entities . includes ( this . name ) ) { table . addEntity ( this ) ; } this . _table = table ; if ( table . Table . entityField ) { this . schema . attributes [ table . Table . entityField ] = { type : 'string' , alias : this . _etAlias , default : this . name } as EntityAttributeConfig ; this . defaults [ table . Table . entityField ] = this . name ; this . schema . attributes [ this . _etAlias ] = { type : 'string' , map : table . Table . entityField , default : this . name } as EntityAttributeConfig ; this . defaults [ this . _etAlias ] = this . name ; } } else { error ( 'Invalid Table' ) ; } } get table ( ) { if ( this . _table ) { return this . _table ; } else { return error ( `The '${ this . name }' entity must be attached to a Table to perform this operation` ) ; } } get DocumentClient ( ) { if ( this . table . DocumentClient ) { return this . table . DocumentClient ; } else { return error ( 'DocumentClient required for this operation' ) ; } } set autoExecute ( val ) { this . _execute = typeof val === 'boolean' ? val : undefined ; } get autoExecute ( ) { return typeof this . _execute === 'boolean' ? this . _execute : typeof this . table . autoExecute === 'boolean' ? this . table . autoExecute : true ; } set autoParse ( val ) { this . _parse = typeof val === 'boolean' ? val : undefined ; } get autoParse ( ) { return typeof this . _parse === 'boolean' ? this . _parse : typeof this . table . autoParse === 'boolean' ? this . table . autoParse : true ; } get partitionKey ( ) { return this . schema . keys . partitionKey ? this . attribute ( this . schema . keys . partitionKey ) : error ( `No partitionKey defined` ) ; } get sortKey ( ) { return this . schema . keys . sortKey ? this . attribute ( this . schema . keys . sortKey ) : null ; } attribute ( attr ) { return this . schema . attributes [ attr ] && this . schema . attributes [ attr ] . map ? this . schema . attributes [ attr ] . map : this . schema . attributes [ attr ] ? attr : error ( `'${ attr }' does not exist or is an invalid alias` ) ; } parse ( input , include = [ ] ) { include = include . map ( attr => { const _attr = attr . split ( '.' ) [ 0 ] . split ( '[' ) [ 0 ] ; return ( this . schema . attributes [ _attr ] && this . schema . attributes [ _attr ] . map ) || _attr ; } ) ; const { schema , linked } = this ; const data = input . Item || input . Items || input ; if ( Array . isArray ( data ) ) { return data . map ( item => formatItem ( this . DocumentClient ) ( schema . attributes , linked , item , include ) ) ; } else { return formatItem ( this . DocumentClient ) ( schema . attributes , linked , data , include ) ; } } param { object } item param { object } options param { object } params async get ( item = { } , options = { } , params = { } ) { const payload = this . getParams ( item , options , params ) ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient . get ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return Object . assign ( result , result . Item ? { Item : this . parse ( result . Item , Array . isArray ( options . include ) ? options . include : [ ] ) } : null ) ; } else { return result ; } } else { return payload ; } } param { object } item getBatch ( item = { } ) { return { Table : this . table , Key : this . getParams ( item ) . Key } ; } param { object } item param { object } options getTransaction ( item = { } , options = { } ) { const { attributes , ... args } = options ; if ( Object . keys ( args ) . length > 0 ) error ( `Invalid get transaction options: ${ Object . keys ( args ) . join ( ', ' ) }` ) ; let payload = this . getParams ( item , options ) ; return { Entity : this , Get : payload } ; } param { object } item param { object } options param { object } params getParams ( item = { } , options = { } , params = { } ) { const { schema , defaults , linked , _table } = this ; const data = normalizeData ( this . DocumentClient ) ( schema . attributes , linked , Object . assign ( { } , defaults , item ) , true ) ; const { consistent , capacity , attributes , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid get options: ${ args . join ( ', ' ) }` ) ; if ( consistent !== undefined && typeof consistent !== 'boolean' ) error ( `'consistent' requires a boolean` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; let ExpressionAttributeNames ; let ProjectionExpression ; if ( attributes ) { const { names , projections } = parseProjections ( attributes , this . table , this . name ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = names ; ProjectionExpression = projections ; } } const payload = Object . assign ( { TableName : _table ! . name , Key : getKey ( this . DocumentClient ) ( data , schema . attributes , schema . keys . partitionKey , schema . keys . sortKey ) } , ExpressionAttributeNames ? { ExpressionAttributeNames } : null , ProjectionExpression ? { ProjectionExpression } : null , consistent ? { ConsistentRead : consistent } : null , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , typeof params === 'object' ? params : { } ) ; return payload ; } param { object } item param { object } options param { object } params async delete ( item = { } , options = { } , params = { } ) { const payload = this . deleteParams ( item , options , params ) ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient . delete ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return Object . assign ( result , result . Attributes ? { Attributes : this . parse ( result . Attributes , Array . isArray ( options . include ) ? options . include : [ ] ) } : null ) ; } else { return result ; } } else { return payload ; } } param { object } item deleteBatch ( item = { } ) { const payload = this . deleteParams ( item ) ; return { [ payload . TableName ] : { DeleteRequest : { Key : payload . Key } } } ; } param { object } item param { object } options deleteTransaction ( item = { } , options = { } ) { const { conditions , returnValues , ... args } = options ; if ( Object . keys ( args ) . length > 0 ) error ( `Invalid delete transaction options: ${ Object . keys ( args ) . join ( ', ' ) }` ) ; let payload = this . deleteParams ( item , options ) ; if ( 'ReturnValues' in payload ) { let { ReturnValues , ... _payload } = payload ; payload = Object . assign ( { } , _payload , { ReturnValuesOnConditionCheckFailure : ReturnValues } ) ; } return { Delete : payload } ; } param { object } item param { object } options param { object } params deleteParams ( item = { } , options = { } , params = { } ) { const { schema , defaults , linked , _table } = this ; const data = normalizeData ( this . DocumentClient ) ( schema . attributes , linked , Object . assign ( { } , defaults , item ) , true ) ; const { conditions , capacity , metrics , returnValues , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid delete options: ${ args . join ( ', ' ) }` ) ; if ( metrics !== undefined && ( typeof metrics !== 'string' || ! [ 'NONE' , 'SIZE' ] . includes ( metrics . toUpperCase ( ) ) ) ) error ( `'metrics' must be one of 'NONE' OR 'SIZE'` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; if ( returnValues !== undefined && ( typeof returnValues !== 'string' || ! [ 'NONE' , 'ALL_OLD' ] . includes ( returnValues . toUpperCase ( ) ) ) ) error ( `'returnValues' must be one of 'NONE' OR 'ALL_OLD'` ) ; let ExpressionAttributeNames ; let ExpressionAttributeValues ; let ConditionExpression ; if ( conditions ) { const { expression , names , values } = parseConditions ( conditions , this . table , this . name ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = names ; ExpressionAttributeValues = values ; ConditionExpression = expression ; } } const payload = Object . assign ( { TableName : _table ! . name , Key : getKey ( this . DocumentClient ) ( data , schema . attributes , schema . keys . partitionKey , schema . keys . sortKey ) } , ExpressionAttributeNames ? { ExpressionAttributeNames } : null , ! isEmpty ( ExpressionAttributeValues ) ? { ExpressionAttributeValues } : null , ConditionExpression ? { ConditionExpression } : null , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , metrics ? { ReturnItemCollectionMetrics : metrics . toUpperCase ( ) } : null , returnValues ? { ReturnValues : returnValues . toUpperCase ( ) } : null , typeof params === 'object' ? params : { } ) ; return payload ; } param { object } item param { object } options param { object } params async update ( item = { } , options = { } , params = { } ) { const payload = this . updateParams ( item , options , params ) ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient . update ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return Object . assign ( result , result . Attributes ? { Attributes : this . parse ( result . Attributes , Array . isArray ( options . include ) ? options . include : [ ] ) } : null ) ; } else { return result ; } } else { return payload ; } } param { object } item param { object } options updateTransaction ( item = { } , options = { } ) { const { conditions , returnValues , ... args } = options ; if ( Object . keys ( args ) . length > 0 ) error ( `Invalid update transaction options: ${ Object . keys ( args ) . join ( ', ' ) }` ) ; let payload = this . updateParams ( item , options ) ; if ( 'ReturnValues' in payload ) { let { ReturnValues , ... _payload } = payload ; payload = Object . assign ( { } , _payload , { ReturnValuesOnConditionCheckFailure : ReturnValues } ) ; } return { Update : payload as DocumentClient . Update } ; } updateParams ( item = { } , options = { } , { SET = [ ] , REMOVE = [ ] , ADD = [ ] , DELETE = [ ] , ExpressionAttributeNames = { } , ExpressionAttributeValues = { } , ... params } = { } ) { if ( ! Array . isArray ( SET ) ) error ( 'SET must be an array' ) ; if ( ! Array . isArray ( REMOVE ) ) error ( 'REMOVE must be an array' ) ; if ( ! Array . isArray ( ADD ) ) error ( 'ADD must be an array' ) ; if ( ! Array . isArray ( DELETE ) ) error ( 'DELETE must be an array' ) ; if ( typeof ExpressionAttributeNames !== 'object' || Array . isArray ( ExpressionAttributeNames ) ) error ( 'ExpressionAttributeNames must be an object' ) ; if ( typeof ExpressionAttributeValues !== 'object' || Array . isArray ( ExpressionAttributeValues ) ) error ( 'ExpressionAttributeValues must be an object' ) ; const { schema , defaults , required , linked , _table } = this ; const validateType = validateTypes ( this . DocumentClient ) ; const data = normalizeData ( this . DocumentClient ) ( schema . attributes , linked , Object . assign ( { } , defaults , item ) ) ; const { conditions , capacity , metrics , returnValues , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid update options: ${ args . join ( ', ' ) }` ) ; if ( metrics !== undefined && ( typeof metrics !== 'string' || ! [ 'NONE' , 'SIZE' ] . includes ( metrics . toUpperCase ( ) ) ) ) error ( `'metrics' must be one of 'NONE' OR 'SIZE'` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; if ( returnValues !== undefined && ( typeof returnValues !== 'string' || ! [ 'NONE' , 'ALL_OLD' , 'UPDATED_OLD' , 'ALL_NEW' , 'UPDATED_NEW' ] . includes ( returnValues . toUpperCase ( ) ) ) ) error ( `'returnValues' must be one of 'NONE', 'ALL_OLD', 'UPDATED_OLD', 'ALL_NEW', OR 'UPDATED_NEW'` ) ; let ConditionExpression ; if ( conditions ) { const { expression , names , values } = parseConditions ( conditions , this . table , this . name ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = Object . assign ( ExpressionAttributeNames , names ) ; ExpressionAttributeValues = Object . assign ( ExpressionAttributeValues , values ) ; ConditionExpression = expression ; } } Object . keys ( required ) . forEach ( field => required [ field ] && ( data [ field ] === undefined || data [ field ] === null ) && error ( `'${ field }${ this . schema . attributes [ field ] . alias ? `/${ this . schema . attributes [ field ] . alias }` : '' }' is a required field` ) ) ; const Key = getKey ( this . DocumentClient ) ( data , schema . attributes , schema . keys . partitionKey , schema . keys . sortKey ) ; const names = { } ; const values = { } ; Object . keys ( data ) . forEach ( ( field ) => { const mapping = schema . attributes [ field ] ; if ( field === '$remove' ) { const attrs = Array . isArray ( data [ field ] ) ? data [ field ] : [ data [ field ] ] ; for ( const i in attrs ) { if ( ! schema . attributes [ attrs [ i ] ] ) error ( `'${ attrs [ i ] }' is not a valid attribute and cannot be removed` ) ; if ( schema . attributes [ attrs [ i ] ] . partitionKey === true || schema . attributes [ attrs [ i ] ] . sortKey === true ) error ( `'${ attrs [ i ] }' is the ${ schema . attributes [ attrs [ i ] ] . partitionKey === true ? 'partitionKey' : 'sortKey' } and cannot be removed` ) ; const attr = schema . attributes [ attrs [ i ] ] . map || attrs [ i ] ; REMOVE . push ( `#${ attr }` ) ; names [ `#${ attr }` ] = attr ; } } else if ( this . _table ! . _removeNulls === true && ( data [ field ] === null || String ( data [ field ] ) . trim ( ) === '' ) && ( ! mapping . link || mapping . save ) ) { REMOVE . push ( `#${ field }` ) ; names [ `#${ field }` ] = field ; } else if ( mapping . partitionKey !== true && mapping . sortKey !== true && ( mapping . save === undefined || mapping . save === true ) && ( ! mapping . link || ( mapping . link && mapping . save === true ) ) ) { if ( [ 'number' , 'set' ] . includes ( mapping . type ) && ( data [ field ] ?. $add !== undefined && data [ field ] ?. $add !== null ) ) { ADD . push ( `#${ field } :${ field }` ) ; values [ `:${ field }` ] = validateType ( mapping , field , data [ field ] . $add ) ; names [ `#${ field }` ] = field ; } else if ( mapping . type === 'set' && data [ field ] ?. $delete ) { DELETE . push ( `#${ field } :${ field }` ) ; values [ `:${ field }` ] = validateType ( mapping , field , data [ field ] . $delete ) ; names [ `#${ field }` ] = field ; } else if ( mapping . type === 'list' && Array . isArray ( data [ field ] ?. $remove ) ) { data [ field ] . $remove . forEach ( ( i ) => { if ( typeof i !== 'number' ) error ( `Remove array for '${ field }' must only contain numeric indexes` ) ; REMOVE . push ( `#${ field }[${ i }]` ) ; } ) ; names [ `#${ field }` ] = field ; } else if ( mapping . type === 'list' && ( data [ field ] ?. $append || data [ field ] ?. $prepend ) ) { if ( data [ field ] . $append ) { SET . push ( `#${ field } = list_append(#${ field },:${ field })` ) ; values [ `:${ field }` ] = validateType ( mapping , field , data [ field ] . $append ) ; } else { SET . push ( `#${ field } = list_append(:${ field },#${ field })` ) ; values [ `:${ field }` ] = validateType ( mapping , field , data [ field ] . $prepend ) ; } names [ `#${ field }` ] = field ; } else if ( mapping . type === 'list' && ! Array . isArray ( data [ field ] ) && typeof data [ field ] === 'object' ) { Object . keys ( data [ field ] ) . forEach ( i => { if ( String ( parseInt ( i ) ) !== i ) error ( `Properties must be numeric to update specific list items in '${ field }'` ) ; SET . push ( `#${ field }[${ i }] = :${ field }_${ i }` ) ; values [ `:${ field }_${ i }` ] = data [ field ] [ i ] ; } ) ; names [ `#${ field }` ] = field ; } else if ( mapping . type === 'map' && data [ field ] ?. $set ) { Object . keys ( data [ field ] . $set ) . forEach ( f => { let props = f . split ( '.' ) ; let acc = [ `#${ field }` ] ; props . forEach ( ( prop , i ) => { let id = `${ field }_${ props . slice ( 0 , i + 1 ) . join ( '_' ) }` ; names [ `#${ id . replace ( /\[(\d+)\]/ , '' ) }` ] = prop . replace ( /\[(\d+)\]/ , '' ) ; if ( i === props . length - 1 ) { let input = data [ field ] . $set [ f ] ; let path = `${ acc . join ( '.' ) }.#${ id }` ; let value = `${ id . replace ( /\[(\d+)\]/ , '_$1' ) }` ; if ( input === undefined ) { REMOVE . push ( `${ path }` ) ; } else if ( input . $add ) { ADD . push ( `${ path } :${ value }` ) ; values [ `:${ value }` ] = input . $add ; } else if ( input . $append ) { SET . push ( `${ path } = list_append(${ path },:${ value })` ) ; values [ `:${ value }` ] = input . $append ; } else if ( input . $prepend ) { SET . push ( `${ path } = list_append(:${ value },${ path })` ) ; values [ `:${ value }` ] = input . $prepend ; } else if ( input . $remove ) { input . $remove . forEach ( ( i ) => { if ( typeof i !== 'number' ) error ( `Remove array for '${ field }' must only contain numeric indexes` ) ; REMOVE . push ( `${ path }[${ i }]` ) ; } ) ; } else { SET . push ( `${ path } = :${ value }` ) ; values [ `:${ value }` ] = input ; } if ( input . $set ) { Object . keys ( input . $set ) . forEach ( i => { if ( String ( parseInt ( i ) ) !== i ) error ( `Properties must be numeric to update specific list items in '${ field }'` ) ; SET . push ( `${ path }[${ i }] = :${ value }_${ i }` ) ; values [ `:${ value }_${ i }` ] = input . $set [ i ] ; } ) ; } } else { acc . push ( `#${ id . replace ( /\[(\d+)\]/ , '' ) }` ) ; } } ) ; } ) ; names [ `#${ field }` ] = field ; } else { let value = transformAttr ( mapping , validateType ( mapping , field , data [ field ] ) , data ) ; if ( value !== undefined ) { SET . push ( mapping . default !== undefined && item [ field ] === undefined && ! mapping . onUpdate ? `#${ field } = if_not_exists(#${ field },:${ field })` : `#${ field } = :${ field }` ) ; names [ `#${ field }` ] = field ; values [ `:${ field }` ] = value ; } } } } ) ; const expression = ( ( SET . length > 0 ? 'SET ' + SET . join ( ', ' ) : '' ) + ( REMOVE . length > 0 ? ' REMOVE ' + REMOVE . join ( ', ' ) : '' ) + ( ADD . length > 0 ? ' ADD ' + ADD . join ( ', ' ) : '' ) + ( DELETE . length > 0 ? ' DELETE ' + DELETE . join ( ', ' ) : '' ) ) . trim ( ) ; ExpressionAttributeValues = Object . assign ( values , ExpressionAttributeValues ) ; const payload = Object . assign ( { TableName : _table ! . name , Key , UpdateExpression : expression , ExpressionAttributeNames : Object . assign ( names , ExpressionAttributeNames ) } , typeof params === 'object' ? params : { } , ! isEmpty ( ExpressionAttributeValues ) ? { ExpressionAttributeValues } : { } , ConditionExpression ? { ConditionExpression } : { } , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , metrics ? { ReturnItemCollectionMetrics : metrics . toUpperCase ( ) } : null , returnValues ? { ReturnValues : returnValues . toUpperCase ( ) } : null ) ; return payload ; } async put ( item = { } , options = { } , params = { } ) { const payload = this . putParams ( item , options , params ) ; if ( options . execute || ( this . autoExecute && options . execute !== false ) ) { const result = await this . DocumentClient . put ( payload ) . promise ( ) ; if ( options . parse || ( this . autoParse && options . parse !== false ) ) { return Object . assign ( result , result . Attributes ? { Attributes : this . parse ( result . Attributes , Array . isArray ( options . include ) ? options . include : [ ] ) } : null ) ; } else { return result ; } } else { return payload ; } } param { object } item putBatch ( item = { } ) { const payload = this . putParams ( item ) ; return { [ payload . TableName ] : { PutRequest : { Item : payload . Item } } } ; } param { object } item param { object } options putTransaction ( item = { } , options = { } ) { const { conditions , returnValues , ... args } = options ; if ( Object . keys ( args ) . length > 0 ) error ( `Invalid put transaction options: ${ Object . keys ( args ) . join ( ', ' ) }` ) ; let payload = this . putParams ( item , options ) ; if ( 'ReturnValues' in payload ) { let { ReturnValues , ... _payload } = payload ; payload = Object . assign ( { } , _payload , { ReturnValuesOnConditionCheckFailure : ReturnValues } ) ; } return { Put : payload } ; } putParams ( item = { } , options = { } , params = { } ) { const { schema , defaults , required , linked , _table } = this ; const validateType = validateTypes ( this . DocumentClient ) ; const data = normalizeData ( this . DocumentClient ) ( schema . attributes , linked , Object . assign ( { } , defaults , item ) ) ; const { conditions , capacity , metrics , returnValues , ... _args } = options ; const args = Object . keys ( _args ) . filter ( x => ! [ 'execute' , 'parse' ] . includes ( x ) ) ; if ( args . length > 0 ) error ( `Invalid put options: ${ args . join ( ', ' ) }` ) ; if ( metrics !== undefined && ( typeof metrics !== 'string' || ! [ 'NONE' , 'SIZE' ] . includes ( metrics . toUpperCase ( ) ) ) ) error ( `'metrics' must be one of 'NONE' OR 'SIZE'` ) ; if ( capacity !== undefined && ( typeof capacity !== 'string' || ! [ 'NONE' , 'TOTAL' , 'INDEXES' ] . includes ( capacity . toUpperCase ( ) ) ) ) error ( `'capacity' must be one of 'NONE','TOTAL', OR 'INDEXES'` ) ; if ( returnValues !== undefined && ( typeof returnValues !== 'string' || ! [ 'NONE' , 'ALL_OLD' , 'UPDATED_OLD' , 'ALL_NEW' , 'UPDATED_NEW' ] . includes ( returnValues . toUpperCase ( ) ) ) ) error ( `'returnValues' must be one of 'NONE', 'ALL_OLD', 'UPDATED_OLD', 'ALL_NEW', or 'UPDATED_NEW'` ) ; let ExpressionAttributeNames ; let ExpressionAttributeValues ; let ConditionExpression ; if ( conditions ) { const { expression , names , values } = parseConditions ( conditions , this . table , this . name ) ; if ( Object . keys ( names ) . length > 0 ) { ExpressionAttributeNames = names ; ExpressionAttributeValues = values ; ConditionExpression = expression ; } } Object . keys ( required ) . forEach ( field => required [ field ] !== undefined && ( data [ field ] === undefined || data [ field ] === null ) && error ( `'${ field }${ this . schema . attributes [ field ] . alias ? `/${ this . schema . attributes [ field ] . alias }` : '' }' is a required field` ) ) ; getKey ( this . DocumentClient ) ( data , schema . attributes , schema . keys . partitionKey , schema . keys . sortKey ) ; const payload = Object . assign ( { TableName : _table ! . name , Item : Object . keys ( data ) . reduce ( ( acc , field ) => { let mapping = schema . attributes [ field ] ; let value = validateType ( mapping , field , data [ field ] ) ; return value !== undefined && ( mapping . save === undefined || mapping . save === true ) && ( ! mapping . link || ( mapping . link && mapping . save === true ) ) && ( ! _table ! . _removeNulls || ( _table ! . _removeNulls && value !== null ) ) ? Object . assign ( acc , { [ field ] : transformAttr ( mapping , value , data ) } ) : acc ; } , { } ) } , ExpressionAttributeNames ? { ExpressionAttributeNames } : null , ! isEmpty ( ExpressionAttributeValues ) ? { ExpressionAttributeValues } : null , ConditionExpression ? { ConditionExpression } : null , capacity ? { ReturnConsumedCapacity : capacity . toUpperCase ( ) } : null , metrics ? { ReturnItemCollectionMetrics : metrics . toUpperCase ( ) } : null , returnValues ? { ReturnValues : returnValues . toUpperCase ( ) } : null , typeof params === 'object' ? params : { } ) ; return payload ; } param { object } item param { object } options conditionCheck ( item = { } , options = { } ) { const { conditions , returnValues , ... args } = options ; if ( Object . keys ( args ) . length > 0 ) error ( `Invalid conditionCheck options: ${ Object . keys ( args ) . join ( ', ' ) }` ) ; let payload = this . deleteParams ( item , options ) ; if ( ! ( 'ConditionExpression' in payload ) ) error ( `'conditions' are required in a conditionCheck` ) ; if ( 'ReturnValues' in payload ) { let { ReturnValues , ... _payload } = payload ; payload = Object . assign ( { } , _payload , { ReturnValuesOnConditionCheckFailure : ReturnValues } ) ; } return { ConditionCheck : payload } ; } query ( pk , options = { } , params = { } ) { options . entity = this . name ; return this . table . query ( pk , options , params ) ; } scan ( options = { } , params = { } ) { options . entity = this . name ; return this . table . scan ( options , params ) ; } } export default Entity ;