mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
feat: add graph inspector implementation
This commit is contained in:
@@ -206,17 +206,16 @@
|
||||
"**/*.js",
|
||||
"**/*.d.ts",
|
||||
"**/*.spec.ts",
|
||||
"packages/**/*.spec.ts",
|
||||
"packages/**/adapters/*.ts",
|
||||
"packages/**/nest-*.ts",
|
||||
"packages/**/test/**/*.ts",
|
||||
"packages/core/errors/**/*",
|
||||
"packages/common/exceptions/*.ts",
|
||||
"packages/common/http/*.ts",
|
||||
"packages/common/utils/load-package.util.ts",
|
||||
"packages/microservices/exceptions/",
|
||||
"packages/microservices/microservices-module.ts",
|
||||
"packages/core/middleware/middleware-module.ts",
|
||||
"packages/core/discovery/discovery-service.ts",
|
||||
"packages/core/injector/module-ref.ts",
|
||||
"packages/core/injector/instance-links-host.ts",
|
||||
"packages/core/helpers/context-id-factory.ts",
|
||||
|
||||
@@ -4,6 +4,7 @@ export const MODULE_METADATA = {
|
||||
CONTROLLERS: 'controllers',
|
||||
EXPORTS: 'exports',
|
||||
};
|
||||
|
||||
export const GLOBAL_MODULE_METADATA = '__module:global__';
|
||||
export const HOST_METADATA = 'host';
|
||||
export const PATH_METADATA = 'path';
|
||||
@@ -20,12 +21,23 @@ export const CUSTOM_ROUTE_ARGS_METADATA = '__customRouteArgs__';
|
||||
* @deprecated Use `CUSTOM_ROUTE_ARGS_METADATA` instead
|
||||
*/
|
||||
export const CUSTOM_ROUTE_AGRS_METADATA = CUSTOM_ROUTE_ARGS_METADATA;
|
||||
export const EXCEPTION_FILTERS_METADATA = '__exceptionFilters__';
|
||||
export const FILTER_CATCH_EXCEPTIONS = '__filterCatchExceptions__';
|
||||
|
||||
export const PIPES_METADATA = '__pipes__';
|
||||
export const GUARDS_METADATA = '__guards__';
|
||||
export const RENDER_METADATA = '__renderTemplate__';
|
||||
export const INTERCEPTORS_METADATA = '__interceptors__';
|
||||
export const EXCEPTION_FILTERS_METADATA = '__exceptionFilters__';
|
||||
export const ENHANCER_KEY_TO_SUBTYPE_MAP = {
|
||||
[GUARDS_METADATA]: 'guard',
|
||||
[INTERCEPTORS_METADATA]: 'interceptor',
|
||||
[PIPES_METADATA]: 'pipe',
|
||||
[EXCEPTION_FILTERS_METADATA]: 'filter',
|
||||
} as const;
|
||||
|
||||
export type EnhancerSubtype =
|
||||
typeof ENHANCER_KEY_TO_SUBTYPE_MAP[keyof typeof ENHANCER_KEY_TO_SUBTYPE_MAP];
|
||||
|
||||
export const RENDER_METADATA = '__renderTemplate__';
|
||||
export const HTTP_CODE_METADATA = '__httpCode__';
|
||||
export const MODULE_PATH = '__module_path__';
|
||||
export const HEADERS_METADATA = '__headers__';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants';
|
||||
|
||||
export const MESSAGES = {
|
||||
APPLICATION_START: `Starting Nest application...`,
|
||||
APPLICATION_READY: `Nest application successfully started`,
|
||||
@@ -12,3 +14,15 @@ export const APP_INTERCEPTOR = 'APP_INTERCEPTOR';
|
||||
export const APP_PIPE = 'APP_PIPE';
|
||||
export const APP_GUARD = 'APP_GUARD';
|
||||
export const APP_FILTER = 'APP_FILTER';
|
||||
export const ENHANCER_TOKEN_TO_SUBTYPE_MAP: Record<
|
||||
| typeof APP_GUARD
|
||||
| typeof APP_PIPE
|
||||
| typeof APP_FILTER
|
||||
| typeof APP_INTERCEPTOR,
|
||||
EnhancerSubtype
|
||||
> = {
|
||||
[APP_GUARD]: 'guard',
|
||||
[APP_INTERCEPTOR]: 'interceptor',
|
||||
[APP_PIPE]: 'pipe',
|
||||
[APP_FILTER]: 'filter',
|
||||
} as const;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { DynamicModule, Provider } from '@nestjs/common';
|
||||
import { GLOBAL_MODULE_METADATA } from '@nestjs/common/constants';
|
||||
import {
|
||||
EnhancerSubtype,
|
||||
GLOBAL_MODULE_METADATA,
|
||||
} from '@nestjs/common/constants';
|
||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
@@ -8,6 +11,7 @@ import {
|
||||
UndefinedForwardRefException,
|
||||
UnknownModuleException,
|
||||
} from '../errors/exceptions';
|
||||
import { SerializedGraph } from '../inspector/serialized-graph';
|
||||
import { REQUEST } from '../router/request/request-constants';
|
||||
import { ModuleCompiler } from './compiler';
|
||||
import { ContextId } from './instance-wrapper';
|
||||
@@ -27,12 +31,17 @@ export class NestContainer {
|
||||
Partial<DynamicModule>
|
||||
>();
|
||||
private readonly internalProvidersStorage = new InternalProvidersStorage();
|
||||
private readonly _serializedGraph = new SerializedGraph();
|
||||
private internalCoreModule: Module;
|
||||
|
||||
constructor(
|
||||
private readonly _applicationConfig: ApplicationConfig = undefined,
|
||||
) {}
|
||||
|
||||
get serializedGraph(): SerializedGraph {
|
||||
return this._serializedGraph;
|
||||
}
|
||||
|
||||
get applicationConfig(): ApplicationConfig | undefined {
|
||||
return this._applicationConfig;
|
||||
}
|
||||
@@ -74,13 +83,11 @@ export class NestContainer {
|
||||
moduleRef.token = token;
|
||||
this.modules.set(token, moduleRef);
|
||||
|
||||
await this.addDynamicMetadata(
|
||||
token,
|
||||
dynamicMetadata,
|
||||
[].concat(scope, type),
|
||||
);
|
||||
const updatedScope = [].concat(scope, type);
|
||||
await this.addDynamicMetadata(token, dynamicMetadata, updatedScope);
|
||||
|
||||
if (this.isGlobalModule(type, dynamicMetadata)) {
|
||||
moduleRef.isGlobal = true;
|
||||
this.addGlobalModule(moduleRef);
|
||||
}
|
||||
return moduleRef;
|
||||
@@ -155,6 +162,7 @@ export class NestContainer {
|
||||
public addProvider(
|
||||
provider: Provider,
|
||||
token: string,
|
||||
enhancerSubtype?: EnhancerSubtype,
|
||||
): string | symbol | Function {
|
||||
const moduleRef = this.modules.get(token);
|
||||
if (!provider) {
|
||||
@@ -163,19 +171,20 @@ export class NestContainer {
|
||||
if (!moduleRef) {
|
||||
throw new UnknownModuleException();
|
||||
}
|
||||
return moduleRef.addProvider(provider);
|
||||
return moduleRef.addProvider(provider, enhancerSubtype) as Function;
|
||||
}
|
||||
|
||||
public addInjectable(
|
||||
injectable: Provider,
|
||||
token: string,
|
||||
enhancerSubtype: EnhancerSubtype,
|
||||
host?: Type<Injectable>,
|
||||
) {
|
||||
if (!this.modules.has(token)) {
|
||||
throw new UnknownModuleException();
|
||||
}
|
||||
const moduleRef = this.modules.get(token);
|
||||
moduleRef.addInjectable(injectable, host);
|
||||
return moduleRef.addInjectable(injectable, enhancerSubtype, host);
|
||||
}
|
||||
|
||||
public addExportedProvider(provider: Type<any>, token: string) {
|
||||
@@ -219,15 +228,16 @@ export class NestContainer {
|
||||
target.addRelatedModule(globalModule);
|
||||
}
|
||||
|
||||
public getDynamicMetadataByToken(token: string): Partial<DynamicModule>;
|
||||
public getDynamicMetadataByToken<
|
||||
K extends Exclude<keyof DynamicModule, 'global' | 'module'>,
|
||||
>(token: string, metadataKey: K): DynamicModule[K];
|
||||
public getDynamicMetadataByToken(
|
||||
token: string,
|
||||
metadataKey: keyof DynamicModule,
|
||||
metadataKey?: Exclude<keyof DynamicModule, 'global' | 'module'>,
|
||||
) {
|
||||
const metadata = this.dynamicModulesMetadata.get(token);
|
||||
if (metadata && metadata[metadataKey]) {
|
||||
return metadata[metadataKey] as any[];
|
||||
}
|
||||
return [];
|
||||
return metadataKey ? metadata?.[metadataKey] ?? [] : metadata;
|
||||
}
|
||||
|
||||
public registerCoreModuleRef(moduleRef: Module) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Logger, LoggerService } from '@nestjs/common';
|
||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
||||
import { MODULE_INIT_MESSAGE } from '../helpers/messages';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { NestContainer } from './container';
|
||||
import { Injector } from './injector';
|
||||
import { InternalCoreModule } from './internal-core-module/internal-core-module';
|
||||
@@ -9,9 +10,11 @@ import { Module } from './module';
|
||||
|
||||
export class InstanceLoader {
|
||||
protected readonly injector = new Injector();
|
||||
|
||||
constructor(
|
||||
protected readonly container: NestContainer,
|
||||
private logger = new Logger(InstanceLoader.name, {
|
||||
protected readonly graphInspector: GraphInspector,
|
||||
private logger: LoggerService = new Logger(InstanceLoader.name, {
|
||||
timestamp: true,
|
||||
}),
|
||||
) {}
|
||||
@@ -42,7 +45,7 @@ export class InstanceLoader {
|
||||
await this.createInstancesOfInjectables(moduleRef);
|
||||
await this.createInstancesOfControllers(moduleRef);
|
||||
|
||||
const { name } = moduleRef.metatype;
|
||||
const { name } = moduleRef;
|
||||
this.isModuleWhitelisted(name) &&
|
||||
this.logger.log(MODULE_INIT_MESSAGE`${name}`);
|
||||
}),
|
||||
@@ -60,7 +63,10 @@ export class InstanceLoader {
|
||||
const { providers } = moduleRef;
|
||||
const wrappers = [...providers.values()];
|
||||
await Promise.all(
|
||||
wrappers.map(item => this.injector.loadProvider(item, moduleRef)),
|
||||
wrappers.map(async item => {
|
||||
await this.injector.loadProvider(item, moduleRef);
|
||||
this.graphInspector.inspectInstanceWrapper(item, moduleRef);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,7 +81,10 @@ export class InstanceLoader {
|
||||
const { controllers } = moduleRef;
|
||||
const wrappers = [...controllers.values()];
|
||||
await Promise.all(
|
||||
wrappers.map(item => this.injector.loadController(item, moduleRef)),
|
||||
wrappers.map(async item => {
|
||||
await this.injector.loadController(item, moduleRef);
|
||||
this.graphInspector.inspectInstanceWrapper(item, moduleRef);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,7 +99,10 @@ export class InstanceLoader {
|
||||
const { injectables } = moduleRef;
|
||||
const wrappers = [...injectables.values()];
|
||||
await Promise.all(
|
||||
wrappers.map(item => this.injector.loadInjectable(item, moduleRef)),
|
||||
wrappers.map(async item => {
|
||||
await this.injector.loadInjectable(item, moduleRef);
|
||||
this.graphInspector.inspectInstanceWrapper(item, moduleRef);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { iterate } from 'iterare';
|
||||
import { EnhancerSubtype } from '../../common/constants';
|
||||
import { STATIC_CONTEXT } from './constants';
|
||||
import {
|
||||
isClassProvider,
|
||||
@@ -60,6 +61,7 @@ export class InstanceWrapper<T = any> {
|
||||
public readonly async?: boolean;
|
||||
public readonly host?: Module;
|
||||
public readonly isAlias: boolean = false;
|
||||
public readonly subtype?: EnhancerSubtype;
|
||||
|
||||
public scope?: Scope = Scope.DEFAULT;
|
||||
public metatype: Type<T> | Function;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { ExternalContextCreator } from '../../helpers/external-context-creator';
|
||||
import { HttpAdapterHost } from '../../helpers/http-adapter-host';
|
||||
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||
import { DependenciesScanner } from '../../scanner';
|
||||
import { ModuleCompiler } from '../compiler';
|
||||
import { NestContainer } from '../container';
|
||||
@@ -15,7 +16,25 @@ export class InternalCoreModuleFactory {
|
||||
scanner: DependenciesScanner,
|
||||
moduleCompiler: ModuleCompiler,
|
||||
httpAdapterHost: HttpAdapterHost,
|
||||
graphInspector: GraphInspector,
|
||||
) {
|
||||
const lazyModuleLoaderFactory = () => {
|
||||
const logger = new Logger(LazyModuleLoader.name, {
|
||||
timestamp: false,
|
||||
});
|
||||
const instanceLoader = new InstanceLoader(
|
||||
container,
|
||||
graphInspector,
|
||||
logger,
|
||||
);
|
||||
return new LazyModuleLoader(
|
||||
scanner,
|
||||
instanceLoader,
|
||||
moduleCompiler,
|
||||
container.getModules(),
|
||||
);
|
||||
};
|
||||
|
||||
return InternalCoreModule.register([
|
||||
{
|
||||
provide: ExternalContextCreator,
|
||||
@@ -35,18 +54,7 @@ export class InternalCoreModuleFactory {
|
||||
},
|
||||
{
|
||||
provide: LazyModuleLoader,
|
||||
useFactory: () => {
|
||||
const logger = new Logger(LazyModuleLoader.name, {
|
||||
timestamp: false,
|
||||
});
|
||||
const instanceLoader = new InstanceLoader(container, logger);
|
||||
return new LazyModuleLoader(
|
||||
scanner,
|
||||
instanceLoader,
|
||||
moduleCompiler,
|
||||
container.getModules(),
|
||||
);
|
||||
},
|
||||
useFactory: lazyModuleLoaderFactory,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { iterate } from 'iterare';
|
||||
import { EnhancerSubtype } from '../../common/constants';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
import {
|
||||
InvalidClassException,
|
||||
@@ -62,6 +63,7 @@ export class Module {
|
||||
>();
|
||||
private readonly _exports = new Set<InstanceToken>();
|
||||
private _distance = 0;
|
||||
private _isGlobal = false;
|
||||
private _token: string;
|
||||
|
||||
constructor(
|
||||
@@ -84,6 +86,18 @@ export class Module {
|
||||
this._token = token;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.metatype.name;
|
||||
}
|
||||
|
||||
get isGlobal() {
|
||||
return this._isGlobal;
|
||||
}
|
||||
|
||||
set isGlobal(global: boolean) {
|
||||
this._isGlobal = global;
|
||||
}
|
||||
|
||||
get providers(): Map<InstanceToken, InstanceWrapper<Injectable>> {
|
||||
return this._providers;
|
||||
}
|
||||
@@ -199,6 +213,7 @@ export class Module {
|
||||
|
||||
public addInjectable<T extends Injectable>(
|
||||
injectable: Provider,
|
||||
enhancerSubtype: EnhancerSubtype,
|
||||
host?: Type<T>,
|
||||
) {
|
||||
if (this.isCustomProvider(injectable)) {
|
||||
@@ -214,6 +229,7 @@ export class Module {
|
||||
isResolved: false,
|
||||
scope: getClassScope(injectable),
|
||||
durable: isDurable(injectable),
|
||||
subtype: enhancerSubtype,
|
||||
host: this,
|
||||
});
|
||||
this._injectables.set(injectable, instanceWrapper);
|
||||
@@ -223,11 +239,17 @@ export class Module {
|
||||
this._controllers.get(host) || this._providers.get(host);
|
||||
hostWrapper && hostWrapper.addEnhancerMetadata(instanceWrapper);
|
||||
}
|
||||
return instanceWrapper;
|
||||
}
|
||||
|
||||
public addProvider(provider: Provider) {
|
||||
public addProvider(provider: Provider): Provider | InjectionToken;
|
||||
public addProvider(
|
||||
provider: Provider,
|
||||
enhancerSubtype: EnhancerSubtype,
|
||||
): Provider | InjectionToken;
|
||||
public addProvider(provider: Provider, enhancerSubtype?: EnhancerSubtype) {
|
||||
if (this.isCustomProvider(provider)) {
|
||||
return this.addCustomProvider(provider, this._providers);
|
||||
return this.addCustomProvider(provider, this._providers, enhancerSubtype);
|
||||
}
|
||||
this._providers.set(
|
||||
provider,
|
||||
@@ -270,15 +292,16 @@ export class Module {
|
||||
| ValueProvider
|
||||
| ExistingProvider,
|
||||
collection: Map<Function | string | symbol, any>,
|
||||
enhancerSubtype?: EnhancerSubtype,
|
||||
) {
|
||||
if (this.isCustomClass(provider)) {
|
||||
this.addCustomClass(provider, collection);
|
||||
this.addCustomClass(provider, collection, enhancerSubtype);
|
||||
} else if (this.isCustomValue(provider)) {
|
||||
this.addCustomValue(provider, collection);
|
||||
this.addCustomValue(provider, collection, enhancerSubtype);
|
||||
} else if (this.isCustomFactory(provider)) {
|
||||
this.addCustomFactory(provider, collection);
|
||||
this.addCustomFactory(provider, collection, enhancerSubtype);
|
||||
} else if (this.isCustomUseExisting(provider)) {
|
||||
this.addCustomUseExisting(provider, collection);
|
||||
this.addCustomUseExisting(provider, collection, enhancerSubtype);
|
||||
}
|
||||
return provider.provide;
|
||||
}
|
||||
@@ -306,6 +329,7 @@ export class Module {
|
||||
public addCustomClass(
|
||||
provider: ClassProvider,
|
||||
collection: Map<InstanceToken, InstanceWrapper>,
|
||||
enhancerSubtype?: EnhancerSubtype,
|
||||
) {
|
||||
let { scope, durable } = provider;
|
||||
|
||||
@@ -327,6 +351,7 @@ export class Module {
|
||||
scope,
|
||||
durable,
|
||||
host: this,
|
||||
subtype: enhancerSubtype,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -334,6 +359,7 @@ export class Module {
|
||||
public addCustomValue(
|
||||
provider: ValueProvider,
|
||||
collection: Map<Function | string | symbol, InstanceWrapper>,
|
||||
enhancerSubtype?: EnhancerSubtype,
|
||||
) {
|
||||
const { useValue: value, provide: providerToken } = provider;
|
||||
collection.set(
|
||||
@@ -346,6 +372,7 @@ export class Module {
|
||||
isResolved: true,
|
||||
async: value instanceof Promise,
|
||||
host: this,
|
||||
subtype: enhancerSubtype,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -353,6 +380,7 @@ export class Module {
|
||||
public addCustomFactory(
|
||||
provider: FactoryProvider,
|
||||
collection: Map<Function | string | symbol, InstanceWrapper>,
|
||||
enhancerSubtype?: EnhancerSubtype,
|
||||
) {
|
||||
const {
|
||||
useFactory: factory,
|
||||
@@ -374,6 +402,7 @@ export class Module {
|
||||
scope,
|
||||
durable,
|
||||
host: this,
|
||||
subtype: enhancerSubtype,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -381,6 +410,7 @@ export class Module {
|
||||
public addCustomUseExisting(
|
||||
provider: ExistingProvider,
|
||||
collection: Map<Function | string | symbol, InstanceWrapper>,
|
||||
enhancerSubtype?: EnhancerSubtype,
|
||||
) {
|
||||
const { useExisting, provide: providerToken } = provider;
|
||||
collection.set(
|
||||
@@ -394,6 +424,7 @@ export class Module {
|
||||
inject: [useExisting],
|
||||
host: this,
|
||||
isAlias: true,
|
||||
subtype: enhancerSubtype,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
201
packages/core/inspector/graph-inspector.ts
Normal file
201
packages/core/inspector/graph-inspector.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { Module } from '../injector/module';
|
||||
import { EnhancerMetadataCacheEntry } from './interfaces/enhancer-metadata-cache-entry.interface';
|
||||
import { Entrypoint } from './interfaces/entrypoint.interface';
|
||||
import { OrphanedEnhancerDefinition } from './interfaces/extras.interface';
|
||||
import { ClassNode, Node } from './interfaces/node.interface';
|
||||
import { SerializedGraph } from './serialized-graph';
|
||||
|
||||
export class GraphInspector {
|
||||
private readonly graph: SerializedGraph;
|
||||
private readonly enhancersMetadataCache =
|
||||
new Array<EnhancerMetadataCacheEntry>();
|
||||
|
||||
constructor(private readonly container: NestContainer) {
|
||||
this.graph = container.serializedGraph;
|
||||
}
|
||||
|
||||
public inspectModules() {
|
||||
const modules = this.container.getModules().values();
|
||||
|
||||
for (const moduleRef of modules) {
|
||||
this.insertModuleNode(moduleRef);
|
||||
this.insertClassNodes(moduleRef);
|
||||
this.insertModuleToModuleEdges(moduleRef);
|
||||
}
|
||||
|
||||
this.enhancersMetadataCache.forEach(entry =>
|
||||
this.insertEnhancerEdge(entry),
|
||||
);
|
||||
}
|
||||
|
||||
public inspectInstanceWrapper<T = any>(
|
||||
source: InstanceWrapper<T>,
|
||||
moduleRef: Module,
|
||||
) {
|
||||
const ctorMetadata = source.getCtorMetadata();
|
||||
ctorMetadata?.forEach((target, index) =>
|
||||
this.insertClassToClassEdge(
|
||||
source,
|
||||
target,
|
||||
moduleRef,
|
||||
index,
|
||||
'constructor',
|
||||
),
|
||||
);
|
||||
|
||||
const propertiesMetadata = source.getPropertiesMetadata();
|
||||
propertiesMetadata?.forEach(({ key, wrapper: target }) =>
|
||||
this.insertClassToClassEdge(source, target, moduleRef, key, 'property'),
|
||||
);
|
||||
}
|
||||
|
||||
public insertEnhancerMetadataCache(entry: EnhancerMetadataCacheEntry) {
|
||||
this.enhancersMetadataCache.push(entry);
|
||||
}
|
||||
|
||||
public insertOrphanedEnhancer(entry: OrphanedEnhancerDefinition) {
|
||||
this.graph.insertOrphanedEnhancer(entry);
|
||||
}
|
||||
|
||||
public insertAttachedEnhancer(wrapper: InstanceWrapper) {
|
||||
const existingNode = this.graph.getNodeById(wrapper.id);
|
||||
existingNode.metadata.global = true;
|
||||
|
||||
this.graph.insertAttachedEnhancer(existingNode.id);
|
||||
}
|
||||
|
||||
public insertEntrypointDefinition<T>(definition: Entrypoint<T>) {
|
||||
this.graph.insertEntrypoint(definition);
|
||||
}
|
||||
|
||||
private insertModuleNode(moduleRef: Module) {
|
||||
const dynamicMetadata = this.container.getDynamicMetadataByToken(
|
||||
moduleRef.token,
|
||||
);
|
||||
const node: Node = {
|
||||
id: moduleRef.id,
|
||||
label: moduleRef.name,
|
||||
metadata: {
|
||||
type: 'module',
|
||||
global: moduleRef.isGlobal,
|
||||
dynamic: !!dynamicMetadata,
|
||||
},
|
||||
};
|
||||
this.graph.insertNode(node);
|
||||
}
|
||||
|
||||
private insertModuleToModuleEdges(moduleRef: Module) {
|
||||
for (const targetModuleRef of moduleRef.imports) {
|
||||
this.graph.insertEdge({
|
||||
source: moduleRef.id,
|
||||
target: targetModuleRef.id,
|
||||
metadata: {
|
||||
type: 'module-to-module',
|
||||
sourceModuleName: moduleRef.name,
|
||||
targetModuleName: targetModuleRef.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private insertEnhancerEdge(entry: EnhancerMetadataCacheEntry) {
|
||||
const moduleRef = this.container.getModuleByKey(entry.moduleToken);
|
||||
const sourceInstanceWrapper =
|
||||
moduleRef.controllers.get(entry.classRef) ??
|
||||
moduleRef.providers.get(entry.classRef);
|
||||
const existingSourceNode = this.graph.getNodeById(
|
||||
sourceInstanceWrapper.id,
|
||||
) as ClassNode;
|
||||
const enhancers = existingSourceNode.metadata.enhancers ?? [];
|
||||
|
||||
if (entry.enhancerInstanceWrapper) {
|
||||
this.insertClassToClassEdge(
|
||||
sourceInstanceWrapper,
|
||||
entry.enhancerInstanceWrapper,
|
||||
moduleRef,
|
||||
undefined,
|
||||
'decorator',
|
||||
);
|
||||
|
||||
enhancers.push({
|
||||
id: entry.enhancerInstanceWrapper.id,
|
||||
methodKey: entry.methodKey,
|
||||
});
|
||||
} else {
|
||||
const name =
|
||||
entry.enhancerRef.constructor?.name ??
|
||||
(entry.enhancerRef as Function).name;
|
||||
|
||||
enhancers.push({
|
||||
name,
|
||||
methodKey: entry.methodKey,
|
||||
});
|
||||
}
|
||||
existingSourceNode.metadata.enhancers = enhancers;
|
||||
}
|
||||
|
||||
private insertClassToClassEdge<T>(
|
||||
source: InstanceWrapper<T>,
|
||||
target: InstanceWrapper,
|
||||
moduleRef: Module,
|
||||
keyOrIndex: number | string | symbol | undefined,
|
||||
injectionType: 'constructor' | 'property' | 'decorator',
|
||||
) {
|
||||
this.graph.insertEdge({
|
||||
source: source.id,
|
||||
target: target.id,
|
||||
metadata: {
|
||||
type: 'class-to-class',
|
||||
sourceModuleName: moduleRef.name,
|
||||
sourceClassName: source.name,
|
||||
targetClassName: target.name,
|
||||
sourceClassToken: source.token,
|
||||
targetClassToken: target.token,
|
||||
targetModuleName: target.host?.name,
|
||||
keyOrIndex,
|
||||
injectionType,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private insertClassNodes(moduleRef: Module) {
|
||||
moduleRef.providers.forEach(value =>
|
||||
this.insertClassNode(moduleRef, value, 'provider'),
|
||||
);
|
||||
moduleRef.middlewares.forEach(item =>
|
||||
this.insertClassNode(moduleRef, item, 'middleware'),
|
||||
);
|
||||
moduleRef.injectables.forEach(value =>
|
||||
this.insertClassNode(moduleRef, value, 'injectable'),
|
||||
);
|
||||
moduleRef.controllers.forEach(value =>
|
||||
this.insertClassNode(moduleRef, value, 'controller'),
|
||||
);
|
||||
}
|
||||
|
||||
private insertClassNode(
|
||||
moduleRef: Module,
|
||||
wrapper: InstanceWrapper,
|
||||
type: Exclude<Node['metadata']['type'], 'module'>,
|
||||
) {
|
||||
if (wrapper.metatype === moduleRef.metatype) {
|
||||
return;
|
||||
}
|
||||
this.graph.insertNode({
|
||||
id: wrapper.id,
|
||||
label: wrapper.name,
|
||||
parent: moduleRef.id,
|
||||
metadata: {
|
||||
type,
|
||||
sourceModuleName: moduleRef.name,
|
||||
durable: wrapper.isDependencyTreeDurable(),
|
||||
static: wrapper.isDependencyTreeStatic(),
|
||||
scope: wrapper.scope,
|
||||
transient: wrapper.isTransient,
|
||||
token: wrapper.token,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
31
packages/core/inspector/interfaces/edge.interface.ts
Normal file
31
packages/core/inspector/interfaces/edge.interface.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
|
||||
type CommonEdgeMetadata = {
|
||||
sourceModuleName: string;
|
||||
targetModuleName: string;
|
||||
};
|
||||
|
||||
type ModuleToModuleEdgeMetadata = {
|
||||
type: 'module-to-module';
|
||||
} & CommonEdgeMetadata;
|
||||
|
||||
type ClassToClassEdgeMetadata = {
|
||||
type: 'class-to-class';
|
||||
sourceClassName: string;
|
||||
targetClassName: string;
|
||||
sourceClassToken: InjectionToken;
|
||||
targetClassToken: InjectionToken;
|
||||
injectionType: 'constructor' | 'property' | 'decorator';
|
||||
keyOrIndex?: string | number | symbol;
|
||||
/**
|
||||
* If true, indicates that this edge represents an internal providers connection
|
||||
*/
|
||||
internal?: boolean;
|
||||
} & CommonEdgeMetadata;
|
||||
|
||||
export interface Edge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
metadata: ModuleToModuleEdgeMetadata | ClassToClassEdgeMetadata;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Type } from '@nestjs/common';
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||
|
||||
export interface EnhancerMetadataCacheEntry {
|
||||
targetNodeId?: string;
|
||||
moduleToken: string;
|
||||
classRef: Type;
|
||||
methodKey: string | undefined;
|
||||
enhancerRef?: unknown;
|
||||
enhancerInstanceWrapper?: InstanceWrapper;
|
||||
subtype: EnhancerSubtype;
|
||||
}
|
||||
23
packages/core/inspector/interfaces/entrypoint.interface.ts
Normal file
23
packages/core/inspector/interfaces/entrypoint.interface.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { RequestMethod } from '@nestjs/common';
|
||||
import { VersionValue } from '@nestjs/common/interfaces';
|
||||
|
||||
export type HttpEntrypointMetadata = {
|
||||
path: string;
|
||||
requestMethod: keyof typeof RequestMethod;
|
||||
methodVersion?: VersionValue;
|
||||
controllerVersion?: VersionValue;
|
||||
};
|
||||
|
||||
export type MiddlewareEntrypointMetadata = {
|
||||
path: string;
|
||||
requestMethod: keyof typeof RequestMethod;
|
||||
version?: VersionValue;
|
||||
};
|
||||
|
||||
export type Entrypoint<T> = {
|
||||
type: string;
|
||||
methodName: string;
|
||||
className: string;
|
||||
classNodeId: string;
|
||||
metadata: T;
|
||||
};
|
||||
21
packages/core/inspector/interfaces/extras.interface.ts
Normal file
21
packages/core/inspector/interfaces/extras.interface.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { EnhancerSubtype } from '@nestjs/common/constants';
|
||||
|
||||
/**
|
||||
* Enhancers attached through APP_PIPE, APP_GUARD, APP_INTERCEPTOR, and APP_FILTER tokens.
|
||||
*/
|
||||
export interface AttachedEnhancerDefinition {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhancers registered through "app.useGlobalPipes()", "app.useGlobalGuards()", "app.useGlobalInterceptors()", and "app.useGlobalFilters()" methods.
|
||||
*/
|
||||
export interface OrphanedEnhancerDefinition {
|
||||
subtype: EnhancerSubtype;
|
||||
ref: unknown;
|
||||
}
|
||||
|
||||
export interface Extras {
|
||||
orphanedEnhancers: Array<OrphanedEnhancerDefinition>;
|
||||
attachedEnhancers: Array<AttachedEnhancerDefinition>;
|
||||
}
|
||||
43
packages/core/inspector/interfaces/node.interface.ts
Normal file
43
packages/core/inspector/interfaces/node.interface.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { InjectionToken, Scope } from '@nestjs/common';
|
||||
|
||||
export type ModuleNode = {
|
||||
metadata: {
|
||||
type: 'module';
|
||||
global: boolean;
|
||||
dynamic: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type ClassNode = {
|
||||
parent: string;
|
||||
metadata: {
|
||||
type: 'provider' | 'controller' | 'middleware' | 'injectable';
|
||||
sourceModuleName: string;
|
||||
durable: boolean;
|
||||
static: boolean;
|
||||
transient: boolean;
|
||||
scope: Scope;
|
||||
token: InjectionToken;
|
||||
/**
|
||||
* Enhancers metadata collection
|
||||
*/
|
||||
enhancers?: Array<{ id: string } | { name: string; methodKey?: string }>;
|
||||
/**
|
||||
* Order in which globally registered enhancers are applied
|
||||
*/
|
||||
enhancerRegistrationOrder?: number;
|
||||
/**
|
||||
* If true, node is a globally registered enhancer
|
||||
*/
|
||||
global?: boolean;
|
||||
/**
|
||||
* If true, indicates that this node represents an internal provider
|
||||
*/
|
||||
internal?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type Node = {
|
||||
id: string;
|
||||
label: string;
|
||||
} & (ClassNode | ModuleNode);
|
||||
97
packages/core/inspector/serialized-graph.ts
Normal file
97
packages/core/inspector/serialized-graph.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
import { ExternalContextCreator } from '../helpers/external-context-creator';
|
||||
import { HttpAdapterHost } from '../helpers/http-adapter-host';
|
||||
import { INQUIRER } from '../injector/inquirer/inquirer-constants';
|
||||
import { LazyModuleLoader } from '../injector/lazy-module-loader/lazy-module-loader';
|
||||
import { ModuleRef } from '../injector/module-ref';
|
||||
import { ModulesContainer } from '../injector/modules-container';
|
||||
import { REQUEST } from '../router/request/request-constants';
|
||||
import { Reflector } from '../services/reflector.service';
|
||||
import { Edge } from './interfaces/edge.interface';
|
||||
import { Entrypoint } from './interfaces/entrypoint.interface';
|
||||
import {
|
||||
Extras,
|
||||
OrphanedEnhancerDefinition,
|
||||
} from './interfaces/extras.interface';
|
||||
import { Node } from './interfaces/node.interface';
|
||||
|
||||
const INTERNAL_PROVIDERS: Array<InjectionToken> = [
|
||||
ApplicationConfig,
|
||||
ModuleRef,
|
||||
HttpAdapterHost,
|
||||
LazyModuleLoader,
|
||||
ExternalContextCreator,
|
||||
ModulesContainer,
|
||||
Reflector,
|
||||
HttpAdapterHost.name,
|
||||
Reflector.name,
|
||||
REQUEST,
|
||||
INQUIRER,
|
||||
];
|
||||
|
||||
type WithOptionalId<T extends Record<'id', string>> = Omit<T, 'id'> &
|
||||
Partial<Pick<T, 'id'>>;
|
||||
|
||||
export class SerializedGraph {
|
||||
private readonly nodes = new Map<string, Node>();
|
||||
private readonly edges = new Map<string, Edge>();
|
||||
private readonly entrypoints = new Set<Entrypoint<unknown>>();
|
||||
private readonly extras: Extras = {
|
||||
orphanedEnhancers: [],
|
||||
attachedEnhancers: [],
|
||||
};
|
||||
|
||||
public insertNode(nodeDefinition: Node) {
|
||||
if (
|
||||
nodeDefinition.metadata.type === 'provider' &&
|
||||
INTERNAL_PROVIDERS.includes(nodeDefinition.metadata.token)
|
||||
) {
|
||||
nodeDefinition.metadata = {
|
||||
...nodeDefinition.metadata,
|
||||
internal: true,
|
||||
};
|
||||
}
|
||||
this.nodes.set(nodeDefinition.id, nodeDefinition);
|
||||
return nodeDefinition;
|
||||
}
|
||||
|
||||
public insertEdge(edgeDefinition: WithOptionalId<Edge>) {
|
||||
if (
|
||||
edgeDefinition.metadata.type === 'class-to-class' &&
|
||||
(INTERNAL_PROVIDERS.includes(edgeDefinition.metadata.sourceClassToken) ||
|
||||
INTERNAL_PROVIDERS.includes(edgeDefinition.metadata.targetClassToken))
|
||||
) {
|
||||
edgeDefinition.metadata = {
|
||||
...edgeDefinition.metadata,
|
||||
internal: true,
|
||||
};
|
||||
}
|
||||
const id = edgeDefinition.id ?? randomUUID();
|
||||
const edge = {
|
||||
...edgeDefinition,
|
||||
id,
|
||||
};
|
||||
this.edges.set(id, edge);
|
||||
return edge;
|
||||
}
|
||||
|
||||
public insertEntrypoint<T>(definition: Entrypoint<T>) {
|
||||
this.entrypoints.add(definition);
|
||||
}
|
||||
|
||||
public insertOrphanedEnhancer(entry: OrphanedEnhancerDefinition) {
|
||||
this.extras.orphanedEnhancers.push(entry);
|
||||
}
|
||||
|
||||
public insertAttachedEnhancer(nodeId: string) {
|
||||
this.extras.attachedEnhancers.push({
|
||||
nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
public getNodeById(id: string) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { HttpServer, VersioningType } from '@nestjs/common';
|
||||
import { RequestMethod } from '@nestjs/common/enums/request-method.enum';
|
||||
import {
|
||||
MiddlewareConfiguration,
|
||||
RouteInfo,
|
||||
NestMiddleware,
|
||||
RouteInfo,
|
||||
} from '@nestjs/common/interfaces/middleware';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
@@ -19,6 +19,11 @@ import { NestContainer } from '../injector/container';
|
||||
import { Injector } from '../injector/injector';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { InstanceToken, Module } from '../injector/module';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import {
|
||||
Entrypoint,
|
||||
MiddlewareEntrypointMetadata,
|
||||
} from '../inspector/interfaces/entrypoint.interface';
|
||||
import { REQUEST_CONTEXT_ID } from '../router/request/request-constants';
|
||||
import { RoutePathFactory } from '../router/route-path-factory';
|
||||
import { RouterExceptionFilters } from '../router/router-exception-filters';
|
||||
@@ -40,6 +45,7 @@ export class MiddlewareModule {
|
||||
private config: ApplicationConfig;
|
||||
private container: NestContainer;
|
||||
private httpAdapter: HttpServer;
|
||||
private graphInspector: GraphInspector;
|
||||
|
||||
constructor(private readonly routePathFactory: RoutePathFactory) {}
|
||||
|
||||
@@ -49,6 +55,7 @@ export class MiddlewareModule {
|
||||
config: ApplicationConfig,
|
||||
injector: Injector,
|
||||
httpAdapter: HttpServer,
|
||||
graphInspector: GraphInspector,
|
||||
) {
|
||||
const appRef = container.getHttpAdapterRef();
|
||||
this.routerExceptionFilter = new RouterExceptionFilters(
|
||||
@@ -63,6 +70,7 @@ export class MiddlewareModule {
|
||||
this.injector = injector;
|
||||
this.container = container;
|
||||
this.httpAdapter = httpAdapter;
|
||||
this.graphInspector = graphInspector;
|
||||
|
||||
const modules = container.getModules();
|
||||
await this.resolveMiddleware(middlewareContainer, modules);
|
||||
@@ -174,6 +182,21 @@ export class MiddlewareModule {
|
||||
if (instanceWrapper.isTransient) {
|
||||
return;
|
||||
}
|
||||
const middlewareDefinition: Entrypoint<MiddlewareEntrypointMetadata> = {
|
||||
type: 'middleware',
|
||||
methodName: 'use',
|
||||
className: instanceWrapper.name,
|
||||
classNodeId: instanceWrapper.id,
|
||||
metadata: {
|
||||
path: routeInfo.path,
|
||||
requestMethod: RequestMethod[
|
||||
routeInfo.method
|
||||
] as keyof typeof RequestMethod,
|
||||
version: routeInfo.version,
|
||||
},
|
||||
};
|
||||
this.graphInspector.insertEntrypointDefinition(middlewareDefinition);
|
||||
|
||||
await this.bindHandler(
|
||||
instanceWrapper,
|
||||
applicationRef,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MODULE_PATH, PATH_METADATA } from '@nestjs/common/constants';
|
||||
import { RouteInfo, Type, VersionValue } from '@nestjs/common/interfaces';
|
||||
import { RouteInfo, Type } from '@nestjs/common/interfaces';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
isString,
|
||||
@@ -8,14 +8,14 @@ import {
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { Module } from '../injector/module';
|
||||
import { MetadataScanner } from '../metadata-scanner';
|
||||
import { RouterExplorer } from '../router/router-explorer';
|
||||
import { PathsExplorer } from '../router/paths-explorer';
|
||||
import { targetModulesByContainer } from '../router/router-module';
|
||||
|
||||
export class RoutesMapper {
|
||||
private readonly routerExplorer: RouterExplorer;
|
||||
private readonly pathsExplorer: PathsExplorer;
|
||||
|
||||
constructor(private readonly container: NestContainer) {
|
||||
this.routerExplorer = new RouterExplorer(new MetadataScanner(), container);
|
||||
this.pathsExplorer = new PathsExplorer(new MetadataScanner());
|
||||
}
|
||||
|
||||
public mapRouteToRouteInfo(
|
||||
@@ -58,7 +58,7 @@ export class RoutesMapper {
|
||||
controller: Type<any>,
|
||||
routePath: string,
|
||||
): RouteInfo[] {
|
||||
const controllerPaths = this.routerExplorer.scanForPaths(
|
||||
const controllerPaths = this.pathsExplorer.scanForPaths(
|
||||
Object.create(controller),
|
||||
controller.prototype,
|
||||
);
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
WebSocketAdapter,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
RouteInfo,
|
||||
GlobalPrefixOptions,
|
||||
NestApplicationOptions,
|
||||
RouteInfo,
|
||||
} from '@nestjs/common/interfaces';
|
||||
import {
|
||||
CorsOptions,
|
||||
@@ -37,6 +37,7 @@ import { ApplicationConfig } from './application-config';
|
||||
import { MESSAGES } from './constants';
|
||||
import { optionalRequire } from './helpers/optional-require';
|
||||
import { NestContainer } from './injector/container';
|
||||
import { GraphInspector } from './inspector/graph-inspector';
|
||||
import { MiddlewareContainer } from './middleware/container';
|
||||
import { MiddlewareModule } from './middleware/middleware-module';
|
||||
import { NestApplicationContext } from './nest-application-context';
|
||||
@@ -80,6 +81,7 @@ export class NestApplication
|
||||
container: NestContainer,
|
||||
private readonly httpAdapter: HttpServer,
|
||||
private readonly config: ApplicationConfig,
|
||||
private readonly graphInspector: GraphInspector,
|
||||
private readonly appOptions: NestApplicationOptions = {},
|
||||
) {
|
||||
super(container);
|
||||
@@ -92,6 +94,7 @@ export class NestApplication
|
||||
this.container,
|
||||
this.config,
|
||||
this.injector,
|
||||
this.graphInspector,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,6 +155,7 @@ export class NestApplication
|
||||
this.config,
|
||||
this.injector,
|
||||
this.httpAdapter,
|
||||
this.graphInspector,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -217,6 +221,7 @@ export class NestApplication
|
||||
const instance = new NestMicroservice(
|
||||
this.container,
|
||||
microserviceOptions,
|
||||
this.graphInspector,
|
||||
applicationConfig,
|
||||
);
|
||||
instance.registerListeners();
|
||||
@@ -365,21 +370,45 @@ export class NestApplication
|
||||
|
||||
public useGlobalFilters(...filters: ExceptionFilter[]): this {
|
||||
this.config.useGlobalFilters(...filters);
|
||||
filters.forEach(item =>
|
||||
this.graphInspector.insertOrphanedEnhancer({
|
||||
subtype: 'filter',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public useGlobalPipes(...pipes: PipeTransform<any>[]): this {
|
||||
this.config.useGlobalPipes(...pipes);
|
||||
pipes.forEach(item =>
|
||||
this.graphInspector.insertOrphanedEnhancer({
|
||||
subtype: 'pipe',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public useGlobalInterceptors(...interceptors: NestInterceptor[]): this {
|
||||
this.config.useGlobalInterceptors(...interceptors);
|
||||
interceptors.forEach(item =>
|
||||
this.graphInspector.insertOrphanedEnhancer({
|
||||
subtype: 'interceptor',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public useGlobalGuards(...guards: CanActivate[]): this {
|
||||
this.config.useGlobalGuards(...guards);
|
||||
guards.forEach(item =>
|
||||
this.graphInspector.insertOrphanedEnhancer({
|
||||
subtype: 'guard',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { loadAdapter } from './helpers/load-adapter';
|
||||
import { rethrow } from './helpers/rethrow';
|
||||
import { NestContainer } from './injector/container';
|
||||
import { InstanceLoader } from './injector/instance-loader';
|
||||
import { GraphInspector } from './inspector/graph-inspector';
|
||||
import { MetadataScanner } from './metadata-scanner';
|
||||
import { NestApplication } from './nest-application';
|
||||
import { NestApplicationContext } from './nest-application-context';
|
||||
@@ -73,15 +74,24 @@ export class NestFactoryStatic {
|
||||
|
||||
const applicationConfig = new ApplicationConfig();
|
||||
const container = new NestContainer(applicationConfig);
|
||||
const graphInspector = new GraphInspector(container);
|
||||
|
||||
this.setAbortOnError(serverOrOptions, options);
|
||||
this.registerLoggerConfiguration(appOptions);
|
||||
|
||||
await this.initialize(module, container, applicationConfig, httpServer);
|
||||
await this.initialize(
|
||||
module,
|
||||
container,
|
||||
graphInspector,
|
||||
applicationConfig,
|
||||
httpServer,
|
||||
);
|
||||
|
||||
const instance = new NestApplication(
|
||||
container,
|
||||
httpServer,
|
||||
applicationConfig,
|
||||
graphInspector,
|
||||
appOptions,
|
||||
);
|
||||
const target = this.createNestInstance(instance);
|
||||
@@ -108,12 +118,19 @@ export class NestFactoryStatic {
|
||||
);
|
||||
const applicationConfig = new ApplicationConfig();
|
||||
const container = new NestContainer(applicationConfig);
|
||||
const graphInspector = new GraphInspector(container);
|
||||
|
||||
this.setAbortOnError(options);
|
||||
this.registerLoggerConfiguration(options);
|
||||
|
||||
await this.initialize(module, container, applicationConfig);
|
||||
await this.initialize(module, container, graphInspector, applicationConfig);
|
||||
return this.createNestInstance<INestMicroservice>(
|
||||
new NestMicroservice(container, options, applicationConfig),
|
||||
new NestMicroservice(
|
||||
container,
|
||||
options,
|
||||
graphInspector,
|
||||
applicationConfig,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,10 +148,12 @@ export class NestFactoryStatic {
|
||||
options?: NestApplicationContextOptions,
|
||||
): Promise<INestApplicationContext> {
|
||||
const container = new NestContainer();
|
||||
const graphInspector = new GraphInspector(container);
|
||||
|
||||
this.setAbortOnError(options);
|
||||
this.registerLoggerConfiguration(options);
|
||||
|
||||
await this.initialize(module, container);
|
||||
await this.initialize(module, container, graphInspector);
|
||||
|
||||
const modules = container.getModules().values();
|
||||
const root = modules.next().value;
|
||||
@@ -155,14 +174,16 @@ export class NestFactoryStatic {
|
||||
private async initialize(
|
||||
module: any,
|
||||
container: NestContainer,
|
||||
graphInspector: GraphInspector,
|
||||
config = new ApplicationConfig(),
|
||||
httpServer: HttpServer = null,
|
||||
) {
|
||||
const instanceLoader = new InstanceLoader(container);
|
||||
const instanceLoader = new InstanceLoader(container, graphInspector);
|
||||
const metadataScanner = new MetadataScanner();
|
||||
const dependenciesScanner = new DependenciesScanner(
|
||||
container,
|
||||
metadataScanner,
|
||||
graphInspector,
|
||||
config,
|
||||
);
|
||||
container.setHttpAdapter(httpServer);
|
||||
|
||||
@@ -9,6 +9,7 @@ export class ReplLogger extends ConsoleLogger {
|
||||
RouterExplorer.name,
|
||||
NestApplication.name,
|
||||
];
|
||||
|
||||
log(_message: any, context?: string) {
|
||||
if (ReplLogger.ignoredContexts.includes(context)) {
|
||||
return;
|
||||
|
||||
74
packages/core/router/paths-explorer.ts
Normal file
74
packages/core/router/paths-explorer.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
METHOD_METADATA,
|
||||
PATH_METADATA,
|
||||
VERSION_METADATA,
|
||||
} from '@nestjs/common/constants';
|
||||
import { RequestMethod } from '@nestjs/common/enums';
|
||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
||||
import { VersionValue } from '@nestjs/common/interfaces/version-options.interface';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { MetadataScanner } from '../metadata-scanner';
|
||||
import { RouterProxyCallback } from './router-proxy';
|
||||
|
||||
export interface RouteDefinition {
|
||||
path: string[];
|
||||
requestMethod: RequestMethod;
|
||||
targetCallback: RouterProxyCallback;
|
||||
methodName: string;
|
||||
version?: VersionValue;
|
||||
}
|
||||
|
||||
export class PathsExplorer {
|
||||
constructor(private readonly metadataScanner: MetadataScanner) {}
|
||||
|
||||
public scanForPaths(
|
||||
instance: Controller,
|
||||
prototype?: object,
|
||||
): RouteDefinition[] {
|
||||
const instancePrototype = isUndefined(prototype)
|
||||
? Object.getPrototypeOf(instance)
|
||||
: prototype;
|
||||
|
||||
return this.metadataScanner.scanFromPrototype<Controller, RouteDefinition>(
|
||||
instance,
|
||||
instancePrototype,
|
||||
method => this.exploreMethodMetadata(instance, instancePrototype, method),
|
||||
);
|
||||
}
|
||||
|
||||
public exploreMethodMetadata(
|
||||
instance: Controller,
|
||||
prototype: object,
|
||||
methodName: string,
|
||||
): RouteDefinition {
|
||||
const instanceCallback = instance[methodName];
|
||||
const prototypeCallback = prototype[methodName];
|
||||
const routePath = Reflect.getMetadata(PATH_METADATA, prototypeCallback);
|
||||
if (isUndefined(routePath)) {
|
||||
return null;
|
||||
}
|
||||
const requestMethod: RequestMethod = Reflect.getMetadata(
|
||||
METHOD_METADATA,
|
||||
prototypeCallback,
|
||||
);
|
||||
const version: VersionValue | undefined = Reflect.getMetadata(
|
||||
VERSION_METADATA,
|
||||
prototypeCallback,
|
||||
);
|
||||
const path = isString(routePath)
|
||||
? [addLeadingSlash(routePath)]
|
||||
: routePath.map((p: string) => addLeadingSlash(p));
|
||||
|
||||
return {
|
||||
path,
|
||||
requestMethod,
|
||||
targetCallback: instanceCallback,
|
||||
methodName,
|
||||
version,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
import { HttpServer } from '@nestjs/common';
|
||||
import {
|
||||
METHOD_METADATA,
|
||||
PATH_METADATA,
|
||||
VERSION_METADATA,
|
||||
} from '@nestjs/common/constants';
|
||||
import { PATH_METADATA } from '@nestjs/common/constants';
|
||||
import { RequestMethod, VersioningType } from '@nestjs/common/enums';
|
||||
import { InternalServerErrorException } from '@nestjs/common/exceptions';
|
||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
||||
@@ -12,7 +8,6 @@ import { VersionValue } from '@nestjs/common/interfaces/version-options.interfac
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import {
|
||||
addLeadingSlash,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import * as pathToRegexp from 'path-to-regexp';
|
||||
@@ -32,6 +27,11 @@ import { NestContainer } from '../injector/container';
|
||||
import { Injector } from '../injector/injector';
|
||||
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { Module } from '../injector/module';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import {
|
||||
Entrypoint,
|
||||
HttpEntrypointMetadata,
|
||||
} from '../inspector/interfaces/entrypoint.interface';
|
||||
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
|
||||
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
|
||||
import { MetadataScanner } from '../metadata-scanner';
|
||||
@@ -39,6 +39,7 @@ import { PipesConsumer } from '../pipes/pipes-consumer';
|
||||
import { PipesContextCreator } from '../pipes/pipes-context-creator';
|
||||
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
|
||||
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
||||
import { PathsExplorer } from './paths-explorer';
|
||||
import { REQUEST_CONTEXT_ID } from './request/request-constants';
|
||||
import { RouteParamsFactory } from './route-params-factory';
|
||||
import { RoutePathFactory } from './route-path-factory';
|
||||
@@ -55,6 +56,7 @@ export interface RouteDefinition {
|
||||
|
||||
export class RouterExplorer {
|
||||
private readonly executionContextCreator: RouterExecutionContext;
|
||||
private readonly pathsExplorer: PathsExplorer;
|
||||
private readonly routerMethodFactory = new RouterMethodFactory();
|
||||
private readonly logger = new Logger(RouterExplorer.name, {
|
||||
timestamp: true,
|
||||
@@ -62,14 +64,17 @@ export class RouterExplorer {
|
||||
private readonly exceptionFiltersCache = new WeakMap();
|
||||
|
||||
constructor(
|
||||
private readonly metadataScanner: MetadataScanner,
|
||||
metadataScanner: MetadataScanner,
|
||||
private readonly container: NestContainer,
|
||||
private readonly injector?: Injector,
|
||||
private readonly routerProxy?: RouterProxy,
|
||||
private readonly exceptionsFilter?: ExceptionsFilter,
|
||||
private readonly config?: ApplicationConfig,
|
||||
private readonly routePathFactory?: RoutePathFactory,
|
||||
private readonly injector: Injector,
|
||||
private readonly routerProxy: RouterProxy,
|
||||
private readonly exceptionsFilter: ExceptionsFilter,
|
||||
config: ApplicationConfig,
|
||||
private readonly routePathFactory: RoutePathFactory,
|
||||
private readonly graphInspector: GraphInspector,
|
||||
) {
|
||||
this.pathsExplorer = new PathsExplorer(metadataScanner);
|
||||
|
||||
const routeParamsFactory = new RouteParamsFactory();
|
||||
const pipesContextCreator = new PipesContextCreator(container, config);
|
||||
const pipesConsumer = new PipesConsumer();
|
||||
@@ -101,7 +106,7 @@ export class RouterExplorer {
|
||||
routePathMetadata: RoutePathMetadata,
|
||||
) {
|
||||
const { instance } = instanceWrapper;
|
||||
const routerPaths = this.scanForPaths(instance);
|
||||
const routerPaths = this.pathsExplorer.scanForPaths(instance);
|
||||
this.applyPathsToRouterProxy(
|
||||
applicationRef,
|
||||
routerPaths,
|
||||
@@ -124,53 +129,6 @@ export class RouterExplorer {
|
||||
return [addLeadingSlash(path)];
|
||||
}
|
||||
|
||||
public scanForPaths(
|
||||
instance: Controller,
|
||||
prototype?: object,
|
||||
): RouteDefinition[] {
|
||||
const instancePrototype = isUndefined(prototype)
|
||||
? Object.getPrototypeOf(instance)
|
||||
: prototype;
|
||||
|
||||
return this.metadataScanner.scanFromPrototype<Controller, RouteDefinition>(
|
||||
instance,
|
||||
instancePrototype,
|
||||
method => this.exploreMethodMetadata(instance, instancePrototype, method),
|
||||
);
|
||||
}
|
||||
|
||||
public exploreMethodMetadata(
|
||||
instance: Controller,
|
||||
prototype: object,
|
||||
methodName: string,
|
||||
): RouteDefinition {
|
||||
const instanceCallback = instance[methodName];
|
||||
const prototypeCallback = prototype[methodName];
|
||||
const routePath = Reflect.getMetadata(PATH_METADATA, prototypeCallback);
|
||||
if (isUndefined(routePath)) {
|
||||
return null;
|
||||
}
|
||||
const requestMethod: RequestMethod = Reflect.getMetadata(
|
||||
METHOD_METADATA,
|
||||
prototypeCallback,
|
||||
);
|
||||
const version: VersionValue | undefined = Reflect.getMetadata(
|
||||
VERSION_METADATA,
|
||||
prototypeCallback,
|
||||
);
|
||||
const path = isString(routePath)
|
||||
? [addLeadingSlash(routePath)]
|
||||
: routePath.map((p: string) => addLeadingSlash(p));
|
||||
|
||||
return {
|
||||
path,
|
||||
requestMethod,
|
||||
targetCallback: instanceCallback,
|
||||
methodName,
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
public applyPathsToRouterProxy<T extends HttpServer>(
|
||||
router: T,
|
||||
routeDefinitions: RouteDefinition[],
|
||||
@@ -255,7 +213,29 @@ export class RouterExplorer {
|
||||
routePathMetadata,
|
||||
requestMethod,
|
||||
);
|
||||
pathsToRegister.forEach(path => routerMethodRef(path, routeHandler));
|
||||
pathsToRegister.forEach(path => {
|
||||
const entrypointDefinition: Entrypoint<HttpEntrypointMetadata> = {
|
||||
type: 'http-endpoint',
|
||||
methodName,
|
||||
className: instanceWrapper.name,
|
||||
classNodeId: instanceWrapper.id,
|
||||
metadata: {
|
||||
path,
|
||||
requestMethod: RequestMethod[
|
||||
requestMethod
|
||||
] as keyof typeof RequestMethod,
|
||||
methodVersion: routePathMetadata.methodVersion as VersionValue,
|
||||
controllerVersion:
|
||||
routePathMetadata.controllerVersion as VersionValue,
|
||||
},
|
||||
};
|
||||
|
||||
routerMethodRef(path, routeHandler);
|
||||
|
||||
this.graphInspector.insertEntrypointDefinition<HttpEntrypointMetadata>(
|
||||
entrypointDefinition,
|
||||
);
|
||||
});
|
||||
|
||||
const pathsToLog = this.routePathFactory.create(
|
||||
{
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
VERSION_METADATA,
|
||||
} from '@nestjs/common/constants';
|
||||
import {
|
||||
Controller,
|
||||
HttpServer,
|
||||
Type,
|
||||
VersionValue,
|
||||
Controller,
|
||||
} from '@nestjs/common/interfaces';
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { Injector } from '../injector/injector';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { MetadataScanner } from '../metadata-scanner';
|
||||
import { Resolver } from './interfaces/resolver.interface';
|
||||
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
||||
@@ -40,6 +41,7 @@ export class RoutesResolver implements Resolver {
|
||||
private readonly container: NestContainer,
|
||||
private readonly applicationConfig: ApplicationConfig,
|
||||
private readonly injector: Injector,
|
||||
graphInspector: GraphInspector,
|
||||
) {
|
||||
const httpAdapterRef = container.getHttpAdapterRef();
|
||||
this.routerExceptionsFilter = new RouterExceptionFilters(
|
||||
@@ -58,6 +60,7 @@ export class RoutesResolver implements Resolver {
|
||||
this.routerExceptionsFilter,
|
||||
this.applicationConfig,
|
||||
this.routePathFactory,
|
||||
graphInspector,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import {
|
||||
DynamicModule,
|
||||
flatten,
|
||||
ForwardReference,
|
||||
Provider,
|
||||
} from '@nestjs/common';
|
||||
import { DynamicModule, ForwardReference, Provider } from '@nestjs/common';
|
||||
import {
|
||||
CATCH_WATERMARK,
|
||||
CONTROLLER_WATERMARK,
|
||||
EnhancerSubtype,
|
||||
ENHANCER_KEY_TO_SUBTYPE_MAP,
|
||||
EXCEPTION_FILTERS_METADATA,
|
||||
GUARDS_METADATA,
|
||||
INJECTABLE_WATERMARK,
|
||||
@@ -38,7 +35,13 @@ import {
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { iterate } from 'iterare';
|
||||
import { ApplicationConfig } from './application-config';
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from './constants';
|
||||
import {
|
||||
APP_FILTER,
|
||||
APP_GUARD,
|
||||
APP_INTERCEPTOR,
|
||||
APP_PIPE,
|
||||
ENHANCER_TOKEN_TO_SUBTYPE_MAP,
|
||||
} from './constants';
|
||||
import { CircularDependencyException } from './errors/exceptions/circular-dependency.exception';
|
||||
import { InvalidClassModuleException } from './errors/exceptions/invalid-class-module.exception';
|
||||
import { InvalidModuleException } from './errors/exceptions/invalid-module.exception';
|
||||
@@ -48,6 +51,7 @@ import { NestContainer } from './injector/container';
|
||||
import { InstanceWrapper } from './injector/instance-wrapper';
|
||||
import { InternalCoreModuleFactory } from './injector/internal-core-module/internal-core-module-factory';
|
||||
import { Module } from './injector/module';
|
||||
import { GraphInspector } from './inspector/graph-inspector';
|
||||
import { MetadataScanner } from './metadata-scanner';
|
||||
|
||||
interface ApplicationProviderWrapper {
|
||||
@@ -64,6 +68,7 @@ export class DependenciesScanner {
|
||||
constructor(
|
||||
private readonly container: NestContainer,
|
||||
private readonly metadataScanner: MetadataScanner,
|
||||
private readonly graphInspector: GraphInspector,
|
||||
private readonly applicationConfig = new ApplicationConfig(),
|
||||
) {}
|
||||
|
||||
@@ -75,6 +80,8 @@ export class DependenciesScanner {
|
||||
|
||||
this.addScopedEnhancersMetadata();
|
||||
this.container.bindGlobalScope();
|
||||
|
||||
this.graphInspector.inspectModules();
|
||||
}
|
||||
|
||||
public async scanForModules(
|
||||
@@ -211,15 +218,15 @@ export class DependenciesScanner {
|
||||
});
|
||||
}
|
||||
|
||||
public reflectDynamicMetadata(obj: Type<Injectable>, token: string) {
|
||||
if (!obj || !obj.prototype) {
|
||||
public reflectDynamicMetadata(cls: Type<Injectable>, token: string) {
|
||||
if (!cls || !cls.prototype) {
|
||||
return;
|
||||
}
|
||||
this.reflectInjectables(obj, token, GUARDS_METADATA);
|
||||
this.reflectInjectables(obj, token, INTERCEPTORS_METADATA);
|
||||
this.reflectInjectables(obj, token, EXCEPTION_FILTERS_METADATA);
|
||||
this.reflectInjectables(obj, token, PIPES_METADATA);
|
||||
this.reflectParamInjectables(obj, token, ROUTE_ARGS_METADATA);
|
||||
this.reflectInjectables(cls, token, GUARDS_METADATA);
|
||||
this.reflectInjectables(cls, token, INTERCEPTORS_METADATA);
|
||||
this.reflectInjectables(cls, token, EXCEPTION_FILTERS_METADATA);
|
||||
this.reflectInjectables(cls, token, PIPES_METADATA);
|
||||
this.reflectParamInjectables(cls, token, ROUTE_ARGS_METADATA);
|
||||
}
|
||||
|
||||
public reflectExports(module: Type<unknown>, token: string) {
|
||||
@@ -240,22 +247,37 @@ export class DependenciesScanner {
|
||||
token: string,
|
||||
metadataKey: string,
|
||||
) {
|
||||
const controllerInjectables = this.reflectMetadata(metadataKey, component);
|
||||
const methodsInjectables = this.metadataScanner.scanFromPrototype(
|
||||
const controllerInjectables = this.reflectMetadata<Type<Injectable>>(
|
||||
metadataKey,
|
||||
component,
|
||||
);
|
||||
const methodInjectables = this.metadataScanner.scanFromPrototype<
|
||||
Type,
|
||||
{ methodKey: string; metadata: Type<Injectable>[] }
|
||||
>(
|
||||
null,
|
||||
component.prototype,
|
||||
this.reflectKeyMetadata.bind(this, component, metadataKey),
|
||||
);
|
||||
|
||||
const flattenMethodsInjectables = this.flatten(methodsInjectables);
|
||||
const combinedInjectables = [
|
||||
...controllerInjectables,
|
||||
...flattenMethodsInjectables,
|
||||
].filter(isFunction);
|
||||
const injectables = Array.from(new Set(combinedInjectables));
|
||||
|
||||
injectables.forEach(injectable =>
|
||||
this.insertInjectable(injectable, token, component),
|
||||
controllerInjectables.forEach(injectable =>
|
||||
this.insertInjectable(
|
||||
injectable,
|
||||
token,
|
||||
component,
|
||||
ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey],
|
||||
),
|
||||
);
|
||||
methodInjectables.forEach(({ methodKey, metadata }) =>
|
||||
metadata.forEach(injectable =>
|
||||
this.insertInjectable(
|
||||
injectable,
|
||||
token,
|
||||
component,
|
||||
ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey],
|
||||
methodKey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -264,32 +286,60 @@ export class DependenciesScanner {
|
||||
token: string,
|
||||
metadataKey: string,
|
||||
) {
|
||||
const paramsMetadata = this.metadataScanner.scanFromPrototype(
|
||||
null,
|
||||
component.prototype,
|
||||
method => Reflect.getMetadata(metadataKey, component, method),
|
||||
);
|
||||
const paramsInjectables = this.flatten(paramsMetadata).map(
|
||||
(param: Record<string, any>) =>
|
||||
flatten(Object.keys(param).map(k => param[k].pipes)).filter(isFunction),
|
||||
);
|
||||
flatten(paramsInjectables).forEach((injectable: Type<Injectable>) =>
|
||||
this.insertInjectable(injectable, token, component),
|
||||
);
|
||||
const paramsMetadata = this.metadataScanner.scanFromPrototype<
|
||||
Type,
|
||||
{
|
||||
methodKey: string;
|
||||
metadata: Record<
|
||||
string,
|
||||
{
|
||||
index: number;
|
||||
data: unknown;
|
||||
pipes: Array<Type<PipeTransform> | PipeTransform>;
|
||||
}
|
||||
>;
|
||||
}
|
||||
>(null, component.prototype, methodKey => {
|
||||
const metadata = Reflect.getMetadata(metadataKey, component, methodKey);
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
return { methodKey, metadata };
|
||||
});
|
||||
|
||||
paramsMetadata.forEach(({ methodKey, metadata }) => {
|
||||
const params = Object.values(metadata);
|
||||
params
|
||||
.map(item => item.pipes)
|
||||
.flat(1)
|
||||
.forEach(injectable =>
|
||||
this.insertInjectable(
|
||||
injectable,
|
||||
token,
|
||||
component,
|
||||
'pipe',
|
||||
methodKey,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public reflectKeyMetadata(
|
||||
component: Type<Injectable>,
|
||||
key: string,
|
||||
method: string,
|
||||
methodKey: string,
|
||||
) {
|
||||
let prototype = component.prototype;
|
||||
do {
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(prototype, method);
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(prototype, methodKey);
|
||||
if (!descriptor) {
|
||||
continue;
|
||||
}
|
||||
return Reflect.getMetadata(key, descriptor.value);
|
||||
const metadata = Reflect.getMetadata(key, descriptor.value);
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
return { methodKey, metadata };
|
||||
} while (
|
||||
(prototype = Reflect.getPrototypeOf(prototype)) &&
|
||||
prototype !== Object.prototype &&
|
||||
@@ -298,7 +348,7 @@ export class DependenciesScanner {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async calculateModulesDistance() {
|
||||
public calculateModulesDistance() {
|
||||
const modulesGenerator = this.container.getModules().values();
|
||||
|
||||
// Skip "InternalCoreModule" from calculating distance
|
||||
@@ -306,7 +356,7 @@ export class DependenciesScanner {
|
||||
|
||||
const modulesStack = [];
|
||||
const calculateDistance = (moduleRef: Module, distance = 1) => {
|
||||
if (modulesStack.includes(moduleRef)) {
|
||||
if (!moduleRef || modulesStack.includes(moduleRef)) {
|
||||
return;
|
||||
}
|
||||
modulesStack.push(moduleRef);
|
||||
@@ -383,21 +433,56 @@ export class DependenciesScanner {
|
||||
scope,
|
||||
} as Provider;
|
||||
|
||||
const enhancerSubtype =
|
||||
ENHANCER_TOKEN_TO_SUBTYPE_MAP[
|
||||
type as
|
||||
| typeof APP_GUARD
|
||||
| typeof APP_PIPE
|
||||
| typeof APP_FILTER
|
||||
| typeof APP_INTERCEPTOR
|
||||
];
|
||||
const factoryOrClassProvider = newProvider as
|
||||
| FactoryProvider
|
||||
| ClassProvider;
|
||||
if (this.isRequestOrTransient(factoryOrClassProvider.scope)) {
|
||||
return this.container.addInjectable(newProvider, token);
|
||||
return this.container.addInjectable(newProvider, token, enhancerSubtype);
|
||||
}
|
||||
this.container.addProvider(newProvider, token);
|
||||
this.container.addProvider(newProvider, token, enhancerSubtype);
|
||||
}
|
||||
|
||||
public insertInjectable(
|
||||
injectable: Type<Injectable>,
|
||||
injectable: Type<Injectable> | object,
|
||||
token: string,
|
||||
host: Type<Injectable>,
|
||||
subtype: EnhancerSubtype,
|
||||
methodKey?: string,
|
||||
) {
|
||||
this.container.addInjectable(injectable, token, host);
|
||||
if (isFunction(injectable)) {
|
||||
const instanceWrapper = this.container.addInjectable(
|
||||
injectable as Type,
|
||||
token,
|
||||
subtype,
|
||||
host,
|
||||
) as InstanceWrapper;
|
||||
|
||||
this.graphInspector.insertEnhancerMetadataCache({
|
||||
moduleToken: token,
|
||||
classRef: host,
|
||||
enhancerInstanceWrapper: instanceWrapper,
|
||||
targetNodeId: instanceWrapper.id,
|
||||
subtype,
|
||||
methodKey,
|
||||
});
|
||||
return instanceWrapper;
|
||||
} else {
|
||||
this.graphInspector.insertEnhancerMetadataCache({
|
||||
moduleToken: token,
|
||||
classRef: host,
|
||||
enhancerRef: injectable,
|
||||
methodKey,
|
||||
subtype,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public insertExportedProvider(
|
||||
@@ -411,7 +496,10 @@ export class DependenciesScanner {
|
||||
this.container.addController(controller, token);
|
||||
}
|
||||
|
||||
public reflectMetadata(metadataKey: string, metatype: Type<any>) {
|
||||
public reflectMetadata<T = any>(
|
||||
metadataKey: string,
|
||||
metatype: Type<any>,
|
||||
): T[] {
|
||||
return Reflect.getMetadata(metadataKey, metatype) || [];
|
||||
}
|
||||
|
||||
@@ -421,6 +509,7 @@ export class DependenciesScanner {
|
||||
this,
|
||||
this.container.getModuleCompiler(),
|
||||
this.container.getHttpAdapterHostRef(),
|
||||
this.graphInspector,
|
||||
);
|
||||
const [instance] = await this.scanForModules(moduleDefinition);
|
||||
this.container.registerCoreModuleRef(instance);
|
||||
@@ -471,6 +560,8 @@ export class DependenciesScanner {
|
||||
providerKey,
|
||||
'injectables',
|
||||
);
|
||||
|
||||
this.graphInspector.insertAttachedEnhancer(instanceWrapper);
|
||||
return applyRequestProvidersMap[type as string](instanceWrapper);
|
||||
}
|
||||
instanceWrapper = getInstanceWrapper(
|
||||
@@ -478,6 +569,7 @@ export class DependenciesScanner {
|
||||
providerKey,
|
||||
'providers',
|
||||
);
|
||||
this.graphInspector.insertAttachedEnhancer(instanceWrapper);
|
||||
applyProvidersMap[type as string](instanceWrapper.instance);
|
||||
},
|
||||
);
|
||||
@@ -545,10 +637,6 @@ export class DependenciesScanner {
|
||||
return module && !!(module as ForwardReference).forwardRef;
|
||||
}
|
||||
|
||||
private flatten<T = any>(arr: T[][]): T[] {
|
||||
return arr.flat(1);
|
||||
}
|
||||
|
||||
private isRequestOrTransient(scope: Scope): boolean {
|
||||
return scope === Scope.REQUEST || scope === Scope.TRANSIENT;
|
||||
}
|
||||
|
||||
@@ -6,51 +6,64 @@ import { NestContainer } from '../../injector/container';
|
||||
import { Injector } from '../../injector/injector';
|
||||
import { InstanceLoader } from '../../injector/instance-loader';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||
|
||||
describe('InstanceLoader', () => {
|
||||
let loader: InstanceLoader;
|
||||
let container: NestContainer;
|
||||
let mockContainer: sinon.SinonMock;
|
||||
|
||||
@Controller('')
|
||||
class TestRoute {}
|
||||
class TestCtrl {}
|
||||
|
||||
@Injectable()
|
||||
class TestProvider {}
|
||||
|
||||
let loader: InstanceLoader;
|
||||
let injector: Injector;
|
||||
let container: NestContainer;
|
||||
let graphInspector: GraphInspector;
|
||||
let inspectInstanceWrapperStub: sinon.SinonStub;
|
||||
let mockContainer: sinon.SinonMock;
|
||||
let moduleMock: Record<string, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
container = new NestContainer();
|
||||
loader = new InstanceLoader(container);
|
||||
mockContainer = sinon.mock(container);
|
||||
});
|
||||
graphInspector = new GraphInspector(container);
|
||||
|
||||
it('should call "loadPrototype" for each provider and route in each module', async () => {
|
||||
const injector = new Injector();
|
||||
inspectInstanceWrapperStub = sinon.stub(
|
||||
graphInspector,
|
||||
'inspectInstanceWrapper',
|
||||
);
|
||||
|
||||
loader = new InstanceLoader(container, graphInspector);
|
||||
injector = new Injector();
|
||||
(loader as any).injector = injector;
|
||||
|
||||
const module = {
|
||||
mockContainer = sinon.mock(container);
|
||||
|
||||
moduleMock = {
|
||||
providers: new Map(),
|
||||
controllers: new Map(),
|
||||
injectables: new Map(),
|
||||
metatype: { name: 'test' },
|
||||
};
|
||||
|
||||
const modules = new Map();
|
||||
modules.set('Test', moduleMock);
|
||||
mockContainer.expects('getModules').returns(modules);
|
||||
});
|
||||
|
||||
it('should call "loadPrototype" for every provider and controller in every module', async () => {
|
||||
const providerWrapper: InstanceWrapper = {
|
||||
instance: null,
|
||||
metatype: TestProvider,
|
||||
token: 'TestProvider',
|
||||
} as any;
|
||||
const routeWrapper: InstanceWrapper = {
|
||||
const ctrlWrapper: InstanceWrapper = {
|
||||
instance: null,
|
||||
metatype: TestRoute,
|
||||
metatype: TestCtrl,
|
||||
token: 'TestRoute',
|
||||
} as any;
|
||||
|
||||
module.providers.set('TestProvider', providerWrapper);
|
||||
module.controllers.set('TestRoute', routeWrapper);
|
||||
|
||||
const modules = new Map();
|
||||
modules.set('Test', module);
|
||||
mockContainer.expects('getModules').returns(modules);
|
||||
moduleMock.providers.set('TestProvider', providerWrapper);
|
||||
moduleMock.controllers.set('TestRoute', ctrlWrapper);
|
||||
|
||||
const loadProviderPrototypeStub = sinon.stub(injector, 'loadPrototype');
|
||||
|
||||
@@ -58,113 +71,125 @@ describe('InstanceLoader', () => {
|
||||
sinon.stub(injector, 'loadProvider');
|
||||
|
||||
await loader.createInstancesOfDependencies();
|
||||
|
||||
expect(
|
||||
loadProviderPrototypeStub.calledWith(providerWrapper, module.providers),
|
||||
loadProviderPrototypeStub.calledWith(
|
||||
providerWrapper,
|
||||
moduleMock.providers,
|
||||
),
|
||||
).to.be.true;
|
||||
expect(
|
||||
loadProviderPrototypeStub.calledWith(routeWrapper, module.controllers),
|
||||
loadProviderPrototypeStub.calledWith(ctrlWrapper, moduleMock.controllers),
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
it('should call "loadProvider" for each provider in each module', async () => {
|
||||
const injector = new Injector();
|
||||
(loader as any).injector = injector;
|
||||
describe('for every provider in every module', () => {
|
||||
const testProviderToken = 'TestProvider';
|
||||
|
||||
const module = {
|
||||
providers: new Map(),
|
||||
controllers: new Map(),
|
||||
injectables: new Map(),
|
||||
metatype: { name: 'test' },
|
||||
};
|
||||
const testComp = new InstanceWrapper({
|
||||
instance: null,
|
||||
metatype: TestProvider,
|
||||
name: 'TestProvider',
|
||||
token: 'TestProvider',
|
||||
let loadProviderStub: sinon.SinonStub;
|
||||
|
||||
beforeEach(async () => {
|
||||
const testProviderWrapper = new InstanceWrapper({
|
||||
instance: null,
|
||||
metatype: TestProvider,
|
||||
name: testProviderToken,
|
||||
token: testProviderToken,
|
||||
});
|
||||
moduleMock.providers.set(testProviderToken, testProviderWrapper);
|
||||
|
||||
loadProviderStub = sinon.stub(injector, 'loadProvider');
|
||||
sinon.stub(injector, 'loadController');
|
||||
|
||||
await loader.createInstancesOfDependencies();
|
||||
});
|
||||
module.providers.set('TestProvider', testComp);
|
||||
|
||||
const modules = new Map();
|
||||
modules.set('Test', module);
|
||||
mockContainer.expects('getModules').returns(modules);
|
||||
it('should call "loadProvider"', async () => {
|
||||
expect(
|
||||
loadProviderStub.calledWith(
|
||||
moduleMock.providers.get(testProviderToken),
|
||||
moduleMock as any,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
const loadProviderStub = sinon.stub(injector, 'loadProvider');
|
||||
sinon.stub(injector, 'loadController');
|
||||
|
||||
await loader.createInstancesOfDependencies();
|
||||
expect(
|
||||
loadProviderStub.calledWith(
|
||||
module.providers.get('TestProvider'),
|
||||
module as any,
|
||||
),
|
||||
).to.be.true;
|
||||
it('should call "inspectInstanceWrapper"', async () => {
|
||||
expect(
|
||||
inspectInstanceWrapperStub.calledWith(
|
||||
moduleMock.providers.get(testProviderToken),
|
||||
moduleMock as any,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('should call "loadController" for each route in each module', async () => {
|
||||
const injector = new Injector();
|
||||
(loader as any).injector = injector;
|
||||
describe('for every controller in every module', () => {
|
||||
let loadControllerStub: sinon.SinonStub;
|
||||
|
||||
const module = {
|
||||
providers: new Map(),
|
||||
controllers: new Map(),
|
||||
injectables: new Map(),
|
||||
metatype: { name: 'test' },
|
||||
};
|
||||
const wrapper = new InstanceWrapper({
|
||||
name: 'TestRoute',
|
||||
token: 'TestRoute',
|
||||
instance: null,
|
||||
metatype: TestRoute,
|
||||
beforeEach(async () => {
|
||||
const wrapper = new InstanceWrapper({
|
||||
name: 'TestRoute',
|
||||
token: 'TestRoute',
|
||||
instance: null,
|
||||
metatype: TestCtrl,
|
||||
});
|
||||
moduleMock.controllers.set('TestRoute', wrapper);
|
||||
|
||||
sinon.stub(injector, 'loadProvider');
|
||||
loadControllerStub = sinon.stub(injector, 'loadController');
|
||||
|
||||
await loader.createInstancesOfDependencies();
|
||||
});
|
||||
it('should call "loadController"', async () => {
|
||||
expect(
|
||||
loadControllerStub.calledWith(
|
||||
moduleMock.controllers.get('TestRoute'),
|
||||
moduleMock as any,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
it('should call "inspectInstanceWrapper"', async () => {
|
||||
expect(
|
||||
inspectInstanceWrapperStub.calledWith(
|
||||
moduleMock.controllers.get('TestRoute'),
|
||||
moduleMock as any,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
module.controllers.set('TestRoute', wrapper);
|
||||
|
||||
const modules = new Map();
|
||||
modules.set('Test', module);
|
||||
mockContainer.expects('getModules').returns(modules);
|
||||
|
||||
sinon.stub(injector, 'loadProvider');
|
||||
const loadRoutesStub = sinon.stub(injector, 'loadController');
|
||||
|
||||
await loader.createInstancesOfDependencies();
|
||||
expect(
|
||||
loadRoutesStub.calledWith(
|
||||
module.controllers.get('TestRoute'),
|
||||
module as any,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
|
||||
it('should call "loadInjectable" for each injectable in each module', async () => {
|
||||
const injector = new Injector();
|
||||
(loader as any).injector = injector;
|
||||
describe('for every injectable in every module', () => {
|
||||
let loadInjectableStub: sinon.SinonStub;
|
||||
|
||||
const module = {
|
||||
providers: new Map(),
|
||||
controllers: new Map(),
|
||||
injectables: new Map(),
|
||||
metatype: { name: 'test' },
|
||||
};
|
||||
const testComp = new InstanceWrapper({
|
||||
instance: null,
|
||||
metatype: TestProvider,
|
||||
name: 'TestProvider',
|
||||
token: 'TestProvider',
|
||||
beforeEach(async () => {
|
||||
const testInjectable = new InstanceWrapper({
|
||||
instance: null,
|
||||
metatype: TestProvider,
|
||||
name: 'TestProvider',
|
||||
token: 'TestProvider',
|
||||
});
|
||||
moduleMock.injectables.set('TestProvider', testInjectable);
|
||||
|
||||
loadInjectableStub = sinon.stub(injector, 'loadInjectable');
|
||||
sinon.stub(injector, 'loadController');
|
||||
|
||||
await loader.createInstancesOfDependencies();
|
||||
});
|
||||
module.injectables.set('TestProvider', testComp);
|
||||
|
||||
const modules = new Map();
|
||||
modules.set('Test', module);
|
||||
mockContainer.expects('getModules').returns(modules);
|
||||
|
||||
const loadInjectableStub = sinon.stub(injector, 'loadInjectable');
|
||||
sinon.stub(injector, 'loadController');
|
||||
|
||||
await loader.createInstancesOfDependencies();
|
||||
expect(
|
||||
loadInjectableStub.calledWith(
|
||||
module.injectables.get('TestProvider'),
|
||||
module as any,
|
||||
),
|
||||
).to.be.true;
|
||||
it('should call "loadInjectable"', async () => {
|
||||
expect(
|
||||
loadInjectableStub.calledWith(
|
||||
moduleMock.injectables.get('TestProvider'),
|
||||
moduleMock as any,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
it('should call "inspectInstanceWrapper"', async () => {
|
||||
expect(
|
||||
inspectInstanceWrapperStub.calledWith(
|
||||
moduleMock.injectables.get('TestProvider'),
|
||||
moduleMock as any,
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('InternalCoreModuleFactory', () => {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
expect(moduleDefinition.module).to.equal(InternalCoreModule);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
NestContainer,
|
||||
} from '../../../injector';
|
||||
import { InstanceLoader } from '../../../injector/instance-loader';
|
||||
import { GraphInspector } from '../../../inspector/graph-inspector';
|
||||
import { MetadataScanner } from '../../../metadata-scanner';
|
||||
import { DependenciesScanner } from '../../../scanner';
|
||||
|
||||
@@ -18,15 +19,23 @@ describe('LazyModuleLoader', () => {
|
||||
|
||||
class NoopLogger {
|
||||
log() {}
|
||||
error() {}
|
||||
warn() {}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
const nestContainer = new NestContainer();
|
||||
const graphInspector = new GraphInspector(nestContainer);
|
||||
dependenciesScanner = new DependenciesScanner(
|
||||
nestContainer,
|
||||
new MetadataScanner(),
|
||||
graphInspector,
|
||||
);
|
||||
instanceLoader = new InstanceLoader(
|
||||
nestContainer,
|
||||
graphInspector,
|
||||
new NoopLogger(),
|
||||
);
|
||||
instanceLoader = new InstanceLoader(nestContainer, new NoopLogger() as any);
|
||||
modulesContainer = nestContainer.getModules();
|
||||
lazyModuleLoader = new LazyModuleLoader(
|
||||
dependenciesScanner,
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('Module', () => {
|
||||
const setSpy = sinon.spy(collection, 'set');
|
||||
(module as any)._injectables = collection;
|
||||
|
||||
module.addInjectable(TestProvider, TestModule);
|
||||
module.addInjectable(TestProvider, 'interceptor', TestModule);
|
||||
expect(
|
||||
setSpy.calledWith(
|
||||
TestProvider,
|
||||
@@ -69,6 +69,7 @@ describe('Module', () => {
|
||||
instance: null,
|
||||
durable: undefined,
|
||||
isResolved: false,
|
||||
subtype: 'interceptor',
|
||||
}),
|
||||
),
|
||||
).to.be.true;
|
||||
@@ -78,7 +79,7 @@ describe('Module', () => {
|
||||
it('should call `addCustomProvider`', () => {
|
||||
const addCustomProviderSpy = sinon.spy(module, 'addCustomProvider');
|
||||
|
||||
module.addInjectable({ provide: 'test' } as any);
|
||||
module.addInjectable({ provide: 'test' } as any, 'guard');
|
||||
expect(addCustomProviderSpy.called).to.be.true;
|
||||
});
|
||||
});
|
||||
@@ -180,6 +181,7 @@ describe('Module', () => {
|
||||
durable: true,
|
||||
instance: null,
|
||||
isResolved: false,
|
||||
subtype: undefined,
|
||||
}),
|
||||
),
|
||||
).to.be.true;
|
||||
@@ -211,6 +213,7 @@ describe('Module', () => {
|
||||
instance: value,
|
||||
isResolved: true,
|
||||
async: false,
|
||||
subtype: undefined,
|
||||
}),
|
||||
),
|
||||
).to.be.true;
|
||||
@@ -244,6 +247,7 @@ describe('Module', () => {
|
||||
instance: null,
|
||||
isResolved: false,
|
||||
inject: inject as any,
|
||||
subtype: undefined,
|
||||
}),
|
||||
),
|
||||
).to.be.true;
|
||||
@@ -279,6 +283,7 @@ describe('Module', () => {
|
||||
inject: [provider.useExisting as any],
|
||||
isResolved: false,
|
||||
isAlias: true,
|
||||
subtype: undefined,
|
||||
}),
|
||||
),
|
||||
).to.be.true;
|
||||
|
||||
217
packages/core/test/inspector/graph-inspector.spec.ts
Normal file
217
packages/core/test/inspector/graph-inspector.spec.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { NestContainer } from '../../injector/container';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||
import { Module } from '../../injector/module';
|
||||
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||
import { EnhancerMetadataCacheEntry } from '../../inspector/interfaces/enhancer-metadata-cache-entry.interface';
|
||||
import { SerializedGraph } from '../../inspector/serialized-graph';
|
||||
|
||||
describe('GraphInspector', () => {
|
||||
let graphInspector: GraphInspector;
|
||||
let enhancersMetadataCache: Array<EnhancerMetadataCacheEntry>;
|
||||
let graph: SerializedGraph;
|
||||
let container: NestContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
container = new NestContainer();
|
||||
graphInspector = new GraphInspector(container);
|
||||
|
||||
enhancersMetadataCache = graphInspector['enhancersMetadataCache'];
|
||||
graph = graphInspector['graph'];
|
||||
});
|
||||
|
||||
describe('insertEnhancerMetadataCache', () => {
|
||||
it('should insert an enhancer metadata cache entry', () => {
|
||||
const entry = {
|
||||
moduleToken: 'moduleToken',
|
||||
classRef: class AppService {},
|
||||
methodKey: undefined,
|
||||
subtype: 'guard' as const,
|
||||
};
|
||||
graphInspector.insertEnhancerMetadataCache(entry);
|
||||
expect(enhancersMetadataCache).includes(entry);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspectInstanceWrapper', () => {
|
||||
class AppService {}
|
||||
|
||||
it('should inspect given instance wrapper and insert appropriate edges', () => {
|
||||
const moduleRef = new Module(class TestModule {}, container);
|
||||
const instanceWrapper = new InstanceWrapper({
|
||||
token: AppService,
|
||||
name: AppService.name,
|
||||
metatype: AppService,
|
||||
});
|
||||
|
||||
const param1 = new InstanceWrapper({
|
||||
token: 'PARAM_1',
|
||||
metatype: class A {},
|
||||
host: new Module(class AModule {}, container),
|
||||
});
|
||||
const param2 = new InstanceWrapper({
|
||||
token: 'PARAM_2',
|
||||
metatype: class B {},
|
||||
host: new Module(class BModule {}, container),
|
||||
});
|
||||
const dependency = new InstanceWrapper({
|
||||
name: 'PROPERTY',
|
||||
token: 'PROPERTY',
|
||||
metatype: class C {},
|
||||
host: new Module(class CModule {}, container),
|
||||
});
|
||||
|
||||
instanceWrapper.addCtorMetadata(0, param1);
|
||||
instanceWrapper.addCtorMetadata(1, param2);
|
||||
instanceWrapper.addCtorMetadata(2, dependency);
|
||||
|
||||
graphInspector.inspectInstanceWrapper(instanceWrapper, moduleRef);
|
||||
|
||||
const edgesArr = [...graph['edges'].values()];
|
||||
expect(edgesArr).to.deep.equal([
|
||||
{
|
||||
id: edgesArr[0].id,
|
||||
metadata: {
|
||||
injectionType: 'constructor',
|
||||
keyOrIndex: 0,
|
||||
sourceClassName: instanceWrapper.metatype.name,
|
||||
sourceClassToken: instanceWrapper.token,
|
||||
sourceModuleName: 'TestModule',
|
||||
targetClassName: param1.name,
|
||||
targetClassToken: 'PARAM_1',
|
||||
targetModuleName: 'AModule',
|
||||
type: 'class-to-class',
|
||||
},
|
||||
source: instanceWrapper.id,
|
||||
target: param1.id,
|
||||
},
|
||||
{
|
||||
id: edgesArr[1].id,
|
||||
metadata: {
|
||||
injectionType: 'constructor',
|
||||
keyOrIndex: 1,
|
||||
sourceClassName: instanceWrapper.metatype.name,
|
||||
sourceClassToken: instanceWrapper.token,
|
||||
sourceModuleName: 'TestModule',
|
||||
targetClassName: param2.name,
|
||||
targetClassToken: 'PARAM_2',
|
||||
targetModuleName: 'BModule',
|
||||
type: 'class-to-class',
|
||||
},
|
||||
source: instanceWrapper.id,
|
||||
target: param2.id,
|
||||
},
|
||||
{
|
||||
id: edgesArr[2].id,
|
||||
metadata: {
|
||||
injectionType: 'constructor',
|
||||
keyOrIndex: 2,
|
||||
sourceClassName: 'AppService',
|
||||
sourceClassToken: AppService,
|
||||
sourceModuleName: 'TestModule',
|
||||
targetClassName: dependency.name,
|
||||
targetClassToken: 'PROPERTY',
|
||||
targetModuleName: 'CModule',
|
||||
type: 'class-to-class',
|
||||
},
|
||||
source: instanceWrapper.id,
|
||||
target: dependency.id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspectModules', () => {
|
||||
class TestModule {}
|
||||
class AController {}
|
||||
class RandomPipe {}
|
||||
|
||||
it('should inspect all modules', async () => {
|
||||
const moduleRef = await container.addModule(TestModule, []);
|
||||
moduleRef.addController(AController);
|
||||
|
||||
const subtype = 'interceptor';
|
||||
const enhancerInstanceWrapper = moduleRef.addInjectable(
|
||||
class Enhancer {},
|
||||
subtype,
|
||||
) as InstanceWrapper;
|
||||
|
||||
const methodKey = 'findOne';
|
||||
enhancersMetadataCache.push(
|
||||
{
|
||||
moduleToken: moduleRef.token,
|
||||
classRef: AController,
|
||||
enhancerRef: new RandomPipe(),
|
||||
methodKey,
|
||||
subtype,
|
||||
},
|
||||
{
|
||||
moduleToken: moduleRef.token,
|
||||
classRef: AController,
|
||||
enhancerRef: function test() {},
|
||||
methodKey,
|
||||
subtype,
|
||||
},
|
||||
{
|
||||
moduleToken: moduleRef.token,
|
||||
classRef: AController,
|
||||
enhancerInstanceWrapper,
|
||||
methodKey: undefined,
|
||||
subtype,
|
||||
},
|
||||
);
|
||||
|
||||
const serializedNode = { metadata: {} };
|
||||
sinon.stub(graph, 'getNodeById').callsFake(() => serializedNode as any);
|
||||
|
||||
graphInspector.inspectModules();
|
||||
|
||||
expect(serializedNode).to.deep.equal({
|
||||
metadata: {
|
||||
enhancers: [
|
||||
{ methodKey, name: RandomPipe.name },
|
||||
{ methodKey, name: 'Function' },
|
||||
{ methodKey: undefined, id: enhancerInstanceWrapper.id },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertAttachedEnhancer', () => {
|
||||
it('should upsert existing node (update metadata) and add node to "attachedEnhancers" array', () => {
|
||||
const instanceWrapper = new InstanceWrapper({
|
||||
metatype: class A {},
|
||||
token: 'A',
|
||||
});
|
||||
|
||||
const nodeDefinition = {
|
||||
id: instanceWrapper.id,
|
||||
label: 'A',
|
||||
parent: '2c989d11-2731-4828-a2eb-c86d10c73621',
|
||||
metadata: {
|
||||
type: 'provider' as const,
|
||||
sourceModuleName: 'AppModule',
|
||||
durable: false,
|
||||
static: true,
|
||||
scope: Scope.DEFAULT,
|
||||
transient: false,
|
||||
token: class A {},
|
||||
},
|
||||
};
|
||||
const insertedNode = graph.insertNode(nodeDefinition);
|
||||
|
||||
graphInspector.insertAttachedEnhancer(instanceWrapper);
|
||||
|
||||
expect(insertedNode.metadata).to.deep.equal({
|
||||
...nodeDefinition.metadata,
|
||||
global: true,
|
||||
});
|
||||
expect(graph['extras'].attachedEnhancers).to.deep.contain({
|
||||
nodeId: insertedNode.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
154
packages/core/test/inspector/serialized-graph.spec.ts
Normal file
154
packages/core/test/inspector/serialized-graph.spec.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { expect } from 'chai';
|
||||
import { ApplicationConfig } from '../../application-config';
|
||||
import { Edge } from '../../inspector/interfaces/edge.interface';
|
||||
import { Node } from '../../inspector/interfaces/node.interface';
|
||||
import { SerializedGraph } from '../../inspector/serialized-graph';
|
||||
|
||||
describe('SerializedGraph', () => {
|
||||
let serializedGraph: SerializedGraph;
|
||||
let nodesCollection: Map<string, Node>;
|
||||
let edgesCollection: Map<string, Edge>;
|
||||
|
||||
beforeEach(() => {
|
||||
serializedGraph = new SerializedGraph();
|
||||
nodesCollection = serializedGraph['nodes'];
|
||||
edgesCollection = serializedGraph['edges'];
|
||||
});
|
||||
|
||||
describe('insertNode', () => {
|
||||
describe('when node definition represents an internal provider', () => {
|
||||
it('should insert a node with the expected schema (internal: true)', () => {
|
||||
const nodeDefinition = {
|
||||
id: '11430093-e992-4ae6-8ba4-c7db80419de8',
|
||||
label: 'ApplicationConfig',
|
||||
parent: '2c989d11-2731-4828-a2eb-c86d10c73621',
|
||||
metadata: {
|
||||
type: 'provider' as const,
|
||||
sourceModuleName: 'AppModule',
|
||||
durable: false,
|
||||
static: true,
|
||||
transient: false,
|
||||
token: ApplicationConfig,
|
||||
scope: Scope.DEFAULT,
|
||||
},
|
||||
};
|
||||
serializedGraph.insertNode(nodeDefinition);
|
||||
|
||||
expect(nodesCollection.get(nodeDefinition.id)).to.deep.equal({
|
||||
...nodeDefinition,
|
||||
metadata: {
|
||||
...nodeDefinition.metadata,
|
||||
internal: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('otherwise', () => {
|
||||
it('should insert a node with the expected schema', () => {
|
||||
class AppService {}
|
||||
|
||||
const nodeDefinition = {
|
||||
id: '11430093-e992-4ae6-8ba4-c7db80419de8',
|
||||
label: 'AppService',
|
||||
parent: '2c989d11-2731-4828-a2eb-c86d10c73621',
|
||||
metadata: {
|
||||
type: 'provider' as const,
|
||||
sourceModuleName: 'AppModule',
|
||||
durable: false,
|
||||
static: true,
|
||||
transient: false,
|
||||
token: AppService,
|
||||
scope: Scope.DEFAULT,
|
||||
},
|
||||
};
|
||||
serializedGraph.insertNode(nodeDefinition);
|
||||
|
||||
expect(nodesCollection.get(nodeDefinition.id)).to.equal(nodeDefinition);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertEdge', () => {
|
||||
describe('when edge definition represents internal providers connection', () => {
|
||||
it('should insert an edge with the expected schema (internal: true)', () => {
|
||||
const edgeDefinition = {
|
||||
source: '8920252f-4e7d-4f9e-9eeb-71da467a35cc',
|
||||
target: 'c97bc04d-cfcf-41b1-96ec-db729f33676e',
|
||||
metadata: {
|
||||
type: 'class-to-class' as const,
|
||||
sourceModuleName: 'UtilsExceptionsModule',
|
||||
sourceClassName: 'AllExceptionsFilter',
|
||||
targetClassName: 'HttpAdapterHost',
|
||||
sourceClassToken:
|
||||
'APP_FILTER (UUID: 4187828c-5c76-4aed-a29f-a6eb40054b9d)',
|
||||
targetClassToken: 'HttpAdapterHost',
|
||||
targetModuleName: 'InternalCoreModule',
|
||||
keyOrIndex: 0,
|
||||
injectionType: 'constructor' as const,
|
||||
},
|
||||
};
|
||||
const edge = serializedGraph.insertEdge(edgeDefinition);
|
||||
|
||||
expect(edgesCollection.get(edge.id)).to.deep.equal({
|
||||
...edgeDefinition,
|
||||
metadata: {
|
||||
...edgeDefinition.metadata,
|
||||
internal: true,
|
||||
},
|
||||
id: edge.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('otherwise', () => {
|
||||
it('should insert an edge with the expected schema', () => {
|
||||
const edgeDefinition = {
|
||||
source: '8920252f-4e7d-4f9e-9eeb-71da467a35cc',
|
||||
target: 'c97bc04d-cfcf-41b1-96ec-db729f33676e',
|
||||
metadata: {
|
||||
type: 'class-to-class' as const,
|
||||
sourceModuleName: 'UtilsExceptionsModule',
|
||||
sourceClassName: 'AllExceptionsFilter',
|
||||
targetClassName: 'AppService',
|
||||
sourceClassToken:
|
||||
'APP_FILTER (UUID: 4187828c-5c76-4aed-a29f-a6eb40054b9d)',
|
||||
targetClassToken: 'AppService',
|
||||
targetModuleName: 'InternalCoreModule',
|
||||
keyOrIndex: 0,
|
||||
injectionType: 'constructor' as const,
|
||||
},
|
||||
};
|
||||
const edge = serializedGraph.insertEdge(edgeDefinition);
|
||||
|
||||
expect(edgesCollection.get(edge.id)).to.deep.equal({
|
||||
...edgeDefinition,
|
||||
id: edge.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNodeById', () => {
|
||||
it('should return a given node', () => {
|
||||
const nodeDefinition = {
|
||||
id: '11430093-e992-4ae6-8ba4-c7db80419de8',
|
||||
label: 'AppService',
|
||||
parent: '2c989d11-2731-4828-a2eb-c86d10c73621',
|
||||
metadata: {
|
||||
type: 'provider' as const,
|
||||
sourceModuleName: 'AppModule',
|
||||
durable: false,
|
||||
static: true,
|
||||
transient: false,
|
||||
scope: Scope.DEFAULT,
|
||||
token: 'AppService',
|
||||
},
|
||||
};
|
||||
|
||||
nodesCollection.set(nodeDefinition.id, nodeDefinition);
|
||||
expect(serializedGraph.getNodeById(nodeDefinition.id)).to.eq(
|
||||
nodeDefinition,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { RoutePathFactory } from '@nestjs/core/router/route-path-factory';
|
||||
import * as chai from 'chai';
|
||||
import { expect } from 'chai';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as sinon from 'sinon';
|
||||
import { Controller } from '../../../common/decorators/core/controller.decorator';
|
||||
import { RequestMapping } from '../../../common/decorators/http/request-mapping.decorator';
|
||||
@@ -12,14 +14,18 @@ import { RuntimeException } from '../../errors/exceptions/runtime.exception';
|
||||
import { NestContainer } from '../../injector/container';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||
import { Module } from '../../injector/module';
|
||||
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||
import { MiddlewareBuilder } from '../../middleware/builder';
|
||||
import { MiddlewareContainer } from '../../middleware/container';
|
||||
import { MiddlewareModule } from '../../middleware/middleware-module';
|
||||
import { RouterExceptionFilters } from '../../router/router-exception-filters';
|
||||
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
describe('MiddlewareModule', () => {
|
||||
let middlewareModule: MiddlewareModule;
|
||||
let graphInspector: GraphInspector;
|
||||
|
||||
@Controller('test')
|
||||
class BasicController {}
|
||||
@@ -39,15 +45,18 @@ describe('MiddlewareModule', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
const container = new NestContainer();
|
||||
const appConfig = new ApplicationConfig();
|
||||
|
||||
graphInspector = new GraphInspector(container);
|
||||
middlewareModule = new MiddlewareModule(new RoutePathFactory(appConfig));
|
||||
(middlewareModule as any).routerExceptionFilter =
|
||||
new RouterExceptionFilters(
|
||||
new NestContainer(),
|
||||
appConfig,
|
||||
new NoopHttpAdapter({}),
|
||||
);
|
||||
(middlewareModule as any).config = appConfig;
|
||||
middlewareModule['routerExceptionFilter'] = new RouterExceptionFilters(
|
||||
container,
|
||||
appConfig,
|
||||
new NoopHttpAdapter({}),
|
||||
);
|
||||
middlewareModule['config'] = appConfig;
|
||||
middlewareModule['graphInspector'] = graphInspector;
|
||||
});
|
||||
|
||||
describe('loadConfiguration', () => {
|
||||
@@ -195,5 +204,68 @@ describe('MiddlewareModule', () => {
|
||||
);
|
||||
expect(createMiddlewareFactoryStub.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should insert the expected middleware definition', async () => {
|
||||
const route = 'testPath';
|
||||
const configuration = {
|
||||
middleware: [TestMiddleware],
|
||||
forRoutes: ['test', BasicController, BaseController],
|
||||
};
|
||||
const instance = new TestMiddleware();
|
||||
const instanceWrapper = new InstanceWrapper({
|
||||
metatype: TestMiddleware,
|
||||
instance,
|
||||
name: TestMiddleware.name,
|
||||
});
|
||||
const createMiddlewareFactoryStub = sinon
|
||||
.stub()
|
||||
.callsFake(() => () => null);
|
||||
const app = {
|
||||
createMiddlewareFactory: createMiddlewareFactoryStub,
|
||||
};
|
||||
|
||||
const stubContainer = new NestContainer();
|
||||
stubContainer
|
||||
.getModules()
|
||||
.set('Test', new Module(TestModule, stubContainer));
|
||||
const container = new MiddlewareContainer(stubContainer);
|
||||
const moduleKey = 'Test';
|
||||
container.insertConfig([configuration], moduleKey);
|
||||
container
|
||||
.getMiddlewareCollection(moduleKey)
|
||||
.set(TestMiddleware, instanceWrapper);
|
||||
sinon
|
||||
.stub(stubContainer, 'getModuleByKey')
|
||||
.callsFake(() => new Module(class {}, stubContainer));
|
||||
middlewareModule['container'] = stubContainer;
|
||||
|
||||
const insertEntrypointDefinitionSpy = sinon.spy(
|
||||
graphInspector,
|
||||
'insertEntrypointDefinition',
|
||||
);
|
||||
|
||||
await middlewareModule.registerRouteMiddleware(
|
||||
container,
|
||||
{ path: route, method: RequestMethod.ALL },
|
||||
configuration,
|
||||
moduleKey,
|
||||
app,
|
||||
);
|
||||
|
||||
expect(createMiddlewareFactoryStub.calledOnce).to.be.true;
|
||||
expect(
|
||||
insertEntrypointDefinitionSpy.calledWith({
|
||||
type: 'middleware',
|
||||
methodName: 'use',
|
||||
className: instanceWrapper.name,
|
||||
classNodeId: instanceWrapper.id,
|
||||
metadata: {
|
||||
path: route,
|
||||
requestMethod: 'ALL',
|
||||
version: undefined,
|
||||
},
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { InjectionToken, Scope } from '@nestjs/common';
|
||||
import { expect } from 'chai';
|
||||
import { InjectionToken, Logger, Scope } from '@nestjs/common';
|
||||
import { ContextIdFactory } from '../helpers/context-id-factory';
|
||||
import { InstanceLoader } from '../injector/instance-loader';
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { InstanceLoader } from '../injector/instance-loader';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { NestApplicationContext } from '../nest-application-context';
|
||||
|
||||
describe('NestApplicationContext', () => {
|
||||
@@ -13,7 +14,10 @@ describe('NestApplicationContext', () => {
|
||||
scope: Scope,
|
||||
): Promise<NestApplicationContext> {
|
||||
const nestContainer = new NestContainer();
|
||||
const instanceLoader = new InstanceLoader(nestContainer);
|
||||
const instanceLoader = new InstanceLoader(
|
||||
nestContainer,
|
||||
new GraphInspector(nestContainer),
|
||||
);
|
||||
const module = await nestContainer.addModule(class T {}, []);
|
||||
|
||||
nestContainer.addProvider(
|
||||
@@ -32,6 +36,7 @@ describe('NestApplicationContext', () => {
|
||||
scope,
|
||||
},
|
||||
module.token,
|
||||
'interceptor',
|
||||
);
|
||||
|
||||
const modules = nestContainer.getModules();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { NestApplication } from '../nest-application';
|
||||
import { NoopHttpAdapter } from './utils/noop-adapter.spec';
|
||||
|
||||
@@ -18,6 +19,7 @@ describe('NestApplication', () => {
|
||||
container,
|
||||
new NoopHttpAdapter({}),
|
||||
applicationConfig,
|
||||
new GraphInspector(container),
|
||||
{},
|
||||
);
|
||||
instance.useGlobalInterceptors(new Interceptor());
|
||||
@@ -36,6 +38,7 @@ describe('NestApplication', () => {
|
||||
container,
|
||||
new NoopHttpAdapter({}),
|
||||
applicationConfig,
|
||||
new GraphInspector(container),
|
||||
{},
|
||||
);
|
||||
instance.useGlobalInterceptors(new Interceptor());
|
||||
|
||||
218
packages/core/test/router/paths-explorer.spec.ts
Normal file
218
packages/core/test/router/paths-explorer.spec.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { expect } from 'chai';
|
||||
import { Controller } from '../../../common/decorators/core/controller.decorator';
|
||||
import {
|
||||
All,
|
||||
Get,
|
||||
Post,
|
||||
} from '../../../common/decorators/http/request-mapping.decorator';
|
||||
import { RequestMethod } from '../../../common/enums/request-method.enum';
|
||||
import { MetadataScanner } from '../../metadata-scanner';
|
||||
import { PathsExplorer } from '../../router/paths-explorer';
|
||||
|
||||
describe('PathsExplorer', () => {
|
||||
@Controller('global')
|
||||
class TestRoute {
|
||||
@Get('test')
|
||||
public getTest() {}
|
||||
|
||||
@Post('test')
|
||||
public postTest() {}
|
||||
|
||||
@All('another-test')
|
||||
public anotherTest() {}
|
||||
|
||||
@Get(['foo', 'bar'])
|
||||
public getTestUsingArray() {}
|
||||
}
|
||||
|
||||
@Controller(['global', 'global-alias'])
|
||||
class TestRouteAlias {
|
||||
@Get('test')
|
||||
public getTest() {}
|
||||
|
||||
@Post('test')
|
||||
public postTest() {}
|
||||
|
||||
@All('another-test')
|
||||
public anotherTest() {}
|
||||
|
||||
@Get(['foo', 'bar'])
|
||||
public getTestUsingArray() {}
|
||||
}
|
||||
|
||||
let pathsExplorer: PathsExplorer;
|
||||
|
||||
beforeEach(() => {
|
||||
pathsExplorer = new PathsExplorer(new MetadataScanner());
|
||||
});
|
||||
|
||||
describe('scanForPaths', () => {
|
||||
it('should method return expected list of route paths', () => {
|
||||
const paths = pathsExplorer.scanForPaths(new TestRoute());
|
||||
|
||||
expect(paths).to.have.length(4);
|
||||
|
||||
expect(paths[0].path).to.eql(['/test']);
|
||||
expect(paths[1].path).to.eql(['/test']);
|
||||
expect(paths[2].path).to.eql(['/another-test']);
|
||||
expect(paths[3].path).to.eql(['/foo', '/bar']);
|
||||
|
||||
expect(paths[0].requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(paths[1].requestMethod).to.eql(RequestMethod.POST);
|
||||
expect(paths[2].requestMethod).to.eql(RequestMethod.ALL);
|
||||
expect(paths[3].requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return expected list of route paths alias', () => {
|
||||
const paths = pathsExplorer.scanForPaths(new TestRouteAlias());
|
||||
|
||||
expect(paths).to.have.length(4);
|
||||
|
||||
expect(paths[0].path).to.eql(['/test']);
|
||||
expect(paths[1].path).to.eql(['/test']);
|
||||
expect(paths[2].path).to.eql(['/another-test']);
|
||||
expect(paths[3].path).to.eql(['/foo', '/bar']);
|
||||
|
||||
expect(paths[0].requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(paths[1].requestMethod).to.eql(RequestMethod.POST);
|
||||
expect(paths[2].requestMethod).to.eql(RequestMethod.ALL);
|
||||
expect(paths[3].requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exploreMethodMetadata', () => {
|
||||
it('should method return expected object which represent single route', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTest);
|
||||
});
|
||||
|
||||
it('should method return expected object which represent single route with alias', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTest);
|
||||
});
|
||||
|
||||
it('should method return expected object which represent multiple routes', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTestUsingArray);
|
||||
});
|
||||
|
||||
it('should method return expected object which represent multiple routes with alias', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTestUsingArray);
|
||||
});
|
||||
|
||||
describe('when new implementation is injected into router', () => {
|
||||
it('should method return changed impl of single route', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTest = newImpl;
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return changed impl of single route which alias applied', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTest = newImpl;
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return changed impl of multiple routes', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTestUsingArray = newImpl;
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return changed impl of multiple routes which alias applied', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTestUsingArray = newImpl;
|
||||
|
||||
const route = pathsExplorer.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,16 +10,17 @@ import { RequestMethod } from '../../../common/enums/request-method.enum';
|
||||
import { VersioningType } from '../../../common/enums/version-type.enum';
|
||||
import { Injector } from '../../../core/injector/injector';
|
||||
import { ApplicationConfig } from '../../application-config';
|
||||
import { UnknownRequestMappingException } from '../../errors/exceptions/unknown-request-mapping.exception';
|
||||
import { ExecutionContextHost } from '../../helpers/execution-context-host';
|
||||
import { NestContainer } from '../../injector/container';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||
import { MetadataScanner } from '../../metadata-scanner';
|
||||
import { RoutePathMetadata } from '../../router/interfaces/route-path-metadata.interface';
|
||||
import { RoutePathFactory } from '../../router/route-path-factory';
|
||||
import { RouterExceptionFilters } from '../../router/router-exception-filters';
|
||||
import { RouterExplorer } from '../../router/router-explorer';
|
||||
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
||||
import { UnknownRequestMappingException } from '../../errors/exceptions/unknown-request-mapping.exception';
|
||||
|
||||
describe('RouterExplorer', () => {
|
||||
@Controller('global')
|
||||
@@ -59,6 +60,7 @@ describe('RouterExplorer', () => {
|
||||
let exceptionsFilter: RouterExceptionFilters;
|
||||
let applicationConfig: ApplicationConfig;
|
||||
let routePathFactory: RoutePathFactory;
|
||||
let graphInspector: GraphInspector;
|
||||
|
||||
beforeEach(() => {
|
||||
const container = new NestContainer();
|
||||
@@ -66,6 +68,7 @@ describe('RouterExplorer', () => {
|
||||
applicationConfig = new ApplicationConfig();
|
||||
injector = new Injector();
|
||||
routePathFactory = new RoutePathFactory(applicationConfig);
|
||||
graphInspector = new GraphInspector(container);
|
||||
exceptionsFilter = new RouterExceptionFilters(
|
||||
container,
|
||||
applicationConfig,
|
||||
@@ -79,179 +82,10 @@ describe('RouterExplorer', () => {
|
||||
exceptionsFilter,
|
||||
applicationConfig,
|
||||
routePathFactory,
|
||||
graphInspector,
|
||||
);
|
||||
});
|
||||
|
||||
describe('scanForPaths', () => {
|
||||
it('should method return expected list of route paths', () => {
|
||||
const paths = routerBuilder.scanForPaths(new TestRoute());
|
||||
|
||||
expect(paths).to.have.length(4);
|
||||
|
||||
expect(paths[0].path).to.eql(['/test']);
|
||||
expect(paths[1].path).to.eql(['/test']);
|
||||
expect(paths[2].path).to.eql(['/another-test']);
|
||||
expect(paths[3].path).to.eql(['/foo', '/bar']);
|
||||
|
||||
expect(paths[0].requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(paths[1].requestMethod).to.eql(RequestMethod.POST);
|
||||
expect(paths[2].requestMethod).to.eql(RequestMethod.ALL);
|
||||
expect(paths[3].requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return expected list of route paths alias', () => {
|
||||
const paths = routerBuilder.scanForPaths(new TestRouteAlias());
|
||||
|
||||
expect(paths).to.have.length(4);
|
||||
|
||||
expect(paths[0].path).to.eql(['/test']);
|
||||
expect(paths[1].path).to.eql(['/test']);
|
||||
expect(paths[2].path).to.eql(['/another-test']);
|
||||
expect(paths[3].path).to.eql(['/foo', '/bar']);
|
||||
|
||||
expect(paths[0].requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(paths[1].requestMethod).to.eql(RequestMethod.POST);
|
||||
expect(paths[2].requestMethod).to.eql(RequestMethod.ALL);
|
||||
expect(paths[3].requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exploreMethodMetadata', () => {
|
||||
it('should method return expected object which represent single route', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTest);
|
||||
});
|
||||
|
||||
it('should method return expected object which represent single route with alias', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTest);
|
||||
});
|
||||
|
||||
it('should method return expected object which represent multiple routes', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTestUsingArray);
|
||||
});
|
||||
|
||||
it('should method return expected object which represent multiple routes with alias', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
expect(route.targetCallback).to.eq(instance.getTestUsingArray);
|
||||
});
|
||||
|
||||
describe('when new implementation is injected into router', () => {
|
||||
it('should method return changed impl of single route', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTest = newImpl;
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return changed impl of single route which alias applied', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTest = newImpl;
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTest',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/test']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return changed impl of multiple routes', () => {
|
||||
const instance = new TestRoute();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTestUsingArray = newImpl;
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
|
||||
it('should method return changed impl of multiple routes which alias applied', () => {
|
||||
const instance = new TestRouteAlias();
|
||||
const instanceProto = Object.getPrototypeOf(instance);
|
||||
|
||||
const newImpl = function () {};
|
||||
instance.getTestUsingArray = newImpl;
|
||||
|
||||
const route = routerBuilder.exploreMethodMetadata(
|
||||
instance,
|
||||
instanceProto,
|
||||
'getTestUsingArray',
|
||||
);
|
||||
|
||||
expect(route.targetCallback).to.eq(newImpl);
|
||||
expect(route.path).to.eql(['/foo', '/bar']);
|
||||
expect(route.requestMethod).to.eql(RequestMethod.GET);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyPathsToRouterProxy', () => {
|
||||
it('should method return expected object which represent single route', () => {
|
||||
const bindStub = sinon.stub(
|
||||
|
||||
@@ -13,6 +13,8 @@ import { ApplicationConfig } from '../../application-config';
|
||||
import { NestContainer } from '../../injector';
|
||||
import { Injector } from '../../injector/injector';
|
||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||
import { SerializedGraph } from '../../inspector/serialized-graph';
|
||||
import { RoutesResolver } from '../../router/routes-resolver';
|
||||
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
||||
|
||||
@@ -65,6 +67,7 @@ describe('RoutesResolver', () => {
|
||||
getModules: () => modules,
|
||||
getModuleByKey: (key: string) => modules.get(key),
|
||||
getHttpAdapterRef: () => applicationRef,
|
||||
serializedGraph: new SerializedGraph(),
|
||||
} as any;
|
||||
router = {
|
||||
get() {},
|
||||
@@ -77,6 +80,7 @@ describe('RoutesResolver', () => {
|
||||
container,
|
||||
new ApplicationConfig(),
|
||||
new Injector(),
|
||||
new GraphInspector(container),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -175,6 +179,7 @@ describe('RoutesResolver', () => {
|
||||
container,
|
||||
applicationConfig,
|
||||
new Injector(),
|
||||
new GraphInspector(container),
|
||||
);
|
||||
|
||||
const routes = new Map();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Catch, Injectable, Logger } from '@nestjs/common';
|
||||
import { Catch, Injectable } from '@nestjs/common';
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { GUARDS_METADATA } from '../../common/constants';
|
||||
@@ -8,13 +8,15 @@ import { Module } from '../../common/decorators/modules/module.decorator';
|
||||
import { Scope } from '../../common/interfaces';
|
||||
import { ApplicationConfig } from '../application-config';
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '../constants';
|
||||
import { InvalidModuleException } from '../errors/exceptions/invalid-module.exception';
|
||||
import { InvalidClassModuleException } from '../errors/exceptions/invalid-class-module.exception';
|
||||
import { InvalidModuleException } from '../errors/exceptions/invalid-module.exception';
|
||||
import { UndefinedModuleException } from '../errors/exceptions/undefined-module.exception';
|
||||
import { NestContainer } from '../injector/container';
|
||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||
import { GraphInspector } from '../inspector/graph-inspector';
|
||||
import { MetadataScanner } from '../metadata-scanner';
|
||||
import { DependenciesScanner } from '../scanner';
|
||||
import Sinon = require('sinon');
|
||||
|
||||
describe('DependenciesScanner', () => {
|
||||
class Guard {}
|
||||
@@ -55,14 +57,17 @@ describe('DependenciesScanner', () => {
|
||||
let scanner: DependenciesScanner;
|
||||
let mockContainer: sinon.SinonMock;
|
||||
let container: NestContainer;
|
||||
let graphInspector: GraphInspector;
|
||||
|
||||
beforeEach(() => {
|
||||
container = new NestContainer();
|
||||
mockContainer = sinon.mock(container);
|
||||
graphInspector = new GraphInspector(container);
|
||||
|
||||
scanner = new DependenciesScanner(
|
||||
container,
|
||||
new MetadataScanner(),
|
||||
graphInspector,
|
||||
new ApplicationConfig(),
|
||||
);
|
||||
sinon.stub(scanner, 'registerCoreModule').callsFake(async () => {});
|
||||
@@ -75,7 +80,7 @@ describe('DependenciesScanner', () => {
|
||||
it('should "insertModule" call twice (2 modules) container method "addModule"', async () => {
|
||||
const expectation = mockContainer.expects('addModule').twice();
|
||||
|
||||
await scanner.scan(TestModule as any);
|
||||
await scanner.scan(TestModule);
|
||||
expectation.verify();
|
||||
});
|
||||
|
||||
@@ -134,34 +139,115 @@ describe('DependenciesScanner', () => {
|
||||
});
|
||||
|
||||
describe('insertInjectable', () => {
|
||||
it('should call "addInjectable"', () => {
|
||||
const addInjectable = sinon
|
||||
.stub((scanner as any).container, 'addInjectable')
|
||||
.callsFake(() => undefined);
|
||||
const comp = {};
|
||||
const token = 'token';
|
||||
class InjectableCls {}
|
||||
class HostCls {}
|
||||
|
||||
scanner.insertInjectable(comp as any, token, null);
|
||||
expect(addInjectable.calledWith(comp, token)).to.be.true;
|
||||
const instanceWrapper = { id: 'random_id' };
|
||||
const token = 'token';
|
||||
const methodKey = 'methodKey';
|
||||
|
||||
let addInjectableStub: Sinon.SinonStub;
|
||||
let insertEnhancerMetadataCacheStub: Sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
addInjectableStub = sinon
|
||||
.stub((scanner as any).container, 'addInjectable')
|
||||
.callsFake(() => instanceWrapper);
|
||||
insertEnhancerMetadataCacheStub = sinon
|
||||
.stub(graphInspector, 'insertEnhancerMetadataCache')
|
||||
.callsFake(() => undefined);
|
||||
});
|
||||
|
||||
describe('when injectable is of type function', () => {
|
||||
const subtype = 'filter';
|
||||
beforeEach(() => {
|
||||
scanner.insertInjectable(
|
||||
InjectableCls,
|
||||
token,
|
||||
HostCls,
|
||||
subtype,
|
||||
methodKey,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call "addInjectable"', () => {
|
||||
expect(addInjectableStub.calledWith(InjectableCls, token)).to.be.true;
|
||||
});
|
||||
|
||||
it('should call "insertEnhancerMetadataCache"', () => {
|
||||
expect(
|
||||
insertEnhancerMetadataCacheStub.calledWith({
|
||||
moduleToken: token,
|
||||
classRef: HostCls,
|
||||
enhancerInstanceWrapper: instanceWrapper,
|
||||
targetNodeId: instanceWrapper.id,
|
||||
methodKey,
|
||||
subtype,
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('when injectable is not of type function', () => {
|
||||
const injectableRef = new InjectableCls();
|
||||
const subtype = 'interceptor';
|
||||
|
||||
beforeEach(() => {
|
||||
scanner.insertInjectable(
|
||||
injectableRef,
|
||||
token,
|
||||
HostCls,
|
||||
subtype,
|
||||
methodKey,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call "addInjectable"', () => {
|
||||
expect(addInjectableStub.notCalled).to.be.true;
|
||||
});
|
||||
|
||||
it('should call "insertEnhancerMetadataCache"', () => {
|
||||
expect(
|
||||
insertEnhancerMetadataCacheStub.calledWith({
|
||||
moduleToken: token,
|
||||
classRef: HostCls,
|
||||
enhancerRef: injectableRef,
|
||||
methodKey,
|
||||
subtype,
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class CompMethod {
|
||||
@UseGuards(Guard)
|
||||
public method() {}
|
||||
|
||||
@UseGuards(Guard, Guard)
|
||||
public method2() {}
|
||||
}
|
||||
describe('reflectKeyMetadata', () => {
|
||||
it('should return undefined', () => {
|
||||
const result = scanner.reflectKeyMetadata(TestComponent, 'key', 'method');
|
||||
expect(result).to.be.undefined;
|
||||
});
|
||||
it('should return array', () => {
|
||||
it('should return an array that consists of 1 element', () => {
|
||||
const methodKey = 'method';
|
||||
const result = scanner.reflectKeyMetadata(
|
||||
CompMethod,
|
||||
GUARDS_METADATA,
|
||||
'method',
|
||||
methodKey,
|
||||
);
|
||||
expect(result).to.be.eql([Guard]);
|
||||
expect(result).to.be.deep.equal({ methodKey, metadata: [Guard] });
|
||||
});
|
||||
it('should return an array that consists of 2 elements', () => {
|
||||
const methodKey = 'method2';
|
||||
const result = scanner.reflectKeyMetadata(
|
||||
CompMethod,
|
||||
GUARDS_METADATA,
|
||||
methodKey,
|
||||
);
|
||||
expect(result).to.be.deep.equal({ methodKey, metadata: [Guard, Guard] });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -312,18 +398,30 @@ describe('DependenciesScanner', () => {
|
||||
(scanner as any).applicationProvidersApplyMap = [provider];
|
||||
|
||||
const expectedInstance = {};
|
||||
const instanceWrapper = {
|
||||
instance: expectedInstance,
|
||||
} as unknown as InstanceWrapper;
|
||||
mockContainer.expects('getModules').callsFake(() => ({
|
||||
get: () => ({
|
||||
providers: { get: () => ({ instance: expectedInstance }) },
|
||||
providers: { get: () => instanceWrapper },
|
||||
}),
|
||||
}));
|
||||
|
||||
const applySpy = sinon.spy();
|
||||
sinon.stub(scanner, 'getApplyProvidersMap').callsFake(() => ({
|
||||
[provider.type]: applySpy,
|
||||
}));
|
||||
|
||||
const insertAttachedEnhancerStub = sinon.stub(
|
||||
graphInspector,
|
||||
'insertAttachedEnhancer',
|
||||
);
|
||||
|
||||
scanner.applyApplicationProviders();
|
||||
|
||||
expect(applySpy.called).to.be.true;
|
||||
expect(applySpy.calledWith(expectedInstance)).to.be.true;
|
||||
expect(insertAttachedEnhancerStub.calledWith(instanceWrapper)).to.be.true;
|
||||
});
|
||||
it('should apply each globally scoped provider', () => {
|
||||
const provider = {
|
||||
@@ -340,13 +438,23 @@ describe('DependenciesScanner', () => {
|
||||
injectables: { get: () => expectedInstanceWrapper },
|
||||
}),
|
||||
}));
|
||||
|
||||
const applySpy = sinon.spy();
|
||||
sinon.stub(scanner, 'getApplyRequestProvidersMap').callsFake(() => ({
|
||||
[provider.type]: applySpy,
|
||||
}));
|
||||
|
||||
const insertAttachedEnhancerStub = sinon.stub(
|
||||
graphInspector,
|
||||
'insertAttachedEnhancer',
|
||||
);
|
||||
|
||||
scanner.applyApplicationProviders();
|
||||
|
||||
expect(applySpy.called).to.be.true;
|
||||
expect(applySpy.calledWith(expectedInstanceWrapper)).to.be.true;
|
||||
expect(insertAttachedEnhancerStub.calledWith(expectedInstanceWrapper)).to
|
||||
.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Transport } from '../enums';
|
||||
import { PatternMetadata } from './pattern-metadata.interface';
|
||||
|
||||
export type MicroserviceEntrypointMetadata = {
|
||||
transportId: keyof typeof Transport | symbol;
|
||||
patterns: PatternMetadata[];
|
||||
isEventHandler: boolean;
|
||||
extras?: Record<string, any>;
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
InstanceWrapper,
|
||||
} from '@nestjs/core/injector/instance-wrapper';
|
||||
import { Module } from '@nestjs/core/injector/module';
|
||||
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||
import { REQUEST_CONTEXT_ID } from '@nestjs/core/router/request/request-constants';
|
||||
import { connectable, Observable, Subject } from 'rxjs';
|
||||
@@ -24,13 +25,18 @@ import {
|
||||
DEFAULT_GRPC_CALLBACK_METADATA,
|
||||
} from './context/rpc-metadata-constants';
|
||||
import { BaseRpcContext } from './ctx-host/base-rpc.context';
|
||||
import { Transport } from './enums';
|
||||
import {
|
||||
CustomTransportStrategy,
|
||||
MessageHandler,
|
||||
PatternMetadata,
|
||||
RequestContext,
|
||||
} from './interfaces';
|
||||
import { ListenerMetadataExplorer } from './listener-metadata-explorer';
|
||||
import { MicroserviceEntrypointMetadata } from './interfaces/microservice-entrypoint-metadata.interface';
|
||||
import {
|
||||
EventOrMessageListenerDefinition,
|
||||
ListenerMetadataExplorer,
|
||||
} from './listener-metadata-explorer';
|
||||
import { ServerGrpc } from './server';
|
||||
import { Server } from './server/server';
|
||||
|
||||
@@ -47,6 +53,7 @@ export class ListenersController {
|
||||
private readonly injector: Injector,
|
||||
private readonly clientFactory: IClientProxyFactory,
|
||||
private readonly exceptionFiltersContext: ExceptionFiltersContext,
|
||||
private readonly graphInspector: GraphInspector,
|
||||
) {}
|
||||
|
||||
public registerPatternHandlers(
|
||||
@@ -78,61 +85,91 @@ export class ListenersController {
|
||||
);
|
||||
return acc;
|
||||
}, [])
|
||||
.forEach(
|
||||
({
|
||||
.forEach((definition: EventOrMessageListenerDefinition) => {
|
||||
const {
|
||||
patterns: [pattern],
|
||||
targetCallback,
|
||||
methodKey,
|
||||
extras,
|
||||
isEventHandler,
|
||||
}) => {
|
||||
if (isStatic) {
|
||||
const proxy = this.contextCreator.create(
|
||||
instance as object,
|
||||
targetCallback,
|
||||
moduleKey,
|
||||
methodKey,
|
||||
STATIC_CONTEXT,
|
||||
undefined,
|
||||
defaultCallMetadata,
|
||||
);
|
||||
if (isEventHandler) {
|
||||
const eventHandler: MessageHandler = (...args: unknown[]) => {
|
||||
const originalArgs = args;
|
||||
const [dataOrContextHost] = originalArgs;
|
||||
if (dataOrContextHost instanceof RequestContextHost) {
|
||||
args = args.slice(1, args.length);
|
||||
}
|
||||
const originalReturnValue = proxy(...args);
|
||||
const returnedValueWrapper = eventHandler.next?.(
|
||||
...(originalArgs as Parameters<MessageHandler>),
|
||||
);
|
||||
returnedValueWrapper?.then(returnedValue =>
|
||||
this.connectIfStream(returnedValue as Observable<unknown>),
|
||||
);
|
||||
return originalReturnValue;
|
||||
};
|
||||
return server.addHandler(
|
||||
pattern,
|
||||
eventHandler,
|
||||
isEventHandler,
|
||||
extras,
|
||||
);
|
||||
} else {
|
||||
return server.addHandler(pattern, proxy, isEventHandler, extras);
|
||||
}
|
||||
}
|
||||
const asyncHandler = this.createRequestScopedHandler(
|
||||
instanceWrapper,
|
||||
pattern,
|
||||
moduleRef,
|
||||
} = definition;
|
||||
|
||||
this.insertEntrypointDefinition(
|
||||
instanceWrapper,
|
||||
definition,
|
||||
server.transportId,
|
||||
);
|
||||
|
||||
if (isStatic) {
|
||||
const proxy = this.contextCreator.create(
|
||||
instance as object,
|
||||
targetCallback,
|
||||
moduleKey,
|
||||
methodKey,
|
||||
STATIC_CONTEXT,
|
||||
undefined,
|
||||
defaultCallMetadata,
|
||||
);
|
||||
server.addHandler(pattern, asyncHandler, isEventHandler, extras);
|
||||
if (isEventHandler) {
|
||||
const eventHandler: MessageHandler = (...args: unknown[]) => {
|
||||
const originalArgs = args;
|
||||
const [dataOrContextHost] = originalArgs;
|
||||
if (dataOrContextHost instanceof RequestContextHost) {
|
||||
args = args.slice(1, args.length);
|
||||
}
|
||||
const originalReturnValue = proxy(...args);
|
||||
const returnedValueWrapper = eventHandler.next?.(
|
||||
...(originalArgs as Parameters<MessageHandler>),
|
||||
);
|
||||
returnedValueWrapper?.then(returnedValue =>
|
||||
this.connectIfStream(returnedValue as Observable<unknown>),
|
||||
);
|
||||
return originalReturnValue;
|
||||
};
|
||||
return server.addHandler(
|
||||
pattern,
|
||||
eventHandler,
|
||||
isEventHandler,
|
||||
extras,
|
||||
);
|
||||
} else {
|
||||
return server.addHandler(pattern, proxy, isEventHandler, extras);
|
||||
}
|
||||
}
|
||||
const asyncHandler = this.createRequestScopedHandler(
|
||||
instanceWrapper,
|
||||
pattern,
|
||||
moduleRef,
|
||||
moduleKey,
|
||||
methodKey,
|
||||
defaultCallMetadata,
|
||||
);
|
||||
server.addHandler(pattern, asyncHandler, isEventHandler, extras);
|
||||
});
|
||||
}
|
||||
|
||||
public insertEntrypointDefinition(
|
||||
instanceWrapper: InstanceWrapper,
|
||||
definition: EventOrMessageListenerDefinition,
|
||||
transportId: Transport | symbol,
|
||||
) {
|
||||
this.graphInspector.insertEntrypointDefinition<MicroserviceEntrypointMetadata>(
|
||||
{
|
||||
type: 'microservice',
|
||||
methodName: definition.methodKey,
|
||||
className: instanceWrapper.metatype?.name,
|
||||
classNodeId: instanceWrapper.id,
|
||||
metadata: {
|
||||
transportId:
|
||||
typeof transportId === 'number'
|
||||
? (Transport[transportId] as keyof typeof Transport)
|
||||
: transportId,
|
||||
patterns: definition.patterns,
|
||||
isEventHandler: definition.isEventHandler,
|
||||
extras: definition.extras,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public assignClientsToProperties(instance: Controller | Injectable) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { GuardsContextCreator } from '@nestjs/core/guards/guards-context-creator
|
||||
import { NestContainer } from '@nestjs/core/injector/container';
|
||||
import { Injector } from '@nestjs/core/injector/injector';
|
||||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
||||
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||
import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer';
|
||||
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
|
||||
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
|
||||
@@ -23,7 +24,11 @@ export class MicroservicesModule {
|
||||
private readonly clientsContainer = new ClientsContainer();
|
||||
private listenersController: ListenersController;
|
||||
|
||||
public register(container: NestContainer, config: ApplicationConfig) {
|
||||
public register(
|
||||
container: NestContainer,
|
||||
graphInspector: GraphInspector,
|
||||
config: ApplicationConfig,
|
||||
) {
|
||||
const exceptionFiltersContext = new ExceptionFiltersContext(
|
||||
container,
|
||||
config,
|
||||
@@ -47,6 +52,7 @@ export class MicroservicesModule {
|
||||
injector,
|
||||
ClientProxyFactory,
|
||||
exceptionFiltersContext,
|
||||
graphInspector,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||
import { MESSAGES } from '@nestjs/core/constants';
|
||||
import { optionalRequire } from '@nestjs/core/helpers/optional-require';
|
||||
import { NestContainer } from '@nestjs/core/injector/container';
|
||||
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||
import { NestApplicationContext } from '@nestjs/core/nest-application-context';
|
||||
import { Transport } from './enums/transport.enum';
|
||||
import { CustomTransportStrategy } from './interfaces/custom-transport-strategy.interface';
|
||||
@@ -42,11 +43,16 @@ export class NestMicroservice
|
||||
constructor(
|
||||
container: NestContainer,
|
||||
config: NestMicroserviceOptions & MicroserviceOptions = {},
|
||||
private readonly graphInspector: GraphInspector,
|
||||
private readonly applicationConfig: ApplicationConfig,
|
||||
) {
|
||||
super(container);
|
||||
|
||||
this.microservicesModule.register(container, this.applicationConfig);
|
||||
this.microservicesModule.register(
|
||||
container,
|
||||
this.graphInspector,
|
||||
this.applicationConfig,
|
||||
);
|
||||
this.createServer(config);
|
||||
this.selectContextModule();
|
||||
}
|
||||
@@ -92,21 +98,45 @@ export class NestMicroservice
|
||||
|
||||
public useGlobalFilters(...filters: ExceptionFilter[]): this {
|
||||
this.applicationConfig.useGlobalFilters(...filters);
|
||||
filters.forEach(item =>
|
||||
this.graphInspector.insertStaticEnhancer({
|
||||
subtype: 'filter',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public useGlobalPipes(...pipes: PipeTransform<any>[]): this {
|
||||
this.applicationConfig.useGlobalPipes(...pipes);
|
||||
pipes.forEach(item =>
|
||||
this.graphInspector.insertStaticEnhancer({
|
||||
subtype: 'pipe',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public useGlobalInterceptors(...interceptors: NestInterceptor[]): this {
|
||||
this.applicationConfig.useGlobalInterceptors(...interceptors);
|
||||
interceptors.forEach(item =>
|
||||
this.graphInspector.insertStaticEnhancer({
|
||||
subtype: 'interceptor',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public useGlobalGuards(...guards: CanActivate[]): this {
|
||||
this.applicationConfig.useGlobalGuards(...guards);
|
||||
guards.forEach(item =>
|
||||
this.graphInspector.insertStaticEnhancer({
|
||||
subtype: 'guard',
|
||||
ref: item,
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,17 @@ import { Injector } from '@nestjs/core/injector/injector';
|
||||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { GraphInspector } from '../../core/inspector/graph-inspector';
|
||||
import { MetadataScanner } from '../../core/metadata-scanner';
|
||||
import { ClientProxyFactory } from '../client';
|
||||
import { ClientsContainer } from '../container';
|
||||
import { ExceptionFiltersContext } from '../context/exception-filters-context';
|
||||
import { RpcContextCreator } from '../context/rpc-context-creator';
|
||||
import { Transport } from '../enums/transport.enum';
|
||||
import { ListenerMetadataExplorer } from '../listener-metadata-explorer';
|
||||
import {
|
||||
EventOrMessageListenerDefinition,
|
||||
ListenerMetadataExplorer,
|
||||
} from '../listener-metadata-explorer';
|
||||
import { ListenersController } from '../listeners-controller';
|
||||
|
||||
describe('ListenersController', () => {
|
||||
@@ -28,6 +32,7 @@ describe('ListenersController', () => {
|
||||
addSpyCustom: sinon.SinonSpy,
|
||||
proxySpy: sinon.SinonSpy,
|
||||
container: NestContainer,
|
||||
graphInspector: GraphInspector,
|
||||
injector: Injector,
|
||||
rpcContextCreator: RpcContextCreator,
|
||||
exceptionFiltersContext: ExceptionFiltersContext;
|
||||
@@ -38,6 +43,7 @@ describe('ListenersController', () => {
|
||||
});
|
||||
beforeEach(() => {
|
||||
container = new NestContainer();
|
||||
graphInspector = new GraphInspector(container);
|
||||
injector = new Injector();
|
||||
exceptionFiltersContext = new ExceptionFiltersContext(
|
||||
container,
|
||||
@@ -46,6 +52,7 @@ describe('ListenersController', () => {
|
||||
rpcContextCreator = sinon.createStubInstance(RpcContextCreator) as any;
|
||||
proxySpy = sinon.spy();
|
||||
(rpcContextCreator as any).create.callsFake(() => proxySpy);
|
||||
|
||||
instance = new ListenersController(
|
||||
new ClientsContainer(),
|
||||
rpcContextCreator,
|
||||
@@ -53,6 +60,7 @@ describe('ListenersController', () => {
|
||||
injector,
|
||||
ClientProxyFactory,
|
||||
exceptionFiltersContext,
|
||||
graphInspector,
|
||||
);
|
||||
(instance as any).metadataExplorer = metadataExplorer;
|
||||
addSpy = sinon.spy();
|
||||
@@ -262,6 +270,48 @@ describe('ListenersController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertEntrypointDefinition', () => {
|
||||
it('should inspect & insert corresponding entrypoint definitions', () => {
|
||||
class TestCtrl {}
|
||||
const instanceWrapper = new InstanceWrapper({
|
||||
metatype: TestCtrl,
|
||||
name: TestCtrl.name,
|
||||
});
|
||||
const definition: EventOrMessageListenerDefinition = {
|
||||
patterns: ['findOne'],
|
||||
methodKey: 'find',
|
||||
isEventHandler: false,
|
||||
targetCallback: null,
|
||||
extras: { qos: 2 },
|
||||
};
|
||||
const transportId = Transport.MQTT;
|
||||
|
||||
const insertEntrypointDefinitionSpy = sinon.spy(
|
||||
graphInspector,
|
||||
'insertEntrypointDefinition',
|
||||
);
|
||||
instance.insertEntrypointDefinition(
|
||||
instanceWrapper,
|
||||
definition,
|
||||
transportId,
|
||||
);
|
||||
expect(
|
||||
insertEntrypointDefinitionSpy.calledWith({
|
||||
type: 'microservice',
|
||||
methodName: definition.methodKey,
|
||||
className: 'TestCtrl',
|
||||
classNodeId: instanceWrapper.id,
|
||||
metadata: {
|
||||
transportId: 'MQTT',
|
||||
patterns: definition.patterns,
|
||||
isEventHandler: definition.isEventHandler,
|
||||
extras: definition.extras,
|
||||
},
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('assignClientToInstance', () => {
|
||||
it('should assign client to instance', () => {
|
||||
const propertyKey = 'key';
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Logger, LoggerService, Module } from '@nestjs/common';
|
||||
import { ModuleMetadata } from '@nestjs/common/interfaces';
|
||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||
import { NestContainer } from '@nestjs/core/injector/container';
|
||||
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||
import { DependenciesScanner } from '@nestjs/core/scanner';
|
||||
import {
|
||||
@@ -16,9 +17,13 @@ import { TestingModule } from './testing-module';
|
||||
export class TestingModuleBuilder {
|
||||
private readonly applicationConfig = new ApplicationConfig();
|
||||
private readonly container = new NestContainer(this.applicationConfig);
|
||||
private readonly graphInspector = new GraphInspector(this.container);
|
||||
private readonly overloadsMap = new Map();
|
||||
private readonly instanceLoader = new TestingInstanceLoader(
|
||||
this.container,
|
||||
this.graphInspector,
|
||||
);
|
||||
private readonly scanner: DependenciesScanner;
|
||||
private readonly instanceLoader = new TestingInstanceLoader(this.container);
|
||||
private readonly module: any;
|
||||
private testingLogger: LoggerService;
|
||||
private mocker?: MockFactory;
|
||||
@@ -27,6 +32,7 @@ export class TestingModuleBuilder {
|
||||
this.scanner = new DependenciesScanner(
|
||||
this.container,
|
||||
metadataScanner,
|
||||
this.graphInspector,
|
||||
this.applicationConfig,
|
||||
);
|
||||
this.module = this.createModule(metadata);
|
||||
|
||||
@@ -18,8 +18,11 @@ import {
|
||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||
import { NestContainer } from '@nestjs/core/injector/container';
|
||||
import { Module } from '@nestjs/core/injector/module';
|
||||
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||
|
||||
export class TestingModule extends NestApplicationContext {
|
||||
protected readonly graphInspector = new GraphInspector(this.container);
|
||||
|
||||
constructor(
|
||||
container: NestContainer,
|
||||
scope: Type<any>[],
|
||||
@@ -65,6 +68,7 @@ export class TestingModule extends NestApplicationContext {
|
||||
this.container,
|
||||
httpAdapter,
|
||||
this.applicationConfig,
|
||||
this.graphInspector,
|
||||
appOptions,
|
||||
);
|
||||
return this.createAdapterProxy<T>(instance, httpAdapter);
|
||||
@@ -82,6 +86,7 @@ export class TestingModule extends NestApplicationContext {
|
||||
return new NestMicroservice(
|
||||
this.container,
|
||||
options,
|
||||
this.graphInspector,
|
||||
this.applicationConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type WebsocketEntrypointMetadata = {
|
||||
port: number;
|
||||
message: unknown;
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { loadAdapter } from '@nestjs/core/helpers/load-adapter';
|
||||
import { NestContainer } from '@nestjs/core/injector/container';
|
||||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
||||
import { InstanceToken } from '@nestjs/core/injector/module';
|
||||
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||
import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer';
|
||||
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
|
||||
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
|
||||
@@ -30,21 +31,23 @@ export class SocketModule<HttpServer = any> {
|
||||
|
||||
public register(
|
||||
container: NestContainer,
|
||||
config: ApplicationConfig,
|
||||
applicationConfig: ApplicationConfig,
|
||||
graphInspector: GraphInspector,
|
||||
httpServer?: HttpServer,
|
||||
) {
|
||||
this.applicationConfig = config;
|
||||
this.applicationConfig = applicationConfig;
|
||||
this.httpServer = httpServer;
|
||||
|
||||
const contextCreator = this.getContextCreator(container);
|
||||
const serverProvider = new SocketServerProvider(
|
||||
this.socketsContainer,
|
||||
config,
|
||||
applicationConfig,
|
||||
);
|
||||
this.webSocketsController = new WebSocketsController(
|
||||
serverProvider,
|
||||
config,
|
||||
applicationConfig,
|
||||
contextCreator,
|
||||
graphInspector,
|
||||
);
|
||||
const modules = container.getModules();
|
||||
modules.forEach(({ providers }, moduleName: string) =>
|
||||
@@ -77,6 +80,7 @@ export class SocketModule<HttpServer = any> {
|
||||
instance as NestGateway,
|
||||
metatype,
|
||||
moduleName,
|
||||
wrapper.id,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { NestContainer } from '@nestjs/core';
|
||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||
import { expect } from 'chai';
|
||||
import { fromEvent, lastValueFrom, Observable, of } from 'rxjs';
|
||||
import * as sinon from 'sinon';
|
||||
import { GraphInspector } from '../../core/inspector/graph-inspector';
|
||||
import { MetadataScanner } from '../../core/metadata-scanner';
|
||||
import { AbstractWsAdapter } from '../adapters/ws-adapter';
|
||||
import { PORT_METADATA } from '../constants';
|
||||
import { WsContextCreator } from '../context/ws-context-creator';
|
||||
import { WebSocketGateway } from '../decorators/socket-gateway.decorator';
|
||||
import { InvalidSocketPortException } from '../errors/invalid-socket-port.exception';
|
||||
import { GatewayMetadataExplorer } from '../gateway-metadata-explorer';
|
||||
import {
|
||||
GatewayMetadataExplorer,
|
||||
MessageMappingProperties,
|
||||
} from '../gateway-metadata-explorer';
|
||||
import { SocketServerProvider } from '../socket-server-provider';
|
||||
import { WebSocketsController } from '../web-sockets-controller';
|
||||
|
||||
@@ -29,8 +34,10 @@ class NoopAdapter extends AbstractWsAdapter {
|
||||
describe('WebSocketsController', () => {
|
||||
let instance: WebSocketsController;
|
||||
let provider: SocketServerProvider,
|
||||
graphInspector: GraphInspector,
|
||||
config: ApplicationConfig,
|
||||
mockProvider: sinon.SinonMock;
|
||||
|
||||
const messageHandlerCallback = () => Promise.resolve();
|
||||
const port = 90,
|
||||
namespace = '/';
|
||||
@@ -40,6 +47,7 @@ describe('WebSocketsController', () => {
|
||||
beforeEach(() => {
|
||||
config = new ApplicationConfig(new NoopAdapter());
|
||||
provider = new SocketServerProvider(null, config);
|
||||
graphInspector = new GraphInspector(new NestContainer());
|
||||
mockProvider = sinon.mock(provider);
|
||||
|
||||
const contextCreator = sinon.createStubInstance(WsContextCreator);
|
||||
@@ -48,6 +56,7 @@ describe('WebSocketsController', () => {
|
||||
provider,
|
||||
config,
|
||||
contextCreator as any,
|
||||
graphInspector,
|
||||
);
|
||||
});
|
||||
describe('connectGatewayToServer', () => {
|
||||
@@ -69,20 +78,37 @@ describe('WebSocketsController', () => {
|
||||
instance.connectGatewayToServer(
|
||||
new InvalidGateway(),
|
||||
InvalidGateway,
|
||||
'',
|
||||
'moduleKey',
|
||||
'instanceWrapperId',
|
||||
),
|
||||
).throws(InvalidSocketPortException);
|
||||
});
|
||||
it('should call "subscribeToServerEvents" with default values when metadata is empty', () => {
|
||||
const gateway = new DefaultGateway();
|
||||
instance.connectGatewayToServer(gateway, DefaultGateway, '');
|
||||
expect(subscribeToServerEvents.calledWith(gateway, {}, 0, '')).to.be.true;
|
||||
instance.connectGatewayToServer(
|
||||
gateway,
|
||||
DefaultGateway,
|
||||
'moduleKey',
|
||||
'instanceWrapperId',
|
||||
);
|
||||
expect(subscribeToServerEvents.calledWith(gateway, {}, 0, 'moduleKey')).to
|
||||
.be.true;
|
||||
});
|
||||
it('should call "subscribeToServerEvents" when metadata is valid', () => {
|
||||
const gateway = new Test();
|
||||
instance.connectGatewayToServer(gateway, Test, '');
|
||||
instance.connectGatewayToServer(
|
||||
gateway,
|
||||
Test,
|
||||
'moduleKey',
|
||||
'instanceWrapperId',
|
||||
);
|
||||
expect(
|
||||
subscribeToServerEvents.calledWith(gateway, { namespace }, port, ''),
|
||||
subscribeToServerEvents.calledWith(
|
||||
gateway,
|
||||
{ namespace },
|
||||
port,
|
||||
'moduleKey',
|
||||
),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
@@ -116,16 +142,28 @@ describe('WebSocketsController', () => {
|
||||
|
||||
assignServerToProperties = sinon.spy();
|
||||
subscribeEvents = sinon.spy();
|
||||
(instance as any).assignServerToProperties = assignServerToProperties;
|
||||
(instance as any).subscribeEvents = subscribeEvents;
|
||||
instance['assignServerToProperties'] = assignServerToProperties;
|
||||
instance['subscribeEvents'] = subscribeEvents;
|
||||
});
|
||||
it('should call "assignServerToProperties" with expected arguments', () => {
|
||||
instance.subscribeToServerEvents(gateway, { namespace }, port, '');
|
||||
instance.subscribeToServerEvents(
|
||||
gateway,
|
||||
{ namespace },
|
||||
port,
|
||||
'moduleKey',
|
||||
'instanceWrapperId',
|
||||
);
|
||||
expect(assignServerToProperties.calledWith(gateway, server.server)).to.be
|
||||
.true;
|
||||
});
|
||||
it('should call "subscribeEvents" with expected arguments', () => {
|
||||
instance.subscribeToServerEvents(gateway, { namespace }, port, '');
|
||||
instance.subscribeToServerEvents(
|
||||
gateway,
|
||||
{ namespace },
|
||||
port,
|
||||
'moduleKey',
|
||||
'instanceWrapperId',
|
||||
);
|
||||
expect(subscribeEvents.firstCall.args[0]).to.be.equal(gateway);
|
||||
expect(subscribeEvents.firstCall.args[2]).to.be.equal(server);
|
||||
expect(subscribeEvents.firstCall.args[1]).to.be.eql([
|
||||
@@ -137,11 +175,67 @@ describe('WebSocketsController', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('inspectEntrypointDefinitions', () => {
|
||||
it('should inspect & insert corresponding entrypoint definitions', () => {
|
||||
class GatewayHostCls {}
|
||||
|
||||
const port = 80;
|
||||
const instanceWrapperId = '1234';
|
||||
const messageHandlers: MessageMappingProperties[] = [
|
||||
{
|
||||
methodName: 'findOne',
|
||||
message: 'find',
|
||||
callback: null,
|
||||
},
|
||||
{
|
||||
methodName: 'create',
|
||||
message: 'insert',
|
||||
callback: null,
|
||||
},
|
||||
];
|
||||
const insertEntrypointDefinitionSpy = sinon.spy(
|
||||
graphInspector,
|
||||
'insertEntrypointDefinition',
|
||||
);
|
||||
instance.inspectEntrypointDefinitions(
|
||||
new GatewayHostCls(),
|
||||
port,
|
||||
messageHandlers,
|
||||
instanceWrapperId,
|
||||
);
|
||||
|
||||
expect(insertEntrypointDefinitionSpy.calledTwice).to.be.true;
|
||||
expect(
|
||||
insertEntrypointDefinitionSpy.calledWith({
|
||||
type: 'websocket',
|
||||
methodName: messageHandlers[0].methodName,
|
||||
className: GatewayHostCls.name,
|
||||
classNodeId: instanceWrapperId,
|
||||
metadata: {
|
||||
port,
|
||||
message: messageHandlers[0].message,
|
||||
},
|
||||
}),
|
||||
).to.be.true;
|
||||
expect(
|
||||
insertEntrypointDefinitionSpy.calledWith({
|
||||
type: 'websocket',
|
||||
methodName: messageHandlers[1].methodName,
|
||||
className: GatewayHostCls.name,
|
||||
classNodeId: instanceWrapperId,
|
||||
metadata: {
|
||||
port,
|
||||
message: messageHandlers[1].message,
|
||||
},
|
||||
}),
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('subscribeEvents', () => {
|
||||
const gateway = new Test();
|
||||
|
||||
let handlers;
|
||||
let server,
|
||||
let handlers: any;
|
||||
let server: any,
|
||||
subscribeConnectionEvent: sinon.SinonSpy,
|
||||
subscribeDisconnectEvent: sinon.SinonSpy,
|
||||
nextSpy: sinon.SinonSpy,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface';
|
||||
import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||
import {
|
||||
from as fromPromise,
|
||||
Observable,
|
||||
isObservable,
|
||||
Observable,
|
||||
of,
|
||||
Subject,
|
||||
} from 'rxjs';
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
import { GatewayMetadata } from './interfaces/gateway-metadata.interface';
|
||||
import { NestGateway } from './interfaces/nest-gateway.interface';
|
||||
import { ServerAndEventStreamsHost } from './interfaces/server-and-event-streams-host.interface';
|
||||
import { WebsocketEntrypointMetadata } from './interfaces/websockets-entrypoint-metadata.interface';
|
||||
import { SocketServerProvider } from './socket-server-provider';
|
||||
import { compareElementAt } from './utils/compare-element.util';
|
||||
|
||||
@@ -35,12 +37,14 @@ export class WebSocketsController {
|
||||
private readonly socketServerProvider: SocketServerProvider,
|
||||
private readonly config: ApplicationConfig,
|
||||
private readonly contextCreator: WsContextCreator,
|
||||
private readonly graphInspector: GraphInspector,
|
||||
) {}
|
||||
|
||||
public connectGatewayToServer(
|
||||
instance: NestGateway,
|
||||
metatype: Type<unknown> | Function,
|
||||
moduleKey: string,
|
||||
instanceWrapperId: string,
|
||||
) {
|
||||
const options = Reflect.getMetadata(GATEWAY_OPTIONS, metatype) || {};
|
||||
const port = Reflect.getMetadata(PORT_METADATA, metatype) || 0;
|
||||
@@ -48,7 +52,13 @@ export class WebSocketsController {
|
||||
if (!Number.isInteger(port)) {
|
||||
throw new InvalidSocketPortException(port, metatype);
|
||||
}
|
||||
this.subscribeToServerEvents(instance, options, port, moduleKey);
|
||||
this.subscribeToServerEvents(
|
||||
instance,
|
||||
options,
|
||||
port,
|
||||
moduleKey,
|
||||
instanceWrapperId,
|
||||
);
|
||||
}
|
||||
|
||||
public subscribeToServerEvents<T extends GatewayMetadata>(
|
||||
@@ -56,6 +66,7 @@ export class WebSocketsController {
|
||||
options: T,
|
||||
port: number,
|
||||
moduleKey: string,
|
||||
instanceWrapperId: string,
|
||||
) {
|
||||
const nativeMessageHandlers = this.metadataExplorer.explore(instance);
|
||||
const messageHandlers = nativeMessageHandlers.map(
|
||||
@@ -76,6 +87,13 @@ export class WebSocketsController {
|
||||
);
|
||||
this.assignServerToProperties(instance, observableServer.server);
|
||||
this.subscribeEvents(instance, messageHandlers, observableServer);
|
||||
|
||||
this.inspectEntrypointDefinitions(
|
||||
instance,
|
||||
port,
|
||||
messageHandlers,
|
||||
instanceWrapperId,
|
||||
);
|
||||
}
|
||||
|
||||
public subscribeEvents(
|
||||
@@ -172,6 +190,28 @@ export class WebSocketsController {
|
||||
return of(result);
|
||||
}
|
||||
|
||||
public inspectEntrypointDefinitions(
|
||||
instance: NestGateway,
|
||||
port: number,
|
||||
messageHandlers: MessageMappingProperties[],
|
||||
instanceWrapperId: string,
|
||||
) {
|
||||
messageHandlers.forEach(handler => {
|
||||
this.graphInspector.insertEntrypointDefinition<WebsocketEntrypointMetadata>(
|
||||
{
|
||||
type: 'websocket',
|
||||
methodName: handler.methodName,
|
||||
className: instance.constructor?.name,
|
||||
classNodeId: instanceWrapperId,
|
||||
metadata: {
|
||||
port,
|
||||
message: handler.message,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private assignServerToProperties<T = any>(
|
||||
instance: NestGateway,
|
||||
server: object,
|
||||
|
||||
Reference in New Issue
Block a user