import { ATTRIBUTE_TYPE , RangeOperator , ScalarType , SimpleOperator , UpdateType , } from '@typedorm/common' ; import { isEmptyObject } from '../../helpers/is-empty-object' ; import { KeyCondition } from './key-condition' ; import { Filter } from './filter' ; import { BaseExpressionInput , MERGE_STRATEGY } from './base-expression-input' ; import { isScalarType } from '../../helpers/is-scalar-type' ; import { FilterOptions } from './filter-options-type' ; import { ConditionOptions } from './condition-options-type' ; import { Condition } from './condition' ; import { Projection } from './projection' ; import { KeyConditionOptions } from './key-condition-options-type' ; import { ProjectionKeys } from './projection-keys-options-type' ; import { isSetOperatorComplexValueType , UpdateBody } from './update-body-type' ; import { SetUpdate } from './update/set-update' ; import { AddUpdate } from './update/add-update' ; import { Update } from './update/update' ; import { DeleteUpdate } from './update/delete-update' ; import { RemoveUpdate } from './update/remove-update' ; import { isObject } from '../../helpers/is-object' ; import { nestedKeyAccessRegex } from '../../helpers/constants' ; /**
 * Parses expression input to expression instances
 */ export class ExpressionInputParser { parseToKeyCondition ( key , options ) { return this . operatorToBaseExpression ( key , options , new KeyCondition ( ) ) ; } parseToFilter < Entity , PrimaryKey > ( options ) { return this . recursiveParseToBaseExpression ( options , Filter ) . pop ( ) ; } parseToCondition < Entity > ( options ) { return this . recursiveParseToBaseExpression ( options , Condition ) . pop ( ) ; } parseToProjection < Entity > ( keys ) { const projection = new Projection ( ) ; projection . addProjectionAttributes ( keys as string [ ] ) ; return projection ; } parseToUpdate < Entity , AdditionalProperties = { } > ( body , attrValueOverrideMap = { } ) { return this . parseToUpdateExpression ( body , attrValueOverrideMap ) ; } /**
     * Parses complex update object to a value and type
     */ parseAttributeToUpdateValue ( attr , value ) { if ( isObject ( value ) && ! isEmptyObject ( value ) ) { const [ operator , operatorValue ] = Object . entries ( value as any ) [ 0 ] ; const parsedUpdate = this . parseValueToUpdateExp ( attr , value , operator as any , operatorValue ) ; const parsedValue = Object . values ( parsedUpdate . values ?? { } ) [ 0 ] ; if ( ! ( parsedUpdate instanceof SetUpdate ) || parsedUpdate . expression . includes ( ' + ' ) || parsedUpdate . expression . includes ( ' - ' ) || nestedKeyAccessRegex . test ( parsedUpdate . expression ) ) { return { value : parsedValue , type : 'dynamic' , } ; } return { type : 'static' , value : parsedValue , } ; } else { if ( nestedKeyAccessRegex . test ( attr ) ) { return { type : 'dynamic' , value , } ; } return { type : 'static' , value } ; } } param options param ExpClass private recursiveParseToBaseExpression < T extends BaseExpressionInput > ( options , ExpClass ) { return Object . entries ( options ) . map ( ( [ operatorOrAttr , value ] ) : T => { if ( [ 'AND' , 'OR' , 'NOT' ] . includes ( operatorOrAttr ) ) { const parsedExpList = this . recursiveParseToBaseExpression < T > ( value , ExpClass ) ; const base = parsedExpList . shift ( ) ; if ( ! base ) { throw new Error ( `Value for operator "${ operatorOrAttr }" can not be empty` ) ; } switch ( operatorOrAttr ) { case 'AND' : { if ( ! parsedExpList ?. length ) { throw new Error ( `Value for operator "${ operatorOrAttr }" must contain more that 1 attributes.` ) ; } return base . mergeMany ( parsedExpList as T [ ] , MERGE_STRATEGY . AND ) ; } case 'OR' : { if ( ! parsedExpList ?. length ) { throw new Error ( `Value for operator "${ operatorOrAttr }" must contain more that 1 attributes.` ) ; } return base . mergeMany ( parsedExpList as T [ ] , MERGE_STRATEGY . OR ) ; } case 'NOT' : { if ( parsedExpList ?. length ) { throw new Error ( `Value for operator "${ operatorOrAttr }" can not contain more than 1 attributes.` ) ; } return base . not ( ) ; } default : { throw new Error ( `Unsupported logical operator "${ operatorOrAttr }"` ) ; } } } else { return this . operatorToBaseExpression ( operatorOrAttr , value , new ExpClass ( ) ) ; } } ) ; } param body private parseToUpdateExpression ( body , attrValueOverrideMap ) { return ( Object . entries ( body ) . map ( ( [ attr , value ] ) => { if ( isObject ( value ) && ! isEmptyObject ( value ) ) { const [ operator , operatorValue ] = Object . entries ( value as any ) [ 0 ] as [ ( UpdateType . ArithmeticOperator | UpdateType . SetUpdateOperator | UpdateType . Action ) , any ] ; return this . parseValueToUpdateExp ( attr , value , operator , operatorValue , attrValueOverrideMap [ attr ] ) ; } else { return new SetUpdate ( ) . setTo ( attr , attrValueOverrideMap [ attr ] || value ) ; } } ) . reduce ( ( acc , currExp ) => { acc . find ( instance => instance . constructor === currExp . constructor ) ! . merge ( currExp ) ; return acc ; } , [ new SetUpdate ( ) , new AddUpdate ( ) , new RemoveUpdate ( ) , new DeleteUpdate ( ) , ] ) . reduce ( ( acc , curr ) => { acc . merge ( curr ) ; return acc ; } , new Update ( ) ) ) ; } param attribute param attributeValue private parseValueToUpdateExp ( attribute , attributeValue , operator , operatorValue , staticValueToOverride ? ) { switch ( operator ) { case 'INCREMENT_BY' : case 'DECREMENT_BY' : { return new SetUpdate ( ) . setTo ( attribute , operatorValue , operator ) ; } case 'IF_NOT_EXISTS' : { if ( isSetOperatorComplexValueType ( operatorValue ) ) { return new SetUpdate ( ) . setToIfNotExists ( attribute , staticValueToOverride || operatorValue . $VALUE , operatorValue . $PATH ) ; } else { return new SetUpdate ( ) . setToIfNotExists ( attribute , operatorValue ) ; } } case 'LIST_APPEND' : { if ( isSetOperatorComplexValueType ( operatorValue ) ) { return new SetUpdate ( ) . setOrAppendToList ( attribute , operatorValue . $VALUE , operatorValue . $PATH ) ; } else { return new SetUpdate ( ) . setOrAppendToList ( attribute , operatorValue ) ; } } case 'SET' : { /**
                 * aliased set support, this allows access patterns like
                 * {id: { SET: '1'}}
                 * behaves similar to {id: '1'}
                 */ if ( isObject ( operatorValue ) && ! isEmptyObject ( operatorValue ) ) { const [ nestedOperator , nestedOperatorValue ] = Object . entries ( operatorValue ) [ 0 ] as [ UpdateType . ArithmeticOperator | UpdateType . SetUpdateOperator , any ] ; return this . parseValueToUpdateExp ( attribute , nestedOperatorValue , nestedOperator , nestedOperatorValue , staticValueToOverride ) ; } else { return new SetUpdate ( ) . setTo ( attribute , staticValueToOverride || operatorValue ) ; } } case 'ADD' : { if ( isEmptyObject ( operatorValue ) ) { throw new Error ( `Invalid value ${ operatorValue } received for action "ADD", Only numbers and lists are supported.` ) ; } return new AddUpdate ( ) . addTo ( attribute , operatorValue ) ; } case 'DELETE' : { return new DeleteUpdate ( ) . delete ( attribute , operatorValue ) ; } case 'REMOVE' : { if ( typeof operatorValue === 'boolean' && ! ! operatorValue ) { return new RemoveUpdate ( ) . remove ( attribute ) ; } else if ( ! isEmptyObject ( operatorValue ) && Array . isArray ( operatorValue . $AT_INDEX ) ) { return new RemoveUpdate ( ) . remove ( attribute , { atIndexes : operatorValue . $AT_INDEX , } ) ; } else { throw new Error ( `Invalid value ${ operatorValue } received for action "REMOVE". Value must be set to boolean
            In addition, You may use special value type {$AT_INDEX: Array<number>} for attribute of type list..` ) ; } } default : { return new SetUpdate ( ) . setTo ( attribute , staticValueToOverride || attributeValue ) ; } } } param attribute param value param exp private operatorToBaseExpression < T extends BaseExpressionInput > ( attribute , value , exp ) { switch ( typeof value ) { case 'string' : { if ( value === 'ATTRIBUTE_EXISTS' ) { exp . attributeExists ( attribute ) ; return exp ; } else if ( value === 'ATTRIBUTE_NOT_EXISTS' ) { exp . attributeNotExists ( attribute ) ; return exp ; } else { throw new Error ( `Operator used must be one of "ATTRIBUTE_EXISTS", "ATTRIBUTE_NOT_EXISTS" for 
            attribute "${ attribute }".` ) ; } } case 'object' : { if ( isEmptyObject ( value ) ) { throw new Error ( `Value for attribute "${ attribute }" can not be empty` ) ; } const operatorAndValue = Object . entries ( value ) ; if ( operatorAndValue . length !== 1 ) { throw new Error ( `Invalid value "${ JSON . stringify ( value ) }" found for attribute: ${ attribute }` ) ; } const [ innerOp , innerVal ] = operatorAndValue [ 0 ] as [ any , ScalarType ] ; if ( isScalarType ( innerVal ) ) { return this . parseScalarValueToExp ( innerOp , attribute , innerVal , exp ) ; } else { return this . parseNonScalarValueToExp ( innerOp , attribute , innerVal , exp ) ; } } default : { throw new Error ( `Value for attribute "${ attribute }" must be of type object or string` ) ; } } } param operator param attrPath param value param exp private parseScalarValueToExp < T extends BaseExpressionInput > ( operator , attrPath , value , exp ) { switch ( operator ) { case 'EQ' : { exp . equals ( attrPath , value ) ; return exp ; } case 'LT' : { exp . lessThan ( attrPath , value ) ; return exp ; } case 'LE' : { exp . lessThanAndEqualTo ( attrPath , value ) ; return exp ; } case 'GT' : { exp . greaterThan ( attrPath , value ) ; return exp ; } case 'GE' : { exp . greaterThanAndEqualTo ( attrPath , value ) ; return exp ; } case 'NE' : { exp . notEquals ( attrPath , value ) ; return exp ; } case 'BEGINS_WITH' : { exp . beginsWith ( attrPath , value ) ; return exp ; } case 'CONTAINS' : { exp . contains ( attrPath , value ) ; return exp ; } case 'ATTRIBUTE_TYPE' : { exp . attributeType ( attrPath , value as ATTRIBUTE_TYPE ) ; return exp ; } default : { throw new Error ( `Unsupported operator: ${ operator }` ) ; } } } param operator param attrPath param value param exp private parseNonScalarValueToExp < T extends BaseExpressionInput > ( operator , attrPath , value , exp ) { switch ( operator ) { case 'BETWEEN' : { if ( ! Array . isArray ( value ) || value . length !== 2 ) { throw new Error ( `Value for operator ${ operator } must be of type array with exact two items.` ) ; } exp . between ( attrPath , value as [ ScalarType , ScalarType ] ) ; return exp ; } case 'IN' : { if ( ! Array . isArray ( value ) || value . length < 1 ) { throw new Error ( `Value for operator ${ operator } must be of type array with at least one item.` ) ; } exp . in ( attrPath , value as ScalarType [ ] ) ; return exp ; } case 'SIZE' : { const operatorAndValue = Object . entries ( value ) ; if ( operatorAndValue . length !== 1 ) { throw new Error ( `Invalid value "${ JSON . stringify ( value ) }" found for operator: ${ operator }` ) ; } const [ innerOp , innerVal ] = operatorAndValue [ 0 ] as [ any , ScalarType ] ; const parsedExp = this . parseScalarValueToExp ( innerOp , attrPath , innerVal , exp ) ; parsedExp . size ( attrPath ) ; return parsedExp ; } default : { throw new Error ( `Unsupported operator: ${ operator }` ) ; } } } }