import { AttributeMetadata } from './../metadata/attribute-metadata' ; import { DynamoEntityIndexesSchema , DynamoEntityIndexSchema , } from './../metadata/entity-metadata' ; import { EntityTarget , Table , SparseIndexParseError , CONSUMED_CAPACITY_TYPE , InvalidDynamicUpdateAttributeValueError , } from '@typedorm/common' ; import { getConstructorForInstance } from '../../helpers/get-constructor-for-instance' ; import { isEmptyObject } from '../../helpers/is-empty-object' ; import { isScalarType } from '../../helpers/is-scalar-type' ; import { parseKey } from '../../helpers/parse-key' ; import { Connection } from '../connection/connection' ; import { IsAutoGeneratedAttributeMetadata } from '../metadata/auto-generated-attribute-metadata' ; import { DynamoEntitySchemaPrimaryKey } from '../metadata/entity-metadata' ; import { isDynamoEntityKeySchema } from '../../helpers/is-dynamo-entity-key-schema' ; import { isKeyOfTypeAliasSchema } from '../../helpers/is-key-of-type-alias-schema' ; import { classToPlain } from 'class-transformer' ; import { ExpressionInputParser } from '../expression/expression-input-parser' ; export interface MetadataOptions { requestId ? : string ; returnConsumedCapacity ? : CONSUMED_CAPACITY_TYPE ; } export abstract class BaseTransformer { protected _expressionInputParser : ExpressionInputParser ; constructor ( protected connection ) { this . _expressionInputParser = new ExpressionInputParser ( ) ; } param entityClass getTableNameForEntity < Entity > ( entityClass ) { const entityMetadata = this . connection . getEntityByTarget ( entityClass ) ; return entityMetadata . table . name ; } applyClassTransformerFormations < Entity > ( entity ) { const transformedPlainEntity = classToPlain < Entity > ( entity , { enableImplicitConversion : true , excludePrefixes : [ '__' ] , } ) ; return transformedPlainEntity as Entity ; } param entity toDynamoEntity < Entity > ( entity ) { const entityClass = getConstructorForInstance ( entity ) ; const entityMetadata = this . connection . getEntityByTarget ( entityClass ) ; this . connection . getAttributesForEntity ( entityClass ) . forEach ( attr => { if ( ! Object . keys ( entity ) . includes ( attr . name ) ) { if ( IsAutoGeneratedAttributeMetadata ( attr ) ) { entity = Object . assign ( entity , { [ attr . name ] : attr . value } ) ; } const attributeDefaultValue = ( attr as AttributeMetadata ) ?. default ; if ( attributeDefaultValue && typeof attributeDefaultValue === 'function' ) { const attrDefaultValue = attributeDefaultValue ( entity ) ; entity = Object . assign ( entity , { [ attr . name ] : attrDefaultValue , } ) ; } } } ) ; const parsedEntity = this . applyClassTransformerFormations ( entity ) ; const parsedPrimaryKey = this . recursiveParseEntity ( entityMetadata . schema . primaryKey . attributes , parsedEntity ) ; const indexesToParse = entityMetadata . schema . indexes ?? { } ; const rawParsedIndexes = this . recursiveParseEntity ( indexesToParse , parsedEntity ) ; const parsedIndexes = Object . keys ( rawParsedIndexes ) . reduce ( ( acc , currIndexKey ) => { const { metadata , attributes } = indexesToParse [ currIndexKey ] ; const currentParsedIndex = rawParsedIndexes [ currIndexKey ] ; Object . keys ( currentParsedIndex ) . forEach ( attr => { if ( acc [ attr ] ) { throw new Error ( `Failed to parse entity "${ entityMetadata . name }", duplicate attribute "${ attr }".` ) ; } } ) ; if ( metadata . isSparse ) { const doesAllAttributesHaveValue = Object . keys ( attributes ) . every ( attr => { if ( ! currentParsedIndex [ attr ] ) { return false ; } return true ; } ) ; if ( ! doesAllAttributesHaveValue ) { return acc ; } } acc = { ... acc , ... currentParsedIndex } ; return acc ; } , { } as { [ key ] : string ; } ) ; const formattedSchema = { ... parsedPrimaryKey , ... parsedIndexes , } ; return { ... parsedEntity , ... formattedSchema } ; } getAffectedPrimaryKeyAttributes < Entity > ( entityClass , attributes , attributesTypeMetadata ) { const { schema : { primaryKey } , } = this . connection . getEntityByTarget ( entityClass ) ; const interpolations = primaryKey . metadata . _interpolations ; if ( ! interpolations || isEmptyObject ( interpolations ) ) { return ; } const affectedKeyAttributes = Object . entries ( attributes ) . reduce ( ( acc , [ attrKey , attrValue ] ) => { if ( ! isScalarType ( attrValue ) ) { return acc ; } Object . entries ( interpolations ) . forEach ( ( [ primaryKeyAttrName , primaryKeyAttrRefs ] ) => { if ( ! primaryKeyAttrRefs . includes ( attrKey ) ) { return ; } if ( attributesTypeMetadata [ attrKey ] === 'dynamic' ) { throw new InvalidDynamicUpdateAttributeValueError ( attrKey , attrValue ) ; } const parsedKey = parseKey ( primaryKey . attributes [ primaryKeyAttrName ] , attributes ) ; acc [ primaryKeyAttrName ] = parsedKey ; } ) ; return acc ; } , { } as Record < string , string > ) ; return affectedKeyAttributes ; } param entityClass param attributes param options getAffectedIndexesForAttributes < Entity > ( entityClass , attributes , attributesTypeMetadata , options ? ) { const nestedKeySeparator = options ?. nestedKeySeparator ?? '.' ; const { schema : { indexes } , } = this . connection . getEntityByTarget ( entityClass ) ; const affectedIndexes = Object . entries ( attributes ) . reduce ( ( acc , [ attrKey , currAttrValue ] ) => { if ( attrKey . includes ( nestedKeySeparator ) || ! isScalarType ( currAttrValue ) ) { return acc ; } if ( ! indexes ) { return acc ; } Object . keys ( indexes ) . forEach ( key => { const currIndex = indexes [ key ] ; const interpolationsForCurrIndex = currIndex . metadata . _interpolations ?? { } ; if ( isEmptyObject ( interpolationsForCurrIndex ) ) { return acc ; } Object . keys ( interpolationsForCurrIndex ) . forEach ( interpolationKey => { const currentInterpolation = interpolationsForCurrIndex [ interpolationKey ] ; if ( currentInterpolation . includes ( attrKey ) ) { if ( attributesTypeMetadata [ attrKey ] === 'dynamic' ) { throw new InvalidDynamicUpdateAttributeValueError ( attrKey , currAttrValue ) ; } try { const parsedIndex = parseKey ( currIndex . attributes [ interpolationKey ] , { ... options ?. additionalAttributesDict , ... attributes } ) ; acc [ interpolationKey ] = parsedIndex ; } catch ( err ) { if ( ! ( err instanceof SparseIndexParseError ) ) { throw err ; } } } } ) ; } ) ; return acc ; } , { } as any ) ; return affectedIndexes ; } param entityClass param attributes getParsedPrimaryKey < Entity > ( table , primaryKey , attributes ) { return this . recursiveParseEntity ( primaryKey . attributes , attributes ) ; } private param schema param entity protected recursiveParseEntity < Entity = any > ( schema , entity , isSparse = false ) { const parsedSchema = Object . keys ( schema ) . reduce ( ( acc , key ) => { const currentValue = ( schema as any ) [ key ] ; if ( typeof currentValue === 'string' || isKeyOfTypeAliasSchema ( currentValue ) ) { try { acc [ key ] = parseKey ( currentValue , entity , { isSparseIndex : isSparse , } ) ; } catch ( err ) { if ( ! ( err instanceof SparseIndexParseError ) ) { throw err ; } } } else if ( isDynamoEntityKeySchema ( currentValue ) ) { acc [ key ] = this . recursiveParseEntity ( currentValue . attributes , entity , ! ! ( currentValue . metadata as DynamoEntityIndexSchema ) ?. isSparse ) ; } else { acc [ key ] = this . recursiveParseEntity ( currentValue , entity ) ; } return acc ; } , { } as any ) ; return parsedSchema ; } }