Как достать метод и метаданные из провайдера в NestJS?

Пишу обёртку NestJS обёртку для Typegoose так как существующая больше не поддерживается и там есть один критический недостаток, который я хочу устранить в своей реализации.

Задача: есть декоратор @EventTrackerFor(schema: AnyClass), который принимает в себя класс Typegoose. Под капотом это работает так:

export const EventTrackerFor = (schema: AnyClass) =>
    applyDecorators(Injectable, SetMetadata('tracker-for', schema.name));

А также декораторы @Pre(eventName: PreEvents) и @Post(eventName: PostEvents), которые реализованы так:

export const Post = (eventName: PreEvents) => SetMetadata('post', eventName);
export const Pre = (eventName: PostEvents) => SetMetadata('pre', eventName);

В итоге у пользователя библиотеки это будет выглядеть так:

@EventTrackerFor(User)
class UserEventTracker {
    constructor(private readonly anyService: AnyService) {}

    @Pre(PreEvents.SAVE)
    @Post(PostEvents.SAVE)
    logOnAndAfterCreate() {
        console.log('user created')
    }
}

// ------------------------ Любой модуль
@Module({ 
    imports: [MyModule.forFeature([ {schema: User} ])],
    providers: [UserEventTracker]
})
class AnyModule {}

Необходимо как-то выцепить значение из декоратора @EventTrackerFor(), а также методы этого провайдера, которые помечены декораторами @Pre() и @Post(), включая значения, переданные в эти декораторы.

Пытался подглядеть реализацию этого поведения в различных пакетах, например таких, как @nestjs/bull, но там везде так много кода, что я так и не смог понять, как же они всё-таки это делают.

Репозиторий проекта: https://github.com/GrapeoffJS/kindagoose


Ответы (1 шт):

Автор решения: Fedor Rybakov

Чтобы получить всех провайдеров в NestJS есть DiscoveryModule. Для получения метаданных из провайдеров необходим будет Reflector и MetadataScanner.

Пример как этим пользоваться ниже:

import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';

@Injectable()
class MetadataExplorer {
  constructor(
    private readonly discoveryService: DiscoveryService,  // экспортируется из DiscoveryModule
    private readonly reflector: Reflector,
    private readonly metadataScanner: MetadataScanner,
  ) {}

  onModuleInit() {
    const allProviders = this.discoveryService.getProviders(); // Список всех провайдеров в приложении

    allProviders.forEach(({ instance }) => {
      const classMetadata = this.reflector.get('your_metadata_key', instance.constructor); // Вернёт все метаданные класса по ключу
    
      this.metadataScanner.scanFromPrototype( // Находит методы класса, которые имеют метаданные
        instance,
        Object.getPrototypeOf(instance),
        (methodName) => {
          const methodMetadata = this.reflector.get('your_metadata_key', instance[methodName]); // Вернёт все метаданные метода в классе по ключу
      },
    );
    });
  }
}

Ну и нужно не забыть добавить этот класс в провайдеры вашего модуля

→ Ссылка