import debugModule from 'debug' ; import _ from 'lodash' ; import { Reflector } from './reflect' ; import { DecoratorType , MetadataKey , MetadataMap } from './types' ; const debug = debugModule ( 'loopback:metadata:decorator' ) ; /**
 * Options for a decorator
 */ export interface DecoratorOptions { /**
     * Controls if inherited metadata will be honored. Default to `true`.
     */ allowInheritance ? : boolean ; /**
     * Controls if the value of `spec` argument will be cloned. Sometimes we
     * use shared spec for the decoration, but the decorator function might need
     * to mutate the object. Cloning the input spec makes it safe to use the same
     * spec (`template`) to decorate different members.
     *
     * Default to `true`.
     */ cloneInputSpec ? : boolean ; /**
     * Name of the decorator for debugging purpose, such as `@inject`
     */ decoratorName ? : string ; [ name ] : any ; } example classDecorator export class DecoratorFactory < T , M extends T | MetadataMap < T > | MetadataMap < T [ ] > , D extends DecoratorType > { protected decoratorName : string ; /**
     * A constant to reference the target of a decoration
     */ static TARGET = '__decoratorTarget' ; param key param spec param options constructor ( protected key , protected spec , protected options = { } ) { this . options = Object . assign ( { allowInheritance : true , cloneInputSpec : true , } , options ) ; const defaultDecoratorName = this . constructor . name . replace ( /Factory$/ , '' ) ; this . decoratorName = this . options . decoratorName ?? defaultDecoratorName ; if ( this . options . cloneInputSpec ) { this . spec = DecoratorFactory . cloneDeep ( spec ) ; } } protected allowInheritance ( ) { return ! ! this . options ?. allowInheritance ; } param inheritedMetadata protected inherit ( inheritedMetadata ) { if ( ! this . allowInheritance ( ) ) return this . spec ; if ( inheritedMetadata == null ) return this . spec ; if ( this . spec == null ) return inheritedMetadata ; if ( typeof inheritedMetadata !== 'object' ) return this . spec ; if ( Array . isArray ( inheritedMetadata ) || Array . isArray ( this . spec ) ) { return this . spec ; } return Object . assign ( inheritedMetadata , this . spec ) ; } remarks param target param member param descriptorOrIndex static getTargetName ( target , member ? , descriptorOrIndex ? ) { let name = target instanceof Function ? target . name : `${ target . constructor . name }.prototype` ; if ( member == null && descriptorOrIndex == null ) { return `class ${ name }` ; } if ( member == null || member === '' ) member = 'constructor' ; const memberAccessor = typeof member === 'symbol' ? '[' + member . toString ( ) + ']' : '.' + member ; if ( typeof descriptorOrIndex === 'number' ) { name = `${ name }${ memberAccessor }[${ descriptorOrIndex }]` ; } else if ( descriptorOrIndex != null ) { name = `${ name }${ memberAccessor }()` ; } else { name = `${ name }${ memberAccessor }` ; } return name ; } param target param member static getNumberOfParameters ( target , member ? ) { if ( typeof target === 'function' && ! member ) { return target . length ; } else { const method = ( < { [ methodName ] : Function ; } > target ) [ member ! ] ; return method . length ; } } param spec param target withTarget ( spec , target ) { if ( typeof spec === 'object' && spec != null ) { Object . defineProperty ( spec , DecoratorFactory . TARGET , { value : target , enumerable : false , configurable : false , } ) ; } return spec ; } param spec getTarget ( spec ) { if ( typeof spec === 'object' && spec != null ) { const specWithTarget = spec as { [ name ] : any ; } ; return specWithTarget [ DecoratorFactory . TARGET ] ; } else { return undefined ; } } param inheritedMetadata param target param member param descriptorOrIndex protected mergeWithInherited ( inheritedMetadata , target , member ? , descriptorOrIndex ? ) { throw new Error ( `mergeWithInherited() is not implemented for ${ this . decoratorName }` ) ; } param ownMetadata param target param member param descriptorOrIndex protected mergeWithOwn ( ownMetadata , target , member ? , descriptorOrIndex ? ) { throw new Error ( `mergeWithOwn() is not implemented for ${ this . decoratorName }` ) ; } param target param member param descriptorOrIndex protected duplicateDecorationError ( target , member ? , descriptorOrIndex ? ) { const targetName = DecoratorFactory . getTargetName ( target , member , descriptorOrIndex ) ; return new Error ( `${ this . decoratorName } cannot be applied more than once on ${ targetName }` ) ; } /**
     * Create a decorator function of the given type. Each sub class MUST
     * implement this method.
     */ create ( ) { throw new Error ( `create() is not implemented for ${ this . decoratorName }` ) ; } param target param member param descriptorOrIndex protected decorate ( target , member ? , descriptorOrIndex ? ) { const targetName = DecoratorFactory . getTargetName ( target , member , descriptorOrIndex ) ; let meta = Reflector . getOwnMetadata ( this . key , target ) ; if ( meta == null && this . allowInheritance ( ) ) { meta = DecoratorFactory . cloneDeep ( Reflector . getMetadata ( this . key , target ) ) ; meta = this . mergeWithInherited ( meta , target , member , descriptorOrIndex ) ; if ( debug . enabled ) { debug ( '%s: %j' , targetName , meta ) ; } Reflector . defineMetadata ( this . key , meta , target ) ; } else { meta = this . mergeWithOwn ( meta , target , member , descriptorOrIndex ) ; if ( debug . enabled ) { debug ( '%s: %j' , targetName , meta ) ; } Reflector . defineMetadata ( this . key , meta , target ) ; } } param key param spec param options protected static _createDecorator < S , MT extends S | MetadataMap < S > | MetadataMap < S [ ] > , DT extends DecoratorType > ( key , spec , options ? ) { const inst = new this < S , MT , DT > ( key . toString ( ) , spec , options ) ; return inst . create ( ) ; } private static _cloneableTypes = [ Object , Array , Set , Map , RegExp , Date , Buffer , ArrayBuffer , Float32Array , Float64Array , Int8Array , Int16Array , Int32Array , Uint8Array , Uint8ClampedArray , Uint16Array , Uint32Array , ] ; static cloneDeep < V > ( val ) { if ( typeof val !== 'object' ) return val ; return _ . cloneDeepWith ( val , v => { if ( typeof v !== 'object' ) return v ; if ( v == null ) return v ; if ( v . constructor != null && ! DecoratorFactory . _cloneableTypes . includes ( v . constructor ) ) { return v ; } return undefined ; } ) ; } } /**
 * Factory for class decorators
 */ export class ClassDecoratorFactory < T > extends DecoratorFactory < T , T , ClassDecorator > { protected mergeWithInherited ( inheritedMetadata , target , member ? , descriptorOrIndex ? ) { return this . withTarget ( < T > this . inherit ( inheritedMetadata ) , target ) ; } protected mergeWithOwn ( ownMetadata , target , member ? , descriptorOrIndex ? ) { if ( ownMetadata != null ) { throw this . duplicateDecorationError ( target , member , descriptorOrIndex ) ; } return this . withTarget ( this . spec , target ) ; } create ( ) { return ( target ) => this . decorate ( target ) ; } param key param spec param options static createDecorator < S > ( key , spec , options ? ) { return super . _createDecorator < S , S , ClassDecorator > ( key , spec , options ) ; } } /**
 * Factory for property decorators
 */ export class PropertyDecoratorFactory < T > extends DecoratorFactory < T , MetadataMap < T > , PropertyDecorator > { protected mergeWithInherited ( inheritedMetadata , target , propertyName ? , descriptorOrIndex ? ) { inheritedMetadata = inheritedMetadata || { } ; const propertyMeta = this . withTarget ( < T > this . inherit ( inheritedMetadata [ propertyName ! ] ) , target ) ; inheritedMetadata [ propertyName ! ] = propertyMeta ; return inheritedMetadata ; } protected mergeWithOwn ( ownMetadata , target , propertyName ? , descriptorOrParameterIndex ? ) { ownMetadata = ownMetadata || { } ; if ( ownMetadata [ propertyName ! ] != null ) { throw this . duplicateDecorationError ( target , propertyName , descriptorOrParameterIndex ) ; } ownMetadata [ propertyName ! ] = this . withTarget ( this . spec , target ) ; return ownMetadata ; } create ( ) { return ( target , propertyName ) => this . decorate ( target , propertyName ) ; } param key param spec param options static createDecorator < S > ( key , spec , options ? ) { return super . _createDecorator < S , MetadataMap < S > , PropertyDecorator > ( key , spec , options ) ; } } /**
 * Factory for method decorators
 */ export class MethodDecoratorFactory < T > extends DecoratorFactory < T , MetadataMap < T > , MethodDecorator > { protected mergeWithInherited ( inheritedMetadata , target , methodName ? , methodDescriptor ? ) { inheritedMetadata = inheritedMetadata || { } ; const methodMeta = this . withTarget ( < T > this . inherit ( inheritedMetadata [ methodName ! ] ) , target ) ; inheritedMetadata [ methodName ! ] = methodMeta ; return inheritedMetadata ; } protected mergeWithOwn ( ownMetadata , target , methodName ? , methodDescriptor ? ) { ownMetadata = ownMetadata || { } ; const methodMeta = ownMetadata [ methodName ! ] ; if ( this . getTarget ( methodMeta ) === target ) { throw this . duplicateDecorationError ( target , methodName , methodDescriptor ) ; } ownMetadata [ methodName ! ] = this . withTarget ( this . spec , target ) ; return ownMetadata ; } create ( ) { return ( target , methodName , descriptor ) => this . decorate ( target , methodName , descriptor ) ; } param key param spec param options static createDecorator < S > ( key , spec , options ? ) { return super . _createDecorator < S , MetadataMap < S > , MethodDecorator > ( key , spec , options ) ; } } /**
 * Factory for parameter decorators
 */ export class ParameterDecoratorFactory < T > extends DecoratorFactory < T , MetadataMap < T [ ] > , ParameterDecorator > { private getOrInitMetadata ( meta , target , methodName ? ) { const method = methodName ? methodName : '' ; let methodMeta = meta [ method ] ; if ( methodMeta == null ) { methodMeta = new Array ( DecoratorFactory . getNumberOfParameters ( target , methodName ) ) . fill ( undefined ) ; meta [ method ] = methodMeta ; } return methodMeta ; } protected mergeWithInherited ( inheritedMetadata , target , methodName ? , parameterIndex ? ) { inheritedMetadata = inheritedMetadata || { } ; const methodMeta = this . getOrInitMetadata ( inheritedMetadata , target , methodName ) ; const index = parameterIndex as number ; methodMeta [ index ] = this . withTarget ( < T > this . inherit ( methodMeta [ index ] ) , target ) ; return inheritedMetadata ; } protected mergeWithOwn ( ownMetadata , target , methodName ? , parameterIndex ? ) { ownMetadata = ownMetadata || { } ; const methodMeta = this . getOrInitMetadata ( ownMetadata , target , methodName ) ; const index = parameterIndex as number ; if ( this . getTarget ( methodMeta [ index ] ) === target ) { throw this . duplicateDecorationError ( target , methodName , parameterIndex ) ; } methodMeta [ index ] = this . withTarget ( < T > this . inherit ( methodMeta [ index ] ) , target ) ; return ownMetadata ; } create ( ) { return ( target , methodName , parameterIndex ) => this . decorate ( target , methodName , parameterIndex ) ; } param key param spec param options static createDecorator < S > ( key , spec , options ? ) { return super . _createDecorator < S , MetadataMap < S [ ] > , ParameterDecorator > ( key , spec , options ) ; } } example param param export class MethodParameterDecoratorFactory < T > extends DecoratorFactory < T , MetadataMap < T [ ] > , MethodDecorator > { param target param methodName param methodDescriptor private getParameterIndex ( target , methodName ? , methodDescriptor ? ) { const numOfParams = DecoratorFactory . getNumberOfParameters ( target , methodName ) ; let index = Reflector . getOwnMetadata ( `${ this . key }:index` , target , methodName ) ; if ( index == null ) index = numOfParams - 1 ; if ( index < 0 ) { const method = DecoratorFactory . getTargetName ( target , methodName , methodDescriptor ) ; throw new Error ( `${ this . decoratorName } is used more than ${ numOfParams } time(s) on ${ method }` ) ; } return index ; } protected mergeWithInherited ( inheritedMetadata , target , methodName ? , methodDescriptor ? ) { inheritedMetadata = inheritedMetadata || { } ; const index = this . getParameterIndex ( target , methodName , methodDescriptor ) ; const inheritedParams = inheritedMetadata [ methodName ! ] || new Array ( index + 1 ) . fill ( undefined ) ; if ( inheritedParams . length ) { inheritedParams [ index ] = this . withTarget ( < T > this . inherit ( inheritedParams [ index ] ) , target ) ; } Reflector . defineMetadata ( `${ this . key }:index` , index - 1 , target , methodName ) ; inheritedMetadata [ methodName ! ] = inheritedParams ; return inheritedMetadata ; } protected mergeWithOwn ( ownMetadata , target , methodName ? , methodDescriptor ? ) { ownMetadata = ownMetadata || { } ; const index = this . getParameterIndex ( target , methodName , methodDescriptor ) ; const params = ownMetadata [ methodName ! ] || new Array ( index + 1 ) . fill ( undefined ) ; params [ index ] = this . withTarget ( < T > this . inherit ( params [ index ] ) , target ) ; ownMetadata [ methodName ! ] = params ; Reflector . defineMetadata ( `${ this . key }:index` , index - 1 , target , methodName ) ; return ownMetadata ; } create ( ) { return ( target , methodName , descriptor ) => this . decorate ( target , methodName , descriptor ) ; } param key param spec param options static createDecorator < S > ( key , spec , options ? ) { return super . _createDecorator < S , MetadataMap < S [ ] > , MethodDecorator > ( key , spec , options ) ; } } example response response response response response export class MethodMultiDecoratorFactory < T > extends MethodDecoratorFactory < T [ ] > { protected mergeWithInherited ( inheritedMetadata , target , methodName ? ) { inheritedMetadata = inheritedMetadata || { } ; inheritedMetadata [ methodName ! ] = this . _mergeArray ( inheritedMetadata [ methodName ! ] , this . withTarget ( this . spec , target ) ) ; return inheritedMetadata ; } protected mergeWithOwn ( ownMetadata , target , methodName ? , methodDescriptor ? ) { ownMetadata = ownMetadata || { } ; ownMetadata [ methodName ! ] = this . _mergeArray ( ownMetadata [ methodName ! ] , this . withTarget ( this . spec , target ) ) ; return ownMetadata ; } private _mergeArray ( result , methodMeta ) { if ( ! result ) { if ( Array . isArray ( methodMeta ) ) { result = methodMeta ; } else { result = [ methodMeta ] ; } } else { if ( Array . isArray ( methodMeta ) ) { result . push ( ... methodMeta ) ; } else { result . push ( methodMeta ) ; } } return result ; } }