export type Decorator = ClassDecorator | MemberDecorator; export type MemberDecorator = ( target: Target, propertyKey: PropertyKey, descriptor?: TypedPropertyDescriptor, ) => TypedPropertyDescriptor | void; export type MetadataKey = string | symbol; export type PropertyKey = string | symbol; export type Target = object | Function; const Metadata = new WeakMap(); function decorateProperty( decorators: MemberDecorator[], target: Target, propertyKey: PropertyKey, descriptor?: PropertyDescriptor, ): PropertyDescriptor | undefined { decorators.reverse().forEach((decorator: MemberDecorator) => { descriptor = decorator(target, propertyKey, descriptor) || descriptor; }); return descriptor; } function decorateConstructor( decorators: ClassDecorator[], target: Function, ): Function { decorators.reverse().forEach((decorator: ClassDecorator) => { const decorated = decorator(target); if (decorated) { target = decorated; } }); return target; } export function decorate( decorators: ClassDecorator[], target: Function, ): Function; export function decorate( decorators: MemberDecorator[], target: object, propertyKey?: PropertyKey, attributes?: PropertyDescriptor, ): PropertyDescriptor | undefined; export function decorate( decorators: Decorator[], target: Target, propertyKey?: PropertyKey, attributes?: PropertyDescriptor, ): Function | PropertyDescriptor | undefined { if (!Array.isArray(decorators) || decorators.length === 0) { throw new TypeError(); } if (propertyKey !== undefined) { return decorateProperty( decorators as MemberDecorator[], target, propertyKey, attributes, ); } if (typeof target === 'function') { return decorateConstructor(decorators as ClassDecorator[], target); } return; } function getMetadataMap( target: Target, propertyKey?: PropertyKey, ): Map | undefined { return Metadata.get(target) && Metadata.get(target).get(propertyKey); } function ordinaryGetOwnMetadata( metadataKey: MetadataKey, target: Target, propertyKey?: PropertyKey, ): MetadataValue | undefined { if (target === undefined) { throw new TypeError(); } const metadataMap = getMetadataMap(target, propertyKey); return metadataMap && metadataMap.get(metadataKey); } function createMetadataMap( target: Target, propertyKey?: PropertyKey, ): Map { const targetMetadata = Metadata.get(target) || new Map>(); Metadata.set(target, targetMetadata); const metadataMap = targetMetadata.get(propertyKey) || new Map(); targetMetadata.set(propertyKey, metadataMap); return metadataMap; } function ordinaryDefineOwnMetadata( metadataKey: MetadataKey, metadataValue: MetadataValue, target: Target, propertyKey?: PropertyKey, ): void { if (propertyKey && !['string', 'symbol'].includes(typeof propertyKey)) { throw new TypeError(); } ( getMetadataMap(target, propertyKey) || createMetadataMap(target, propertyKey) ).set(metadataKey, metadataValue); } function ordinaryGetMetadata( metadataKey: MetadataKey, target: Target, propertyKey?: PropertyKey, ): MetadataValue | undefined { return ordinaryGetOwnMetadata(metadataKey, target, propertyKey) ? ordinaryGetOwnMetadata(metadataKey, target, propertyKey) : Object.getPrototypeOf(target) ? ordinaryGetMetadata( metadataKey, Object.getPrototypeOf(target), propertyKey, ) : undefined; } export function metadata( metadataKey: MetadataKey, metadataValue: MetadataValue, ) { return function decorator(target: Target, propertyKey?: PropertyKey): void { ordinaryDefineOwnMetadata( metadataKey, metadataValue, target, propertyKey, ); }; } export function getMetadata( metadataKey: MetadataKey, target: Target, propertyKey?: PropertyKey, ): MetadataValue | undefined { return ordinaryGetMetadata(metadataKey, target, propertyKey); } export function getOwnMetadata( metadataKey: MetadataKey, target: Target, propertyKey?: PropertyKey, ): MetadataValue | undefined { return ordinaryGetOwnMetadata( metadataKey, target, propertyKey, ); } export function hasOwnMetadata( metadataKey: MetadataKey, target: Target, propertyKey?: PropertyKey, ): boolean { return !!ordinaryGetOwnMetadata(metadataKey, target, propertyKey); } export function hasMetadata( metadataKey: MetadataKey, target: Target, propertyKey?: PropertyKey, ): boolean { return !!ordinaryGetMetadata(metadataKey, target, propertyKey); } export function defineMetadata( metadataKey: MetadataKey, metadataValue: MetadataValue, target: Target, propertyKey?: PropertyKey, ): void { ordinaryDefineOwnMetadata(metadataKey, metadataValue, target, propertyKey); } export const Reflection = { decorate, defineMetadata, getMetadata, getOwnMetadata, hasMetadata, hasOwnMetadata, metadata, }; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Reflect { let decorate: typeof Reflection.decorate; let defineMetadata: typeof Reflection.defineMetadata; let getMetadata: typeof Reflection.getMetadata; let getOwnMetadata: typeof Reflection.getOwnMetadata; let hasOwnMetadata: typeof Reflection.hasOwnMetadata; let hasMetadata: typeof Reflection.hasMetadata; let metadata: typeof Reflection.metadata; } } Object.assign(Reflect, Reflection);