import { getGlobalType , isClass , symbolString } from './utils' ; import { GetReturnType , RegKeyType , KeyType , ClassType } from './utils/types' ; import { getMetadata , getMetadataField } from './class/metadata' ; import { classLoader , TypeWithInfo , ClassLoader } from './class/ClassLoader' ; import { guard } from './utils/guard' ; import { genAspectWrapper } from './decorator/aspect' ; export class Config { /** auto register class self, when class not found. default: false */ autoRegisterSelf ? : boolean = false ; /** constructor inject, MUST in TypeScript with emitDecoratorMetadata and use decorator with class, default: true */ constructorInject ? : boolean = true ; /** use class loader for autowired default: true */ useClassLoader ? : boolean | ClassLoader = true ; /** when implement class not found */ notFoundHandler ? : ( type : KeyType ) => any ; /** when have multi implement class */ conflictHandler ? : ( type : KeyType , implCls : TypeWithInfo [ ] , sourceCls ? : TypeWithInfo ) => ClassType | undefined ; /** create instance hook, return value will replace instance */ createInstanceHook ? : ( inst : any , ioc : IocContext ) => any ; /** parent ioc context */ parentContext ? : IocContext ; } export const DefaultRegisterOption = { singleton : true , autoNew : true , } ; export interface RegisterOptions { /** default: true */ singleton ? : boolean ; /** if data a class, auto new a instance.
     *  if data a function, auto run(lazy).
     *  default: true */ autoNew ? : boolean ; } export interface Store { inited : boolean ; factory : any ; value : any ; options : RegisterOptions ; } /** ioc context */ export class IocContext { private static defaultInstance : IocContext ; readonly classLoader = classLoader ; private components = new Map < string | symbol , Store > ( ) ; public static get DefaultInstance ( ) { return ( this . defaultInstance || ( ( this . defaultInstance = new IocContext ( ) ) , this . defaultInstance ) ) ; } constructor ( readonly config = { } ) { this . config = Object . assign ( { } , new Config ( ) , config ) ; if ( config . useClassLoader instanceof ClassLoader ) { this . classLoader = config . useClassLoader ; } } param config public setConfig ( config ) { Object . assign ( this . config , config ) ; } param keyOrType public remove ( keyOrType ) { const key = getGlobalType ( keyOrType ) ; if ( this . components . has ( key ) ) { this . preDestroyInstance ( this . components . get ( key ) ) ; } return this . components . delete ( key ) ; } /** clear all */ public clear ( ) { Array . from ( this . components . values ( ) ) . forEach ( ele => { this . preDestroyInstance ( ele ) ; } ) ; this . components . clear ( ) ; } param keyOrType param opt public get < T = undefined , KeyOrType = any > ( keyOrType , opt = { } ) { const key = getGlobalType ( keyOrType ) ; if ( this . components . has ( key ) ) { return this . returnValue ( this . components . get ( key ) , opt . forceNew ) ; } if ( this . config . notFoundHandler ) { const data = this . config . notFoundHandler ( keyOrType as any ) ; if ( data !== undefined ) { return data ; } } if ( opt . useClassLoader || this . config . useClassLoader ) { const type = keyOrType as any ; const classes = this . classLoader . getImplementClasses ( type ) ; switch ( classes . length ) { case 1 : return this . get ( classes [ 0 ] . type as any , opt ) ; case 0 : break ; default : if ( this . config . conflictHandler ) { const one = this . config . conflictHandler ( type , classes , opt . sourceCls ? { type : opt . sourceCls , info : classLoader . getClassInfo ( opt . sourceCls ) , } : undefined ) ; if ( one !== undefined ) { return this . get ( one as any , opt ) ; } } if ( isClass ( keyOrType ) && getMetadata ( type ) . injectable ) { this . register ( type ) ; return this . get ( type , opt ) ; } throw new MultiImplementError ( type , key ) ; } } if ( this . config . parentContext ) { return this . config . parentContext . get ( keyOrType ) ; } const canAutoRegister = isClass ( keyOrType ) && ( this . config . autoRegisterSelf || getMetadata ( keyOrType as any ) . injectable ) ; if ( canAutoRegister ) { this . register ( keyOrType ) ; return this . get ( keyOrType , opt ) ; } throw new NotfoundTypeError ( keyOrType , key ) ; } param keyOrType param opt public getImports < T = undefined , KeyOrType = any > ( keyOrType , { cache , } = { } ) { const type = keyOrType as any ; if ( cache && this . has ( type ) ) { return this . get ( type ) ; } const data = this . classLoader . getImplementClasses ( type ) . map ( clsInfo => { return this . get ( clsInfo . type , { useClassLoader : true , } ) ; } ) ; if ( cache ) { this . register ( data , type ) ; } return data ; } param keyOrType public has ( keyOrType ) { return this . components . has ( getGlobalType ( keyOrType ) ) ; } public replace ( keyOrType , newData , options ? , registerIfNotExist = false ) { const key = getGlobalType ( keyOrType ) ; const data = this . components . get ( key ) ; if ( data ) { data . inited = false ; data . factory = this . genValueFactory ( newData , options || data . options ) ; data . value = undefined ; } else if ( registerIfNotExist ) { this . register ( newData , keyOrType , options ) ; } else { throw new NoRegistryError ( key ) ; } } param data param key param options public register ( data , key ? , options = DefaultRegisterOption ) { if ( key ) { if ( ! this . canBeKey ( key ) ) { throw new Error ( 'key require string, symbol or class.' ) ; } } else { if ( ! this . canBeKey ( data ) ) { throw new Error ( 'when data is not string, symbol or class, require a key.' ) ; } } const dataType = ( key && getGlobalType ( key ) ) || ( data && getGlobalType ( data ) ) ; if ( this . components . has ( dataType ) ) { throw new AlreadyRegistryError ( dataType ) ; } options = { ... DefaultRegisterOption , ... options , } ; const store = this . newStore ( data , options ) ; this . components . set ( dataType , store ) ; } param instance param opt public inject ( instance , opt = { autoRunPostConstruct : true , } ) { const iocSelf = this ; const classType = instance . constructor ; getMetadataField ( classType , 'injects' ) . forEach ( inject => { const { key , typeCls , optional , singleton } = inject ; const descriptor = Object . getOwnPropertyDescriptor ( instance , key ) ; let defaultValue = descriptor && descriptor . value ; const allowOptional = optional || defaultValue !== undefined ; if ( 'inject' === inject . type ) { Object . defineProperty ( instance , key , { configurable : true , writable : true , value : guard ( ( ) => this . get ( typeCls , { sourceCls : classType , ... ( singleton ? { } : { forceNew : true } ) , } ) , { defaultValue , onError : err => { if ( ! allowOptional ) { err . message += `\n\tSource: ${ classType . name }.${ key . toString ( ) }` ; throw err ; } } , } ) , } ) ; } if ( [ 'lazyInject' , 'imports' ] . includes ( inject . type ) ) { const { always } = inject ; Object . defineProperty ( instance , key , { configurable : true , get : function ( ) { let hasErr = false ; const data = guard ( ( ) => { switch ( inject . type ) { case 'lazyInject' : return iocSelf . get ( typeCls , { sourceCls : classType , ... ( singleton ? { } : { forceNew : true } ) , } ) ; case 'imports' : return iocSelf . getImports ( typeCls , { cache : ! always } ) ; } } , { defaultValue , onError : err => { hasErr = true ; if ( ! allowOptional ) { err . message += `\n\tSource: ${ classType . name }.${ key . toString ( ) }` ; throw err ; } } , } ) ; if ( ! hasErr && ! always ) { Object . defineProperty ( this , key , { configurable : true , writable : true , value : data , } ) ; } return data ; } , set : function ( value ) { Object . defineProperty ( this , key , { configurable : true , writable : true , value , } ) ; } , } ) ; } } ) ; getMetadataField ( classType , 'aspects' ) . forEach ( aspect => { const oriFn = instance [ aspect . key ] ; const newFn = genAspectWrapper ( this , aspect . point , oriFn ) ; Object . defineProperty ( instance , aspect . key , { configurable : true , value : newFn , } ) ; } ) ; if ( opt . autoRunPostConstruct ) { this . runPostConstruct ( instance ) ; } } public runPostConstruct ( instance ) { const classType = instance . constructor ; Array . from ( new Set ( getMetadataField ( classType , 'postConstruct' ) . map ( p => p . key ) ) ) . forEach ( key => { instance [ key ] ( ) ; } ) ; } param config public createChildContext ( config ? ) { return new IocContext ( { ... this . config , ... ( config || { } ) , parentContext : this , } ) ; } param store private preDestroyInstance ( store ) { if ( ! store . inited ) { return ; } const inst = store . value ; Array . from ( new Set ( getMetadataField ( inst . constructor , 'preDestroy' ) . map ( p => p . key ) ) ) . forEach ( key => { inst [ key ] ( ) ; } ) ; } private newStore ( data , options ) { return { inited : false , factory : this . genValueFactory ( data , options ) , value : undefined , options , } as Store ; } private canBeKey ( obj ) { const type = typeof obj ; if ( type === 'object' ) { return obj . toString ( ) !== '[object Object]' ; } return [ 'function' , 'string' , 'symbol' ] . includes ( type ) ; } private genValueFactory ( data , options ) { const dataIsFunction = typeof data === 'function' ; const dataIsClass = dataIsFunction && isClass ( data ) ; return ( ) => { if ( dataIsFunction && options . autoNew ) { if ( dataIsClass ) { const ClsType = data ; let args = [ ] ; if ( this . config . constructorInject && Reflect && Reflect . getMetadata ) { const paramTypes = Reflect . getMetadata ( 'design:paramtypes' , ClsType ) ; if ( paramTypes ) { args = paramTypes . map ( ( type ) => { if ( type === ClsType || type === undefined || type === null || type === Number || type === Error || type === Object || type === String || type === Boolean || type === Array || type === Function ) { return null ; } return this . get ( type ) ; } ) ; } } const value = new ClsType ( ... args ) ; this . inject ( value ) ; return this . config . createInstanceHook ? this . config . createInstanceHook ( value , this ) : value ; } else { const func = data ; return func ( this ) ; } } else { return data ; } } ; } private returnValue ( data , forceNew = false ) { if ( data . options . singleton && ! forceNew ) { return data . inited ? data . value : ( ( data . inited = true ) , ( data . value = data . factory ( ) ) , data . value ) ; } else { return data . factory ( ) ; } } toJSON ( ) { return { type : 'power-di.IocContext' , message : 'NoSerializable' } ; } } export class MultiImplementError extends Error { constructor ( type , key ) { super ( `Has multi Classes of implement type: ${ type . name }(${ symbolString ( key ) })` ) ; } } export class NotfoundTypeError extends Error { constructor ( type , key ) { super ( `Notfound type: ${ type . name || type }(${ symbolString ( key ) })` ) ; } } export class NoRegistryError extends Error { constructor ( key ) { super ( `the key:[${ symbolString ( key ) }] is no registry.` ) ; } } export class AlreadyRegistryError extends Error { constructor ( key ) { super ( `the key:[${ symbolString ( key ) }] is already registry.` ) ; } }