import { Homefront } from 'homefront' ; import { EntityRepository } from './EntityRepository' ; import { MetaData } from './MetaData' ; import { EntityManager } from './EntityManager' ; import { EntityCtor , EntityInterface , ProxyInterface } from './EntityInterface' ; import { ArrayCollection } from './ArrayCollection' ; export class Mapping < T > { type { string } public static RELATION_ONE_TO_MANY = 'oneToMany' ; type { string } public static RELATION_MANY_TO_MANY = 'manyToMany' ; type { string } public static RELATION_MANY_TO_ONE = 'manyToOne' ; type { string } public static RELATION_ONE_TO_ONE = 'oneToOne' ; type { string } public static CASCADE_PERSIST = 'persist' ; type { Homefront } private mapping : Homefront = new Homefront ; type { EntityCtor } private target : EntityCtor < T > ; type { EntityManager } private entityManager : EntityManager ; type { Array } private stagedMappings : Array < { method : string ; parameters : any ; } > = [ ] ; param { EntityCtor } entity public constructor ( entity ? ) { if ( ! entity ) { return ; } this . target = entity ; this . entity ( ) ; } param { EntityCtor } target return { Mapping } public static forEntity < T > ( target ) { if ( ! target ) { throw new Error ( 'Trying to get mapping for non-target.' ) ; } target = target [ 'isEntityProxy' ] ? ( target as ProxyInterface ) . getTarget ( ) : target ; const entity = MetaData . getConstructor ( target ) as EntityCtor < T > ; const metadata = MetaData . forTarget ( entity ) ; if ( ! metadata . fetch ( 'mapping' ) ) { metadata . put ( 'mapping' , new Mapping ( entity ) ) ; } return metadata . fetch ( 'mapping' ) as Mapping < T > ; } returns { { } } public static now ( ) { return Mapping . raw ( 'CURRENT_TIMESTAMP' ) ; } returns { { } } public static onUpdateNow ( ) { return Mapping . raw ( 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP' ) ; } param { string } raw returns { { __raw : string } } public static raw ( raw ) { return { __raw : raw } ; } param { { } } mappingData returns { Mapping } public static restore ( mappingData ) { const mapping = new Mapping ( ) ; mapping . getMappingData ( ) . merge ( mappingData ) ; return mapping ; } returns { EntityCtor } public getTarget ( ) { return this . target ; } param { EntityManager } entityManager returns { Mapping } public setEntityManager ( entityManager ) { this . entityManager = entityManager ; this . applyStagedMappings ( ) ; return this ; } returns { { __raw : string } } public now ( ) { return Mapping . now ( ) ; } returns { { __raw : string } } public onUpdateNow ( ) { return Mapping . onUpdateNow ( ) ; } param { string } property param { FieldOptions } options return { Mapping } public field ( property , options ) { const entityManager = this . stageOrGetManager ( 'field' , arguments ) ; if ( ! entityManager ) { return this ; } const toUnderscore = entityManager . getConfig ( ) . fetch ( 'mapping.defaultNamesToUnderscore' ) ; const propertyName = toUnderscore ? this . nameToUnderscore ( property ) : property ; const field = this . mapping . fetchOrPut ( `fields.${ property }` , { } ) ; if ( field . name ) { this . mapping . remove ( `columns.${ this . getColumnName ( property ) }` ) ; } else { field . name = propertyName ; } Homefront . merge ( field , options ) ; this . mapColumn ( this . getColumnName ( property ) , property ) ; return this ; } returns { EntityRepository } public getRepository ( ) { return this . mapping . fetch ( 'entity.repository' ) ; } param { string } property returns { string | null } public getColumnName ( property ) { return this . getField ( property ) . name ; } param { string } property param { boolean } tolerant returns { FieldOptions } public getField ( property , tolerant = false ) { const field = this . mapping . fetch ( `fields.${ property }` ) ; if ( ! field ) { if ( tolerant ) { return null ; } throw new Error ( `Unknown field "${ property }" supplied on entity "${ this . getEntityName ( ) }".` ) ; } return field ; } returns { Homefront } public getMappingData ( ) { return this . mapping ; } param { string } column returns { string | null } public getPropertyName ( column ) { return this . mapping . fetch ( `columns.${ column }` , null ) ; } param { boolean } includeRelations public getFieldNames ( includeRelations = false ) { const fields = this . getFields ( ) ; return Reflect . ownKeys ( fields ) . reduce ( ( fieldNames , fieldName ) => { if ( ! fields [ fieldName ] . relationship || includeRelations ) { fieldNames . push ( fieldName ) ; } return fieldNames ; } , [ ] ) ; } param { { } } options return { Mapping } public entity ( options = { } ) { const entityManager = this . stageOrGetManager ( 'entity' , arguments ) ; if ( ! entityManager ) { return ; } const toUnderscore = entityManager . getConfig ( ) . fetch ( 'mapping.defaultNamesToUnderscore' ) ; const tableName = toUnderscore ? this . nameToUnderscore ( this . target . name ) : this . target . name . toLowerCase ( ) ; const defaultMapping = { repository : EntityRepository , name : this . target . name , tableName : tableName , store : null , } ; Homefront . merge ( this . mapping . fetchOrPut ( `entity` , defaultMapping ) , options ) ; return this ; } param { string | string [ ] } properties returns { string [ ] } public getColumnNames ( properties ) { if ( ! Array . isArray ( properties ) ) { properties = [ ( properties as string ) ] ; } return properties . map ( column => this . getColumnName ( column ) ) ; } param { Array | string } indexName param { Array | string } fields return { Mapping } public index ( indexName , fields ? ) { const unprocessed = this . mapping . fetchOrPut ( `unprocessed_indexes` , [ ] ) ; unprocessed . push ( { indexName , fields } ) ; return this ; } returns { { } } public getIndexes ( ) { this . processIndexes ( ) ; return this . mapping . fetch ( 'index' , { } ) ; } param { Array | string } constraintName param { Array | string } fields return { Mapping } public uniqueConstraint ( constraintName , fields ? ) { const unprocessed = this . mapping . fetchOrPut ( `unprocessed_uniques` , [ ] ) ; unprocessed . push ( { constraintName , fields } ) ; return this ; } returns { { } } public getUniqueConstraints ( ) { this . processUniqueConstraints ( ) ; return this . mapping . fetch ( 'unique' , { } ) ; } param { string } property return { Mapping } public primary ( property ) { this . mapping . put ( 'primary' , property ) ; this . extendField ( property , { primary : true } ) ; return this ; } returns { Mapping } public autoPK ( ) { return this . increments ( 'id' ) . primary ( 'id' ) ; } returns { Mapping } public autoCreatedAt ( ) { return this . field ( 'createdAt' , { type : 'datetime' , defaultTo : this . now ( ) } ) ; } returns { Mapping } public autoUpdatedAt ( ) { return this . field ( 'updatedAt' , { type : 'datetime' , defaultTo : this . now ( ) } ) ; } returns { Mapping } public autoFields ( ) { return this . autoPK ( ) . autoCreatedAt ( ) . autoUpdatedAt ( ) ; } returns { string | null } public getPrimaryKeyField ( ) { const primaryKey = this . getPrimaryKey ( ) ; if ( ! primaryKey ) { return null ; } return this . getFieldName ( primaryKey , primaryKey ) ; } returns { string } public getPrimaryKey ( ) { return this . mapping . fetch ( 'primary' , null ) ; } param { string } property param { string } defaultValue returns { string } public getFieldName ( property , defaultValue = null ) { return this . mapping . fetch ( `fields.${ property }.name` , defaultValue ) ; } returns { FieldOptions [ ] } public getFields ( ) { return this . mapping . fetch ( 'fields' , null ) ; } returns { string } public getEntityName ( ) { return this . mapping . fetch ( 'entity.name' ) ; } returns { string } public getTableName ( ) { return this . mapping . fetch ( 'entity.tableName' ) ; } returns { string } public getStoreName ( ) { return this . mapping . fetch ( 'entity.store' ) ; } param { string } property param { string } type return { Mapping } public generatedValue ( property , type ) { this . extendField ( property , { generatedValue : type } ) ; return this ; } param { string } property returns { Mapping } public increments ( property ) { return this . generatedValue ( property , 'autoIncrement' ) ; } param { string } property param { string [ ] } cascades returns { Mapping } public cascade ( property , cascades ) { return this . extendField ( property , { cascades : cascades } ) ; } param { string } property param { Relationship } options returns { Mapping } public addRelation ( property , options ) { if ( ! options . targetEntity ) { throw new Error ( 'Required property "targetEntity" not found in options.' ) ; } this . setDefaultCascades ( property ) ; this . extendField ( property , { relationship : options } ) ; Homefront . merge ( this . mapping . fetchOrPut ( 'relations' , { } ) , { [ property ] : options } ) ; return this ; } param { string } property returns { boolean } public isRelation ( property ) { return ! ! this . mapping . fetch ( `relations.${ property }` ) ; } param { string } property returns { string } public getType ( property ) { return this . mapping . fetch ( `fields.${ property }.type` , 'string' ) ; } returns { { } } public getRelations ( ) { return this . mapping . fetch ( 'relations' ) ; } returns { Relationship } public getRelation ( property ) { return this . mapping . fetch ( `relations.${ property }` ) ; } param { string } property param { Relationship } options returns { Mapping } public oneToOne ( property , options ) { this . addRelation ( property , { type : Mapping . RELATION_ONE_TO_ONE , targetEntity : options . targetEntity , inversedBy : options . inversedBy , mappedBy : options . mappedBy , } ) ; if ( ! options . mappedBy ) { this . ensureJoinColumn ( property ) ; } return this ; } param { string } property param { Relationship } options returns { Mapping } public oneToMany ( property , options ) { return this . addRelation ( property , { targetEntity : options . targetEntity , type : Mapping . RELATION_ONE_TO_MANY , mappedBy : options . mappedBy , } ) ; } param { string } property param { Relationship } options returns { Mapping } public manyToOne ( property , options ) { this . addRelation ( property , { targetEntity : options . targetEntity , type : Mapping . RELATION_MANY_TO_ONE , inversedBy : options . inversedBy , } ) ; this . ensureJoinColumn ( property ) ; return this ; } param { string } property param { Relationship } options returns { Mapping } public manyToMany ( property , options ) { return this . addRelation ( property , { targetEntity : options . targetEntity , type : Mapping . RELATION_MANY_TO_MANY , inversedBy : options . inversedBy , mappedBy : options . mappedBy , } ) ; } param { string } property param { JoinTable } options returns { Mapping } public joinTable ( property , options ) { this . extendField ( property , { joinTable : options } ) ; return this ; } param { string } property param { JoinTable } options returns { Mapping } public joinColumn ( property , options ) { return this . ensureJoinColumn ( property , options ) ; } param { string } property param { JoinTable } options returns { Mapping } public ensureJoinColumn ( property , options ? ) { const field = this . mapping . fetchOrPut ( `fields.${ property }` , { joinColumn : { } } ) ; field . joinColumn = Homefront . merge ( { name : `${ property }_id` , referencedColumnName : 'id' , unique : false , nullable : true , } , options || field . joinColumn ) ; return this ; } param { string } property returns { JoinTable } public getJoinTable ( property ) { if ( ! this . entityManager ) { throw new Error ( 'EntityManager is required on the mapping. Make sure you registered the entity.' ) ; } const field = this . mapping . fetchOrPut ( `fields.${ property }` , { joinTable : { name : '' } } ) as FieldOptions ; if ( field . joinTable && field . joinTable . complete ) { return field . joinTable ; } const relationMapping = Mapping . forEntity ( this . entityManager . resolveEntityReference ( field . relationship . targetEntity ) ) ; const ownTableName = this . getTableName ( ) ; const withTableName = relationMapping . getTableName ( ) ; const ownPrimary = this . getPrimaryKeyField ( ) ; const withPrimary = relationMapping . getPrimaryKeyField ( ) ; field . joinTable = Homefront . merge ( { complete : true , name : `${ ownTableName }_${ withTableName }` , joinColumns : [ { referencedColumnName : ownPrimary , name : `${ ownTableName }_id` , type : 'integer' , } ] , inverseJoinColumns : [ { referencedColumnName : withPrimary , name : `${ withTableName }_id` , type : 'integer' , } ] , } , field . joinTable ) as JoinTable ; return field . joinTable ; } param { string } property returns { JoinColumn } public getJoinColumn ( property ) { return this . getField ( property ) . joinColumn ; } param { string } property param { { } } additional returns { Mapping } public extendField ( property , additional ) { const field = this . mapping . fetchOrPut ( `fields.${ property }` , { } ) ; const needsName = ! field . name ; if ( needsName ) { field . name = property ; } Homefront . merge ( field , additional ) ; if ( needsName ) { this . mapColumn ( this . getColumnName ( property ) || property , property ) ; } return this ; } param { string } property returns { Field } public forProperty ( property ) { return new Field ( property , this ) ; } returns { Mapping } public completeMapping ( ) { const relations = this . getRelations ( ) ; const manager = this . entityManager ; for ( const property in relations ) { const relation = relations [ property ] ; this . setDefaultCascades ( property ) ; if ( relation . type === Mapping . RELATION_MANY_TO_MANY && relation . inversedBy ) { this . getJoinTable ( property ) ; } if ( ! relation . mappedBy ) { this . ensureJoinColumn ( property ) ; } if ( typeof relation . targetEntity !== 'string' ) { const reference = manager . resolveEntityReference ( relation . targetEntity ) ; relation . targetEntity = Mapping . forEntity ( reference ) . getEntityName ( ) ; } } this . processIndexes ( ) ; this . processUniqueConstraints ( ) ; return this ; } returns { { } } public serializable ( ) { return this . completeMapping ( ) . mapping . expand ( ) ; } returns { Mapping } private applyStagedMappings ( ) { this . stagedMappings . forEach ( stagedMapping => { this [ stagedMapping . method ] ( ... stagedMapping . parameters ) ; } ) ; this . stagedMappings = [ ] ; return this ; } param { string } method param { * } parameters returns { EntityManager } private stageOrGetManager ( method , parameters ) { if ( ! this . entityManager ) { this . stagedMappings . push ( { method , parameters } ) ; return ; } return this . entityManager ; } param { string } property returns { string } private nameToUnderscore ( property ) { const name = property [ 0 ] . toLowerCase ( ) + property . slice ( 1 ) ; return name . replace ( /[A-Z]/g , '_$&' ) . replace ( '__' , '_' ) . toLowerCase ( ) ; } param { string } column param { string } property returns { Mapping } private mapColumn ( column , property ) { this . mapping . put ( `columns.${ column }` , property ) ; return this ; } returns { Mapping } private processIndexes ( ) { const unprocessed = this . mapping . fetch ( `unprocessed_indexes` ) ; if ( ! unprocessed ) { return ; } unprocessed . forEach ( index => { let indexName = index . indexName ; let fields = index . fields ; if ( ! fields ) { fields = this . getColumnNames ( Array . isArray ( indexName ) ? indexName : [ indexName ] ) ; indexName = `idx_${ this . getTableName ( ) }_${ fields . join ( '_' ) . toLowerCase ( ) }` ; } else { fields = this . getColumnNames ( fields ) ; } const indexes = this . mapping . fetchOrPut ( `index.${ indexName }` , new ArrayCollection ) ; indexes . add ( ... fields ) ; } ) ; this . mapping . remove ( `unprocessed_indexes` ) ; return this ; } returns { Mapping } private processUniqueConstraints ( ) { const unprocessed = this . mapping . fetch ( `unprocessed_uniques` ) ; if ( ! unprocessed ) { return ; } unprocessed . forEach ( constraint => { let constraintName = constraint . constraintName ; let fields = constraint . fields ; if ( ! fields ) { fields = this . getColumnNames ( Array . isArray ( constraintName ) ? constraintName : [ constraintName ] ) ; constraintName = `${ this . getTableName ( ) }_${ fields . join ( '_' ) . toLowerCase ( ) }_unique` ; } else { fields = this . getColumnNames ( fields ) ; } const constraints = this . mapping . fetchOrPut ( `unique.${ constraintName }` , new ArrayCollection ) ; constraints . add ( ... fields ) ; } ) ; this . mapping . remove ( `unprocessed_uniques` ) ; return this ; } param { string } property returns { Mapping } private setDefaultCascades ( property ) { if ( ! this . entityManager ) { return ; } const field = this . getField ( property , true ) ; if ( field && typeof field . cascades === 'undefined' ) { this . cascade ( property , this . entityManager . getConfig ( ) . fetch ( 'mapping.defaults.cascades' , [ ] ) ) ; } return this ; } } /**
 * Convenience class for chaining field definitions.
 */ export class Field { type { string } private property : string ; type { Mapping } private mapping : Mapping < EntityInterface > ; param { string } property param { Mapping } mapping constructor ( property , mapping ) { this . property = property ; this . mapping = mapping ; } param { FieldOptions } options return { Field } public field ( options ) { this . mapping . field ( this . property , options ) ; return this ; } return { Field } public primary ( ) { this . mapping . primary ( this . property ) ; return this ; } return { Field } public autoPK ( ) { this . mapping . autoPK ( ) ; return this ; } return { Field } public autoCreatedAt ( ) { this . mapping . autoCreatedAt ( ) ; return this ; } return { Field } public autoUpdatedAt ( ) { this . mapping . autoUpdatedAt ( ) ; return this ; } return { Field } public autoFields ( ) { this . mapping . autoFields ( ) ; return this ; } param { string } type return { Field } public generatedValue ( type ) { this . mapping . generatedValue ( this . property , type ) ; return this ; } param { string [ ] } cascades returns { Field } public cascade ( cascades ) { this . mapping . cascade ( this . property , cascades ) ; return this ; } returns { Field } public increments ( ) { this . mapping . increments ( this . property ) ; return this ; } param { Relationship } options returns { Field } public oneToOne ( options ) { this . mapping . oneToOne ( this . property , options ) ; return this ; } param { Relationship } options returns { Field } public oneToMany ( options ) { this . mapping . oneToMany ( this . property , options ) ; return this ; } param { Relationship } options returns { Field } public manyToOne ( options ) { this . mapping . manyToOne ( this . property , options ) ; return this ; } param { Relationship } options returns { Field } public manyToMany ( options ) { this . mapping . manyToMany ( this . property , options ) ; return this ; } param { JoinTable } options returns { Field } public joinTable ( options ) { this . mapping . joinTable ( this . property , options ) ; return this ; } param { JoinTable } options returns { Field } public joinColumn ( options ) { this . mapping . joinColumn ( this . property , options ) ; return this ; } } export interface FieldOptions { type : string ; primary ? : boolean ; textType ? : string ; precision ? : number ; enumeration ? : Array < any > ; generatedValue ? : string ; scale ? : number ; nullable ? : boolean ; defaultTo ? : any | Object ; unsigned ? : boolean ; comment ? : string ; size ? : number ; name ? : string ; cascades ? : Array < string > ; relationship ? : Relationship ; joinColumn ? : JoinColumn ; joinTable ? : JoinTable ; [ key ] : any ; } export interface JoinTable { name : string ; complete ? : boolean ; joinColumns ? : Array < JoinColumn > ; inverseJoinColumns ? : Array < JoinColumn > ; } export interface JoinColumn { referencedColumnName ? : string ; name ? : string ; type ? : string ; size ? : number ; indexName ? : string ; onDelete ? : string ; onUpdate ? : string ; unique ? : boolean ; nullable ? : boolean ; } export interface Relationship { targetEntity : string | EntityCtor < EntityInterface > ; type ? : string ; inversedBy ? : string ; mappedBy ? : string ; }