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",
|
"**/*.js",
|
||||||
"**/*.d.ts",
|
"**/*.d.ts",
|
||||||
"**/*.spec.ts",
|
"**/*.spec.ts",
|
||||||
"packages/**/*.spec.ts",
|
|
||||||
"packages/**/adapters/*.ts",
|
"packages/**/adapters/*.ts",
|
||||||
"packages/**/nest-*.ts",
|
"packages/**/nest-*.ts",
|
||||||
"packages/**/test/**/*.ts",
|
"packages/**/test/**/*.ts",
|
||||||
"packages/core/errors/**/*",
|
"packages/core/errors/**/*",
|
||||||
"packages/common/exceptions/*.ts",
|
"packages/common/exceptions/*.ts",
|
||||||
"packages/common/http/*.ts",
|
|
||||||
"packages/common/utils/load-package.util.ts",
|
"packages/common/utils/load-package.util.ts",
|
||||||
"packages/microservices/exceptions/",
|
"packages/microservices/exceptions/",
|
||||||
"packages/microservices/microservices-module.ts",
|
"packages/microservices/microservices-module.ts",
|
||||||
"packages/core/middleware/middleware-module.ts",
|
"packages/core/middleware/middleware-module.ts",
|
||||||
|
"packages/core/discovery/discovery-service.ts",
|
||||||
"packages/core/injector/module-ref.ts",
|
"packages/core/injector/module-ref.ts",
|
||||||
"packages/core/injector/instance-links-host.ts",
|
"packages/core/injector/instance-links-host.ts",
|
||||||
"packages/core/helpers/context-id-factory.ts",
|
"packages/core/helpers/context-id-factory.ts",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export const MODULE_METADATA = {
|
|||||||
CONTROLLERS: 'controllers',
|
CONTROLLERS: 'controllers',
|
||||||
EXPORTS: 'exports',
|
EXPORTS: 'exports',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GLOBAL_MODULE_METADATA = '__module:global__';
|
export const GLOBAL_MODULE_METADATA = '__module:global__';
|
||||||
export const HOST_METADATA = 'host';
|
export const HOST_METADATA = 'host';
|
||||||
export const PATH_METADATA = 'path';
|
export const PATH_METADATA = 'path';
|
||||||
@@ -20,12 +21,23 @@ export const CUSTOM_ROUTE_ARGS_METADATA = '__customRouteArgs__';
|
|||||||
* @deprecated Use `CUSTOM_ROUTE_ARGS_METADATA` instead
|
* @deprecated Use `CUSTOM_ROUTE_ARGS_METADATA` instead
|
||||||
*/
|
*/
|
||||||
export const CUSTOM_ROUTE_AGRS_METADATA = CUSTOM_ROUTE_ARGS_METADATA;
|
export const CUSTOM_ROUTE_AGRS_METADATA = CUSTOM_ROUTE_ARGS_METADATA;
|
||||||
export const EXCEPTION_FILTERS_METADATA = '__exceptionFilters__';
|
|
||||||
export const FILTER_CATCH_EXCEPTIONS = '__filterCatchExceptions__';
|
export const FILTER_CATCH_EXCEPTIONS = '__filterCatchExceptions__';
|
||||||
|
|
||||||
export const PIPES_METADATA = '__pipes__';
|
export const PIPES_METADATA = '__pipes__';
|
||||||
export const GUARDS_METADATA = '__guards__';
|
export const GUARDS_METADATA = '__guards__';
|
||||||
export const RENDER_METADATA = '__renderTemplate__';
|
|
||||||
export const INTERCEPTORS_METADATA = '__interceptors__';
|
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 HTTP_CODE_METADATA = '__httpCode__';
|
||||||
export const MODULE_PATH = '__module_path__';
|
export const MODULE_PATH = '__module_path__';
|
||||||
export const HEADERS_METADATA = '__headers__';
|
export const HEADERS_METADATA = '__headers__';
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { EnhancerSubtype } from '@nestjs/common/constants';
|
||||||
|
|
||||||
export const MESSAGES = {
|
export const MESSAGES = {
|
||||||
APPLICATION_START: `Starting Nest application...`,
|
APPLICATION_START: `Starting Nest application...`,
|
||||||
APPLICATION_READY: `Nest application successfully started`,
|
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_PIPE = 'APP_PIPE';
|
||||||
export const APP_GUARD = 'APP_GUARD';
|
export const APP_GUARD = 'APP_GUARD';
|
||||||
export const APP_FILTER = 'APP_FILTER';
|
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 { 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 { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
||||||
import { Type } from '@nestjs/common/interfaces/type.interface';
|
import { Type } from '@nestjs/common/interfaces/type.interface';
|
||||||
import { ApplicationConfig } from '../application-config';
|
import { ApplicationConfig } from '../application-config';
|
||||||
@@ -8,6 +11,7 @@ import {
|
|||||||
UndefinedForwardRefException,
|
UndefinedForwardRefException,
|
||||||
UnknownModuleException,
|
UnknownModuleException,
|
||||||
} from '../errors/exceptions';
|
} from '../errors/exceptions';
|
||||||
|
import { SerializedGraph } from '../inspector/serialized-graph';
|
||||||
import { REQUEST } from '../router/request/request-constants';
|
import { REQUEST } from '../router/request/request-constants';
|
||||||
import { ModuleCompiler } from './compiler';
|
import { ModuleCompiler } from './compiler';
|
||||||
import { ContextId } from './instance-wrapper';
|
import { ContextId } from './instance-wrapper';
|
||||||
@@ -27,12 +31,17 @@ export class NestContainer {
|
|||||||
Partial<DynamicModule>
|
Partial<DynamicModule>
|
||||||
>();
|
>();
|
||||||
private readonly internalProvidersStorage = new InternalProvidersStorage();
|
private readonly internalProvidersStorage = new InternalProvidersStorage();
|
||||||
|
private readonly _serializedGraph = new SerializedGraph();
|
||||||
private internalCoreModule: Module;
|
private internalCoreModule: Module;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _applicationConfig: ApplicationConfig = undefined,
|
private readonly _applicationConfig: ApplicationConfig = undefined,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
get serializedGraph(): SerializedGraph {
|
||||||
|
return this._serializedGraph;
|
||||||
|
}
|
||||||
|
|
||||||
get applicationConfig(): ApplicationConfig | undefined {
|
get applicationConfig(): ApplicationConfig | undefined {
|
||||||
return this._applicationConfig;
|
return this._applicationConfig;
|
||||||
}
|
}
|
||||||
@@ -74,13 +83,11 @@ export class NestContainer {
|
|||||||
moduleRef.token = token;
|
moduleRef.token = token;
|
||||||
this.modules.set(token, moduleRef);
|
this.modules.set(token, moduleRef);
|
||||||
|
|
||||||
await this.addDynamicMetadata(
|
const updatedScope = [].concat(scope, type);
|
||||||
token,
|
await this.addDynamicMetadata(token, dynamicMetadata, updatedScope);
|
||||||
dynamicMetadata,
|
|
||||||
[].concat(scope, type),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.isGlobalModule(type, dynamicMetadata)) {
|
if (this.isGlobalModule(type, dynamicMetadata)) {
|
||||||
|
moduleRef.isGlobal = true;
|
||||||
this.addGlobalModule(moduleRef);
|
this.addGlobalModule(moduleRef);
|
||||||
}
|
}
|
||||||
return moduleRef;
|
return moduleRef;
|
||||||
@@ -155,6 +162,7 @@ export class NestContainer {
|
|||||||
public addProvider(
|
public addProvider(
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
token: string,
|
token: string,
|
||||||
|
enhancerSubtype?: EnhancerSubtype,
|
||||||
): string | symbol | Function {
|
): string | symbol | Function {
|
||||||
const moduleRef = this.modules.get(token);
|
const moduleRef = this.modules.get(token);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
@@ -163,19 +171,20 @@ export class NestContainer {
|
|||||||
if (!moduleRef) {
|
if (!moduleRef) {
|
||||||
throw new UnknownModuleException();
|
throw new UnknownModuleException();
|
||||||
}
|
}
|
||||||
return moduleRef.addProvider(provider);
|
return moduleRef.addProvider(provider, enhancerSubtype) as Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addInjectable(
|
public addInjectable(
|
||||||
injectable: Provider,
|
injectable: Provider,
|
||||||
token: string,
|
token: string,
|
||||||
|
enhancerSubtype: EnhancerSubtype,
|
||||||
host?: Type<Injectable>,
|
host?: Type<Injectable>,
|
||||||
) {
|
) {
|
||||||
if (!this.modules.has(token)) {
|
if (!this.modules.has(token)) {
|
||||||
throw new UnknownModuleException();
|
throw new UnknownModuleException();
|
||||||
}
|
}
|
||||||
const moduleRef = this.modules.get(token);
|
const moduleRef = this.modules.get(token);
|
||||||
moduleRef.addInjectable(injectable, host);
|
return moduleRef.addInjectable(injectable, enhancerSubtype, host);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addExportedProvider(provider: Type<any>, token: string) {
|
public addExportedProvider(provider: Type<any>, token: string) {
|
||||||
@@ -219,15 +228,16 @@ export class NestContainer {
|
|||||||
target.addRelatedModule(globalModule);
|
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(
|
public getDynamicMetadataByToken(
|
||||||
token: string,
|
token: string,
|
||||||
metadataKey: keyof DynamicModule,
|
metadataKey?: Exclude<keyof DynamicModule, 'global' | 'module'>,
|
||||||
) {
|
) {
|
||||||
const metadata = this.dynamicModulesMetadata.get(token);
|
const metadata = this.dynamicModulesMetadata.get(token);
|
||||||
if (metadata && metadata[metadataKey]) {
|
return metadataKey ? metadata?.[metadataKey] ?? [] : metadata;
|
||||||
return metadata[metadataKey] as any[];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerCoreModuleRef(moduleRef: Module) {
|
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 { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
||||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
||||||
import { MODULE_INIT_MESSAGE } from '../helpers/messages';
|
import { MODULE_INIT_MESSAGE } from '../helpers/messages';
|
||||||
|
import { GraphInspector } from '../inspector/graph-inspector';
|
||||||
import { NestContainer } from './container';
|
import { NestContainer } from './container';
|
||||||
import { Injector } from './injector';
|
import { Injector } from './injector';
|
||||||
import { InternalCoreModule } from './internal-core-module/internal-core-module';
|
import { InternalCoreModule } from './internal-core-module/internal-core-module';
|
||||||
@@ -9,9 +10,11 @@ import { Module } from './module';
|
|||||||
|
|
||||||
export class InstanceLoader {
|
export class InstanceLoader {
|
||||||
protected readonly injector = new Injector();
|
protected readonly injector = new Injector();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly container: NestContainer,
|
protected readonly container: NestContainer,
|
||||||
private logger = new Logger(InstanceLoader.name, {
|
protected readonly graphInspector: GraphInspector,
|
||||||
|
private logger: LoggerService = new Logger(InstanceLoader.name, {
|
||||||
timestamp: true,
|
timestamp: true,
|
||||||
}),
|
}),
|
||||||
) {}
|
) {}
|
||||||
@@ -42,7 +45,7 @@ export class InstanceLoader {
|
|||||||
await this.createInstancesOfInjectables(moduleRef);
|
await this.createInstancesOfInjectables(moduleRef);
|
||||||
await this.createInstancesOfControllers(moduleRef);
|
await this.createInstancesOfControllers(moduleRef);
|
||||||
|
|
||||||
const { name } = moduleRef.metatype;
|
const { name } = moduleRef;
|
||||||
this.isModuleWhitelisted(name) &&
|
this.isModuleWhitelisted(name) &&
|
||||||
this.logger.log(MODULE_INIT_MESSAGE`${name}`);
|
this.logger.log(MODULE_INIT_MESSAGE`${name}`);
|
||||||
}),
|
}),
|
||||||
@@ -60,7 +63,10 @@ export class InstanceLoader {
|
|||||||
const { providers } = moduleRef;
|
const { providers } = moduleRef;
|
||||||
const wrappers = [...providers.values()];
|
const wrappers = [...providers.values()];
|
||||||
await Promise.all(
|
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 { controllers } = moduleRef;
|
||||||
const wrappers = [...controllers.values()];
|
const wrappers = [...controllers.values()];
|
||||||
await Promise.all(
|
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 { injectables } = moduleRef;
|
||||||
const wrappers = [...injectables.values()];
|
const wrappers = [...injectables.values()];
|
||||||
await Promise.all(
|
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,
|
isUndefined,
|
||||||
} from '@nestjs/common/utils/shared.utils';
|
} from '@nestjs/common/utils/shared.utils';
|
||||||
import { iterate } from 'iterare';
|
import { iterate } from 'iterare';
|
||||||
|
import { EnhancerSubtype } from '../../common/constants';
|
||||||
import { STATIC_CONTEXT } from './constants';
|
import { STATIC_CONTEXT } from './constants';
|
||||||
import {
|
import {
|
||||||
isClassProvider,
|
isClassProvider,
|
||||||
@@ -60,6 +61,7 @@ export class InstanceWrapper<T = any> {
|
|||||||
public readonly async?: boolean;
|
public readonly async?: boolean;
|
||||||
public readonly host?: Module;
|
public readonly host?: Module;
|
||||||
public readonly isAlias: boolean = false;
|
public readonly isAlias: boolean = false;
|
||||||
|
public readonly subtype?: EnhancerSubtype;
|
||||||
|
|
||||||
public scope?: Scope = Scope.DEFAULT;
|
public scope?: Scope = Scope.DEFAULT;
|
||||||
public metatype: Type<T> | Function;
|
public metatype: Type<T> | Function;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { ExternalContextCreator } from '../../helpers/external-context-creator';
|
import { ExternalContextCreator } from '../../helpers/external-context-creator';
|
||||||
import { HttpAdapterHost } from '../../helpers/http-adapter-host';
|
import { HttpAdapterHost } from '../../helpers/http-adapter-host';
|
||||||
|
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||||
import { DependenciesScanner } from '../../scanner';
|
import { DependenciesScanner } from '../../scanner';
|
||||||
import { ModuleCompiler } from '../compiler';
|
import { ModuleCompiler } from '../compiler';
|
||||||
import { NestContainer } from '../container';
|
import { NestContainer } from '../container';
|
||||||
@@ -15,7 +16,25 @@ export class InternalCoreModuleFactory {
|
|||||||
scanner: DependenciesScanner,
|
scanner: DependenciesScanner,
|
||||||
moduleCompiler: ModuleCompiler,
|
moduleCompiler: ModuleCompiler,
|
||||||
httpAdapterHost: HttpAdapterHost,
|
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([
|
return InternalCoreModule.register([
|
||||||
{
|
{
|
||||||
provide: ExternalContextCreator,
|
provide: ExternalContextCreator,
|
||||||
@@ -35,18 +54,7 @@ export class InternalCoreModuleFactory {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: LazyModuleLoader,
|
provide: LazyModuleLoader,
|
||||||
useFactory: () => {
|
useFactory: lazyModuleLoaderFactory,
|
||||||
const logger = new Logger(LazyModuleLoader.name, {
|
|
||||||
timestamp: false,
|
|
||||||
});
|
|
||||||
const instanceLoader = new InstanceLoader(container, logger);
|
|
||||||
return new LazyModuleLoader(
|
|
||||||
scanner,
|
|
||||||
instanceLoader,
|
|
||||||
moduleCompiler,
|
|
||||||
container.getModules(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
isUndefined,
|
isUndefined,
|
||||||
} from '@nestjs/common/utils/shared.utils';
|
} from '@nestjs/common/utils/shared.utils';
|
||||||
import { iterate } from 'iterare';
|
import { iterate } from 'iterare';
|
||||||
|
import { EnhancerSubtype } from '../../common/constants';
|
||||||
import { ApplicationConfig } from '../application-config';
|
import { ApplicationConfig } from '../application-config';
|
||||||
import {
|
import {
|
||||||
InvalidClassException,
|
InvalidClassException,
|
||||||
@@ -62,6 +63,7 @@ export class Module {
|
|||||||
>();
|
>();
|
||||||
private readonly _exports = new Set<InstanceToken>();
|
private readonly _exports = new Set<InstanceToken>();
|
||||||
private _distance = 0;
|
private _distance = 0;
|
||||||
|
private _isGlobal = false;
|
||||||
private _token: string;
|
private _token: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -84,6 +86,18 @@ export class Module {
|
|||||||
this._token = token;
|
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>> {
|
get providers(): Map<InstanceToken, InstanceWrapper<Injectable>> {
|
||||||
return this._providers;
|
return this._providers;
|
||||||
}
|
}
|
||||||
@@ -199,6 +213,7 @@ export class Module {
|
|||||||
|
|
||||||
public addInjectable<T extends Injectable>(
|
public addInjectable<T extends Injectable>(
|
||||||
injectable: Provider,
|
injectable: Provider,
|
||||||
|
enhancerSubtype: EnhancerSubtype,
|
||||||
host?: Type<T>,
|
host?: Type<T>,
|
||||||
) {
|
) {
|
||||||
if (this.isCustomProvider(injectable)) {
|
if (this.isCustomProvider(injectable)) {
|
||||||
@@ -214,6 +229,7 @@ export class Module {
|
|||||||
isResolved: false,
|
isResolved: false,
|
||||||
scope: getClassScope(injectable),
|
scope: getClassScope(injectable),
|
||||||
durable: isDurable(injectable),
|
durable: isDurable(injectable),
|
||||||
|
subtype: enhancerSubtype,
|
||||||
host: this,
|
host: this,
|
||||||
});
|
});
|
||||||
this._injectables.set(injectable, instanceWrapper);
|
this._injectables.set(injectable, instanceWrapper);
|
||||||
@@ -223,11 +239,17 @@ export class Module {
|
|||||||
this._controllers.get(host) || this._providers.get(host);
|
this._controllers.get(host) || this._providers.get(host);
|
||||||
hostWrapper && hostWrapper.addEnhancerMetadata(instanceWrapper);
|
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)) {
|
if (this.isCustomProvider(provider)) {
|
||||||
return this.addCustomProvider(provider, this._providers);
|
return this.addCustomProvider(provider, this._providers, enhancerSubtype);
|
||||||
}
|
}
|
||||||
this._providers.set(
|
this._providers.set(
|
||||||
provider,
|
provider,
|
||||||
@@ -270,15 +292,16 @@ export class Module {
|
|||||||
| ValueProvider
|
| ValueProvider
|
||||||
| ExistingProvider,
|
| ExistingProvider,
|
||||||
collection: Map<Function | string | symbol, any>,
|
collection: Map<Function | string | symbol, any>,
|
||||||
|
enhancerSubtype?: EnhancerSubtype,
|
||||||
) {
|
) {
|
||||||
if (this.isCustomClass(provider)) {
|
if (this.isCustomClass(provider)) {
|
||||||
this.addCustomClass(provider, collection);
|
this.addCustomClass(provider, collection, enhancerSubtype);
|
||||||
} else if (this.isCustomValue(provider)) {
|
} else if (this.isCustomValue(provider)) {
|
||||||
this.addCustomValue(provider, collection);
|
this.addCustomValue(provider, collection, enhancerSubtype);
|
||||||
} else if (this.isCustomFactory(provider)) {
|
} else if (this.isCustomFactory(provider)) {
|
||||||
this.addCustomFactory(provider, collection);
|
this.addCustomFactory(provider, collection, enhancerSubtype);
|
||||||
} else if (this.isCustomUseExisting(provider)) {
|
} else if (this.isCustomUseExisting(provider)) {
|
||||||
this.addCustomUseExisting(provider, collection);
|
this.addCustomUseExisting(provider, collection, enhancerSubtype);
|
||||||
}
|
}
|
||||||
return provider.provide;
|
return provider.provide;
|
||||||
}
|
}
|
||||||
@@ -306,6 +329,7 @@ export class Module {
|
|||||||
public addCustomClass(
|
public addCustomClass(
|
||||||
provider: ClassProvider,
|
provider: ClassProvider,
|
||||||
collection: Map<InstanceToken, InstanceWrapper>,
|
collection: Map<InstanceToken, InstanceWrapper>,
|
||||||
|
enhancerSubtype?: EnhancerSubtype,
|
||||||
) {
|
) {
|
||||||
let { scope, durable } = provider;
|
let { scope, durable } = provider;
|
||||||
|
|
||||||
@@ -327,6 +351,7 @@ export class Module {
|
|||||||
scope,
|
scope,
|
||||||
durable,
|
durable,
|
||||||
host: this,
|
host: this,
|
||||||
|
subtype: enhancerSubtype,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -334,6 +359,7 @@ export class Module {
|
|||||||
public addCustomValue(
|
public addCustomValue(
|
||||||
provider: ValueProvider,
|
provider: ValueProvider,
|
||||||
collection: Map<Function | string | symbol, InstanceWrapper>,
|
collection: Map<Function | string | symbol, InstanceWrapper>,
|
||||||
|
enhancerSubtype?: EnhancerSubtype,
|
||||||
) {
|
) {
|
||||||
const { useValue: value, provide: providerToken } = provider;
|
const { useValue: value, provide: providerToken } = provider;
|
||||||
collection.set(
|
collection.set(
|
||||||
@@ -346,6 +372,7 @@ export class Module {
|
|||||||
isResolved: true,
|
isResolved: true,
|
||||||
async: value instanceof Promise,
|
async: value instanceof Promise,
|
||||||
host: this,
|
host: this,
|
||||||
|
subtype: enhancerSubtype,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -353,6 +380,7 @@ export class Module {
|
|||||||
public addCustomFactory(
|
public addCustomFactory(
|
||||||
provider: FactoryProvider,
|
provider: FactoryProvider,
|
||||||
collection: Map<Function | string | symbol, InstanceWrapper>,
|
collection: Map<Function | string | symbol, InstanceWrapper>,
|
||||||
|
enhancerSubtype?: EnhancerSubtype,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
useFactory: factory,
|
useFactory: factory,
|
||||||
@@ -374,6 +402,7 @@ export class Module {
|
|||||||
scope,
|
scope,
|
||||||
durable,
|
durable,
|
||||||
host: this,
|
host: this,
|
||||||
|
subtype: enhancerSubtype,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -381,6 +410,7 @@ export class Module {
|
|||||||
public addCustomUseExisting(
|
public addCustomUseExisting(
|
||||||
provider: ExistingProvider,
|
provider: ExistingProvider,
|
||||||
collection: Map<Function | string | symbol, InstanceWrapper>,
|
collection: Map<Function | string | symbol, InstanceWrapper>,
|
||||||
|
enhancerSubtype?: EnhancerSubtype,
|
||||||
) {
|
) {
|
||||||
const { useExisting, provide: providerToken } = provider;
|
const { useExisting, provide: providerToken } = provider;
|
||||||
collection.set(
|
collection.set(
|
||||||
@@ -394,6 +424,7 @@ export class Module {
|
|||||||
inject: [useExisting],
|
inject: [useExisting],
|
||||||
host: this,
|
host: this,
|
||||||
isAlias: true,
|
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 { RequestMethod } from '@nestjs/common/enums/request-method.enum';
|
||||||
import {
|
import {
|
||||||
MiddlewareConfiguration,
|
MiddlewareConfiguration,
|
||||||
RouteInfo,
|
|
||||||
NestMiddleware,
|
NestMiddleware,
|
||||||
|
RouteInfo,
|
||||||
} from '@nestjs/common/interfaces/middleware';
|
} from '@nestjs/common/interfaces/middleware';
|
||||||
import {
|
import {
|
||||||
addLeadingSlash,
|
addLeadingSlash,
|
||||||
@@ -19,6 +19,11 @@ import { NestContainer } from '../injector/container';
|
|||||||
import { Injector } from '../injector/injector';
|
import { Injector } from '../injector/injector';
|
||||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||||
import { InstanceToken, Module } from '../injector/module';
|
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 { REQUEST_CONTEXT_ID } from '../router/request/request-constants';
|
||||||
import { RoutePathFactory } from '../router/route-path-factory';
|
import { RoutePathFactory } from '../router/route-path-factory';
|
||||||
import { RouterExceptionFilters } from '../router/router-exception-filters';
|
import { RouterExceptionFilters } from '../router/router-exception-filters';
|
||||||
@@ -40,6 +45,7 @@ export class MiddlewareModule {
|
|||||||
private config: ApplicationConfig;
|
private config: ApplicationConfig;
|
||||||
private container: NestContainer;
|
private container: NestContainer;
|
||||||
private httpAdapter: HttpServer;
|
private httpAdapter: HttpServer;
|
||||||
|
private graphInspector: GraphInspector;
|
||||||
|
|
||||||
constructor(private readonly routePathFactory: RoutePathFactory) {}
|
constructor(private readonly routePathFactory: RoutePathFactory) {}
|
||||||
|
|
||||||
@@ -49,6 +55,7 @@ export class MiddlewareModule {
|
|||||||
config: ApplicationConfig,
|
config: ApplicationConfig,
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
httpAdapter: HttpServer,
|
httpAdapter: HttpServer,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
) {
|
) {
|
||||||
const appRef = container.getHttpAdapterRef();
|
const appRef = container.getHttpAdapterRef();
|
||||||
this.routerExceptionFilter = new RouterExceptionFilters(
|
this.routerExceptionFilter = new RouterExceptionFilters(
|
||||||
@@ -63,6 +70,7 @@ export class MiddlewareModule {
|
|||||||
this.injector = injector;
|
this.injector = injector;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.httpAdapter = httpAdapter;
|
this.httpAdapter = httpAdapter;
|
||||||
|
this.graphInspector = graphInspector;
|
||||||
|
|
||||||
const modules = container.getModules();
|
const modules = container.getModules();
|
||||||
await this.resolveMiddleware(middlewareContainer, modules);
|
await this.resolveMiddleware(middlewareContainer, modules);
|
||||||
@@ -174,6 +182,21 @@ export class MiddlewareModule {
|
|||||||
if (instanceWrapper.isTransient) {
|
if (instanceWrapper.isTransient) {
|
||||||
return;
|
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(
|
await this.bindHandler(
|
||||||
instanceWrapper,
|
instanceWrapper,
|
||||||
applicationRef,
|
applicationRef,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MODULE_PATH, PATH_METADATA } from '@nestjs/common/constants';
|
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 {
|
import {
|
||||||
addLeadingSlash,
|
addLeadingSlash,
|
||||||
isString,
|
isString,
|
||||||
@@ -8,14 +8,14 @@ import {
|
|||||||
import { NestContainer } from '../injector/container';
|
import { NestContainer } from '../injector/container';
|
||||||
import { Module } from '../injector/module';
|
import { Module } from '../injector/module';
|
||||||
import { MetadataScanner } from '../metadata-scanner';
|
import { MetadataScanner } from '../metadata-scanner';
|
||||||
import { RouterExplorer } from '../router/router-explorer';
|
import { PathsExplorer } from '../router/paths-explorer';
|
||||||
import { targetModulesByContainer } from '../router/router-module';
|
import { targetModulesByContainer } from '../router/router-module';
|
||||||
|
|
||||||
export class RoutesMapper {
|
export class RoutesMapper {
|
||||||
private readonly routerExplorer: RouterExplorer;
|
private readonly pathsExplorer: PathsExplorer;
|
||||||
|
|
||||||
constructor(private readonly container: NestContainer) {
|
constructor(private readonly container: NestContainer) {
|
||||||
this.routerExplorer = new RouterExplorer(new MetadataScanner(), container);
|
this.pathsExplorer = new PathsExplorer(new MetadataScanner());
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapRouteToRouteInfo(
|
public mapRouteToRouteInfo(
|
||||||
@@ -58,7 +58,7 @@ export class RoutesMapper {
|
|||||||
controller: Type<any>,
|
controller: Type<any>,
|
||||||
routePath: string,
|
routePath: string,
|
||||||
): RouteInfo[] {
|
): RouteInfo[] {
|
||||||
const controllerPaths = this.routerExplorer.scanForPaths(
|
const controllerPaths = this.pathsExplorer.scanForPaths(
|
||||||
Object.create(controller),
|
Object.create(controller),
|
||||||
controller.prototype,
|
controller.prototype,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
WebSocketAdapter,
|
WebSocketAdapter,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
RouteInfo,
|
|
||||||
GlobalPrefixOptions,
|
GlobalPrefixOptions,
|
||||||
NestApplicationOptions,
|
NestApplicationOptions,
|
||||||
|
RouteInfo,
|
||||||
} from '@nestjs/common/interfaces';
|
} from '@nestjs/common/interfaces';
|
||||||
import {
|
import {
|
||||||
CorsOptions,
|
CorsOptions,
|
||||||
@@ -37,6 +37,7 @@ import { ApplicationConfig } from './application-config';
|
|||||||
import { MESSAGES } from './constants';
|
import { MESSAGES } from './constants';
|
||||||
import { optionalRequire } from './helpers/optional-require';
|
import { optionalRequire } from './helpers/optional-require';
|
||||||
import { NestContainer } from './injector/container';
|
import { NestContainer } from './injector/container';
|
||||||
|
import { GraphInspector } from './inspector/graph-inspector';
|
||||||
import { MiddlewareContainer } from './middleware/container';
|
import { MiddlewareContainer } from './middleware/container';
|
||||||
import { MiddlewareModule } from './middleware/middleware-module';
|
import { MiddlewareModule } from './middleware/middleware-module';
|
||||||
import { NestApplicationContext } from './nest-application-context';
|
import { NestApplicationContext } from './nest-application-context';
|
||||||
@@ -80,6 +81,7 @@ export class NestApplication
|
|||||||
container: NestContainer,
|
container: NestContainer,
|
||||||
private readonly httpAdapter: HttpServer,
|
private readonly httpAdapter: HttpServer,
|
||||||
private readonly config: ApplicationConfig,
|
private readonly config: ApplicationConfig,
|
||||||
|
private readonly graphInspector: GraphInspector,
|
||||||
private readonly appOptions: NestApplicationOptions = {},
|
private readonly appOptions: NestApplicationOptions = {},
|
||||||
) {
|
) {
|
||||||
super(container);
|
super(container);
|
||||||
@@ -92,6 +94,7 @@ export class NestApplication
|
|||||||
this.container,
|
this.container,
|
||||||
this.config,
|
this.config,
|
||||||
this.injector,
|
this.injector,
|
||||||
|
this.graphInspector,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +155,7 @@ export class NestApplication
|
|||||||
this.config,
|
this.config,
|
||||||
this.injector,
|
this.injector,
|
||||||
this.httpAdapter,
|
this.httpAdapter,
|
||||||
|
this.graphInspector,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +221,7 @@ export class NestApplication
|
|||||||
const instance = new NestMicroservice(
|
const instance = new NestMicroservice(
|
||||||
this.container,
|
this.container,
|
||||||
microserviceOptions,
|
microserviceOptions,
|
||||||
|
this.graphInspector,
|
||||||
applicationConfig,
|
applicationConfig,
|
||||||
);
|
);
|
||||||
instance.registerListeners();
|
instance.registerListeners();
|
||||||
@@ -365,21 +370,45 @@ export class NestApplication
|
|||||||
|
|
||||||
public useGlobalFilters(...filters: ExceptionFilter[]): this {
|
public useGlobalFilters(...filters: ExceptionFilter[]): this {
|
||||||
this.config.useGlobalFilters(...filters);
|
this.config.useGlobalFilters(...filters);
|
||||||
|
filters.forEach(item =>
|
||||||
|
this.graphInspector.insertOrphanedEnhancer({
|
||||||
|
subtype: 'filter',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGlobalPipes(...pipes: PipeTransform<any>[]): this {
|
public useGlobalPipes(...pipes: PipeTransform<any>[]): this {
|
||||||
this.config.useGlobalPipes(...pipes);
|
this.config.useGlobalPipes(...pipes);
|
||||||
|
pipes.forEach(item =>
|
||||||
|
this.graphInspector.insertOrphanedEnhancer({
|
||||||
|
subtype: 'pipe',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGlobalInterceptors(...interceptors: NestInterceptor[]): this {
|
public useGlobalInterceptors(...interceptors: NestInterceptor[]): this {
|
||||||
this.config.useGlobalInterceptors(...interceptors);
|
this.config.useGlobalInterceptors(...interceptors);
|
||||||
|
interceptors.forEach(item =>
|
||||||
|
this.graphInspector.insertOrphanedEnhancer({
|
||||||
|
subtype: 'interceptor',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGlobalGuards(...guards: CanActivate[]): this {
|
public useGlobalGuards(...guards: CanActivate[]): this {
|
||||||
this.config.useGlobalGuards(...guards);
|
this.config.useGlobalGuards(...guards);
|
||||||
|
guards.forEach(item =>
|
||||||
|
this.graphInspector.insertOrphanedEnhancer({
|
||||||
|
subtype: 'guard',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { loadAdapter } from './helpers/load-adapter';
|
|||||||
import { rethrow } from './helpers/rethrow';
|
import { rethrow } from './helpers/rethrow';
|
||||||
import { NestContainer } from './injector/container';
|
import { NestContainer } from './injector/container';
|
||||||
import { InstanceLoader } from './injector/instance-loader';
|
import { InstanceLoader } from './injector/instance-loader';
|
||||||
|
import { GraphInspector } from './inspector/graph-inspector';
|
||||||
import { MetadataScanner } from './metadata-scanner';
|
import { MetadataScanner } from './metadata-scanner';
|
||||||
import { NestApplication } from './nest-application';
|
import { NestApplication } from './nest-application';
|
||||||
import { NestApplicationContext } from './nest-application-context';
|
import { NestApplicationContext } from './nest-application-context';
|
||||||
@@ -73,15 +74,24 @@ export class NestFactoryStatic {
|
|||||||
|
|
||||||
const applicationConfig = new ApplicationConfig();
|
const applicationConfig = new ApplicationConfig();
|
||||||
const container = new NestContainer(applicationConfig);
|
const container = new NestContainer(applicationConfig);
|
||||||
|
const graphInspector = new GraphInspector(container);
|
||||||
|
|
||||||
this.setAbortOnError(serverOrOptions, options);
|
this.setAbortOnError(serverOrOptions, options);
|
||||||
this.registerLoggerConfiguration(appOptions);
|
this.registerLoggerConfiguration(appOptions);
|
||||||
|
|
||||||
await this.initialize(module, container, applicationConfig, httpServer);
|
await this.initialize(
|
||||||
|
module,
|
||||||
|
container,
|
||||||
|
graphInspector,
|
||||||
|
applicationConfig,
|
||||||
|
httpServer,
|
||||||
|
);
|
||||||
|
|
||||||
const instance = new NestApplication(
|
const instance = new NestApplication(
|
||||||
container,
|
container,
|
||||||
httpServer,
|
httpServer,
|
||||||
applicationConfig,
|
applicationConfig,
|
||||||
|
graphInspector,
|
||||||
appOptions,
|
appOptions,
|
||||||
);
|
);
|
||||||
const target = this.createNestInstance(instance);
|
const target = this.createNestInstance(instance);
|
||||||
@@ -108,12 +118,19 @@ export class NestFactoryStatic {
|
|||||||
);
|
);
|
||||||
const applicationConfig = new ApplicationConfig();
|
const applicationConfig = new ApplicationConfig();
|
||||||
const container = new NestContainer(applicationConfig);
|
const container = new NestContainer(applicationConfig);
|
||||||
|
const graphInspector = new GraphInspector(container);
|
||||||
|
|
||||||
this.setAbortOnError(options);
|
this.setAbortOnError(options);
|
||||||
this.registerLoggerConfiguration(options);
|
this.registerLoggerConfiguration(options);
|
||||||
|
|
||||||
await this.initialize(module, container, applicationConfig);
|
await this.initialize(module, container, graphInspector, applicationConfig);
|
||||||
return this.createNestInstance<INestMicroservice>(
|
return this.createNestInstance<INestMicroservice>(
|
||||||
new NestMicroservice(container, options, applicationConfig),
|
new NestMicroservice(
|
||||||
|
container,
|
||||||
|
options,
|
||||||
|
graphInspector,
|
||||||
|
applicationConfig,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,10 +148,12 @@ export class NestFactoryStatic {
|
|||||||
options?: NestApplicationContextOptions,
|
options?: NestApplicationContextOptions,
|
||||||
): Promise<INestApplicationContext> {
|
): Promise<INestApplicationContext> {
|
||||||
const container = new NestContainer();
|
const container = new NestContainer();
|
||||||
|
const graphInspector = new GraphInspector(container);
|
||||||
|
|
||||||
this.setAbortOnError(options);
|
this.setAbortOnError(options);
|
||||||
this.registerLoggerConfiguration(options);
|
this.registerLoggerConfiguration(options);
|
||||||
|
|
||||||
await this.initialize(module, container);
|
await this.initialize(module, container, graphInspector);
|
||||||
|
|
||||||
const modules = container.getModules().values();
|
const modules = container.getModules().values();
|
||||||
const root = modules.next().value;
|
const root = modules.next().value;
|
||||||
@@ -155,14 +174,16 @@ export class NestFactoryStatic {
|
|||||||
private async initialize(
|
private async initialize(
|
||||||
module: any,
|
module: any,
|
||||||
container: NestContainer,
|
container: NestContainer,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
config = new ApplicationConfig(),
|
config = new ApplicationConfig(),
|
||||||
httpServer: HttpServer = null,
|
httpServer: HttpServer = null,
|
||||||
) {
|
) {
|
||||||
const instanceLoader = new InstanceLoader(container);
|
const instanceLoader = new InstanceLoader(container, graphInspector);
|
||||||
const metadataScanner = new MetadataScanner();
|
const metadataScanner = new MetadataScanner();
|
||||||
const dependenciesScanner = new DependenciesScanner(
|
const dependenciesScanner = new DependenciesScanner(
|
||||||
container,
|
container,
|
||||||
metadataScanner,
|
metadataScanner,
|
||||||
|
graphInspector,
|
||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
container.setHttpAdapter(httpServer);
|
container.setHttpAdapter(httpServer);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export class ReplLogger extends ConsoleLogger {
|
|||||||
RouterExplorer.name,
|
RouterExplorer.name,
|
||||||
NestApplication.name,
|
NestApplication.name,
|
||||||
];
|
];
|
||||||
|
|
||||||
log(_message: any, context?: string) {
|
log(_message: any, context?: string) {
|
||||||
if (ReplLogger.ignoredContexts.includes(context)) {
|
if (ReplLogger.ignoredContexts.includes(context)) {
|
||||||
return;
|
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 { HttpServer } from '@nestjs/common';
|
||||||
import {
|
import { PATH_METADATA } from '@nestjs/common/constants';
|
||||||
METHOD_METADATA,
|
|
||||||
PATH_METADATA,
|
|
||||||
VERSION_METADATA,
|
|
||||||
} from '@nestjs/common/constants';
|
|
||||||
import { RequestMethod, VersioningType } from '@nestjs/common/enums';
|
import { RequestMethod, VersioningType } from '@nestjs/common/enums';
|
||||||
import { InternalServerErrorException } from '@nestjs/common/exceptions';
|
import { InternalServerErrorException } from '@nestjs/common/exceptions';
|
||||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
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 { Logger } from '@nestjs/common/services/logger.service';
|
||||||
import {
|
import {
|
||||||
addLeadingSlash,
|
addLeadingSlash,
|
||||||
isString,
|
|
||||||
isUndefined,
|
isUndefined,
|
||||||
} from '@nestjs/common/utils/shared.utils';
|
} from '@nestjs/common/utils/shared.utils';
|
||||||
import * as pathToRegexp from 'path-to-regexp';
|
import * as pathToRegexp from 'path-to-regexp';
|
||||||
@@ -32,6 +27,11 @@ import { NestContainer } from '../injector/container';
|
|||||||
import { Injector } from '../injector/injector';
|
import { Injector } from '../injector/injector';
|
||||||
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
|
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
|
||||||
import { Module } from '../injector/module';
|
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 { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
|
||||||
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
|
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
|
||||||
import { MetadataScanner } from '../metadata-scanner';
|
import { MetadataScanner } from '../metadata-scanner';
|
||||||
@@ -39,6 +39,7 @@ import { PipesConsumer } from '../pipes/pipes-consumer';
|
|||||||
import { PipesContextCreator } from '../pipes/pipes-context-creator';
|
import { PipesContextCreator } from '../pipes/pipes-context-creator';
|
||||||
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
|
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
|
||||||
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
||||||
|
import { PathsExplorer } from './paths-explorer';
|
||||||
import { REQUEST_CONTEXT_ID } from './request/request-constants';
|
import { REQUEST_CONTEXT_ID } from './request/request-constants';
|
||||||
import { RouteParamsFactory } from './route-params-factory';
|
import { RouteParamsFactory } from './route-params-factory';
|
||||||
import { RoutePathFactory } from './route-path-factory';
|
import { RoutePathFactory } from './route-path-factory';
|
||||||
@@ -55,6 +56,7 @@ export interface RouteDefinition {
|
|||||||
|
|
||||||
export class RouterExplorer {
|
export class RouterExplorer {
|
||||||
private readonly executionContextCreator: RouterExecutionContext;
|
private readonly executionContextCreator: RouterExecutionContext;
|
||||||
|
private readonly pathsExplorer: PathsExplorer;
|
||||||
private readonly routerMethodFactory = new RouterMethodFactory();
|
private readonly routerMethodFactory = new RouterMethodFactory();
|
||||||
private readonly logger = new Logger(RouterExplorer.name, {
|
private readonly logger = new Logger(RouterExplorer.name, {
|
||||||
timestamp: true,
|
timestamp: true,
|
||||||
@@ -62,14 +64,17 @@ export class RouterExplorer {
|
|||||||
private readonly exceptionFiltersCache = new WeakMap();
|
private readonly exceptionFiltersCache = new WeakMap();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly metadataScanner: MetadataScanner,
|
metadataScanner: MetadataScanner,
|
||||||
private readonly container: NestContainer,
|
private readonly container: NestContainer,
|
||||||
private readonly injector?: Injector,
|
private readonly injector: Injector,
|
||||||
private readonly routerProxy?: RouterProxy,
|
private readonly routerProxy: RouterProxy,
|
||||||
private readonly exceptionsFilter?: ExceptionsFilter,
|
private readonly exceptionsFilter: ExceptionsFilter,
|
||||||
private readonly config?: ApplicationConfig,
|
config: ApplicationConfig,
|
||||||
private readonly routePathFactory?: RoutePathFactory,
|
private readonly routePathFactory: RoutePathFactory,
|
||||||
|
private readonly graphInspector: GraphInspector,
|
||||||
) {
|
) {
|
||||||
|
this.pathsExplorer = new PathsExplorer(metadataScanner);
|
||||||
|
|
||||||
const routeParamsFactory = new RouteParamsFactory();
|
const routeParamsFactory = new RouteParamsFactory();
|
||||||
const pipesContextCreator = new PipesContextCreator(container, config);
|
const pipesContextCreator = new PipesContextCreator(container, config);
|
||||||
const pipesConsumer = new PipesConsumer();
|
const pipesConsumer = new PipesConsumer();
|
||||||
@@ -101,7 +106,7 @@ export class RouterExplorer {
|
|||||||
routePathMetadata: RoutePathMetadata,
|
routePathMetadata: RoutePathMetadata,
|
||||||
) {
|
) {
|
||||||
const { instance } = instanceWrapper;
|
const { instance } = instanceWrapper;
|
||||||
const routerPaths = this.scanForPaths(instance);
|
const routerPaths = this.pathsExplorer.scanForPaths(instance);
|
||||||
this.applyPathsToRouterProxy(
|
this.applyPathsToRouterProxy(
|
||||||
applicationRef,
|
applicationRef,
|
||||||
routerPaths,
|
routerPaths,
|
||||||
@@ -124,53 +129,6 @@ export class RouterExplorer {
|
|||||||
return [addLeadingSlash(path)];
|
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>(
|
public applyPathsToRouterProxy<T extends HttpServer>(
|
||||||
router: T,
|
router: T,
|
||||||
routeDefinitions: RouteDefinition[],
|
routeDefinitions: RouteDefinition[],
|
||||||
@@ -255,7 +213,29 @@ export class RouterExplorer {
|
|||||||
routePathMetadata,
|
routePathMetadata,
|
||||||
requestMethod,
|
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(
|
const pathsToLog = this.routePathFactory.create(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
VERSION_METADATA,
|
VERSION_METADATA,
|
||||||
} from '@nestjs/common/constants';
|
} from '@nestjs/common/constants';
|
||||||
import {
|
import {
|
||||||
|
Controller,
|
||||||
HttpServer,
|
HttpServer,
|
||||||
Type,
|
Type,
|
||||||
VersionValue,
|
VersionValue,
|
||||||
Controller,
|
|
||||||
} from '@nestjs/common/interfaces';
|
} from '@nestjs/common/interfaces';
|
||||||
import { Logger } from '@nestjs/common/services/logger.service';
|
import { Logger } from '@nestjs/common/services/logger.service';
|
||||||
import { ApplicationConfig } from '../application-config';
|
import { ApplicationConfig } from '../application-config';
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
import { NestContainer } from '../injector/container';
|
import { NestContainer } from '../injector/container';
|
||||||
import { Injector } from '../injector/injector';
|
import { Injector } from '../injector/injector';
|
||||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||||
|
import { GraphInspector } from '../inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '../metadata-scanner';
|
import { MetadataScanner } from '../metadata-scanner';
|
||||||
import { Resolver } from './interfaces/resolver.interface';
|
import { Resolver } from './interfaces/resolver.interface';
|
||||||
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
import { RoutePathMetadata } from './interfaces/route-path-metadata.interface';
|
||||||
@@ -40,6 +41,7 @@ export class RoutesResolver implements Resolver {
|
|||||||
private readonly container: NestContainer,
|
private readonly container: NestContainer,
|
||||||
private readonly applicationConfig: ApplicationConfig,
|
private readonly applicationConfig: ApplicationConfig,
|
||||||
private readonly injector: Injector,
|
private readonly injector: Injector,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
) {
|
) {
|
||||||
const httpAdapterRef = container.getHttpAdapterRef();
|
const httpAdapterRef = container.getHttpAdapterRef();
|
||||||
this.routerExceptionsFilter = new RouterExceptionFilters(
|
this.routerExceptionsFilter = new RouterExceptionFilters(
|
||||||
@@ -58,6 +60,7 @@ export class RoutesResolver implements Resolver {
|
|||||||
this.routerExceptionsFilter,
|
this.routerExceptionsFilter,
|
||||||
this.applicationConfig,
|
this.applicationConfig,
|
||||||
this.routePathFactory,
|
this.routePathFactory,
|
||||||
|
graphInspector,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import { DynamicModule, ForwardReference, Provider } from '@nestjs/common';
|
||||||
DynamicModule,
|
|
||||||
flatten,
|
|
||||||
ForwardReference,
|
|
||||||
Provider,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import {
|
import {
|
||||||
CATCH_WATERMARK,
|
CATCH_WATERMARK,
|
||||||
CONTROLLER_WATERMARK,
|
CONTROLLER_WATERMARK,
|
||||||
|
EnhancerSubtype,
|
||||||
|
ENHANCER_KEY_TO_SUBTYPE_MAP,
|
||||||
EXCEPTION_FILTERS_METADATA,
|
EXCEPTION_FILTERS_METADATA,
|
||||||
GUARDS_METADATA,
|
GUARDS_METADATA,
|
||||||
INJECTABLE_WATERMARK,
|
INJECTABLE_WATERMARK,
|
||||||
@@ -38,7 +35,13 @@ import {
|
|||||||
} from '@nestjs/common/utils/shared.utils';
|
} from '@nestjs/common/utils/shared.utils';
|
||||||
import { iterate } from 'iterare';
|
import { iterate } from 'iterare';
|
||||||
import { ApplicationConfig } from './application-config';
|
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 { CircularDependencyException } from './errors/exceptions/circular-dependency.exception';
|
||||||
import { InvalidClassModuleException } from './errors/exceptions/invalid-class-module.exception';
|
import { InvalidClassModuleException } from './errors/exceptions/invalid-class-module.exception';
|
||||||
import { InvalidModuleException } from './errors/exceptions/invalid-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 { InstanceWrapper } from './injector/instance-wrapper';
|
||||||
import { InternalCoreModuleFactory } from './injector/internal-core-module/internal-core-module-factory';
|
import { InternalCoreModuleFactory } from './injector/internal-core-module/internal-core-module-factory';
|
||||||
import { Module } from './injector/module';
|
import { Module } from './injector/module';
|
||||||
|
import { GraphInspector } from './inspector/graph-inspector';
|
||||||
import { MetadataScanner } from './metadata-scanner';
|
import { MetadataScanner } from './metadata-scanner';
|
||||||
|
|
||||||
interface ApplicationProviderWrapper {
|
interface ApplicationProviderWrapper {
|
||||||
@@ -64,6 +68,7 @@ export class DependenciesScanner {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly container: NestContainer,
|
private readonly container: NestContainer,
|
||||||
private readonly metadataScanner: MetadataScanner,
|
private readonly metadataScanner: MetadataScanner,
|
||||||
|
private readonly graphInspector: GraphInspector,
|
||||||
private readonly applicationConfig = new ApplicationConfig(),
|
private readonly applicationConfig = new ApplicationConfig(),
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -75,6 +80,8 @@ export class DependenciesScanner {
|
|||||||
|
|
||||||
this.addScopedEnhancersMetadata();
|
this.addScopedEnhancersMetadata();
|
||||||
this.container.bindGlobalScope();
|
this.container.bindGlobalScope();
|
||||||
|
|
||||||
|
this.graphInspector.inspectModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async scanForModules(
|
public async scanForModules(
|
||||||
@@ -211,15 +218,15 @@ export class DependenciesScanner {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public reflectDynamicMetadata(obj: Type<Injectable>, token: string) {
|
public reflectDynamicMetadata(cls: Type<Injectable>, token: string) {
|
||||||
if (!obj || !obj.prototype) {
|
if (!cls || !cls.prototype) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.reflectInjectables(obj, token, GUARDS_METADATA);
|
this.reflectInjectables(cls, token, GUARDS_METADATA);
|
||||||
this.reflectInjectables(obj, token, INTERCEPTORS_METADATA);
|
this.reflectInjectables(cls, token, INTERCEPTORS_METADATA);
|
||||||
this.reflectInjectables(obj, token, EXCEPTION_FILTERS_METADATA);
|
this.reflectInjectables(cls, token, EXCEPTION_FILTERS_METADATA);
|
||||||
this.reflectInjectables(obj, token, PIPES_METADATA);
|
this.reflectInjectables(cls, token, PIPES_METADATA);
|
||||||
this.reflectParamInjectables(obj, token, ROUTE_ARGS_METADATA);
|
this.reflectParamInjectables(cls, token, ROUTE_ARGS_METADATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
public reflectExports(module: Type<unknown>, token: string) {
|
public reflectExports(module: Type<unknown>, token: string) {
|
||||||
@@ -240,22 +247,37 @@ export class DependenciesScanner {
|
|||||||
token: string,
|
token: string,
|
||||||
metadataKey: string,
|
metadataKey: string,
|
||||||
) {
|
) {
|
||||||
const controllerInjectables = this.reflectMetadata(metadataKey, component);
|
const controllerInjectables = this.reflectMetadata<Type<Injectable>>(
|
||||||
const methodsInjectables = this.metadataScanner.scanFromPrototype(
|
metadataKey,
|
||||||
|
component,
|
||||||
|
);
|
||||||
|
const methodInjectables = this.metadataScanner.scanFromPrototype<
|
||||||
|
Type,
|
||||||
|
{ methodKey: string; metadata: Type<Injectable>[] }
|
||||||
|
>(
|
||||||
null,
|
null,
|
||||||
component.prototype,
|
component.prototype,
|
||||||
this.reflectKeyMetadata.bind(this, component, metadataKey),
|
this.reflectKeyMetadata.bind(this, component, metadataKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
const flattenMethodsInjectables = this.flatten(methodsInjectables);
|
controllerInjectables.forEach(injectable =>
|
||||||
const combinedInjectables = [
|
this.insertInjectable(
|
||||||
...controllerInjectables,
|
injectable,
|
||||||
...flattenMethodsInjectables,
|
token,
|
||||||
].filter(isFunction);
|
component,
|
||||||
const injectables = Array.from(new Set(combinedInjectables));
|
ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey],
|
||||||
|
),
|
||||||
injectables.forEach(injectable =>
|
);
|
||||||
this.insertInjectable(injectable, token, component),
|
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,
|
token: string,
|
||||||
metadataKey: string,
|
metadataKey: string,
|
||||||
) {
|
) {
|
||||||
const paramsMetadata = this.metadataScanner.scanFromPrototype(
|
const paramsMetadata = this.metadataScanner.scanFromPrototype<
|
||||||
null,
|
Type,
|
||||||
component.prototype,
|
{
|
||||||
method => Reflect.getMetadata(metadataKey, component, method),
|
methodKey: string;
|
||||||
);
|
metadata: Record<
|
||||||
const paramsInjectables = this.flatten(paramsMetadata).map(
|
string,
|
||||||
(param: Record<string, any>) =>
|
{
|
||||||
flatten(Object.keys(param).map(k => param[k].pipes)).filter(isFunction),
|
index: number;
|
||||||
);
|
data: unknown;
|
||||||
flatten(paramsInjectables).forEach((injectable: Type<Injectable>) =>
|
pipes: Array<Type<PipeTransform> | PipeTransform>;
|
||||||
this.insertInjectable(injectable, token, component),
|
}
|
||||||
);
|
>;
|
||||||
|
}
|
||||||
|
>(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(
|
public reflectKeyMetadata(
|
||||||
component: Type<Injectable>,
|
component: Type<Injectable>,
|
||||||
key: string,
|
key: string,
|
||||||
method: string,
|
methodKey: string,
|
||||||
) {
|
) {
|
||||||
let prototype = component.prototype;
|
let prototype = component.prototype;
|
||||||
do {
|
do {
|
||||||
const descriptor = Reflect.getOwnPropertyDescriptor(prototype, method);
|
const descriptor = Reflect.getOwnPropertyDescriptor(prototype, methodKey);
|
||||||
if (!descriptor) {
|
if (!descriptor) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
return Reflect.getMetadata(key, descriptor.value);
|
const metadata = Reflect.getMetadata(key, descriptor.value);
|
||||||
|
if (!metadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return { methodKey, metadata };
|
||||||
} while (
|
} while (
|
||||||
(prototype = Reflect.getPrototypeOf(prototype)) &&
|
(prototype = Reflect.getPrototypeOf(prototype)) &&
|
||||||
prototype !== Object.prototype &&
|
prototype !== Object.prototype &&
|
||||||
@@ -298,7 +348,7 @@ export class DependenciesScanner {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async calculateModulesDistance() {
|
public calculateModulesDistance() {
|
||||||
const modulesGenerator = this.container.getModules().values();
|
const modulesGenerator = this.container.getModules().values();
|
||||||
|
|
||||||
// Skip "InternalCoreModule" from calculating distance
|
// Skip "InternalCoreModule" from calculating distance
|
||||||
@@ -306,7 +356,7 @@ export class DependenciesScanner {
|
|||||||
|
|
||||||
const modulesStack = [];
|
const modulesStack = [];
|
||||||
const calculateDistance = (moduleRef: Module, distance = 1) => {
|
const calculateDistance = (moduleRef: Module, distance = 1) => {
|
||||||
if (modulesStack.includes(moduleRef)) {
|
if (!moduleRef || modulesStack.includes(moduleRef)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
modulesStack.push(moduleRef);
|
modulesStack.push(moduleRef);
|
||||||
@@ -383,21 +433,56 @@ export class DependenciesScanner {
|
|||||||
scope,
|
scope,
|
||||||
} as Provider;
|
} 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
|
const factoryOrClassProvider = newProvider as
|
||||||
| FactoryProvider
|
| FactoryProvider
|
||||||
| ClassProvider;
|
| ClassProvider;
|
||||||
if (this.isRequestOrTransient(factoryOrClassProvider.scope)) {
|
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(
|
public insertInjectable(
|
||||||
injectable: Type<Injectable>,
|
injectable: Type<Injectable> | object,
|
||||||
token: string,
|
token: string,
|
||||||
host: Type<Injectable>,
|
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(
|
public insertExportedProvider(
|
||||||
@@ -411,7 +496,10 @@ export class DependenciesScanner {
|
|||||||
this.container.addController(controller, token);
|
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) || [];
|
return Reflect.getMetadata(metadataKey, metatype) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +509,7 @@ export class DependenciesScanner {
|
|||||||
this,
|
this,
|
||||||
this.container.getModuleCompiler(),
|
this.container.getModuleCompiler(),
|
||||||
this.container.getHttpAdapterHostRef(),
|
this.container.getHttpAdapterHostRef(),
|
||||||
|
this.graphInspector,
|
||||||
);
|
);
|
||||||
const [instance] = await this.scanForModules(moduleDefinition);
|
const [instance] = await this.scanForModules(moduleDefinition);
|
||||||
this.container.registerCoreModuleRef(instance);
|
this.container.registerCoreModuleRef(instance);
|
||||||
@@ -471,6 +560,8 @@ export class DependenciesScanner {
|
|||||||
providerKey,
|
providerKey,
|
||||||
'injectables',
|
'injectables',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.graphInspector.insertAttachedEnhancer(instanceWrapper);
|
||||||
return applyRequestProvidersMap[type as string](instanceWrapper);
|
return applyRequestProvidersMap[type as string](instanceWrapper);
|
||||||
}
|
}
|
||||||
instanceWrapper = getInstanceWrapper(
|
instanceWrapper = getInstanceWrapper(
|
||||||
@@ -478,6 +569,7 @@ export class DependenciesScanner {
|
|||||||
providerKey,
|
providerKey,
|
||||||
'providers',
|
'providers',
|
||||||
);
|
);
|
||||||
|
this.graphInspector.insertAttachedEnhancer(instanceWrapper);
|
||||||
applyProvidersMap[type as string](instanceWrapper.instance);
|
applyProvidersMap[type as string](instanceWrapper.instance);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -545,10 +637,6 @@ export class DependenciesScanner {
|
|||||||
return module && !!(module as ForwardReference).forwardRef;
|
return module && !!(module as ForwardReference).forwardRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private flatten<T = any>(arr: T[][]): T[] {
|
|
||||||
return arr.flat(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isRequestOrTransient(scope: Scope): boolean {
|
private isRequestOrTransient(scope: Scope): boolean {
|
||||||
return scope === Scope.REQUEST || scope === Scope.TRANSIENT;
|
return scope === Scope.REQUEST || scope === Scope.TRANSIENT;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,51 +6,64 @@ import { NestContainer } from '../../injector/container';
|
|||||||
import { Injector } from '../../injector/injector';
|
import { Injector } from '../../injector/injector';
|
||||||
import { InstanceLoader } from '../../injector/instance-loader';
|
import { InstanceLoader } from '../../injector/instance-loader';
|
||||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||||
|
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||||
|
|
||||||
describe('InstanceLoader', () => {
|
describe('InstanceLoader', () => {
|
||||||
let loader: InstanceLoader;
|
|
||||||
let container: NestContainer;
|
|
||||||
let mockContainer: sinon.SinonMock;
|
|
||||||
|
|
||||||
@Controller('')
|
@Controller('')
|
||||||
class TestRoute {}
|
class TestCtrl {}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class TestProvider {}
|
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(() => {
|
beforeEach(() => {
|
||||||
container = new NestContainer();
|
container = new NestContainer();
|
||||||
loader = new InstanceLoader(container);
|
graphInspector = new GraphInspector(container);
|
||||||
mockContainer = sinon.mock(container);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call "loadPrototype" for each provider and route in each module', async () => {
|
inspectInstanceWrapperStub = sinon.stub(
|
||||||
const injector = new Injector();
|
graphInspector,
|
||||||
|
'inspectInstanceWrapper',
|
||||||
|
);
|
||||||
|
|
||||||
|
loader = new InstanceLoader(container, graphInspector);
|
||||||
|
injector = new Injector();
|
||||||
(loader as any).injector = injector;
|
(loader as any).injector = injector;
|
||||||
|
|
||||||
const module = {
|
mockContainer = sinon.mock(container);
|
||||||
|
|
||||||
|
moduleMock = {
|
||||||
providers: new Map(),
|
providers: new Map(),
|
||||||
controllers: new Map(),
|
controllers: new Map(),
|
||||||
injectables: new Map(),
|
injectables: new Map(),
|
||||||
metatype: { name: 'test' },
|
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 = {
|
const providerWrapper: InstanceWrapper = {
|
||||||
instance: null,
|
instance: null,
|
||||||
metatype: TestProvider,
|
metatype: TestProvider,
|
||||||
token: 'TestProvider',
|
token: 'TestProvider',
|
||||||
} as any;
|
} as any;
|
||||||
const routeWrapper: InstanceWrapper = {
|
const ctrlWrapper: InstanceWrapper = {
|
||||||
instance: null,
|
instance: null,
|
||||||
metatype: TestRoute,
|
metatype: TestCtrl,
|
||||||
token: 'TestRoute',
|
token: 'TestRoute',
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
module.providers.set('TestProvider', providerWrapper);
|
moduleMock.providers.set('TestProvider', providerWrapper);
|
||||||
module.controllers.set('TestRoute', routeWrapper);
|
moduleMock.controllers.set('TestRoute', ctrlWrapper);
|
||||||
|
|
||||||
const modules = new Map();
|
|
||||||
modules.set('Test', module);
|
|
||||||
mockContainer.expects('getModules').returns(modules);
|
|
||||||
|
|
||||||
const loadProviderPrototypeStub = sinon.stub(injector, 'loadPrototype');
|
const loadProviderPrototypeStub = sinon.stub(injector, 'loadPrototype');
|
||||||
|
|
||||||
@@ -58,113 +71,125 @@ describe('InstanceLoader', () => {
|
|||||||
sinon.stub(injector, 'loadProvider');
|
sinon.stub(injector, 'loadProvider');
|
||||||
|
|
||||||
await loader.createInstancesOfDependencies();
|
await loader.createInstancesOfDependencies();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
loadProviderPrototypeStub.calledWith(providerWrapper, module.providers),
|
loadProviderPrototypeStub.calledWith(
|
||||||
|
providerWrapper,
|
||||||
|
moduleMock.providers,
|
||||||
|
),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
expect(
|
expect(
|
||||||
loadProviderPrototypeStub.calledWith(routeWrapper, module.controllers),
|
loadProviderPrototypeStub.calledWith(ctrlWrapper, moduleMock.controllers),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call "loadProvider" for each provider in each module', async () => {
|
describe('for every provider in every module', () => {
|
||||||
const injector = new Injector();
|
const testProviderToken = 'TestProvider';
|
||||||
(loader as any).injector = injector;
|
|
||||||
|
|
||||||
const module = {
|
let loadProviderStub: sinon.SinonStub;
|
||||||
providers: new Map(),
|
|
||||||
controllers: new Map(),
|
beforeEach(async () => {
|
||||||
injectables: new Map(),
|
const testProviderWrapper = new InstanceWrapper({
|
||||||
metatype: { name: 'test' },
|
instance: null,
|
||||||
};
|
metatype: TestProvider,
|
||||||
const testComp = new InstanceWrapper({
|
name: testProviderToken,
|
||||||
instance: null,
|
token: testProviderToken,
|
||||||
metatype: TestProvider,
|
});
|
||||||
name: 'TestProvider',
|
moduleMock.providers.set(testProviderToken, testProviderWrapper);
|
||||||
token: 'TestProvider',
|
|
||||||
|
loadProviderStub = sinon.stub(injector, 'loadProvider');
|
||||||
|
sinon.stub(injector, 'loadController');
|
||||||
|
|
||||||
|
await loader.createInstancesOfDependencies();
|
||||||
});
|
});
|
||||||
module.providers.set('TestProvider', testComp);
|
|
||||||
|
|
||||||
const modules = new Map();
|
it('should call "loadProvider"', async () => {
|
||||||
modules.set('Test', module);
|
expect(
|
||||||
mockContainer.expects('getModules').returns(modules);
|
loadProviderStub.calledWith(
|
||||||
|
moduleMock.providers.get(testProviderToken),
|
||||||
|
moduleMock as any,
|
||||||
|
),
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
const loadProviderStub = sinon.stub(injector, 'loadProvider');
|
it('should call "inspectInstanceWrapper"', async () => {
|
||||||
sinon.stub(injector, 'loadController');
|
expect(
|
||||||
|
inspectInstanceWrapperStub.calledWith(
|
||||||
await loader.createInstancesOfDependencies();
|
moduleMock.providers.get(testProviderToken),
|
||||||
expect(
|
moduleMock as any,
|
||||||
loadProviderStub.calledWith(
|
),
|
||||||
module.providers.get('TestProvider'),
|
).to.be.true;
|
||||||
module as any,
|
});
|
||||||
),
|
|
||||||
).to.be.true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call "loadController" for each route in each module', async () => {
|
describe('for every controller in every module', () => {
|
||||||
const injector = new Injector();
|
let loadControllerStub: sinon.SinonStub;
|
||||||
(loader as any).injector = injector;
|
|
||||||
|
|
||||||
const module = {
|
beforeEach(async () => {
|
||||||
providers: new Map(),
|
const wrapper = new InstanceWrapper({
|
||||||
controllers: new Map(),
|
name: 'TestRoute',
|
||||||
injectables: new Map(),
|
token: 'TestRoute',
|
||||||
metatype: { name: 'test' },
|
instance: null,
|
||||||
};
|
metatype: TestCtrl,
|
||||||
const wrapper = new InstanceWrapper({
|
});
|
||||||
name: 'TestRoute',
|
moduleMock.controllers.set('TestRoute', wrapper);
|
||||||
token: 'TestRoute',
|
|
||||||
instance: null,
|
sinon.stub(injector, 'loadProvider');
|
||||||
metatype: TestRoute,
|
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 () => {
|
describe('for every injectable in every module', () => {
|
||||||
const injector = new Injector();
|
let loadInjectableStub: sinon.SinonStub;
|
||||||
(loader as any).injector = injector;
|
|
||||||
|
|
||||||
const module = {
|
beforeEach(async () => {
|
||||||
providers: new Map(),
|
const testInjectable = new InstanceWrapper({
|
||||||
controllers: new Map(),
|
instance: null,
|
||||||
injectables: new Map(),
|
metatype: TestProvider,
|
||||||
metatype: { name: 'test' },
|
name: 'TestProvider',
|
||||||
};
|
token: 'TestProvider',
|
||||||
const testComp = new InstanceWrapper({
|
});
|
||||||
instance: null,
|
moduleMock.injectables.set('TestProvider', testInjectable);
|
||||||
metatype: TestProvider,
|
|
||||||
name: 'TestProvider',
|
loadInjectableStub = sinon.stub(injector, 'loadInjectable');
|
||||||
token: 'TestProvider',
|
sinon.stub(injector, 'loadController');
|
||||||
|
|
||||||
|
await loader.createInstancesOfDependencies();
|
||||||
});
|
});
|
||||||
module.injectables.set('TestProvider', testComp);
|
|
||||||
|
|
||||||
const modules = new Map();
|
it('should call "loadInjectable"', async () => {
|
||||||
modules.set('Test', module);
|
expect(
|
||||||
mockContainer.expects('getModules').returns(modules);
|
loadInjectableStub.calledWith(
|
||||||
|
moduleMock.injectables.get('TestProvider'),
|
||||||
const loadInjectableStub = sinon.stub(injector, 'loadInjectable');
|
moduleMock as any,
|
||||||
sinon.stub(injector, 'loadController');
|
),
|
||||||
|
).to.be.true;
|
||||||
await loader.createInstancesOfDependencies();
|
});
|
||||||
expect(
|
it('should call "inspectInstanceWrapper"', async () => {
|
||||||
loadInjectableStub.calledWith(
|
expect(
|
||||||
module.injectables.get('TestProvider'),
|
inspectInstanceWrapperStub.calledWith(
|
||||||
module as any,
|
moduleMock.injectables.get('TestProvider'),
|
||||||
),
|
moduleMock as any,
|
||||||
).to.be.true;
|
),
|
||||||
|
).to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ describe('InternalCoreModuleFactory', () => {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(moduleDefinition.module).to.equal(InternalCoreModule);
|
expect(moduleDefinition.module).to.equal(InternalCoreModule);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
NestContainer,
|
NestContainer,
|
||||||
} from '../../../injector';
|
} from '../../../injector';
|
||||||
import { InstanceLoader } from '../../../injector/instance-loader';
|
import { InstanceLoader } from '../../../injector/instance-loader';
|
||||||
|
import { GraphInspector } from '../../../inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '../../../metadata-scanner';
|
import { MetadataScanner } from '../../../metadata-scanner';
|
||||||
import { DependenciesScanner } from '../../../scanner';
|
import { DependenciesScanner } from '../../../scanner';
|
||||||
|
|
||||||
@@ -18,15 +19,23 @@ describe('LazyModuleLoader', () => {
|
|||||||
|
|
||||||
class NoopLogger {
|
class NoopLogger {
|
||||||
log() {}
|
log() {}
|
||||||
|
error() {}
|
||||||
|
warn() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const nestContainer = new NestContainer();
|
const nestContainer = new NestContainer();
|
||||||
|
const graphInspector = new GraphInspector(nestContainer);
|
||||||
dependenciesScanner = new DependenciesScanner(
|
dependenciesScanner = new DependenciesScanner(
|
||||||
nestContainer,
|
nestContainer,
|
||||||
new MetadataScanner(),
|
new MetadataScanner(),
|
||||||
|
graphInspector,
|
||||||
|
);
|
||||||
|
instanceLoader = new InstanceLoader(
|
||||||
|
nestContainer,
|
||||||
|
graphInspector,
|
||||||
|
new NoopLogger(),
|
||||||
);
|
);
|
||||||
instanceLoader = new InstanceLoader(nestContainer, new NoopLogger() as any);
|
|
||||||
modulesContainer = nestContainer.getModules();
|
modulesContainer = nestContainer.getModules();
|
||||||
lazyModuleLoader = new LazyModuleLoader(
|
lazyModuleLoader = new LazyModuleLoader(
|
||||||
dependenciesScanner,
|
dependenciesScanner,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ describe('Module', () => {
|
|||||||
const setSpy = sinon.spy(collection, 'set');
|
const setSpy = sinon.spy(collection, 'set');
|
||||||
(module as any)._injectables = collection;
|
(module as any)._injectables = collection;
|
||||||
|
|
||||||
module.addInjectable(TestProvider, TestModule);
|
module.addInjectable(TestProvider, 'interceptor', TestModule);
|
||||||
expect(
|
expect(
|
||||||
setSpy.calledWith(
|
setSpy.calledWith(
|
||||||
TestProvider,
|
TestProvider,
|
||||||
@@ -69,6 +69,7 @@ describe('Module', () => {
|
|||||||
instance: null,
|
instance: null,
|
||||||
durable: undefined,
|
durable: undefined,
|
||||||
isResolved: false,
|
isResolved: false,
|
||||||
|
subtype: 'interceptor',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
@@ -78,7 +79,7 @@ describe('Module', () => {
|
|||||||
it('should call `addCustomProvider`', () => {
|
it('should call `addCustomProvider`', () => {
|
||||||
const addCustomProviderSpy = sinon.spy(module, '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;
|
expect(addCustomProviderSpy.called).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -180,6 +181,7 @@ describe('Module', () => {
|
|||||||
durable: true,
|
durable: true,
|
||||||
instance: null,
|
instance: null,
|
||||||
isResolved: false,
|
isResolved: false,
|
||||||
|
subtype: undefined,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
@@ -211,6 +213,7 @@ describe('Module', () => {
|
|||||||
instance: value,
|
instance: value,
|
||||||
isResolved: true,
|
isResolved: true,
|
||||||
async: false,
|
async: false,
|
||||||
|
subtype: undefined,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
@@ -244,6 +247,7 @@ describe('Module', () => {
|
|||||||
instance: null,
|
instance: null,
|
||||||
isResolved: false,
|
isResolved: false,
|
||||||
inject: inject as any,
|
inject: inject as any,
|
||||||
|
subtype: undefined,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
@@ -279,6 +283,7 @@ describe('Module', () => {
|
|||||||
inject: [provider.useExisting as any],
|
inject: [provider.useExisting as any],
|
||||||
isResolved: false,
|
isResolved: false,
|
||||||
isAlias: true,
|
isAlias: true,
|
||||||
|
subtype: undefined,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).to.be.true;
|
).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 { Injectable } from '@nestjs/common';
|
||||||
import { RoutePathFactory } from '@nestjs/core/router/route-path-factory';
|
import { RoutePathFactory } from '@nestjs/core/router/route-path-factory';
|
||||||
|
import * as chai from 'chai';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import * as chaiAsPromised from 'chai-as-promised';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { Controller } from '../../../common/decorators/core/controller.decorator';
|
import { Controller } from '../../../common/decorators/core/controller.decorator';
|
||||||
import { RequestMapping } from '../../../common/decorators/http/request-mapping.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 { NestContainer } from '../../injector/container';
|
||||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||||
import { Module } from '../../injector/module';
|
import { Module } from '../../injector/module';
|
||||||
|
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||||
import { MiddlewareBuilder } from '../../middleware/builder';
|
import { MiddlewareBuilder } from '../../middleware/builder';
|
||||||
import { MiddlewareContainer } from '../../middleware/container';
|
import { MiddlewareContainer } from '../../middleware/container';
|
||||||
import { MiddlewareModule } from '../../middleware/middleware-module';
|
import { MiddlewareModule } from '../../middleware/middleware-module';
|
||||||
import { RouterExceptionFilters } from '../../router/router-exception-filters';
|
import { RouterExceptionFilters } from '../../router/router-exception-filters';
|
||||||
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
||||||
|
|
||||||
|
chai.use(chaiAsPromised);
|
||||||
|
|
||||||
describe('MiddlewareModule', () => {
|
describe('MiddlewareModule', () => {
|
||||||
let middlewareModule: MiddlewareModule;
|
let middlewareModule: MiddlewareModule;
|
||||||
|
let graphInspector: GraphInspector;
|
||||||
|
|
||||||
@Controller('test')
|
@Controller('test')
|
||||||
class BasicController {}
|
class BasicController {}
|
||||||
@@ -39,15 +45,18 @@ describe('MiddlewareModule', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
const container = new NestContainer();
|
||||||
const appConfig = new ApplicationConfig();
|
const appConfig = new ApplicationConfig();
|
||||||
|
|
||||||
|
graphInspector = new GraphInspector(container);
|
||||||
middlewareModule = new MiddlewareModule(new RoutePathFactory(appConfig));
|
middlewareModule = new MiddlewareModule(new RoutePathFactory(appConfig));
|
||||||
(middlewareModule as any).routerExceptionFilter =
|
middlewareModule['routerExceptionFilter'] = new RouterExceptionFilters(
|
||||||
new RouterExceptionFilters(
|
container,
|
||||||
new NestContainer(),
|
appConfig,
|
||||||
appConfig,
|
new NoopHttpAdapter({}),
|
||||||
new NoopHttpAdapter({}),
|
);
|
||||||
);
|
middlewareModule['config'] = appConfig;
|
||||||
(middlewareModule as any).config = appConfig;
|
middlewareModule['graphInspector'] = graphInspector;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadConfiguration', () => {
|
describe('loadConfiguration', () => {
|
||||||
@@ -195,5 +204,68 @@ describe('MiddlewareModule', () => {
|
|||||||
);
|
);
|
||||||
expect(createMiddlewareFactoryStub.calledOnce).to.be.true;
|
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 { expect } from 'chai';
|
||||||
import { InjectionToken, Logger, Scope } from '@nestjs/common';
|
|
||||||
import { ContextIdFactory } from '../helpers/context-id-factory';
|
import { ContextIdFactory } from '../helpers/context-id-factory';
|
||||||
import { InstanceLoader } from '../injector/instance-loader';
|
|
||||||
import { NestContainer } from '../injector/container';
|
import { NestContainer } from '../injector/container';
|
||||||
|
import { InstanceLoader } from '../injector/instance-loader';
|
||||||
|
import { GraphInspector } from '../inspector/graph-inspector';
|
||||||
import { NestApplicationContext } from '../nest-application-context';
|
import { NestApplicationContext } from '../nest-application-context';
|
||||||
|
|
||||||
describe('NestApplicationContext', () => {
|
describe('NestApplicationContext', () => {
|
||||||
@@ -13,7 +14,10 @@ describe('NestApplicationContext', () => {
|
|||||||
scope: Scope,
|
scope: Scope,
|
||||||
): Promise<NestApplicationContext> {
|
): Promise<NestApplicationContext> {
|
||||||
const nestContainer = new NestContainer();
|
const nestContainer = new NestContainer();
|
||||||
const instanceLoader = new InstanceLoader(nestContainer);
|
const instanceLoader = new InstanceLoader(
|
||||||
|
nestContainer,
|
||||||
|
new GraphInspector(nestContainer),
|
||||||
|
);
|
||||||
const module = await nestContainer.addModule(class T {}, []);
|
const module = await nestContainer.addModule(class T {}, []);
|
||||||
|
|
||||||
nestContainer.addProvider(
|
nestContainer.addProvider(
|
||||||
@@ -32,6 +36,7 @@ describe('NestApplicationContext', () => {
|
|||||||
scope,
|
scope,
|
||||||
},
|
},
|
||||||
module.token,
|
module.token,
|
||||||
|
'interceptor',
|
||||||
);
|
);
|
||||||
|
|
||||||
const modules = nestContainer.getModules();
|
const modules = nestContainer.getModules();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { ApplicationConfig } from '../application-config';
|
import { ApplicationConfig } from '../application-config';
|
||||||
import { NestContainer } from '../injector/container';
|
import { NestContainer } from '../injector/container';
|
||||||
|
import { GraphInspector } from '../inspector/graph-inspector';
|
||||||
import { NestApplication } from '../nest-application';
|
import { NestApplication } from '../nest-application';
|
||||||
import { NoopHttpAdapter } from './utils/noop-adapter.spec';
|
import { NoopHttpAdapter } from './utils/noop-adapter.spec';
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ describe('NestApplication', () => {
|
|||||||
container,
|
container,
|
||||||
new NoopHttpAdapter({}),
|
new NoopHttpAdapter({}),
|
||||||
applicationConfig,
|
applicationConfig,
|
||||||
|
new GraphInspector(container),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
instance.useGlobalInterceptors(new Interceptor());
|
instance.useGlobalInterceptors(new Interceptor());
|
||||||
@@ -36,6 +38,7 @@ describe('NestApplication', () => {
|
|||||||
container,
|
container,
|
||||||
new NoopHttpAdapter({}),
|
new NoopHttpAdapter({}),
|
||||||
applicationConfig,
|
applicationConfig,
|
||||||
|
new GraphInspector(container),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
instance.useGlobalInterceptors(new Interceptor());
|
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 { VersioningType } from '../../../common/enums/version-type.enum';
|
||||||
import { Injector } from '../../../core/injector/injector';
|
import { Injector } from '../../../core/injector/injector';
|
||||||
import { ApplicationConfig } from '../../application-config';
|
import { ApplicationConfig } from '../../application-config';
|
||||||
|
import { UnknownRequestMappingException } from '../../errors/exceptions/unknown-request-mapping.exception';
|
||||||
import { ExecutionContextHost } from '../../helpers/execution-context-host';
|
import { ExecutionContextHost } from '../../helpers/execution-context-host';
|
||||||
import { NestContainer } from '../../injector/container';
|
import { NestContainer } from '../../injector/container';
|
||||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
||||||
|
import { GraphInspector } from '../../inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '../../metadata-scanner';
|
import { MetadataScanner } from '../../metadata-scanner';
|
||||||
import { RoutePathMetadata } from '../../router/interfaces/route-path-metadata.interface';
|
import { RoutePathMetadata } from '../../router/interfaces/route-path-metadata.interface';
|
||||||
import { RoutePathFactory } from '../../router/route-path-factory';
|
import { RoutePathFactory } from '../../router/route-path-factory';
|
||||||
import { RouterExceptionFilters } from '../../router/router-exception-filters';
|
import { RouterExceptionFilters } from '../../router/router-exception-filters';
|
||||||
import { RouterExplorer } from '../../router/router-explorer';
|
import { RouterExplorer } from '../../router/router-explorer';
|
||||||
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
||||||
import { UnknownRequestMappingException } from '../../errors/exceptions/unknown-request-mapping.exception';
|
|
||||||
|
|
||||||
describe('RouterExplorer', () => {
|
describe('RouterExplorer', () => {
|
||||||
@Controller('global')
|
@Controller('global')
|
||||||
@@ -59,6 +60,7 @@ describe('RouterExplorer', () => {
|
|||||||
let exceptionsFilter: RouterExceptionFilters;
|
let exceptionsFilter: RouterExceptionFilters;
|
||||||
let applicationConfig: ApplicationConfig;
|
let applicationConfig: ApplicationConfig;
|
||||||
let routePathFactory: RoutePathFactory;
|
let routePathFactory: RoutePathFactory;
|
||||||
|
let graphInspector: GraphInspector;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const container = new NestContainer();
|
const container = new NestContainer();
|
||||||
@@ -66,6 +68,7 @@ describe('RouterExplorer', () => {
|
|||||||
applicationConfig = new ApplicationConfig();
|
applicationConfig = new ApplicationConfig();
|
||||||
injector = new Injector();
|
injector = new Injector();
|
||||||
routePathFactory = new RoutePathFactory(applicationConfig);
|
routePathFactory = new RoutePathFactory(applicationConfig);
|
||||||
|
graphInspector = new GraphInspector(container);
|
||||||
exceptionsFilter = new RouterExceptionFilters(
|
exceptionsFilter = new RouterExceptionFilters(
|
||||||
container,
|
container,
|
||||||
applicationConfig,
|
applicationConfig,
|
||||||
@@ -79,179 +82,10 @@ describe('RouterExplorer', () => {
|
|||||||
exceptionsFilter,
|
exceptionsFilter,
|
||||||
applicationConfig,
|
applicationConfig,
|
||||||
routePathFactory,
|
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', () => {
|
describe('applyPathsToRouterProxy', () => {
|
||||||
it('should method return expected object which represent single route', () => {
|
it('should method return expected object which represent single route', () => {
|
||||||
const bindStub = sinon.stub(
|
const bindStub = sinon.stub(
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { ApplicationConfig } from '../../application-config';
|
|||||||
import { NestContainer } from '../../injector';
|
import { NestContainer } from '../../injector';
|
||||||
import { Injector } from '../../injector/injector';
|
import { Injector } from '../../injector/injector';
|
||||||
import { InstanceWrapper } from '../../injector/instance-wrapper';
|
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 { RoutesResolver } from '../../router/routes-resolver';
|
||||||
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
|
||||||
|
|
||||||
@@ -65,6 +67,7 @@ describe('RoutesResolver', () => {
|
|||||||
getModules: () => modules,
|
getModules: () => modules,
|
||||||
getModuleByKey: (key: string) => modules.get(key),
|
getModuleByKey: (key: string) => modules.get(key),
|
||||||
getHttpAdapterRef: () => applicationRef,
|
getHttpAdapterRef: () => applicationRef,
|
||||||
|
serializedGraph: new SerializedGraph(),
|
||||||
} as any;
|
} as any;
|
||||||
router = {
|
router = {
|
||||||
get() {},
|
get() {},
|
||||||
@@ -77,6 +80,7 @@ describe('RoutesResolver', () => {
|
|||||||
container,
|
container,
|
||||||
new ApplicationConfig(),
|
new ApplicationConfig(),
|
||||||
new Injector(),
|
new Injector(),
|
||||||
|
new GraphInspector(container),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -175,6 +179,7 @@ describe('RoutesResolver', () => {
|
|||||||
container,
|
container,
|
||||||
applicationConfig,
|
applicationConfig,
|
||||||
new Injector(),
|
new Injector(),
|
||||||
|
new GraphInspector(container),
|
||||||
);
|
);
|
||||||
|
|
||||||
const routes = new Map();
|
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 { expect } from 'chai';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import { GUARDS_METADATA } from '../../common/constants';
|
import { GUARDS_METADATA } from '../../common/constants';
|
||||||
@@ -8,13 +8,15 @@ import { Module } from '../../common/decorators/modules/module.decorator';
|
|||||||
import { Scope } from '../../common/interfaces';
|
import { Scope } from '../../common/interfaces';
|
||||||
import { ApplicationConfig } from '../application-config';
|
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 } from '../constants';
|
||||||
import { InvalidModuleException } from '../errors/exceptions/invalid-module.exception';
|
|
||||||
import { InvalidClassModuleException } from '../errors/exceptions/invalid-class-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 { UndefinedModuleException } from '../errors/exceptions/undefined-module.exception';
|
||||||
import { NestContainer } from '../injector/container';
|
import { NestContainer } from '../injector/container';
|
||||||
import { InstanceWrapper } from '../injector/instance-wrapper';
|
import { InstanceWrapper } from '../injector/instance-wrapper';
|
||||||
|
import { GraphInspector } from '../inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '../metadata-scanner';
|
import { MetadataScanner } from '../metadata-scanner';
|
||||||
import { DependenciesScanner } from '../scanner';
|
import { DependenciesScanner } from '../scanner';
|
||||||
|
import Sinon = require('sinon');
|
||||||
|
|
||||||
describe('DependenciesScanner', () => {
|
describe('DependenciesScanner', () => {
|
||||||
class Guard {}
|
class Guard {}
|
||||||
@@ -55,14 +57,17 @@ describe('DependenciesScanner', () => {
|
|||||||
let scanner: DependenciesScanner;
|
let scanner: DependenciesScanner;
|
||||||
let mockContainer: sinon.SinonMock;
|
let mockContainer: sinon.SinonMock;
|
||||||
let container: NestContainer;
|
let container: NestContainer;
|
||||||
|
let graphInspector: GraphInspector;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = new NestContainer();
|
container = new NestContainer();
|
||||||
mockContainer = sinon.mock(container);
|
mockContainer = sinon.mock(container);
|
||||||
|
graphInspector = new GraphInspector(container);
|
||||||
|
|
||||||
scanner = new DependenciesScanner(
|
scanner = new DependenciesScanner(
|
||||||
container,
|
container,
|
||||||
new MetadataScanner(),
|
new MetadataScanner(),
|
||||||
|
graphInspector,
|
||||||
new ApplicationConfig(),
|
new ApplicationConfig(),
|
||||||
);
|
);
|
||||||
sinon.stub(scanner, 'registerCoreModule').callsFake(async () => {});
|
sinon.stub(scanner, 'registerCoreModule').callsFake(async () => {});
|
||||||
@@ -75,7 +80,7 @@ describe('DependenciesScanner', () => {
|
|||||||
it('should "insertModule" call twice (2 modules) container method "addModule"', async () => {
|
it('should "insertModule" call twice (2 modules) container method "addModule"', async () => {
|
||||||
const expectation = mockContainer.expects('addModule').twice();
|
const expectation = mockContainer.expects('addModule').twice();
|
||||||
|
|
||||||
await scanner.scan(TestModule as any);
|
await scanner.scan(TestModule);
|
||||||
expectation.verify();
|
expectation.verify();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,34 +139,115 @@ describe('DependenciesScanner', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('insertInjectable', () => {
|
describe('insertInjectable', () => {
|
||||||
it('should call "addInjectable"', () => {
|
class InjectableCls {}
|
||||||
const addInjectable = sinon
|
class HostCls {}
|
||||||
.stub((scanner as any).container, 'addInjectable')
|
|
||||||
.callsFake(() => undefined);
|
|
||||||
const comp = {};
|
|
||||||
const token = 'token';
|
|
||||||
|
|
||||||
scanner.insertInjectable(comp as any, token, null);
|
const instanceWrapper = { id: 'random_id' };
|
||||||
expect(addInjectable.calledWith(comp, token)).to.be.true;
|
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 {
|
class CompMethod {
|
||||||
@UseGuards(Guard)
|
@UseGuards(Guard)
|
||||||
public method() {}
|
public method() {}
|
||||||
|
|
||||||
|
@UseGuards(Guard, Guard)
|
||||||
|
public method2() {}
|
||||||
}
|
}
|
||||||
describe('reflectKeyMetadata', () => {
|
describe('reflectKeyMetadata', () => {
|
||||||
it('should return undefined', () => {
|
it('should return undefined', () => {
|
||||||
const result = scanner.reflectKeyMetadata(TestComponent, 'key', 'method');
|
const result = scanner.reflectKeyMetadata(TestComponent, 'key', 'method');
|
||||||
expect(result).to.be.undefined;
|
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(
|
const result = scanner.reflectKeyMetadata(
|
||||||
CompMethod,
|
CompMethod,
|
||||||
GUARDS_METADATA,
|
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];
|
(scanner as any).applicationProvidersApplyMap = [provider];
|
||||||
|
|
||||||
const expectedInstance = {};
|
const expectedInstance = {};
|
||||||
|
const instanceWrapper = {
|
||||||
|
instance: expectedInstance,
|
||||||
|
} as unknown as InstanceWrapper;
|
||||||
mockContainer.expects('getModules').callsFake(() => ({
|
mockContainer.expects('getModules').callsFake(() => ({
|
||||||
get: () => ({
|
get: () => ({
|
||||||
providers: { get: () => ({ instance: expectedInstance }) },
|
providers: { get: () => instanceWrapper },
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const applySpy = sinon.spy();
|
const applySpy = sinon.spy();
|
||||||
sinon.stub(scanner, 'getApplyProvidersMap').callsFake(() => ({
|
sinon.stub(scanner, 'getApplyProvidersMap').callsFake(() => ({
|
||||||
[provider.type]: applySpy,
|
[provider.type]: applySpy,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const insertAttachedEnhancerStub = sinon.stub(
|
||||||
|
graphInspector,
|
||||||
|
'insertAttachedEnhancer',
|
||||||
|
);
|
||||||
|
|
||||||
scanner.applyApplicationProviders();
|
scanner.applyApplicationProviders();
|
||||||
|
|
||||||
expect(applySpy.called).to.be.true;
|
expect(applySpy.called).to.be.true;
|
||||||
expect(applySpy.calledWith(expectedInstance)).to.be.true;
|
expect(applySpy.calledWith(expectedInstance)).to.be.true;
|
||||||
|
expect(insertAttachedEnhancerStub.calledWith(instanceWrapper)).to.be.true;
|
||||||
});
|
});
|
||||||
it('should apply each globally scoped provider', () => {
|
it('should apply each globally scoped provider', () => {
|
||||||
const provider = {
|
const provider = {
|
||||||
@@ -340,13 +438,23 @@ describe('DependenciesScanner', () => {
|
|||||||
injectables: { get: () => expectedInstanceWrapper },
|
injectables: { get: () => expectedInstanceWrapper },
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const applySpy = sinon.spy();
|
const applySpy = sinon.spy();
|
||||||
sinon.stub(scanner, 'getApplyRequestProvidersMap').callsFake(() => ({
|
sinon.stub(scanner, 'getApplyRequestProvidersMap').callsFake(() => ({
|
||||||
[provider.type]: applySpy,
|
[provider.type]: applySpy,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const insertAttachedEnhancerStub = sinon.stub(
|
||||||
|
graphInspector,
|
||||||
|
'insertAttachedEnhancer',
|
||||||
|
);
|
||||||
|
|
||||||
scanner.applyApplicationProviders();
|
scanner.applyApplicationProviders();
|
||||||
|
|
||||||
expect(applySpy.called).to.be.true;
|
expect(applySpy.called).to.be.true;
|
||||||
expect(applySpy.calledWith(expectedInstanceWrapper)).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,
|
InstanceWrapper,
|
||||||
} from '@nestjs/core/injector/instance-wrapper';
|
} from '@nestjs/core/injector/instance-wrapper';
|
||||||
import { Module } from '@nestjs/core/injector/module';
|
import { Module } from '@nestjs/core/injector/module';
|
||||||
|
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||||
import { REQUEST_CONTEXT_ID } from '@nestjs/core/router/request/request-constants';
|
import { REQUEST_CONTEXT_ID } from '@nestjs/core/router/request/request-constants';
|
||||||
import { connectable, Observable, Subject } from 'rxjs';
|
import { connectable, Observable, Subject } from 'rxjs';
|
||||||
@@ -24,13 +25,18 @@ import {
|
|||||||
DEFAULT_GRPC_CALLBACK_METADATA,
|
DEFAULT_GRPC_CALLBACK_METADATA,
|
||||||
} from './context/rpc-metadata-constants';
|
} from './context/rpc-metadata-constants';
|
||||||
import { BaseRpcContext } from './ctx-host/base-rpc.context';
|
import { BaseRpcContext } from './ctx-host/base-rpc.context';
|
||||||
|
import { Transport } from './enums';
|
||||||
import {
|
import {
|
||||||
CustomTransportStrategy,
|
CustomTransportStrategy,
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
PatternMetadata,
|
PatternMetadata,
|
||||||
RequestContext,
|
RequestContext,
|
||||||
} from './interfaces';
|
} 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 { ServerGrpc } from './server';
|
||||||
import { Server } from './server/server';
|
import { Server } from './server/server';
|
||||||
|
|
||||||
@@ -47,6 +53,7 @@ export class ListenersController {
|
|||||||
private readonly injector: Injector,
|
private readonly injector: Injector,
|
||||||
private readonly clientFactory: IClientProxyFactory,
|
private readonly clientFactory: IClientProxyFactory,
|
||||||
private readonly exceptionFiltersContext: ExceptionFiltersContext,
|
private readonly exceptionFiltersContext: ExceptionFiltersContext,
|
||||||
|
private readonly graphInspector: GraphInspector,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public registerPatternHandlers(
|
public registerPatternHandlers(
|
||||||
@@ -78,61 +85,91 @@ export class ListenersController {
|
|||||||
);
|
);
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
.forEach(
|
.forEach((definition: EventOrMessageListenerDefinition) => {
|
||||||
({
|
const {
|
||||||
patterns: [pattern],
|
patterns: [pattern],
|
||||||
targetCallback,
|
targetCallback,
|
||||||
methodKey,
|
methodKey,
|
||||||
extras,
|
extras,
|
||||||
isEventHandler,
|
isEventHandler,
|
||||||
}) => {
|
} = definition;
|
||||||
if (isStatic) {
|
|
||||||
const proxy = this.contextCreator.create(
|
this.insertEntrypointDefinition(
|
||||||
instance as object,
|
instanceWrapper,
|
||||||
targetCallback,
|
definition,
|
||||||
moduleKey,
|
server.transportId,
|
||||||
methodKey,
|
);
|
||||||
STATIC_CONTEXT,
|
|
||||||
undefined,
|
if (isStatic) {
|
||||||
defaultCallMetadata,
|
const proxy = this.contextCreator.create(
|
||||||
);
|
instance as object,
|
||||||
if (isEventHandler) {
|
targetCallback,
|
||||||
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,
|
moduleKey,
|
||||||
methodKey,
|
methodKey,
|
||||||
|
STATIC_CONTEXT,
|
||||||
|
undefined,
|
||||||
defaultCallMetadata,
|
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) {
|
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 { NestContainer } from '@nestjs/core/injector/container';
|
||||||
import { Injector } from '@nestjs/core/injector/injector';
|
import { Injector } from '@nestjs/core/injector/injector';
|
||||||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
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 { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer';
|
||||||
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
|
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
|
||||||
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
|
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
|
||||||
@@ -23,7 +24,11 @@ export class MicroservicesModule {
|
|||||||
private readonly clientsContainer = new ClientsContainer();
|
private readonly clientsContainer = new ClientsContainer();
|
||||||
private listenersController: ListenersController;
|
private listenersController: ListenersController;
|
||||||
|
|
||||||
public register(container: NestContainer, config: ApplicationConfig) {
|
public register(
|
||||||
|
container: NestContainer,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
|
config: ApplicationConfig,
|
||||||
|
) {
|
||||||
const exceptionFiltersContext = new ExceptionFiltersContext(
|
const exceptionFiltersContext = new ExceptionFiltersContext(
|
||||||
container,
|
container,
|
||||||
config,
|
config,
|
||||||
@@ -47,6 +52,7 @@ export class MicroservicesModule {
|
|||||||
injector,
|
injector,
|
||||||
ClientProxyFactory,
|
ClientProxyFactory,
|
||||||
exceptionFiltersContext,
|
exceptionFiltersContext,
|
||||||
|
graphInspector,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { ApplicationConfig } from '@nestjs/core/application-config';
|
|||||||
import { MESSAGES } from '@nestjs/core/constants';
|
import { MESSAGES } from '@nestjs/core/constants';
|
||||||
import { optionalRequire } from '@nestjs/core/helpers/optional-require';
|
import { optionalRequire } from '@nestjs/core/helpers/optional-require';
|
||||||
import { NestContainer } from '@nestjs/core/injector/container';
|
import { NestContainer } from '@nestjs/core/injector/container';
|
||||||
|
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||||
import { NestApplicationContext } from '@nestjs/core/nest-application-context';
|
import { NestApplicationContext } from '@nestjs/core/nest-application-context';
|
||||||
import { Transport } from './enums/transport.enum';
|
import { Transport } from './enums/transport.enum';
|
||||||
import { CustomTransportStrategy } from './interfaces/custom-transport-strategy.interface';
|
import { CustomTransportStrategy } from './interfaces/custom-transport-strategy.interface';
|
||||||
@@ -42,11 +43,16 @@ export class NestMicroservice
|
|||||||
constructor(
|
constructor(
|
||||||
container: NestContainer,
|
container: NestContainer,
|
||||||
config: NestMicroserviceOptions & MicroserviceOptions = {},
|
config: NestMicroserviceOptions & MicroserviceOptions = {},
|
||||||
|
private readonly graphInspector: GraphInspector,
|
||||||
private readonly applicationConfig: ApplicationConfig,
|
private readonly applicationConfig: ApplicationConfig,
|
||||||
) {
|
) {
|
||||||
super(container);
|
super(container);
|
||||||
|
|
||||||
this.microservicesModule.register(container, this.applicationConfig);
|
this.microservicesModule.register(
|
||||||
|
container,
|
||||||
|
this.graphInspector,
|
||||||
|
this.applicationConfig,
|
||||||
|
);
|
||||||
this.createServer(config);
|
this.createServer(config);
|
||||||
this.selectContextModule();
|
this.selectContextModule();
|
||||||
}
|
}
|
||||||
@@ -92,21 +98,45 @@ export class NestMicroservice
|
|||||||
|
|
||||||
public useGlobalFilters(...filters: ExceptionFilter[]): this {
|
public useGlobalFilters(...filters: ExceptionFilter[]): this {
|
||||||
this.applicationConfig.useGlobalFilters(...filters);
|
this.applicationConfig.useGlobalFilters(...filters);
|
||||||
|
filters.forEach(item =>
|
||||||
|
this.graphInspector.insertStaticEnhancer({
|
||||||
|
subtype: 'filter',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGlobalPipes(...pipes: PipeTransform<any>[]): this {
|
public useGlobalPipes(...pipes: PipeTransform<any>[]): this {
|
||||||
this.applicationConfig.useGlobalPipes(...pipes);
|
this.applicationConfig.useGlobalPipes(...pipes);
|
||||||
|
pipes.forEach(item =>
|
||||||
|
this.graphInspector.insertStaticEnhancer({
|
||||||
|
subtype: 'pipe',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGlobalInterceptors(...interceptors: NestInterceptor[]): this {
|
public useGlobalInterceptors(...interceptors: NestInterceptor[]): this {
|
||||||
this.applicationConfig.useGlobalInterceptors(...interceptors);
|
this.applicationConfig.useGlobalInterceptors(...interceptors);
|
||||||
|
interceptors.forEach(item =>
|
||||||
|
this.graphInspector.insertStaticEnhancer({
|
||||||
|
subtype: 'interceptor',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public useGlobalGuards(...guards: CanActivate[]): this {
|
public useGlobalGuards(...guards: CanActivate[]): this {
|
||||||
this.applicationConfig.useGlobalGuards(...guards);
|
this.applicationConfig.useGlobalGuards(...guards);
|
||||||
|
guards.forEach(item =>
|
||||||
|
this.graphInspector.insertStaticEnhancer({
|
||||||
|
subtype: 'guard',
|
||||||
|
ref: item,
|
||||||
|
}),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,17 @@ import { Injector } from '@nestjs/core/injector/injector';
|
|||||||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
|
import { GraphInspector } from '../../core/inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '../../core/metadata-scanner';
|
import { MetadataScanner } from '../../core/metadata-scanner';
|
||||||
import { ClientProxyFactory } from '../client';
|
import { ClientProxyFactory } from '../client';
|
||||||
import { ClientsContainer } from '../container';
|
import { ClientsContainer } from '../container';
|
||||||
import { ExceptionFiltersContext } from '../context/exception-filters-context';
|
import { ExceptionFiltersContext } from '../context/exception-filters-context';
|
||||||
import { RpcContextCreator } from '../context/rpc-context-creator';
|
import { RpcContextCreator } from '../context/rpc-context-creator';
|
||||||
import { Transport } from '../enums/transport.enum';
|
import { Transport } from '../enums/transport.enum';
|
||||||
import { ListenerMetadataExplorer } from '../listener-metadata-explorer';
|
import {
|
||||||
|
EventOrMessageListenerDefinition,
|
||||||
|
ListenerMetadataExplorer,
|
||||||
|
} from '../listener-metadata-explorer';
|
||||||
import { ListenersController } from '../listeners-controller';
|
import { ListenersController } from '../listeners-controller';
|
||||||
|
|
||||||
describe('ListenersController', () => {
|
describe('ListenersController', () => {
|
||||||
@@ -28,6 +32,7 @@ describe('ListenersController', () => {
|
|||||||
addSpyCustom: sinon.SinonSpy,
|
addSpyCustom: sinon.SinonSpy,
|
||||||
proxySpy: sinon.SinonSpy,
|
proxySpy: sinon.SinonSpy,
|
||||||
container: NestContainer,
|
container: NestContainer,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
rpcContextCreator: RpcContextCreator,
|
rpcContextCreator: RpcContextCreator,
|
||||||
exceptionFiltersContext: ExceptionFiltersContext;
|
exceptionFiltersContext: ExceptionFiltersContext;
|
||||||
@@ -38,6 +43,7 @@ describe('ListenersController', () => {
|
|||||||
});
|
});
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = new NestContainer();
|
container = new NestContainer();
|
||||||
|
graphInspector = new GraphInspector(container);
|
||||||
injector = new Injector();
|
injector = new Injector();
|
||||||
exceptionFiltersContext = new ExceptionFiltersContext(
|
exceptionFiltersContext = new ExceptionFiltersContext(
|
||||||
container,
|
container,
|
||||||
@@ -46,6 +52,7 @@ describe('ListenersController', () => {
|
|||||||
rpcContextCreator = sinon.createStubInstance(RpcContextCreator) as any;
|
rpcContextCreator = sinon.createStubInstance(RpcContextCreator) as any;
|
||||||
proxySpy = sinon.spy();
|
proxySpy = sinon.spy();
|
||||||
(rpcContextCreator as any).create.callsFake(() => proxySpy);
|
(rpcContextCreator as any).create.callsFake(() => proxySpy);
|
||||||
|
|
||||||
instance = new ListenersController(
|
instance = new ListenersController(
|
||||||
new ClientsContainer(),
|
new ClientsContainer(),
|
||||||
rpcContextCreator,
|
rpcContextCreator,
|
||||||
@@ -53,6 +60,7 @@ describe('ListenersController', () => {
|
|||||||
injector,
|
injector,
|
||||||
ClientProxyFactory,
|
ClientProxyFactory,
|
||||||
exceptionFiltersContext,
|
exceptionFiltersContext,
|
||||||
|
graphInspector,
|
||||||
);
|
);
|
||||||
(instance as any).metadataExplorer = metadataExplorer;
|
(instance as any).metadataExplorer = metadataExplorer;
|
||||||
addSpy = sinon.spy();
|
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', () => {
|
describe('assignClientToInstance', () => {
|
||||||
it('should assign client to instance', () => {
|
it('should assign client to instance', () => {
|
||||||
const propertyKey = 'key';
|
const propertyKey = 'key';
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Logger, LoggerService, Module } from '@nestjs/common';
|
|||||||
import { ModuleMetadata } from '@nestjs/common/interfaces';
|
import { ModuleMetadata } from '@nestjs/common/interfaces';
|
||||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||||
import { NestContainer } from '@nestjs/core/injector/container';
|
import { NestContainer } from '@nestjs/core/injector/container';
|
||||||
|
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||||
import { DependenciesScanner } from '@nestjs/core/scanner';
|
import { DependenciesScanner } from '@nestjs/core/scanner';
|
||||||
import {
|
import {
|
||||||
@@ -16,9 +17,13 @@ import { TestingModule } from './testing-module';
|
|||||||
export class TestingModuleBuilder {
|
export class TestingModuleBuilder {
|
||||||
private readonly applicationConfig = new ApplicationConfig();
|
private readonly applicationConfig = new ApplicationConfig();
|
||||||
private readonly container = new NestContainer(this.applicationConfig);
|
private readonly container = new NestContainer(this.applicationConfig);
|
||||||
|
private readonly graphInspector = new GraphInspector(this.container);
|
||||||
private readonly overloadsMap = new Map();
|
private readonly overloadsMap = new Map();
|
||||||
|
private readonly instanceLoader = new TestingInstanceLoader(
|
||||||
|
this.container,
|
||||||
|
this.graphInspector,
|
||||||
|
);
|
||||||
private readonly scanner: DependenciesScanner;
|
private readonly scanner: DependenciesScanner;
|
||||||
private readonly instanceLoader = new TestingInstanceLoader(this.container);
|
|
||||||
private readonly module: any;
|
private readonly module: any;
|
||||||
private testingLogger: LoggerService;
|
private testingLogger: LoggerService;
|
||||||
private mocker?: MockFactory;
|
private mocker?: MockFactory;
|
||||||
@@ -27,6 +32,7 @@ export class TestingModuleBuilder {
|
|||||||
this.scanner = new DependenciesScanner(
|
this.scanner = new DependenciesScanner(
|
||||||
this.container,
|
this.container,
|
||||||
metadataScanner,
|
metadataScanner,
|
||||||
|
this.graphInspector,
|
||||||
this.applicationConfig,
|
this.applicationConfig,
|
||||||
);
|
);
|
||||||
this.module = this.createModule(metadata);
|
this.module = this.createModule(metadata);
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ import {
|
|||||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||||
import { NestContainer } from '@nestjs/core/injector/container';
|
import { NestContainer } from '@nestjs/core/injector/container';
|
||||||
import { Module } from '@nestjs/core/injector/module';
|
import { Module } from '@nestjs/core/injector/module';
|
||||||
|
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||||
|
|
||||||
export class TestingModule extends NestApplicationContext {
|
export class TestingModule extends NestApplicationContext {
|
||||||
|
protected readonly graphInspector = new GraphInspector(this.container);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: NestContainer,
|
container: NestContainer,
|
||||||
scope: Type<any>[],
|
scope: Type<any>[],
|
||||||
@@ -65,6 +68,7 @@ export class TestingModule extends NestApplicationContext {
|
|||||||
this.container,
|
this.container,
|
||||||
httpAdapter,
|
httpAdapter,
|
||||||
this.applicationConfig,
|
this.applicationConfig,
|
||||||
|
this.graphInspector,
|
||||||
appOptions,
|
appOptions,
|
||||||
);
|
);
|
||||||
return this.createAdapterProxy<T>(instance, httpAdapter);
|
return this.createAdapterProxy<T>(instance, httpAdapter);
|
||||||
@@ -82,6 +86,7 @@ export class TestingModule extends NestApplicationContext {
|
|||||||
return new NestMicroservice(
|
return new NestMicroservice(
|
||||||
this.container,
|
this.container,
|
||||||
options,
|
options,
|
||||||
|
this.graphInspector,
|
||||||
this.applicationConfig,
|
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 { NestContainer } from '@nestjs/core/injector/container';
|
||||||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
||||||
import { InstanceToken } from '@nestjs/core/injector/module';
|
import { InstanceToken } from '@nestjs/core/injector/module';
|
||||||
|
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||||
import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer';
|
import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer';
|
||||||
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
|
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
|
||||||
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
|
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
|
||||||
@@ -30,21 +31,23 @@ export class SocketModule<HttpServer = any> {
|
|||||||
|
|
||||||
public register(
|
public register(
|
||||||
container: NestContainer,
|
container: NestContainer,
|
||||||
config: ApplicationConfig,
|
applicationConfig: ApplicationConfig,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
httpServer?: HttpServer,
|
httpServer?: HttpServer,
|
||||||
) {
|
) {
|
||||||
this.applicationConfig = config;
|
this.applicationConfig = applicationConfig;
|
||||||
this.httpServer = httpServer;
|
this.httpServer = httpServer;
|
||||||
|
|
||||||
const contextCreator = this.getContextCreator(container);
|
const contextCreator = this.getContextCreator(container);
|
||||||
const serverProvider = new SocketServerProvider(
|
const serverProvider = new SocketServerProvider(
|
||||||
this.socketsContainer,
|
this.socketsContainer,
|
||||||
config,
|
applicationConfig,
|
||||||
);
|
);
|
||||||
this.webSocketsController = new WebSocketsController(
|
this.webSocketsController = new WebSocketsController(
|
||||||
serverProvider,
|
serverProvider,
|
||||||
config,
|
applicationConfig,
|
||||||
contextCreator,
|
contextCreator,
|
||||||
|
graphInspector,
|
||||||
);
|
);
|
||||||
const modules = container.getModules();
|
const modules = container.getModules();
|
||||||
modules.forEach(({ providers }, moduleName: string) =>
|
modules.forEach(({ providers }, moduleName: string) =>
|
||||||
@@ -77,6 +80,7 @@ export class SocketModule<HttpServer = any> {
|
|||||||
instance as NestGateway,
|
instance as NestGateway,
|
||||||
metatype,
|
metatype,
|
||||||
moduleName,
|
moduleName,
|
||||||
|
wrapper.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
|
import { NestContainer } from '@nestjs/core';
|
||||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { fromEvent, lastValueFrom, Observable, of } from 'rxjs';
|
import { fromEvent, lastValueFrom, Observable, of } from 'rxjs';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
|
import { GraphInspector } from '../../core/inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '../../core/metadata-scanner';
|
import { MetadataScanner } from '../../core/metadata-scanner';
|
||||||
import { AbstractWsAdapter } from '../adapters/ws-adapter';
|
import { AbstractWsAdapter } from '../adapters/ws-adapter';
|
||||||
import { PORT_METADATA } from '../constants';
|
import { PORT_METADATA } from '../constants';
|
||||||
import { WsContextCreator } from '../context/ws-context-creator';
|
import { WsContextCreator } from '../context/ws-context-creator';
|
||||||
import { WebSocketGateway } from '../decorators/socket-gateway.decorator';
|
import { WebSocketGateway } from '../decorators/socket-gateway.decorator';
|
||||||
import { InvalidSocketPortException } from '../errors/invalid-socket-port.exception';
|
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 { SocketServerProvider } from '../socket-server-provider';
|
||||||
import { WebSocketsController } from '../web-sockets-controller';
|
import { WebSocketsController } from '../web-sockets-controller';
|
||||||
|
|
||||||
@@ -29,8 +34,10 @@ class NoopAdapter extends AbstractWsAdapter {
|
|||||||
describe('WebSocketsController', () => {
|
describe('WebSocketsController', () => {
|
||||||
let instance: WebSocketsController;
|
let instance: WebSocketsController;
|
||||||
let provider: SocketServerProvider,
|
let provider: SocketServerProvider,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
config: ApplicationConfig,
|
config: ApplicationConfig,
|
||||||
mockProvider: sinon.SinonMock;
|
mockProvider: sinon.SinonMock;
|
||||||
|
|
||||||
const messageHandlerCallback = () => Promise.resolve();
|
const messageHandlerCallback = () => Promise.resolve();
|
||||||
const port = 90,
|
const port = 90,
|
||||||
namespace = '/';
|
namespace = '/';
|
||||||
@@ -40,6 +47,7 @@ describe('WebSocketsController', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config = new ApplicationConfig(new NoopAdapter());
|
config = new ApplicationConfig(new NoopAdapter());
|
||||||
provider = new SocketServerProvider(null, config);
|
provider = new SocketServerProvider(null, config);
|
||||||
|
graphInspector = new GraphInspector(new NestContainer());
|
||||||
mockProvider = sinon.mock(provider);
|
mockProvider = sinon.mock(provider);
|
||||||
|
|
||||||
const contextCreator = sinon.createStubInstance(WsContextCreator);
|
const contextCreator = sinon.createStubInstance(WsContextCreator);
|
||||||
@@ -48,6 +56,7 @@ describe('WebSocketsController', () => {
|
|||||||
provider,
|
provider,
|
||||||
config,
|
config,
|
||||||
contextCreator as any,
|
contextCreator as any,
|
||||||
|
graphInspector,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
describe('connectGatewayToServer', () => {
|
describe('connectGatewayToServer', () => {
|
||||||
@@ -69,20 +78,37 @@ describe('WebSocketsController', () => {
|
|||||||
instance.connectGatewayToServer(
|
instance.connectGatewayToServer(
|
||||||
new InvalidGateway(),
|
new InvalidGateway(),
|
||||||
InvalidGateway,
|
InvalidGateway,
|
||||||
'',
|
'moduleKey',
|
||||||
|
'instanceWrapperId',
|
||||||
),
|
),
|
||||||
).throws(InvalidSocketPortException);
|
).throws(InvalidSocketPortException);
|
||||||
});
|
});
|
||||||
it('should call "subscribeToServerEvents" with default values when metadata is empty', () => {
|
it('should call "subscribeToServerEvents" with default values when metadata is empty', () => {
|
||||||
const gateway = new DefaultGateway();
|
const gateway = new DefaultGateway();
|
||||||
instance.connectGatewayToServer(gateway, DefaultGateway, '');
|
instance.connectGatewayToServer(
|
||||||
expect(subscribeToServerEvents.calledWith(gateway, {}, 0, '')).to.be.true;
|
gateway,
|
||||||
|
DefaultGateway,
|
||||||
|
'moduleKey',
|
||||||
|
'instanceWrapperId',
|
||||||
|
);
|
||||||
|
expect(subscribeToServerEvents.calledWith(gateway, {}, 0, 'moduleKey')).to
|
||||||
|
.be.true;
|
||||||
});
|
});
|
||||||
it('should call "subscribeToServerEvents" when metadata is valid', () => {
|
it('should call "subscribeToServerEvents" when metadata is valid', () => {
|
||||||
const gateway = new Test();
|
const gateway = new Test();
|
||||||
instance.connectGatewayToServer(gateway, Test, '');
|
instance.connectGatewayToServer(
|
||||||
|
gateway,
|
||||||
|
Test,
|
||||||
|
'moduleKey',
|
||||||
|
'instanceWrapperId',
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
subscribeToServerEvents.calledWith(gateway, { namespace }, port, ''),
|
subscribeToServerEvents.calledWith(
|
||||||
|
gateway,
|
||||||
|
{ namespace },
|
||||||
|
port,
|
||||||
|
'moduleKey',
|
||||||
|
),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -116,16 +142,28 @@ describe('WebSocketsController', () => {
|
|||||||
|
|
||||||
assignServerToProperties = sinon.spy();
|
assignServerToProperties = sinon.spy();
|
||||||
subscribeEvents = sinon.spy();
|
subscribeEvents = sinon.spy();
|
||||||
(instance as any).assignServerToProperties = assignServerToProperties;
|
instance['assignServerToProperties'] = assignServerToProperties;
|
||||||
(instance as any).subscribeEvents = subscribeEvents;
|
instance['subscribeEvents'] = subscribeEvents;
|
||||||
});
|
});
|
||||||
it('should call "assignServerToProperties" with expected arguments', () => {
|
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
|
expect(assignServerToProperties.calledWith(gateway, server.server)).to.be
|
||||||
.true;
|
.true;
|
||||||
});
|
});
|
||||||
it('should call "subscribeEvents" with expected arguments', () => {
|
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[0]).to.be.equal(gateway);
|
||||||
expect(subscribeEvents.firstCall.args[2]).to.be.equal(server);
|
expect(subscribeEvents.firstCall.args[2]).to.be.equal(server);
|
||||||
expect(subscribeEvents.firstCall.args[1]).to.be.eql([
|
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', () => {
|
describe('subscribeEvents', () => {
|
||||||
const gateway = new Test();
|
const gateway = new Test();
|
||||||
|
|
||||||
let handlers;
|
let handlers: any;
|
||||||
let server,
|
let server: any,
|
||||||
subscribeConnectionEvent: sinon.SinonSpy,
|
subscribeConnectionEvent: sinon.SinonSpy,
|
||||||
subscribeDisconnectEvent: sinon.SinonSpy,
|
subscribeDisconnectEvent: sinon.SinonSpy,
|
||||||
nextSpy: sinon.SinonSpy,
|
nextSpy: sinon.SinonSpy,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Type } from '@nestjs/common/interfaces/type.interface';
|
import { Type } from '@nestjs/common/interfaces/type.interface';
|
||||||
import { Logger } from '@nestjs/common/services/logger.service';
|
import { Logger } from '@nestjs/common/services/logger.service';
|
||||||
import { ApplicationConfig } from '@nestjs/core/application-config';
|
import { ApplicationConfig } from '@nestjs/core/application-config';
|
||||||
|
import { GraphInspector } from '@nestjs/core/inspector/graph-inspector';
|
||||||
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
|
||||||
import {
|
import {
|
||||||
from as fromPromise,
|
from as fromPromise,
|
||||||
Observable,
|
|
||||||
isObservable,
|
isObservable,
|
||||||
|
Observable,
|
||||||
of,
|
of,
|
||||||
Subject,
|
Subject,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
import { GatewayMetadata } from './interfaces/gateway-metadata.interface';
|
import { GatewayMetadata } from './interfaces/gateway-metadata.interface';
|
||||||
import { NestGateway } from './interfaces/nest-gateway.interface';
|
import { NestGateway } from './interfaces/nest-gateway.interface';
|
||||||
import { ServerAndEventStreamsHost } from './interfaces/server-and-event-streams-host.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 { SocketServerProvider } from './socket-server-provider';
|
||||||
import { compareElementAt } from './utils/compare-element.util';
|
import { compareElementAt } from './utils/compare-element.util';
|
||||||
|
|
||||||
@@ -35,12 +37,14 @@ export class WebSocketsController {
|
|||||||
private readonly socketServerProvider: SocketServerProvider,
|
private readonly socketServerProvider: SocketServerProvider,
|
||||||
private readonly config: ApplicationConfig,
|
private readonly config: ApplicationConfig,
|
||||||
private readonly contextCreator: WsContextCreator,
|
private readonly contextCreator: WsContextCreator,
|
||||||
|
private readonly graphInspector: GraphInspector,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public connectGatewayToServer(
|
public connectGatewayToServer(
|
||||||
instance: NestGateway,
|
instance: NestGateway,
|
||||||
metatype: Type<unknown> | Function,
|
metatype: Type<unknown> | Function,
|
||||||
moduleKey: string,
|
moduleKey: string,
|
||||||
|
instanceWrapperId: string,
|
||||||
) {
|
) {
|
||||||
const options = Reflect.getMetadata(GATEWAY_OPTIONS, metatype) || {};
|
const options = Reflect.getMetadata(GATEWAY_OPTIONS, metatype) || {};
|
||||||
const port = Reflect.getMetadata(PORT_METADATA, metatype) || 0;
|
const port = Reflect.getMetadata(PORT_METADATA, metatype) || 0;
|
||||||
@@ -48,7 +52,13 @@ export class WebSocketsController {
|
|||||||
if (!Number.isInteger(port)) {
|
if (!Number.isInteger(port)) {
|
||||||
throw new InvalidSocketPortException(port, metatype);
|
throw new InvalidSocketPortException(port, metatype);
|
||||||
}
|
}
|
||||||
this.subscribeToServerEvents(instance, options, port, moduleKey);
|
this.subscribeToServerEvents(
|
||||||
|
instance,
|
||||||
|
options,
|
||||||
|
port,
|
||||||
|
moduleKey,
|
||||||
|
instanceWrapperId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscribeToServerEvents<T extends GatewayMetadata>(
|
public subscribeToServerEvents<T extends GatewayMetadata>(
|
||||||
@@ -56,6 +66,7 @@ export class WebSocketsController {
|
|||||||
options: T,
|
options: T,
|
||||||
port: number,
|
port: number,
|
||||||
moduleKey: string,
|
moduleKey: string,
|
||||||
|
instanceWrapperId: string,
|
||||||
) {
|
) {
|
||||||
const nativeMessageHandlers = this.metadataExplorer.explore(instance);
|
const nativeMessageHandlers = this.metadataExplorer.explore(instance);
|
||||||
const messageHandlers = nativeMessageHandlers.map(
|
const messageHandlers = nativeMessageHandlers.map(
|
||||||
@@ -76,6 +87,13 @@ export class WebSocketsController {
|
|||||||
);
|
);
|
||||||
this.assignServerToProperties(instance, observableServer.server);
|
this.assignServerToProperties(instance, observableServer.server);
|
||||||
this.subscribeEvents(instance, messageHandlers, observableServer);
|
this.subscribeEvents(instance, messageHandlers, observableServer);
|
||||||
|
|
||||||
|
this.inspectEntrypointDefinitions(
|
||||||
|
instance,
|
||||||
|
port,
|
||||||
|
messageHandlers,
|
||||||
|
instanceWrapperId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public subscribeEvents(
|
public subscribeEvents(
|
||||||
@@ -172,6 +190,28 @@ export class WebSocketsController {
|
|||||||
return of(result);
|
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>(
|
private assignServerToProperties<T = any>(
|
||||||
instance: NestGateway,
|
instance: NestGateway,
|
||||||
server: object,
|
server: object,
|
||||||
|
|||||||
Reference in New Issue
Block a user