UNPKG

nestjs-aop

Version:

<!-- PROJECT LOGO --> <br /> <div align="center"> <a href="https://github.com/toss/nestjs-aop"> <img src="https://toss.tech/wp-content/uploads/2022/11/tech-article-nest-js-02.png" alt="Logo" height="200"> </a>

101 lines (85 loc) 3.26 kB
import { Injectable, OnModuleInit } from '@nestjs/common'; import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { ASPECT } from './aspect'; import { LazyDecorator } from './lazy-decorator'; /** * Aspect 가 선언되어 있고 LazyDecorator 가 구현되어 있는 provider 가 있는 경우 ioc 에 등록된 모든 provider 를 순회하면서 LazyDecorator 를 적용함. */ @Injectable() export class AutoAspectExecutor implements OnModuleInit { constructor( private readonly discoveryService: DiscoveryService, private readonly metadataScanner: MetadataScanner, private readonly reflector: Reflector, ) {} onModuleInit() { const controllers = this.discoveryService.getControllers(); const providers = this.discoveryService.getProviders(); const lazyDecorators = this.lookupLazyDecorators(providers); if (lazyDecorators.length === 0) { return; } const singletonWrapper = providers .concat(controllers) .filter(({ instance }) => instance && Object.getPrototypeOf(instance)); for (const wrapper of singletonWrapper) { const { instance } = wrapper; // Use scanFromPrototype for support nestjs 8 const methodNames = this.metadataScanner.scanFromPrototype( instance, Object.getPrototypeOf(instance), (name) => name, ); for (const methodName of methodNames) { lazyDecorators.forEach((lazyDecorator) => { const metadataKey = this.reflector.get(ASPECT, lazyDecorator.constructor); const metadataList: { originalFn: any; metadata?: unknown; aopSymbol: symbol; }[] = this.reflector.get(metadataKey, instance[methodName]); if (!metadataList) { return; } // TODO: Support request scope providers if (!wrapper.isDependencyTreeStatic()) { console.warn( `[${ wrapper.instance?.constructor?.name || 'Unknown' }] "@tossteam/nestjs-aop" does not yet support request scope providers`, ); return; } for (const { originalFn, metadata, aopSymbol } of metadataList) { const wrappedMethod = lazyDecorator.wrap({ instance, methodName, method: originalFn.bind(instance), metadata, }); Object.setPrototypeOf(wrappedMethod, instance[methodName]); instance[aopSymbol] ??= {}; instance[aopSymbol][methodName] = wrappedMethod; } }); } } } private lookupLazyDecorators(providers: InstanceWrapper[]): LazyDecorator[] { const { reflector } = this; return providers .filter((wrapper) => wrapper.isDependencyTreeStatic()) .filter(({ instance, metatype }) => { if (!instance || !metatype) { return false; } const aspect = reflector.get<string>(ASPECT, metatype); if (!aspect) { return false; } return typeof instance.wrap === 'function'; }) .map(({ instance }) => instance); } }