import { EntityTarget , INDEX_TYPE , QUERY_ORDER , Replace , RETURN_VALUES , Table , TRANSFORM_TYPE , IndexOptions , QUERY_SELECT_TYPE , NoSuchIndexFoundError , InvalidFilterInputError , InvalidSelectInputError , InvalidUniqueAttributeUpdateError , InvalidPrimaryKeyAttributesUpdateError , InvalidDynamicUpdateAttributeValueError , } 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 { KeyCondition } from '../expression/key-condition' ; import { Connection } from '../connection/connection' ; import { ExpressionBuilder } from '../expression/expression-builder' ; import { AttributeMetadata } from '../metadata/attribute-metadata' ; import { DynamoEntitySchemaPrimaryKey } from '../metadata/entity-metadata' ; import { BaseTransformer , MetadataOptions } from './base-transformer' ; import { LazyTransactionWriteItemListLoader } from './is-lazy-transaction-write-item-list-loader' ; import { KeyConditionOptions } from '../expression/key-condition-options-type' ; import { UpdateBody } from '../expression/update-body-type' ; import { isObject } from '../../helpers/is-object' ; export interface ManagerToDynamoPutItemOptions { default overwriteIfExists ? : boolean ; where ? : any ; } export interface ManagerToDynamoUpdateItemsOptions { default nestedKeySeparator ? : string ; where ? : any ; } export interface ManagerToDynamoDeleteItemsOptions { where ? : any ; } export interface ManagerToDynamoQueryItemsOptions { /**
     * Index to query, when omitted, query will be run against main table
     */ queryIndex ? : string ; default keyCondition ? : KeyConditionOptions ; default limit ? : number ; default orderBy ? : QUERY_ORDER ; where ? : any ; select ? : any [ ] ; onlyCount ? : boolean ; } export interface ManagerToDynamoGetItemOptions { select ? : any [ ] ; } export class DocumentClientRequestTransformer extends BaseTransformer { protected _expressionBuilder : ExpressionBuilder ; constructor ( connection ) { super ( connection ) ; this . _expressionBuilder = new ExpressionBuilder ( ) ; } get expressionBuilder ( ) { return this . _expressionBuilder ; } get expressionInputParser ( ) { return this . _expressionInputParser ; } toDynamoPutItem < Entity > ( entity , options ? , metadataOptions ? ) { const entityClass = getConstructorForInstance ( entity ) ; const { table , internalAttributes , name } = this . connection . getEntityByTarget ( entityClass ) ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . PUT , prefix : 'Before' , entityName : name , primaryKey : null , body : entity , options , } ) ; const uniqueAttributes = this . connection . getUniqueAttributesForEntity ( entityClass ) as AttributeMetadata [ ] ; const dynamoEntity = this . toDynamoEntity ( entity ) ; const entityInternalAttributes = internalAttributes . reduce ( ( acc , attr ) => { acc [ attr . name ] = attr . value ; return acc ; } , { } as DynamoDB . DocumentClient . PutItemInputAttributeMap ) ; let dynamoPutItem = { Item : { ... entityInternalAttributes , ... dynamoEntity , } , TableName : table . name , ReturnConsumedCapacity : metadataOptions ?. returnConsumedCapacity , } as DynamoDB . DocumentClient . PutItemInput ; const uniqueRecordConditionExpression = this . expressionBuilder . buildUniqueRecordConditionExpression ( table ) ; if ( ! options ?. overwriteIfExists ) { dynamoPutItem = { ... dynamoPutItem , ... uniqueRecordConditionExpression , } ; } if ( options ?. where && ! isEmptyObject ( options ?. where ) ) { const condition = this . expressionInputParser . parseToCondition ( options ?. where ) ; if ( ! condition ) { throw new Error ( `Failed to build condition expression for input: ${ JSON . stringify ( options ?. where ) }` ) ; } const { ConditionExpression , ExpressionAttributeNames , ExpressionAttributeValues , } = this . expressionBuilder . buildConditionExpression ( condition ) ; const mergedExp = this . _expressionBuilder . andMergeConditionExpressions ( { ConditionExpression : dynamoPutItem . ConditionExpression , ExpressionAttributeNames : dynamoPutItem . ExpressionAttributeNames , ExpressionAttributeValues : dynamoPutItem . ExpressionAttributeValues , } , { ConditionExpression , ExpressionAttributeNames , ExpressionAttributeValues , } ) ; dynamoPutItem . ConditionExpression = mergedExp . ConditionExpression ; dynamoPutItem . ExpressionAttributeNames = mergedExp . ExpressionAttributeNames ; dynamoPutItem . ExpressionAttributeValues = mergedExp . ExpressionAttributeValues ; } if ( ! uniqueAttributes . length ) { this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . PUT , prefix : 'After' , entityName : name , primaryKey : null , body : dynamoPutItem , } ) ; return dynamoPutItem ; } let uniqueAttributePutItems = [ ] ; if ( uniqueAttributes . length ) { uniqueAttributePutItems = uniqueAttributes . map ( attr => { const attributeValue = ( entity as any ) [ attr . name ] ; if ( ! attributeValue ) { throw new Error ( `All unique attributes are required, Could not resolve value for unique attribute "${ attr . name }."` ) ; } if ( ! attr . unique ) { throw new Error ( 'All unique attributes metadata must be marked unique.' ) ; } const uniqueItemPrimaryKey = this . getParsedPrimaryKey ( table , attr . unique , entity ) ; return { Put : { Item : uniqueItemPrimaryKey , TableName : table . name , ... uniqueRecordConditionExpression , } , } ; } ) ; } const uniqueAttributesPutItems = [ { Put : dynamoPutItem } , ... uniqueAttributePutItems , ] ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . PUT , prefix : 'After' , entityName : name , primaryKey : null , body : uniqueAttributesPutItems , } ) ; return uniqueAttributesPutItems ; } toDynamoGetItem < Entity , PrimaryKey > ( entityClass , primaryKey , options ? , metadataOptions ? ) { const metadata = this . connection . getEntityByTarget ( entityClass ) ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . GET , prefix : 'Before' , entityName : metadata . name , primaryKey , } ) ; const tableName = this . getTableNameForEntity ( entityClass ) ; const parsedPrimaryKey = this . getParsedPrimaryKey ( metadata . table , metadata . schema . primaryKey , primaryKey ) ; if ( isEmptyObject ( parsedPrimaryKey ) ) { throw new Error ( 'Primary could not be resolved' ) ; } let transformBody = { TableName : tableName , Key : { ... parsedPrimaryKey , } , ReturnConsumedCapacity : metadataOptions ?. returnConsumedCapacity , } as DynamoDB . DocumentClient . GetItemInput ; if ( ! options || isEmptyObject ( options ) ) { this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . GET , prefix : 'After' , entityName : metadata . name , primaryKey : null , body : transformBody , } ) ; return transformBody ; } if ( options . select ?. length ) { const projection = this . expressionInputParser . parseToProjection ( options . select ) ; if ( ! projection ) { throw new Error ( `Failed to build projection expression for input: ${ JSON . stringify ( options . select ) }` ) ; } const { ProjectionExpression , ExpressionAttributeNames , } = this . expressionBuilder . buildProjectionExpression ( projection ) ; transformBody = { ... transformBody , ProjectionExpression , ExpressionAttributeNames : { ... transformBody . ExpressionAttributeNames , ... ExpressionAttributeNames , } , } ; } this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . GET , prefix : 'After' , entityName : metadata . name , primaryKey : null , body : transformBody , } ) ; return transformBody ; } toDynamoUpdateItem < Entity , PrimaryKey , AdditionalProperties = Entity > ( entityClass , primaryKeyAttributes , body , options = { } , metadataOptions ? ) { const { nestedKeySeparator = '.' } = options ; if ( ! this . connection . hasMetadata ( entityClass ) ) { throw new Error ( `No metadata found for class "${ entityClass . name }".` ) ; } const metadata = this . connection . getEntityByTarget ( entityClass ) ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . UPDATE , prefix : 'Before' , entityName : metadata . name , primaryKey : primaryKeyAttributes , body , options , } ) ; const tableName = metadata . table . name ; const parsedPrimaryKey = this . getParsedPrimaryKey ( metadata . table , metadata . schema . primaryKey , primaryKeyAttributes ) ; if ( isEmptyObject ( parsedPrimaryKey ) ) { throw new Error ( 'Primary could not be resolved' ) ; } const autoUpdateAttributes = this . connection . getAutoUpdateAttributes ( entityClass ) ; const formattedAutoUpdateAttributes = autoUpdateAttributes . reduce ( ( acc , attr ) => { acc [ attr . name ] = attr . autoGenerateValue ( attr . strategy ) ; return acc ; } , { } as { [ key ] : any ; } ) ; const rawAttributesToUpdate = { ... body , ... formattedAutoUpdateAttributes , } ; /**
         * 1.0 - analyze attributes' value type (static/dynamic)
         *
         * Here we parse all attributes to it's update value and determine
         * if it's value can be statically inferred
         */ const staticOrDynamicUpdateAttributesWithMetadata = Object . entries ( { ... rawAttributesToUpdate , } ) . reduce ( ( acc , [ attrName , attrValue ] ) => { const valueWithType = this . expressionInputParser . parseAttributeToUpdateValue ( attrName , attrValue ) as { value : any ; type : 'static' | 'dynamic' ; } ; acc . transformed [ attrName ] = valueWithType . value ; acc . typeMetadata [ attrName ] = valueWithType . type ; return acc ; } , { transformed : { } , typeMetadata : { } } as { transformed : Record < string , any > ; typeMetadata : Record < string , 'dynamic' | 'static' > ; } ) ; /**
         * 2.0 - apply custom class transformation on static attributes
         *
         * we manually need to replace the constructor of the attributes to update
         * with the entity class, so that we can pass it through to class-transformer
         * to have all transformer metadata applied.
         */ const onlyStaticAttributes = Object . entries ( staticOrDynamicUpdateAttributesWithMetadata . transformed ) . reduce ( ( acc , [ attrKey , attrValue ] ) => { if ( staticOrDynamicUpdateAttributesWithMetadata . typeMetadata [ attrKey ] === 'static' ) { acc [ attrKey ] = attrValue ; } return acc ; } , { } as any ) ; onlyStaticAttributes . constructor = entityClass ; const classTransformedStaticAttributes = this . applyClassTransformerFormations ( onlyStaticAttributes ) as Entity ; staticOrDynamicUpdateAttributesWithMetadata . transformed = { ... staticOrDynamicUpdateAttributesWithMetadata . transformed , ... classTransformedStaticAttributes , } ; /**
         * 3.0 - Get referenced unique attributes and validate that current update body can be safely applied
         */ const uniqueAttributesToUpdate = this . connection . getUniqueAttributesForEntity ( entityClass ) . filter ( attr => ! ! ( body as any ) [ attr . name ] ) . map ( attr => { if ( staticOrDynamicUpdateAttributesWithMetadata . typeMetadata [ attr . name ] === 'dynamic' ) { throw new InvalidDynamicUpdateAttributeValueError ( attr . name , staticOrDynamicUpdateAttributesWithMetadata . transformed [ attr . name ] ) ; } return attr ; } ) ; /**
         * 3.1 - Get referenced primary key attributes and validate that current update body can be safely applied
         */ const affectedPrimaryKeyAttributes = this . getAffectedPrimaryKeyAttributes < Entity > ( entityClass , staticOrDynamicUpdateAttributesWithMetadata . transformed , staticOrDynamicUpdateAttributesWithMetadata . typeMetadata ) ; if ( ! isEmptyObject ( affectedPrimaryKeyAttributes ) ) { const primaryKeyReferencedAttributes = this . connection . getPrimaryKeyAttributeInterpolationsForEntity ( entityClass ) ; const nonKeyAttributesToUpdate = Object . keys ( body ) . filter ( attr => ! primaryKeyReferencedAttributes . includes ( attr ) ) ; if ( uniqueAttributesToUpdate . length ) { throw new InvalidUniqueAttributeUpdateError ( affectedPrimaryKeyAttributes ! , uniqueAttributesToUpdate . map ( attr => attr . name ) ) ; } if ( nonKeyAttributesToUpdate . length ) { throw new InvalidPrimaryKeyAttributesUpdateError ( affectedPrimaryKeyAttributes ! , nonKeyAttributesToUpdate ) ; } } /**
         * 3.2 - Get referenced indexes' attributes and validate that current update body can be safely applied
         */ const affectedIndexes = this . getAffectedIndexesForAttributes < Entity > ( entityClass , staticOrDynamicUpdateAttributesWithMetadata . transformed , staticOrDynamicUpdateAttributesWithMetadata . typeMetadata , { nestedKeySeparator , additionalAttributesDict : { ... primaryKeyAttributes , } , } ) ; /**
         * 4.0 - Build update Item body with given condition and options
         */ const itemToUpdate = { TableName : tableName , Key : { ... parsedPrimaryKey , } , ReturnConsumedCapacity : metadataOptions ?. returnConsumedCapacity , ReturnValues : RETURN_VALUES . ALL_NEW , } ; /**
         * 4.1 - if 'where' was provided, build condition expression
         */ if ( options . where && ! isEmptyObject ( options . where ) ) { const condition = this . expressionInputParser . parseToCondition ( options . where ) ; if ( ! condition ) { throw new Error ( `Failed to build condition expression for input: ${ JSON . stringify ( options . where ) }` ) ; } const { ConditionExpression , ExpressionAttributeNames , ExpressionAttributeValues , } = this . expressionBuilder . buildConditionExpression ( condition ) ; itemToUpdate . ConditionExpression = ConditionExpression ; itemToUpdate . ExpressionAttributeNames = { ... ExpressionAttributeNames , ... itemToUpdate . ExpressionAttributeNames , } ; itemToUpdate . ExpressionAttributeValues = { ... ExpressionAttributeValues , ... itemToUpdate . ExpressionAttributeValues , } ; } /**
         * 5.0 - update contains primary key attributes so it must be lazily updated
         * This requires deleting old item and writing new item to the table both in a transaction
         */ if ( isObject ( affectedPrimaryKeyAttributes ) && ! isEmptyObject ( affectedPrimaryKeyAttributes ) ) { const lazyLoadTransactionWriteItems = this . lazyToDynamoUpdatePrimaryKeyFactory ( metadata . table , metadata . name , metadata . schema . primaryKey , { Item : { ... affectedPrimaryKeyAttributes , ... affectedIndexes , ... staticOrDynamicUpdateAttributesWithMetadata . transformed , } , TableName : metadata . table . name , ReturnConsumedCapacity : itemToUpdate . ReturnConsumedCapacity , ReturnValues : itemToUpdate . ReturnValues , ConditionExpression : itemToUpdate . ConditionExpression , ExpressionAttributeNames : itemToUpdate . ExpressionAttributeNames , ExpressionAttributeValues : itemToUpdate . ExpressionAttributeValues , } , metadataOptions ) ; return { primaryKeyAttributes , entityClass , lazyLoadTransactionWriteItems , } ; } /**
         * 5.0.1 - build update expression with user provided body and all other auto transformation
         */ const update = this . expressionInputParser . parseToUpdate ( { ... rawAttributesToUpdate , ... affectedIndexes , } , staticOrDynamicUpdateAttributesWithMetadata . transformed ) ; const { UpdateExpression , ExpressionAttributeNames , ExpressionAttributeValues , } = this . expressionBuilder . buildUpdateExpression ( update ) ; itemToUpdate . UpdateExpression = UpdateExpression ; itemToUpdate . ExpressionAttributeNames = { ... ExpressionAttributeNames , ... itemToUpdate . ExpressionAttributeNames , } ; itemToUpdate . ExpressionAttributeValues = { ... ExpressionAttributeValues , ... itemToUpdate . ExpressionAttributeValues , } ; /**
         * 5.1 - Update contains unique attributes, build a lazy unique attributes loader and return
         */ if ( uniqueAttributesToUpdate . length ) { const lazyLoadTransactionWriteItems = this . lazyToDynamoUpdateUniqueItemFactory < Entity > ( metadata . table , metadata . name , uniqueAttributesToUpdate , dropProp ( itemToUpdate , 'ReturnValues' ) , staticOrDynamicUpdateAttributesWithMetadata . transformed , metadataOptions ) ; return { primaryKeyAttributes , entityClass , lazyLoadTransactionWriteItems , } ; } /**
         * 5.2 - return simple update body
         */ this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . UPDATE , prefix : 'After' , entityName : metadata . name , primaryKey : null , body : itemToUpdate , } ) ; return itemToUpdate ; } toDynamoDeleteItem < Entity , PrimaryKey > ( entityClass , primaryKey , options ? , metadataOptions ? ) { const metadata = this . connection . getEntityByTarget ( entityClass ) ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . DELETE , prefix : 'Before' , entityName : metadata . name , primaryKey , } ) ; const tableName = metadata . table . name ; const parsedPrimaryKey = this . getParsedPrimaryKey ( metadata . table , metadata . schema . primaryKey , primaryKey ) ; if ( isEmptyObject ( parsedPrimaryKey ) ) { throw new Error ( 'Primary could not be resolved' ) ; } const uniqueAttributesToRemove = this . connection . getUniqueAttributesForEntity ( entityClass ) ; const mainItemToRemove = { TableName : tableName , Key : { ... parsedPrimaryKey , } , ReturnConsumedCapacity : metadataOptions ?. returnConsumedCapacity , } ; if ( options ?. where && ! isEmptyObject ( options . where ) ) { const condition = this . expressionInputParser . parseToCondition ( options ?. where ) ; if ( ! condition ) { throw new Error ( `Failed to build condition expression for input: ${ JSON . stringify ( options ?. where ) }` ) ; } const { ConditionExpression , ExpressionAttributeNames , ExpressionAttributeValues , } = this . expressionBuilder . buildConditionExpression ( condition ) ; mainItemToRemove . ConditionExpression = ConditionExpression ; mainItemToRemove . ExpressionAttributeNames = { ... mainItemToRemove . ExpressionAttributeNames , ... ExpressionAttributeNames , } ; mainItemToRemove . ExpressionAttributeValues = { ... mainItemToRemove . ExpressionAttributeValues , ... ExpressionAttributeValues , } ; } if ( ! uniqueAttributesToRemove ?. length ) { this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . DELETE , prefix : 'After' , entityName : metadata . name , primaryKey , } ) ; return mainItemToRemove ; } const lazyLoadTransactionWriteItems = this . lazyToDynamoRemoveItemFactory ( metadata . table , metadata . name , uniqueAttributesToRemove , mainItemToRemove , metadataOptions ) ; return { primaryKeyAttributes : primaryKey , entityClass , lazyLoadTransactionWriteItems , } ; } toDynamoQueryItem < Entity , PartitionKeyAttributes > ( entityClass , partitionKeyAttributes , queryOptions ? , metadataOptions ? ) { const { table , schema , name } = this . connection . getEntityByTarget ( entityClass ) ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . QUERY , prefix : 'Before' , entityName : name , primaryKey : partitionKeyAttributes , options : queryOptions , } ) ; const queryIndexName = queryOptions ?. queryIndex ; let indexToQuery ; if ( queryIndexName ) { const matchingIndex = table . getIndexByKey ( queryIndexName ) ; if ( ! matchingIndex ) { throw new NoSuchIndexFoundError ( table . name , queryIndexName ) ; } const matchingIndexOnEntity = schema . indexes && schema . indexes [ queryIndexName ] ; if ( ! matchingIndexOnEntity ) { throw new Error ( `Requested to query items from index "${ queryIndexName }", but no such index exists on entity.` ) ; } indexToQuery = matchingIndex ; } const parsedPartitionKey = { } as { name : string ; value : any ; } ; if ( ! queryIndexName || ! indexToQuery || indexToQuery ?. type === INDEX_TYPE . LSI ) { parsedPartitionKey . name = table . partitionKey ; parsedPartitionKey . value = typeof partitionKeyAttributes === 'string' ? partitionKeyAttributes : parseKey ( schema . primaryKey . attributes [ table . partitionKey ] , partitionKeyAttributes ) ; } else { parsedPartitionKey . name = indexToQuery . partitionKey ; const schemaForIndexToQuery = ( schema . indexes ?? { } ) [ queryIndexName ] ; parsedPartitionKey . value = typeof partitionKeyAttributes === 'string' ? partitionKeyAttributes : parseKey ( schemaForIndexToQuery . attributes [ indexToQuery . partitionKey ] , partitionKeyAttributes ) ; } const partitionKeyCondition = new KeyCondition ( ) . equals ( parsedPartitionKey . name , parsedPartitionKey . value ) ; const partitionKeyConditionExpression = this . expressionBuilder . buildKeyConditionExpression ( partitionKeyCondition ) ; if ( ! queryOptions || isEmptyObject ( queryOptions ) ) { const transformedQueryItem = { TableName : table . name , IndexName : queryIndexName , ReturnConsumedCapacity : metadataOptions ?. returnConsumedCapacity , ... partitionKeyConditionExpression , } ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . QUERY , prefix : 'After' , entityName : name , primaryKey : partitionKeyAttributes , body : transformedQueryItem , } ) ; return transformedQueryItem ; } const parsedSortKey = { } as { name : string ; } ; if ( ! indexToQuery ) { if ( ! table . usesCompositeKey ( ) ) { throw new Error ( `Table ${ table . name } does not use composite key, thus querying a sort key is not allowed` ) ; } parsedSortKey . name = table . sortKey ; } else { parsedSortKey . name = indexToQuery . sortKey ; } const { keyCondition , limit , orderBy : order , where , select , onlyCount , } = queryOptions ; let queryInputParams = { TableName : table . name , IndexName : queryIndexName , ReturnConsumedCapacity : metadataOptions ?. returnConsumedCapacity , Limit : limit , ScanIndexForward : ! order || order === QUERY_ORDER . ASC , ... partitionKeyConditionExpression , } as DynamoDB . DocumentClient . QueryInput ; if ( keyCondition && ! isEmptyObject ( keyCondition ) ) { const sortKeyCondition = this . expressionInputParser . parseToKeyCondition ( parsedSortKey . name , keyCondition ) ; const { KeyConditionExpression , ExpressionAttributeNames , ExpressionAttributeValues , } = this . expressionBuilder . buildKeyConditionExpression ( partitionKeyCondition . merge ( sortKeyCondition ) ) ; queryInputParams = { ... queryInputParams , KeyConditionExpression , ExpressionAttributeNames : { ... queryInputParams . ExpressionAttributeNames , ... ExpressionAttributeNames , } , ExpressionAttributeValues : { ... queryInputParams . ExpressionAttributeValues , ... ExpressionAttributeValues , } , } ; } if ( where && ! isEmptyObject ( where ) ) { const filter = this . expressionInputParser . parseToFilter ( where ) ; if ( ! filter ) { throw new InvalidFilterInputError ( where ) ; } const { FilterExpression , ExpressionAttributeNames , ExpressionAttributeValues , } = this . expressionBuilder . buildFilterExpression ( filter ) ; queryInputParams = { ... queryInputParams , FilterExpression , ExpressionAttributeNames : { ... queryInputParams . ExpressionAttributeNames , ... ExpressionAttributeNames , } , ExpressionAttributeValues : { ... queryInputParams . ExpressionAttributeValues , ... ExpressionAttributeValues , } , } ; } if ( onlyCount ) { if ( select ?. length ) { throw new Error ( 'Attributes projection and count can not be used together' ) ; } queryInputParams . Select = QUERY_SELECT_TYPE . COUNT ; } if ( select && select . length ) { const projection = this . expressionInputParser . parseToProjection ( select ) ; if ( ! projection ) { throw new InvalidSelectInputError ( select ) ; } const { ProjectionExpression , ExpressionAttributeNames , } = this . expressionBuilder . buildProjectionExpression ( projection ) ; queryInputParams = { ... queryInputParams , ProjectionExpression , ExpressionAttributeNames : { ... queryInputParams . ExpressionAttributeNames , ... ExpressionAttributeNames , } , } ; } this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . QUERY , prefix : 'After' , entityName : name , primaryKey : partitionKeyAttributes , body : queryInputParams , } ) ; return queryInputParams ; } private lazyToDynamoUpdatePrimaryKeyFactory ( table , entityName , primaryKeySchema , newItemBody , metadataOptions ? ) { return ( previousItemBody ) => { const updateTransactionItems = [ { Put : { ... newItemBody , Item : { ... previousItemBody , ... newItemBody . Item } , } , } , ] as DynamoDB . DocumentClient . TransactWriteItem [ ] ; if ( previousItemBody && ! isEmptyObject ( previousItemBody ) ) { updateTransactionItems . push ( { Delete : { TableName : table . name , Key : { ... this . getParsedPrimaryKey ( table , primaryKeySchema , previousItemBody ) , } , } , } ) ; } this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . UPDATE , prefix : 'After' , entityName , primaryKey : null , body : updateTransactionItems , } ) ; return updateTransactionItems ; } ; } /**
     * 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 lazyToDynamoUpdateUniqueItemFactory < Entity > ( table , entityName , uniqueAttributesToUpdate , mainItem , newBody , metadataOptions ? ) { return ( previousItemBody ) => { const uniqueRecordConditionExpression = this . expressionBuilder . buildUniqueRecordConditionExpression ( table ) ; const uniqueAttributeInputs = uniqueAttributesToUpdate . flatMap ( attr => { const uniqueAttributeWriteItems = [ { Put : { TableName : table . name , Item : { ... this . getParsedPrimaryKey ( table , attr . unique , newBody as Partial < Entity > ) , } , ... uniqueRecordConditionExpression , } , } , ] ; if ( previousItemBody && previousItemBody [ attr . name ] ) { uniqueAttributeWriteItems . push ( { Delete : { TableName : table . name , Key : { ... this . getParsedPrimaryKey ( table , attr . unique , previousItemBody ) , } , } , } ) ; } return uniqueAttributeWriteItems ; } ) ; const updateTransactionItems = [ { Update : mainItem } , ... uniqueAttributeInputs , ] as DynamoDB . DocumentClient . TransactWriteItem [ ] ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . UPDATE , prefix : 'After' , entityName , primaryKey : null , body : updateTransactionItems , } ) ; return updateTransactionItems ; } ; } param table param uniqueAttributesToRemove param mainItem private lazyToDynamoRemoveItemFactory ( table , entityName , uniqueAttributesToRemove , mainItem , metadataOptions ? ) { return ( existingItemBody ) => { let uniqueAttributeInputs = [ ] ; if ( existingItemBody ) { uniqueAttributeInputs = uniqueAttributesToRemove . map ( attr => { return { Delete : { TableName : table . name , Key : { ... this . getParsedPrimaryKey ( table , attr . unique , existingItemBody ) , } , } , } ; } ) ; } const deleteTransactionItems = [ { Delete : mainItem , } , ... uniqueAttributeInputs , ] as DynamoDB . DocumentClient . TransactWriteItem [ ] ; this . connection . logger . logTransform ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_TYPE . DELETE , prefix : 'After' , entityName , primaryKey : null , body : deleteTransactionItems , } ) ; return deleteTransactionItems ; } ; } }