import { IdentityMap } from './IdentityMap' ; import { Mapping } from './Mapping' ; import { ArrayCollection } from './ArrayCollection' ; import { EntityProxy } from './EntityProxy' ; import { UnitOfWork } from './UnitOfWork' ; import { EntityCtor , EntityInterface , ProxyInterface } from './EntityInterface' ; import { Entity , Scope } from './Scope' ; export class Hydrator { type { { } } private recipeIndex : { [ key ] : Recipe ; } = { } ; type { Recipe } private recipe : Recipe ; type { IdentityMap } private identityMap : IdentityMap ; type { UnitOfWork } private unitOfWork : UnitOfWork ; type { Scope } private readonly entityManager : Scope ; type { boolean } private readonly managed : boolean = true ; param { Scope } entityManager param { boolean } managed public constructor ( entityManager , managed = true ) { this . managed = managed ; this . unitOfWork = entityManager . getUnitOfWork ( ) ; this . entityManager = entityManager ; this . identityMap = entityManager . getIdentityMap ( ) ; } param { { } } values param { Function } EntityClass returns { EntityInterface | Function | EntityCtor < EntityInterface > } public fromSchema ( values , EntityClass ) { const mapping = Mapping . forEntity ( EntityClass ) ; let entity = typeof EntityClass === 'function' ? new ( EntityClass as EntityCtor < EntityInterface > ) : EntityClass ; entity = EntityProxy . patchEntity ( entity as EntityInterface , this . entityManager ) ; Object . getOwnPropertyNames ( values ) . forEach ( column => { const property = mapping . getPropertyName ( column ) ; if ( ! property ) { return ; } entity [ property ] = { _skipDirty : values [ column ] } ; } ) ; return entity ; } param { string } alias returns { any } public getRecipe ( alias ? ) { if ( alias ) { return this . recipeIndex [ alias ] ; } return this . recipe ; } param { string } alias returns { void } public removeRecipe ( alias ) { const recipe = this . recipeIndex [ alias ] ; if ( recipe && recipe . parentRecipe ) { delete recipe . parentRecipe . joins [ alias ] ; } delete this . recipeIndex [ alias ] ; } param { string | null } parent param { string } alias param { Mapping } mapping param { string } joinType param { string } property returns { Recipe } public addRecipe ( parent , alias , mapping , joinType ? , property ? ) { const primaryKey = mapping . getPrimaryKey ( ) ; const primaryKeyAliased = `${ alias }.${ primaryKey }` ; const recipe = { hydrate : false , parent : null , entity : mapping . getTarget ( ) as EntityCtor < EntityInterface > , primaryKey : { alias : primaryKeyAliased , property : primaryKey } , type : joinType , columns : { } , property , alias , } ; this . recipeIndex [ alias ] = recipe ; if ( parent ) { const parentRecipe = this . recipeIndex [ parent ] ; parentRecipe . joins = parentRecipe . joins || { } ; parentRecipe . joins [ alias ] = recipe ; recipe . parentRecipe = parentRecipe ; } else { this . recipe = recipe ; } return recipe ; } param { string } alias param { { } } columns returns { Hydrator } public addColumns ( alias , columns ) { Object . assign ( this . recipeIndex [ alias ] . columns , columns ) ; return this ; } param { [ ] } rows returns { ArrayCollection } public hydrateAll ( rows ) { const entities = new ArrayCollection ; rows . forEach ( row => { const hydrated = this . hydrate ( row , this . recipe ) ; if ( hydrated ) { entities . add ( hydrated ) ; } } ) ; return entities ; } param { { } } row param { Recipe } recipe returns { EntityInterface } public hydrate ( row , recipe ) { if ( ! recipe . hydrate ) { return null ; } let entity = this . identityMap . fetch ( recipe . entity , row [ recipe . primaryKey . alias ] ) as ProxyInterface ; if ( ! entity ) { entity = this . applyMapping ( recipe , row ) ; if ( ! entity ) { return null ; } } if ( recipe . parent ) { recipe . parent . entities [ row [ recipe . parent . column ] ] [ recipe . parent . property ] . add ( { _skipDirty : entity } ) ; } if ( recipe . joins ) { this . hydrateJoins ( recipe , row , entity ) ; } if ( this . managed ) { entity . activateProxying ( ) ; } return entity ; } param { string } alias returns { Hydrator } public clearCatalogue ( alias ? ) { const recipe = this . getRecipe ( alias ) ; delete recipe . catalogue ; return this ; } param { string } alias returns { Catalogue } public enableCatalogue ( alias ) { return this . getCatalogue ( alias ) ; } param { string } alias returns { boolean } public hasCatalogue ( alias ) { return ! ! this . getRecipe ( alias ) . catalogue ; } param { string } alias returns { Catalogue } public getCatalogue ( alias ) { const recipe = this . getRecipe ( alias ) ; recipe . catalogue = recipe . catalogue || { entities : { } , primaries : new ArrayCollection ( ) } ; return recipe . catalogue ; } param { Recipe } recipe param { { } } row param { EntityInterface } entity private hydrateJoins ( recipe , row , entity ) { Object . getOwnPropertyNames ( recipe . joins ) . forEach ( alias => { const joinRecipe = recipe . joins [ alias ] ; const hydrated = this . hydrate ( row , joinRecipe ) ; if ( ! joinRecipe . hydrate ) { return ; } if ( joinRecipe . type === 'single' ) { entity [ joinRecipe . property ] = { _skipDirty : hydrated } ; return ; } if ( ! hydrated ) { return ; } entity [ joinRecipe . property ] . add ( { _skipDirty : hydrated } ) ; } ) ; } param { Recipe } recipe param { ProxyInterface } entity returns { Hydrator } private addToCatalogue ( recipe , entity ) { const primary = entity [ recipe . primaryKey . property ] ; const catalogue = this . getCatalogue ( recipe . alias ) ; catalogue . entities [ primary ] = entity ; catalogue . primaries . add ( primary ) ; return this ; } param { Recipe } recipe param { { } } row returns { EntityInterface } private applyMapping ( recipe , row ) { if ( ! row [ recipe . primaryKey . alias ] ) { return null ; } const entity = new recipe . entity ; entity [ recipe . primaryKey . property ] = row [ recipe . primaryKey . alias ] ; Object . getOwnPropertyNames ( recipe . columns ) . forEach ( alias => { entity [ recipe . columns [ alias ] ] = row [ alias ] ; } ) ; const workingEntity = this . managed ? EntityProxy . patchEntity ( entity , this . entityManager ) : entity ; if ( this . managed ) { this . unitOfWork . registerClean ( entity , true ) ; } this . identityMap . register ( entity , workingEntity ) ; if ( recipe . catalogue ) { this . addToCatalogue ( recipe , workingEntity ) ; } return workingEntity ; } } export interface Catalogue { entities : Object ; primaries : ArrayCollection < string | number > ; } export interface Recipe { hydrate : boolean ; alias : string ; entity : EntityCtor < EntityInterface > ; primaryKey : { alias : string ; property : string ; } ; columns : { } ; catalogue ? : Catalogue ; parent ? : { property : string ; column : string ; entities : Object ; } ; parentRecipe ? : Recipe ; joins ? : { [ key ] : Recipe ; } ; property ? : string ; type ? : string ; }