import { EntityTarget , INTERNAL_ENTITY_ATTRIBUTE , InvalidFilterInputError , InvalidSelectInputError , NoSuchEntityExistsError , NoSuchIndexFoundError , QUERY_SELECT_TYPE , TRANSFORM_SCAN_TYPE , } from '@typedorm/common' ; import { DynamoDB } from 'aws-sdk' ; import { isEmptyObject } from '../../helpers/is-empty-object' ; import { Connection } from '../connection/connection' ; import { MERGE_STRATEGY } from '../expression/base-expression-input' ; import { Filter } from '../expression/filter' ; import { MetadataOptions } from './base-transformer' ; import { LowOrderTransformers } from './low-order-transformers' ; interface ScanTransformerToDynamoScanOptions { entity ? : EntityTarget < any > ; scanIndex ? : string ; limit ? : number ; cursor ? : DynamoDB . DocumentClient . Key ; where ? : any ; select ? : any [ ] ; onlyCount ? : boolean ; segment ? : number ; totalSegments ? : number ; } export class DocumentClientScanTransformer extends LowOrderTransformers { constructor ( connection ) { super ( connection ) ; } /**
     * Transforms TypeDORM input into dynamo scan operation input
     */ toDynamoScanItem ( scanOptions ? , metadataOptions ? ) { this . connection . logger . logTransformScan ( { requestId : metadataOptions ?. requestId , operation : TRANSFORM_SCAN_TYPE . SCAN , prefix : 'Before' , options : scanOptions , } ) ; const tableToScan = scanOptions ?. entity ? this . connection . getEntityByTarget ( scanOptions ?. entity ) ?. table : this . connection . table ; let verifiedIndexToScan ; if ( scanOptions ?. scanIndex ) { const scanIndexOptions = tableToScan . getIndexByKey ( scanOptions ?. scanIndex ) ; if ( ! scanIndexOptions ) { throw new NoSuchIndexFoundError ( tableToScan . name , scanOptions ?. scanIndex ) ; } } let transformedScanInput = { TableName : tableToScan . name , IndexName : verifiedIndexToScan , ReturnConsumedCapacity : metadataOptions ?. returnConsumedCapacity , } ; if ( scanOptions && ! isEmptyObject ( scanOptions ) ) { const { cursor , limit , where , onlyCount , select , entity } = scanOptions ; transformedScanInput . Limit = limit ; transformedScanInput . ExclusiveStartKey = cursor ; if ( onlyCount ) { if ( select ?. length ) { throw new Error ( 'Attributes projection and count can not be used together' ) ; } transformedScanInput . Select = QUERY_SELECT_TYPE . COUNT ; } let entityFilter = undefined ; if ( entity ) { const metadata = this . connection . getEntityByTarget ( entity ) ; if ( ! metadata ) { throw new NoSuchEntityExistsError ( entity . name ) ; } entityFilter = this . expressionInputParser . parseToFilter ( { [ INTERNAL_ENTITY_ATTRIBUTE . ENTITY_NAME ] : { EQ : metadata . name , } , } as any ) ; } let optionsFilter = undefined ; if ( where && ! isEmptyObject ( where ) ) { const inputFilter = this . expressionInputParser . parseToFilter ( where ) ; if ( ! inputFilter ) { throw new InvalidFilterInputError ( where ) ; } optionsFilter = inputFilter ; } let filter ; if ( entityFilter && optionsFilter ) { filter = new Filter ( ) . mergeMany ( [ entityFilter , optionsFilter ] , MERGE_STRATEGY . AND ) ; } else { filter = entityFilter || optionsFilter || null ; } if ( filter ) { const { FilterExpression , ExpressionAttributeNames , ExpressionAttributeValues , } = this . expressionBuilder . buildFilterExpression ( filter ) ; transformedScanInput = { ... transformedScanInput , FilterExpression , ExpressionAttributeNames : { ... transformedScanInput . ExpressionAttributeNames , ... ExpressionAttributeNames , } , ExpressionAttributeValues : { ... transformedScanInput . ExpressionAttributeValues , ... ExpressionAttributeValues , } , } ; } if ( select ?. length ) { const projection = this . expressionInputParser . parseToProjection ( select ) ; if ( ! projection ) { throw new InvalidSelectInputError ( select ) ; } const { ProjectionExpression , ExpressionAttributeNames , } = this . expressionBuilder . buildProjectionExpression ( projection ) ; transformedScanInput = { ... transformedScanInput , ProjectionExpression , ExpressionAttributeNames : { ... transformedScanInput . ExpressionAttributeNames , ... ExpressionAttributeNames , } , } ; } } if ( scanOptions ?. totalSegments !== undefined && scanOptions ?. totalSegments !== null ) { if ( scanOptions ?. totalSegments === 0 ) { throw new Error ( `Invalid scan option totalSegment: ${ scanOptions ?. totalSegments }.
        totalSegments is optional, but when provided it's value must be greater than 0.` ) ; } if ( scanOptions ?. segment === undefined || scanOptions ?. segment === null ) { throw new Error ( `Invalid scan option segment: ${ scanOptions ?. segment }.
        When totalSegments value is defined, value for option 'segment' must also be defined.` ) ; } if ( scanOptions ?. segment >= scanOptions ?. totalSegments ) { throw new Error ( `Invalid scan option segment: ${ scanOptions ?. segment }.
        When totalSegments value is defined, value for option 'segment' must be one less than totalSegment size.` ) ; } transformedScanInput . TotalSegments = scanOptions ?. totalSegments ; transformedScanInput . Segment = scanOptions ?. segment ; } this . connection . logger . logTransformScan ( { requestId : metadataOptions ?. requestId , prefix : 'After' , operation : TRANSFORM_SCAN_TYPE . SCAN , body : transformedScanInput , } ) ; return transformedScanInput ; } /**
     * Transforms DynamoDB scan output into entities
     */ fromDynamoScanResponseItemList < T > ( itemList , metadataOptions ? ) { const initialResponse = { items : [ ] , unknownItems : [ ] , } ; if ( ! itemList . length ) { return initialResponse ; } return itemList . reduce ( ( acc , responseItem ) => { const entityPhysicalName = responseItem [ INTERNAL_ENTITY_ATTRIBUTE . ENTITY_NAME ] ; if ( ! entityPhysicalName ) { acc . unknownItems . push ( responseItem ) ; return acc ; } const entityMetadata = this . connection . getEntityByPhysicalName ( entityPhysicalName ) ; const reverseTransformedItem = this . fromDynamoEntity ( entityMetadata . target , responseItem , metadataOptions ) ; acc . items . push ( reverseTransformedItem ) ; return acc ; } , initialResponse ) ; } }