import { UnitOfWork } from './UnitOfWork' ; import { EntityInterface , ProxyInterface } from './EntityInterface' ; import { ArrayCollection as Collection } from './ArrayCollection' ; import { Mapping } from './Mapping' ; import { Scope } from './Scope' ; import { MetaData } from './MetaData' ; export class EntityProxy { param { EntityInterface } entity param { Scope } entityManager param { boolean } active returns { Object } public static patchEntity < T > ( entity , entityManager , active = false ) { if ( entity [ 'isEntityProxy' ] ) { return entity ; } const metaData = MetaData . forInstance ( entity ) ; const mapping = Mapping . forEntity ( entity ) ; const unitOfWork = entityManager . getUnitOfWork ( ) ; const relations = mapping . getRelations ( ) ; const expected = { } ; let proxyActive = active ; let entityProxy ; if ( relations ) { Object . getOwnPropertyNames ( relations ) . forEach ( property => { const type = relations [ property ] . type ; if ( type === Mapping . RELATION_ONE_TO_MANY || type === Mapping . RELATION_MANY_TO_MANY ) { proxyCollection ( property ) ; } } ) ; } returns { boolean } function isProxyActive ( ) { return proxyActive && metaData . fetch ( 'entityState.state' ) !== UnitOfWork . STATE_UNKNOWN ; } param { string } property returns { any } function getExpected ( property ) { if ( ! expected [ property ] ) { expected [ property ] = entityManager . resolveEntityReference ( relations [ property ] . targetEntity ) ; } return expected [ property ] ; } param { string } property param { Date } current param { Date } next returns { boolean } function areDifferent ( property , current , next ) { const isDate = [ 'date' , 'dateTime' , 'datetime' , 'time' ] . indexOf ( mapping . getType ( property ) ) > - 1 ; if ( ! isDate ) { return current !== next ; } return ( new Date ( current ) ) . toString ( ) !== ( new Date ( next ) ) . toString ( ) ; } param { { } } target param { string } property param { * } value returns { boolean } function setDirty ( target , property , value ) { if ( typeof value === 'object' && value !== null && '_skipDirty' in value ) { target [ property ] = value . _skipDirty ; return true ; } return false ; } param { string } property param { boolean } forceNew returns { void } function proxyCollection ( property , forceNew = false ) { const ExpectedEntity = getExpected ( property ) ; const collection = ( forceNew || ! entity [ property ] ) ? new Collection : entity [ property ] ; entity [ property ] = new Proxy < Object > ( collection , < Object > { set : ( collection , key , relationEntity ) => { if ( setDirty ( collection , key as string , relationEntity ) ) { return true ; } if ( isNaN ( parseInt ( key as string , 10 ) ) || ! isProxyActive ( ) ) { collection [ key ] = relationEntity ; return true ; } const entityIndex = collection . indexOf ( relationEntity ) ; const previousValue = collection [ key ] ; collection [ key ] = relationEntity ; if ( typeof previousValue !== 'undefined' && collection . indexOf ( previousValue ) === - 1 ) { unitOfWork . registerCollectionChange ( UnitOfWork . RELATIONSHIP_REMOVED , entityProxy , property , previousValue ) ; } if ( entityIndex > - 1 ) { return true ; } if ( typeof relationEntity !== 'object' || ! ( relationEntity instanceof ExpectedEntity ) ) { throw new TypeError ( `Can't add to '${ entity . constructor . name }.${ property }'. Expected instance of '${ ExpectedEntity . name }'.` ) ; } unitOfWork . registerCollectionChange ( UnitOfWork . RELATIONSHIP_ADDED , entityProxy , property , relationEntity ) ; return true ; } , get : ( target , property ) => { if ( property === 'isCollectionProxy' ) { return true ; } return target [ property ] ; } , deleteProperty : ( collection , key ) => { if ( ! isProxyActive ( ) ) { collection . splice ( parseInt ( key , 10 ) , 1 ) ; return true ; } const previousValue = collection [ key ] ; collection . splice ( parseInt ( key , 10 ) , 1 ) ; if ( collection . indexOf ( previousValue ) > - 1 ) { return true ; } unitOfWork . registerCollectionChange ( UnitOfWork . RELATIONSHIP_REMOVED , entityProxy , property , previousValue ) ; return true ; } , } ) ; } const proxyMethods = { isEntityProxy : true , activateProxying : ( ) => { proxyActive = true ; return entityProxy ; } , deactivateProxying : ( ) => { proxyActive = false ; return entityProxy ; } , getTarget : ( ) => { return entity ; } , isProxyingActive : ( ) => { return isProxyActive ( ) ; } , } ; entityProxy = new Proxy < T & ProxyInterface > ( entity , < Object > { set : ( target , property , value ) => { if ( typeof value === 'undefined' ) { value = null ; } if ( setDirty ( target , property , value ) ) { return true ; } if ( ! relations || ! relations [ property ] ) { if ( isProxyActive ( ) && areDifferent ( property , target [ property ] , value ) ) { unitOfWork . registerDirty ( target , property ) ; } target [ property ] = value ; return true ; } if ( entity [ property ] instanceof Array ) { if ( entity [ property ] . length > 0 && entity [ property ] === target [ property ] ) { return true ; } if ( entity [ property ] . length > 0 ) { throw new Error ( `Can't assign to '${ target . constructor . name }.${ property }'. Collection is not empty.` ) ; } proxyCollection ( property , true ) ; target [ property ] . add ( ... value ) ; return true ; } const ExpectedEntity = getExpected ( property ) ; if ( ! ( value instanceof ExpectedEntity ) ) { throw new TypeError ( `Can't assign to '${ target . constructor . name }.${ property }'. Expected instance of '${ ExpectedEntity . name }'.` ) ; } if ( target [ property ] === value ) { return true ; } if ( target [ property ] ) { unitOfWork . registerRelationChange ( UnitOfWork . RELATIONSHIP_REMOVED , entityProxy , property , target [ property ] ) ; } unitOfWork . registerRelationChange ( UnitOfWork . RELATIONSHIP_ADDED , entityProxy , property , value ) ; target [ property ] = EntityProxy . patchEntity ( value , entityManager ) ; return true ; } , get : ( target , property ) => { if ( proxyMethods [ property ] ) { return proxyMethods [ property ] ; } return target [ property ] ; } , deleteProperty : ( target , property ) => { const relation = relations [ property ] ; if ( relation && ( relation . type === Mapping . RELATION_MANY_TO_MANY || relation . type === Mapping . RELATION_ONE_TO_MANY ) ) { throw new Error ( `It is not allowed to delete a collection; trying to delete '${ target . constructor . name }.${ property }'.` ) ; } unitOfWork . registerRelationChange ( UnitOfWork . RELATIONSHIP_REMOVED , entityProxy , property , target [ property ] ) ; delete target [ property ] ; return true ; } , } ) ; return entityProxy ; } }