@golevelup/nestjs-discovery
Version:
A Badass NestJS module for querying your app's controllers, providers and handlers
183 lines • 8.71 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@nestjs/common");
const constants_1 = require("@nestjs/common/constants");
const constants_2 = require("@nestjs/core/injector/constants");
const modules_container_1 = require("@nestjs/core/injector/modules-container");
const metadata_scanner_1 = require("@nestjs/core/metadata-scanner");
const lodash_1 = require("lodash");
/**
* Attempts to retrieve meta information from a Nest DiscoveredClass component
* @param key The meta key to retrieve data from
* @param component The discovered component to retrieve meta from
*/
function getComponentMetaAtKey(key, component) {
const dependencyMeta = Reflect.getMetadata(key, component.dependencyType);
if (dependencyMeta) {
return dependencyMeta;
}
if (component.injectType != null) {
return Reflect.getMetadata(key, component.injectType);
}
}
exports.getComponentMetaAtKey = getComponentMetaAtKey;
/**
* A filter that can be used to search for DiscoveredClasses in an App that contain meta attached to a
* certain key
* @param key The meta key to search for
*/
exports.withMetaAtKey = key => component => {
const metaTargets = [
lodash_1.get(component, 'instance.constructor'),
component.injectType
].filter(x => x != null);
return lodash_1.some(metaTargets, x => Reflect.getMetadata(key, x));
};
let DiscoveryService = class DiscoveryService {
constructor(modulesContainer, metadataScanner) {
this.modulesContainer = modulesContainer;
this.metadataScanner = metadataScanner;
const modulesMap = [...this.modulesContainer.entries()];
this.discoveredControllers = Promise.all(lodash_1.flatMap(modulesMap, ([key, nestModule]) => {
const components = [...nestModule.routes.values()];
return components
.filter(component => component.scope !== common_1.Scope.REQUEST)
.map(component => this.toDiscoveredClass(nestModule, component));
}));
this.discoveredProviders = Promise.all(lodash_1.flatMap(modulesMap, ([key, nestModule]) => {
const components = [...nestModule.components.values()];
return components
.filter(component => component.scope !== common_1.Scope.REQUEST)
.map(component => this.toDiscoveredClass(nestModule, component));
}));
}
/**
* Discovers all providers in a Nest App that match a filter
* @param providerFilter
*/
async providers(filter) {
return (await this.discoveredProviders).filter(x => filter(x));
}
/**
* Discovers all controller methods that either directly have a certain meta key attached to them
* or belong to a controller that has the same meta key attached to them
* @param metaKey The meta key to scan for
* @param metaFilter An optional filter for the contents of the meta object
*/
async methodsAndControllerMethodsWithMetaAtKey(metaKey, metaFilter = meta => true) {
const controllersWithMeta = (await this.controllersWithMetaAtKey(metaKey)).filter(x => metaFilter(x.meta));
const methodsFromDecoratedControllers = lodash_1.flatMap(controllersWithMeta, controller => {
return this.classMethodsWithMetaAtKey(controller.discoveredClass, constants_1.PATH_METADATA);
});
const decoratedMethods = (await this.controllerMethodsWithMetaAtKey(metaKey)).filter(x => metaFilter(x.meta));
return lodash_1.uniqBy([...methodsFromDecoratedControllers, ...decoratedMethods], x => x.discoveredMethod.handler);
}
/**
* Discovers all providers in an App that have meta at a specific key and returns the provider(s) and associated meta
* @param metaKey The metakey to scan for
*/
async providersWithMetaAtKey(metaKey) {
const providers = await this.providers(exports.withMetaAtKey(metaKey));
return providers.map(x => ({
meta: getComponentMetaAtKey(metaKey, x),
discoveredClass: x
}));
}
/**
* Discovers all controllers in a Nest App that match a filter
* @param providerFilter
*/
async controllers(filter) {
return (await this.discoveredControllers).filter(x => filter(x));
}
/**
* Discovers all controllers in an App that have meta at a specific key and returns the controller(s) and associated meta
* @param metaKey The metakey to scan for
*/
async controllersWithMetaAtKey(metaKey) {
const controllers = await this.controllers(exports.withMetaAtKey(metaKey));
return controllers.map(x => ({
meta: getComponentMetaAtKey(metaKey, x),
discoveredClass: x
}));
}
/**
* Discovers all method handlers matching a particular metakey from a Provider or Controller
* @param component
* @param metaKey
*/
classMethodsWithMetaAtKey(component, metaKey) {
const { instance } = component;
if (!instance) {
return [];
}
const prototype = Object.getPrototypeOf(instance);
return this.metadataScanner
.scanFromPrototype(instance, prototype, name => this.extractMethodMetaAtKey(metaKey, component, prototype, name))
.filter(x => !!x.meta);
}
/**
* Discovers all the methods that exist on providers in a Nest App that contain metadata under a specific key
* @param metaKey The metakey to scan for
* @param providerFilter A predicate used to limit the providers being scanned. Defaults to all providers in the app module
*/
async providerMethodsWithMetaAtKey(metaKey, providerFilter = x => true) {
const providers = await this.providers(providerFilter);
return lodash_1.flatMap(providers, provider => this.classMethodsWithMetaAtKey(provider, metaKey));
}
/**
* Discovers all the methods that exist on controllers in a Nest App that contain metadata under a specific key
* @param metaKey The metakey to scan for
* @param controllerFilter A predicate used to limit the controllers being scanned. Defaults to all providers in the app module
*/
async controllerMethodsWithMetaAtKey(metaKey, controllerFilter = x => true) {
const controllers = await this.controllers(controllerFilter);
return lodash_1.flatMap(controllers, controller => this.classMethodsWithMetaAtKey(controller, metaKey));
}
async toDiscoveredClass(nestModule, wrapper) {
const instanceHost = wrapper.getInstanceByContextId(constants_2.STATIC_CONTEXT, wrapper && wrapper.id ? wrapper.id : undefined);
if (instanceHost.isPending && !instanceHost.isResolved) {
await instanceHost.donePromise;
}
return {
name: wrapper.name,
instance: instanceHost.instance,
injectType: wrapper.metatype,
dependencyType: lodash_1.get(instanceHost, 'instance.constructor'),
parentModule: {
name: nestModule.metatype.name,
instance: nestModule.instance,
injectType: nestModule.metatype,
dependencyType: nestModule.instance.constructor
}
};
}
extractMethodMetaAtKey(metaKey, discoveredClass, prototype, methodName) {
const handler = prototype[methodName];
const meta = Reflect.getMetadata(metaKey, handler);
return {
meta,
discoveredMethod: {
handler,
methodName,
parentClass: discoveredClass
}
};
}
};
DiscoveryService = __decorate([
common_1.Injectable(),
__metadata("design:paramtypes", [modules_container_1.ModulesContainer,
metadata_scanner_1.MetadataScanner])
], DiscoveryService);
exports.DiscoveryService = DiscoveryService;
//# sourceMappingURL=discovery.service.js.map