feat(core): use class refs as keys (container)

This commit is contained in:
Kamil Myśliwiec
2021-01-13 11:24:02 +01:00
parent 5bc15dfda9
commit cc6d48b4c3
27 changed files with 275 additions and 239 deletions

View File

@@ -1,6 +1,6 @@
import { CanActivate } from '@nestjs/common';
import { GUARDS_METADATA } from '@nestjs/common/constants';
import { Controller } from '@nestjs/common/interfaces';
import { Controller, Type } from '@nestjs/common/interfaces';
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { ApplicationConfig } from '../application-config';
@@ -54,15 +54,17 @@ export class GuardsContextCreator extends ContextCreator {
}
public getGuardInstance(
guard: Function | CanActivate,
metatype: Function | CanActivate,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): CanActivate | null {
const isObject = (guard as CanActivate).canActivate;
const isObject = (metatype as CanActivate).canActivate;
if (isObject) {
return guard as CanActivate;
return metatype as CanActivate;
}
const instanceWrapper = this.getInstanceByMetatype(guard);
const instanceWrapper = this.getInstanceByMetatype(
metatype as Type<unknown>,
);
if (!instanceWrapper) {
return null;
}
@@ -73,8 +75,8 @@ export class GuardsContextCreator extends ContextCreator {
return instanceHost && instanceHost.instance;
}
public getInstanceByMetatype<T extends Record<string, any> | Function>(
guard: T,
public getInstanceByMetatype(
metatype: Type<unknown>,
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return;
@@ -85,7 +87,7 @@ export class GuardsContextCreator extends ContextCreator {
return;
}
const injectables = moduleRef.injectables;
return injectables.get(guard.name as string);
return injectables.get(metatype);
}
public getGlobalMetadata<T extends unknown[]>(

View File

@@ -13,7 +13,6 @@ import { GuardsContextCreator } from '../guards/guards-context-creator';
import { STATIC_CONTEXT } from '../injector/constants';
import { NestContainer } from '../injector/container';
import { ContextId } from '../injector/instance-wrapper';
import { Module } from '../injector/module';
import { ModulesContainer } from '../injector/modules-container';
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
@@ -38,9 +37,7 @@ export interface ExternalContextOptions {
export class ExternalContextCreator {
private readonly contextUtils = new ContextUtils();
private readonly externalErrorProxy = new ExternalErrorProxy();
private readonly handlerMetadataStorage = new HandlerMetadataStorage<
ExternalHandlerMetadata
>();
private readonly handlerMetadataStorage = new HandlerMetadataStorage<ExternalHandlerMetadata>();
private container: NestContainer;
constructor(
@@ -107,7 +104,7 @@ export class ExternalContextCreator {
},
contextType: TContext = 'http' as TContext,
) {
const module = this.getContextModuleName(instance.constructor);
const module = this.getContextModuleKey(instance.constructor);
const { argsLength, paramtypes, getParamsMetadata } = this.getMetadata<
TParamsMetadata,
TContext
@@ -240,26 +237,18 @@ export class ExternalContextCreator {
return handlerMetadata;
}
public getContextModuleName(constructor: Function): string {
const defaultModuleName = '';
const className = constructor.name;
if (!className) {
return defaultModuleName;
public getContextModuleKey(moduleCtor: Function | undefined): string {
const emptyModuleKey = '';
if (!moduleCtor) {
return emptyModuleKey;
}
for (const [key, module] of [...this.modulesContainer.entries()]) {
if (this.getProviderByClassName(module, className)) {
const moduleContainerEntries = this.modulesContainer.entries();
for (const [key, moduleRef] of moduleContainerEntries) {
if (moduleRef.hasProvider(moduleCtor)) {
return key;
}
}
return defaultModuleName;
}
public getProviderByClassName(module: Module, className: string): boolean {
const { providers } = module;
const hasProvider = [...providers.keys()].some(
provider => provider === className,
);
return hasProvider;
return emptyModuleKey;
}
public exchangeKeysForValues<TMetadata = any>(

View File

@@ -143,7 +143,10 @@ export class NestContainer {
moduleRef.addRelatedModule(related);
}
public addProvider(provider: Provider, token: string): string {
public addProvider(
provider: Provider,
token: string,
): string | symbol | Function {
if (!provider) {
throw new CircularDependencyException();
}

View File

@@ -1,12 +1,13 @@
import { iterate } from 'iterare';
import { InstanceWrapper } from '../instance-wrapper';
import { InstanceToken } from '../module';
/**
* Returns the instances which are transient
* @param instances The instances which should be checked whether they are transcient
*/
export function getTransientInstances(
instances: [string, InstanceWrapper][],
instances: [InstanceToken, InstanceWrapper][],
): InstanceWrapper[] {
return iterate(instances)
.filter(([_, wrapper]) => wrapper.isDependencyTreeStatic())
@@ -22,7 +23,7 @@ export function getTransientInstances(
* @param instances The instances which should be checked whether they are transcient
*/
export function getNonTransientInstances(
instances: [string, InstanceWrapper][],
instances: [InstanceToken, InstanceWrapper][],
): InstanceWrapper[] {
return iterate(instances)
.filter(

View File

@@ -26,7 +26,7 @@ import {
InstanceWrapper,
PropertyMetadata,
} from './instance-wrapper';
import { Module } from './module';
import { InstanceToken, Module } from './module';
/**
* The type of an injectable dependency
@@ -69,27 +69,27 @@ export interface InjectorDependencyContext {
export class Injector {
public loadPrototype<T>(
{ name }: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper<T>>,
{ token }: InstanceWrapper<T>,
collection: Map<InstanceToken, InstanceWrapper<T>>,
contextId = STATIC_CONTEXT,
) {
if (!collection) {
return;
}
const target = collection.get(name);
const target = collection.get(token);
const instance = target.createPrototype(contextId);
if (instance) {
const wrapper = new InstanceWrapper({
...target,
instance,
});
collection.set(name, wrapper);
collection.set(token, wrapper);
}
}
public async loadInstance<T>(
wrapper: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper>,
collection: Map<InstanceToken, InstanceWrapper>,
moduleRef: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
@@ -100,9 +100,10 @@ export class Injector {
return instanceHost.donePromise;
}
const done = this.applyDoneHook(instanceHost);
const { name, inject } = wrapper;
const token = wrapper.token || wrapper.name;
const targetWrapper = collection.get(name);
const { inject } = wrapper;
const targetWrapper = collection.get(token);
if (isUndefined(targetWrapper)) {
throw new RuntimeException();
}
@@ -141,13 +142,13 @@ export class Injector {
public async loadMiddleware(
wrapper: InstanceWrapper,
collection: Map<string, InstanceWrapper>,
collection: Map<InstanceToken, InstanceWrapper>,
moduleRef: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const { metatype } = wrapper;
const targetWrapper = collection.get(metatype.name);
const { metatype, token } = wrapper;
const targetWrapper = collection.get(token);
if (!isUndefined(targetWrapper.instance)) {
return;
}
@@ -338,7 +339,7 @@ export class Injector {
public async resolveComponentInstance<T>(
moduleRef: Module,
name: any,
token: InstanceToken,
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
@@ -349,7 +350,7 @@ export class Injector {
const instanceWrapper = await this.lookupComponent(
providers,
moduleRef,
{ ...dependencyContext, name },
{ ...dependencyContext, name: token },
wrapper,
contextId,
inquirer,
@@ -466,7 +467,7 @@ export class Injector {
public async lookupComponentInImports(
moduleRef: Module,
name: any,
name: InstanceToken,
wrapper: InstanceWrapper,
moduleRegistry: any[] = [],
contextId = STATIC_CONTEXT,
@@ -483,7 +484,7 @@ export class Injector {
if (isTraversing) {
const contextModuleExports = moduleRef.exports;
children = children.filter(child =>
contextModuleExports.has(child.metatype && child.metatype.name),
contextModuleExports.has(child.metatype),
);
}
for (const relatedModule of children) {
@@ -655,15 +656,12 @@ export class Injector {
public async loadPerContext<T = any>(
instance: T,
moduleRef: Module,
collection: Map<string, InstanceWrapper>,
collection: Map<InstanceToken, InstanceWrapper>,
ctx: ContextId,
wrapper?: InstanceWrapper,
): Promise<T> {
if (!wrapper) {
const providerCtor = instance.constructor;
const injectionToken =
(providerCtor && providerCtor.name) ||
((providerCtor as unknown) as string);
const injectionToken = instance.constructor;
wrapper = collection.get(injectionToken);
}
await this.loadInstance(wrapper, collection, moduleRef, ctx, wrapper);

View File

@@ -1,11 +1,9 @@
import { Abstract, Type } from '@nestjs/common';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import { UnknownElementException } from '../errors/exceptions/unknown-element.exception';
import { NestContainer } from './container';
import { InstanceWrapper } from './instance-wrapper';
import { Module } from './module';
import { InstanceToken, Module } from './module';
type InstanceToken = string | symbol | Type<any> | Abstract<any> | Function;
type HostCollection = 'providers' | 'controllers' | 'injectables';
export interface InstanceLink<T = any> {
@@ -23,20 +21,17 @@ export class InstanceLinksHost {
}
get<T = any>(token: InstanceToken, moduleId?: string): InstanceLink<T> {
const name = isFunction(token)
? (token as Function).name
: (token as string | symbol);
const modulesMap = this.instanceLinks.get(name);
const modulesMap = this.instanceLinks.get(token);
if (!modulesMap) {
throw new UnknownElementException(name);
throw new UnknownElementException(this.getInstanceNameByToken(token));
}
const instanceLink = moduleId
? modulesMap.find(item => item.moduleId === moduleId)
: modulesMap[modulesMap.length - 1];
if (!instanceLink) {
throw new UnknownElementException(name);
throw new UnknownElementException(this.getInstanceNameByToken(token));
}
return instanceLink;
}
@@ -76,4 +71,8 @@ export class InstanceLinksHost {
existingLinks.push(instanceLink);
}
}
private getInstanceNameByToken(token: InstanceToken): string {
return isFunction(token) ? (token as Function)?.name : (token as string);
}
}

View File

@@ -21,69 +21,69 @@ export class InstanceLoader {
}
private createPrototypes(modules: Map<string, Module>) {
modules.forEach(module => {
this.createPrototypesOfProviders(module);
this.createPrototypesOfInjectables(module);
this.createPrototypesOfControllers(module);
modules.forEach(moduleRef => {
this.createPrototypesOfProviders(moduleRef);
this.createPrototypesOfInjectables(moduleRef);
this.createPrototypesOfControllers(moduleRef);
});
}
private async createInstances(modules: Map<string, Module>) {
await Promise.all(
[...modules.values()].map(async module => {
await this.createInstancesOfProviders(module);
await this.createInstancesOfInjectables(module);
await this.createInstancesOfControllers(module);
[...modules.values()].map(async moduleRef => {
await this.createInstancesOfProviders(moduleRef);
await this.createInstancesOfInjectables(moduleRef);
await this.createInstancesOfControllers(moduleRef);
const { name } = module.metatype;
const { name } = moduleRef.metatype;
this.isModuleWhitelisted(name) &&
this.logger.log(MODULE_INIT_MESSAGE`${name}`);
}),
);
}
private createPrototypesOfProviders(module: Module) {
const { providers } = module;
private createPrototypesOfProviders(moduleRef: Module) {
const { providers } = moduleRef;
providers.forEach(wrapper =>
this.injector.loadPrototype<Injectable>(wrapper, providers),
);
}
private async createInstancesOfProviders(module: Module) {
const { providers } = module;
private async createInstancesOfProviders(moduleRef: Module) {
const { providers } = moduleRef;
const wrappers = [...providers.values()];
await Promise.all(
wrappers.map(item => this.injector.loadProvider(item, module)),
wrappers.map(item => this.injector.loadProvider(item, moduleRef)),
);
}
private createPrototypesOfControllers(module: Module) {
const { controllers } = module;
private createPrototypesOfControllers(moduleRef: Module) {
const { controllers } = moduleRef;
controllers.forEach(wrapper =>
this.injector.loadPrototype<Controller>(wrapper, controllers),
);
}
private async createInstancesOfControllers(module: Module) {
const { controllers } = module;
private async createInstancesOfControllers(moduleRef: Module) {
const { controllers } = moduleRef;
const wrappers = [...controllers.values()];
await Promise.all(
wrappers.map(item => this.injector.loadController(item, module)),
wrappers.map(item => this.injector.loadController(item, moduleRef)),
);
}
private createPrototypesOfInjectables(module: Module) {
const { injectables } = module;
private createPrototypesOfInjectables(moduleRef: Module) {
const { injectables } = moduleRef;
injectables.forEach(wrapper =>
this.injector.loadPrototype(wrapper, injectables),
);
}
private async createInstancesOfInjectables(module: Module) {
const { injectables } = module;
private async createInstancesOfInjectables(moduleRef: Module) {
const { injectables } = moduleRef;
const wrappers = [...injectables.values()];
await Promise.all(
wrappers.map(item => this.injector.loadInjectable(item, module)),
wrappers.map(item => this.injector.loadInjectable(item, moduleRef)),
);
}

View File

@@ -8,7 +8,7 @@ import { randomStringGenerator } from '@nestjs/common/utils/random-string-genera
import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { STATIC_CONTEXT } from './constants';
import { Module } from './module';
import { InstanceToken, Module } from './module';
export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
@@ -36,6 +36,7 @@ interface InstanceMetadataStore {
export class InstanceWrapper<T = any> {
public readonly name: any;
public readonly token: InstanceToken;
public readonly async?: boolean;
public readonly host?: Module;
public readonly isAlias: boolean = false;

View File

@@ -31,21 +31,33 @@ import { NestContainer } from './container';
import { InstanceWrapper } from './instance-wrapper';
import { ModuleRef } from './module-ref';
interface ProviderName {
name?: string | symbol;
}
export type InstanceToken =
| string
| symbol
| Type<any>
| Abstract<any>
| Function;
export class Module {
private readonly _id: string;
private readonly _imports = new Set<Module>();
private readonly _providers = new Map<any, InstanceWrapper<Injectable>>();
private readonly _injectables = new Map<any, InstanceWrapper<Injectable>>();
private readonly _middlewares = new Map<any, InstanceWrapper<Injectable>>();
private readonly _providers = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _injectables = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _middlewares = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _controllers = new Map<
string,
InstanceToken,
InstanceWrapper<Controller>
>();
private readonly _exports = new Set<string | symbol>();
private readonly _exports = new Set<InstanceToken>();
private _distance = 0;
constructor(
@@ -60,11 +72,11 @@ export class Module {
return this._id;
}
get providers(): Map<any, InstanceWrapper<Injectable>> {
get providers(): Map<InstanceToken, InstanceWrapper<Injectable>> {
return this._providers;
}
get middlewares(): Map<any, InstanceWrapper<Injectable>> {
get middlewares(): Map<InstanceToken, InstanceWrapper<Injectable>> {
return this._middlewares;
}
@@ -82,34 +94,34 @@ export class Module {
/**
* Left for backward-compatibility reasons
*/
get components(): Map<string, InstanceWrapper<Injectable>> {
get components(): Map<InstanceToken, InstanceWrapper<Injectable>> {
return this._providers;
}
/**
* Left for backward-compatibility reasons
*/
get routes(): Map<string, InstanceWrapper<Controller>> {
get routes(): Map<InstanceToken, InstanceWrapper<Controller>> {
return this._controllers;
}
get injectables(): Map<string, InstanceWrapper<Injectable>> {
get injectables(): Map<InstanceToken, InstanceWrapper<Injectable>> {
return this._injectables;
}
get controllers(): Map<string, InstanceWrapper<Controller>> {
get controllers(): Map<InstanceToken, InstanceWrapper<Controller>> {
return this._controllers;
}
get exports(): Set<string | symbol> {
get exports(): Set<InstanceToken> {
return this._exports;
}
get instance(): NestModule {
if (!this._providers.has(this._metatype.name)) {
if (!this._providers.has(this._metatype)) {
throw new RuntimeException();
}
const module = this._providers.get(this._metatype.name);
const module = this._providers.get(this._metatype);
return module.instance as NestModule;
}
@@ -134,8 +146,9 @@ export class Module {
public addModuleRef() {
const moduleRef = this.createModuleReferenceType();
this._providers.set(
ModuleRef.name,
ModuleRef,
new InstanceWrapper({
token: ModuleRef,
name: ModuleRef.name,
metatype: ModuleRef as any,
isResolved: true,
@@ -147,8 +160,9 @@ export class Module {
public addModuleAsProvider() {
this._providers.set(
this._metatype.name,
this._metatype,
new InstanceWrapper({
token: this._metatype,
name: this._metatype.name,
metatype: this._metatype,
isResolved: false,
@@ -160,8 +174,9 @@ export class Module {
public addApplicationConfig() {
this._providers.set(
ApplicationConfig.name,
ApplicationConfig,
new InstanceWrapper({
token: ApplicationConfig,
name: ApplicationConfig.name,
isResolved: true,
instance: this.container.applicationConfig,
@@ -177,9 +192,10 @@ export class Module {
if (this.isCustomProvider(injectable)) {
return this.addCustomProvider(injectable, this._injectables);
}
let instanceWrapper = this.injectables.get(injectable.name);
let instanceWrapper = this.injectables.get(injectable);
if (!instanceWrapper) {
instanceWrapper = new InstanceWrapper({
token: injectable,
name: injectable.name,
metatype: injectable,
instance: null,
@@ -187,23 +203,23 @@ export class Module {
scope: getClassScope(injectable),
host: this,
});
this._injectables.set(injectable.name, instanceWrapper);
this._injectables.set(injectable, instanceWrapper);
}
if (host) {
const token = host && host.name;
const hostWrapper =
this._controllers.get(host && host.name) || this._providers.get(token);
this._controllers.get(host) || this._providers.get(host);
hostWrapper && hostWrapper.addEnhancerMetadata(instanceWrapper);
}
}
public addProvider(provider: Provider): string {
public addProvider(provider: Provider) {
if (this.isCustomProvider(provider)) {
return this.addCustomProvider(provider, this._providers);
}
this._providers.set(
(provider as Type<Injectable>).name,
provider,
new InstanceWrapper({
token: provider,
name: (provider as Type<Injectable>).name,
metatype: provider as Type<Injectable>,
instance: null,
@@ -212,7 +228,7 @@ export class Module {
host: this,
}),
);
return (provider as Type<Injectable>).name;
return provider as Type<Injectable>;
}
public isCustomProvider(
@@ -232,20 +248,13 @@ export class Module {
}
public addCustomProvider(
provider: (
provider:
| ClassProvider
| FactoryProvider
| ValueProvider
| ExistingProvider
) &
ProviderName,
collection: Map<string, any>,
): string {
const name = this.getProviderStaticToken(provider.provide) as string;
provider = {
...provider,
name,
};
| ExistingProvider,
collection: Map<Function | string | symbol, any>,
) {
if (this.isCustomClass(provider)) {
this.addCustomClass(provider, collection);
} else if (this.isCustomValue(provider)) {
@@ -255,7 +264,7 @@ export class Module {
} else if (this.isCustomUseExisting(provider)) {
this.addCustomUseExisting(provider, collection);
}
return name;
return provider.provide;
}
public isCustomClass(provider: any): provider is ClassProvider {
@@ -279,19 +288,20 @@ export class Module {
}
public addCustomClass(
provider: ClassProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
provider: ClassProvider,
collection: Map<InstanceToken, InstanceWrapper>,
) {
const { name, useClass } = provider;
let { scope } = provider;
const { useClass } = provider;
if (isUndefined(scope)) {
scope = getClassScope(useClass);
}
collection.set(
name as string,
provider.provide,
new InstanceWrapper({
name,
token: provider.provide,
name: useClass?.name || useClass,
metatype: useClass,
instance: null,
isResolved: false,
@@ -302,14 +312,15 @@ export class Module {
}
public addCustomValue(
provider: ValueProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
provider: ValueProvider,
collection: Map<Function | string | symbol, InstanceWrapper>,
) {
const { name, useValue: value } = provider;
const { useValue: value, provide: providerToken } = provider;
collection.set(
name as string,
providerToken,
new InstanceWrapper({
name,
token: providerToken,
name: (providerToken as Function)?.name || providerToken,
metatype: null,
instance: value,
isResolved: true,
@@ -320,14 +331,21 @@ export class Module {
}
public addCustomFactory(
provider: FactoryProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
provider: FactoryProvider,
collection: Map<Function | string | symbol, InstanceWrapper>,
) {
const { name, useFactory: factory, inject, scope } = provider;
const {
useFactory: factory,
inject,
scope,
provide: providerToken,
} = provider;
collection.set(
name as string,
providerToken,
new InstanceWrapper({
name,
token: providerToken,
name: (providerToken as Function)?.name || providerToken,
metatype: factory as any,
instance: null,
isResolved: false,
@@ -339,14 +357,15 @@ export class Module {
}
public addCustomUseExisting(
provider: ExistingProvider & ProviderName,
collection: Map<string, InstanceWrapper>,
provider: ExistingProvider,
collection: Map<Function | string | symbol, InstanceWrapper>,
) {
const { name, useExisting } = provider;
const { useExisting, provide: providerToken } = provider;
collection.set(
name as string,
providerToken,
new InstanceWrapper({
name,
token: providerToken,
name: (providerToken as Function)?.name || providerToken,
metatype: (instance => instance) as any,
instance: null,
isResolved: false,
@@ -358,9 +377,9 @@ export class Module {
}
public addExportedProvider(
provider: (Provider & ProviderName) | string | symbol | DynamicModule,
provider: Provider | string | symbol | DynamicModule,
) {
const addExportedUnit = (token: string | symbol) =>
const addExportedUnit = (token: InstanceToken) =>
this._exports.add(this.validateExportedProvider(token));
if (this.isCustomProvider(provider as any)) {
@@ -371,7 +390,7 @@ export class Module {
const { module } = provider;
return addExportedUnit(module.name);
}
addExportedUnit(provider.name);
addExportedUnit(provider as Type<any>);
}
public addCustomExportedProvider(
@@ -385,10 +404,10 @@ export class Module {
if (isString(provide) || isSymbol(provide)) {
return this._exports.add(this.validateExportedProvider(provide));
}
this._exports.add(this.validateExportedProvider(provide.name));
this._exports.add(this.validateExportedProvider(provide));
}
public validateExportedProvider(token: string | symbol) {
public validateExportedProvider(token: InstanceToken) {
if (this._providers.has(token)) {
return token;
}
@@ -402,15 +421,17 @@ export class Module {
if (!importsNames.includes(token as string)) {
const { name } = this.metatype;
throw new UnknownExportException(token, name);
const providerName = isFunction(token) ? (token as Function).name : token;
throw new UnknownExportException(providerName as string, name);
}
return token;
}
public addController(controller: Type<Controller>) {
this._controllers.set(
controller.name,
controller,
new InstanceWrapper({
token: controller,
name: controller.name,
metatype: controller,
instance: null,
@@ -436,15 +457,13 @@ export class Module {
this._imports.add(module);
}
public replace(toReplace: string | symbol | Type<any>, options: any) {
public replace(toReplace: InstanceToken, options: any) {
if (options.isProvider && this.hasProvider(toReplace)) {
const name = this.getProviderStaticToken(toReplace);
const originalProvider = this._providers.get(name);
const originalProvider = this._providers.get(toReplace);
return originalProvider.mergeWith({ provide: toReplace, ...options });
} else if (!options.isProvider && this.hasInjectable(toReplace)) {
const name = this.getProviderStaticToken(toReplace);
const originalInjectable = this._injectables.get(name);
const originalInjectable = this._injectables.get(toReplace);
return originalInjectable.mergeWith({
provide: toReplace,
@@ -453,29 +472,21 @@ export class Module {
}
}
public hasProvider(token: string | symbol | Type<any>): boolean {
const name = this.getProviderStaticToken(token);
return this._providers.has(name);
public hasProvider(token: InstanceToken): boolean {
return this._providers.has(token);
}
public hasInjectable(token: string | symbol | Type<any>): boolean {
const name = this.getProviderStaticToken(token);
return this._injectables.has(name);
public hasInjectable(token: InstanceToken): boolean {
return this._injectables.has(token);
}
public getProviderStaticToken(
provider: string | symbol | Type<any> | Abstract<any>,
): string | symbol {
return isFunction(provider)
? (provider as Function).name
: (provider as string | symbol);
}
public getProviderByKey<T = any>(name: string | symbol): InstanceWrapper<T> {
public getProviderByKey<T = any>(name: InstanceToken): InstanceWrapper<T> {
return this._providers.get(name) as InstanceWrapper<T>;
}
public getNonAliasProviders(): Array<[string, InstanceWrapper<Injectable>]> {
public getNonAliasProviders(): Array<
[InstanceToken, InstanceWrapper<Injectable>]
> {
return [...this._providers].filter(([_, wrapper]) => !wrapper.isAlias);
}

View File

@@ -1,5 +1,5 @@
import { INTERCEPTORS_METADATA } from '@nestjs/common/constants';
import { Controller, NestInterceptor } from '@nestjs/common/interfaces';
import { Controller, NestInterceptor, Type } from '@nestjs/common/interfaces';
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { ApplicationConfig } from '../application-config';
@@ -59,15 +59,17 @@ export class InterceptorsContextCreator extends ContextCreator {
}
public getInterceptorInstance(
interceptor: Function | NestInterceptor,
metatype: Function | NestInterceptor,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): NestInterceptor | null {
const isObject = (interceptor as NestInterceptor).intercept;
const isObject = (metatype as NestInterceptor).intercept;
if (isObject) {
return interceptor as NestInterceptor;
return metatype as NestInterceptor;
}
const instanceWrapper = this.getInstanceByMetatype(interceptor);
const instanceWrapper = this.getInstanceByMetatype(
metatype as Type<unknown>,
);
if (!instanceWrapper) {
return null;
}
@@ -78,8 +80,8 @@ export class InterceptorsContextCreator extends ContextCreator {
return instanceHost && instanceHost.instance;
}
public getInstanceByMetatype<T extends Record<string, any> = any>(
metatype: T,
public getInstanceByMetatype(
metatype: Type<unknown>,
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return;
@@ -89,7 +91,7 @@ export class InterceptorsContextCreator extends ContextCreator {
if (!moduleRef) {
return;
}
return moduleRef.injectables.get(metatype.name as string);
return moduleRef.injectables.get(metatype);
}
public getGlobalMetadata<T extends unknown[]>(

View File

@@ -3,9 +3,13 @@ import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants';
import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface';
import { NestContainer } from '../injector';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { InstanceToken } from '../injector/module';
export class MiddlewareContainer {
private readonly middleware = new Map<string, Map<string, InstanceWrapper>>();
private readonly middleware = new Map<
string,
Map<InstanceToken, InstanceWrapper>
>();
private readonly configurationSets = new Map<
string,
Set<MiddlewareConfiguration>
@@ -15,7 +19,7 @@ export class MiddlewareContainer {
public getMiddlewareCollection(
moduleKey: string,
): Map<string, InstanceWrapper> {
): Map<InstanceToken, InstanceWrapper> {
if (!this.middleware.has(moduleKey)) {
const moduleRef = this.container.getModuleByKey(moduleKey);
this.middleware.set(moduleKey, moduleRef.middlewares);
@@ -36,13 +40,14 @@ export class MiddlewareContainer {
const configurations = configList || [];
const insertMiddleware = <T extends Type<unknown>>(metatype: T) => {
const token = metatype.name;
const token = metatype;
middleware.set(
token,
new InstanceWrapper({
scope: this.getClassScope(metatype),
metatype,
name: token,
metatype,
token,
}),
);
};

View File

@@ -19,7 +19,7 @@ import { STATIC_CONTEXT } from '../injector/constants';
import { NestContainer } from '../injector/container';
import { Injector } from '../injector/injector';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { Module } from '../injector/module';
import { InstanceToken, Module } from '../injector/module';
import { REQUEST_CONTEXT_ID } from '../router/request/request-constants';
import { RouterExceptionFilters } from '../router/router-exception-filters';
import { RouterProxy } from '../router/router-proxy';
@@ -70,13 +70,13 @@ export class MiddlewareModule {
modules: Map<string, Module>,
) {
const moduleEntries = [...modules.entries()];
const loadMiddlewareConfiguration = async ([name, module]: [
const loadMiddlewareConfiguration = async ([moduleName, moduleRef]: [
string,
Module,
]) => {
const instance = module.instance;
await this.loadConfiguration(middlewareContainer, instance, name);
await this.resolver.resolveInstances(module, name);
const instance = moduleRef.instance;
await this.loadConfiguration(middlewareContainer, instance, moduleName);
await this.resolver.resolveInstances(moduleRef, moduleName);
};
await Promise.all(moduleEntries.map(loadMiddlewareConfiguration));
}
@@ -164,7 +164,7 @@ export class MiddlewareModule {
for (const metatype of middlewareCollection) {
const collection = middlewareContainer.getMiddlewareCollection(moduleKey);
const instanceWrapper = collection.get(metatype.name);
const instanceWrapper = collection.get(metatype);
if (isUndefined(instanceWrapper)) {
throw new RuntimeException();
}
@@ -188,7 +188,7 @@ export class MiddlewareModule {
method: RequestMethod,
path: string,
moduleRef: Module,
collection: Map<string, InstanceWrapper>,
collection: Map<InstanceToken, InstanceWrapper>,
) {
const { instance, metatype } = wrapper;
if (isUndefined(instance.use)) {

View File

@@ -1,6 +1,6 @@
import { Injector } from '../injector/injector';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { Module } from '../injector/module';
import { InstanceToken, Module } from '../injector/module';
import { MiddlewareContainer } from './container';
export class MiddlewareResolver {
@@ -19,7 +19,7 @@ export class MiddlewareResolver {
private async resolveMiddlewareInstance(
wrapper: InstanceWrapper,
middleware: Map<string, InstanceWrapper>,
middleware: Map<InstanceToken, InstanceWrapper>,
moduleRef: Module,
) {
await this.instanceLoader.loadMiddleware(wrapper, middleware, moduleRef);

View File

@@ -1,5 +1,5 @@
import { PIPES_METADATA } from '@nestjs/common/constants';
import { Controller, PipeTransform } from '@nestjs/common/interfaces';
import { Controller, PipeTransform, Type } from '@nestjs/common/interfaces';
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils';
import { iterate } from 'iterare';
import { ApplicationConfig } from '../application-config';
@@ -59,7 +59,7 @@ export class PipesContextCreator extends ContextCreator {
if (isObject) {
return pipe as PipeTransform;
}
const instanceWrapper = this.getInstanceByMetatype(pipe as Function);
const instanceWrapper = this.getInstanceByMetatype(pipe as Type<unknown>);
if (!instanceWrapper) {
return null;
}
@@ -70,8 +70,8 @@ export class PipesContextCreator extends ContextCreator {
return instanceHost && instanceHost.instance;
}
public getInstanceByMetatype<T extends Record<'name', string> = any>(
metatype: T,
public getInstanceByMetatype(
metatype: Type<unknown>,
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return;
@@ -81,7 +81,7 @@ export class PipesContextCreator extends ContextCreator {
if (!moduleRef) {
return;
}
return moduleRef.injectables.get(metatype.name);
return moduleRef.injectables.get(metatype);
}
public getGlobalMetadata<T extends unknown[]>(

View File

@@ -51,7 +51,7 @@ export class RoutesResolver implements Resolver {
}
public registerRouters(
routes: Map<string, InstanceWrapper<Controller>>,
routes: Map<string | symbol | Function, InstanceWrapper<Controller>>,
moduleName: string,
basePath: string,
applicationRef: HttpServer,

View File

@@ -357,11 +357,10 @@ export class DependenciesScanner {
scope,
} as Provider;
if (
this.isRequestOrTransient(
(newProvider as FactoryProvider | ClassProvider).scope,
)
) {
const factoryOrClassProvider = newProvider as
| FactoryProvider
| ClassProvider;
if (this.isRequestOrTransient(factoryOrClassProvider.scope)) {
return this.container.addInjectable(newProvider, token);
}
this.container.addProvider(newProvider, token);

View File

@@ -112,10 +112,11 @@ describe('GuardsContextCreator', () => {
(guardsContextCreator as any).moduleContext = 'test';
});
describe('and when module exists', () => {
describe('bud module does not exist', () => {
it('should return undefined', () => {
expect(guardsContextCreator.getInstanceByMetatype({})).to.be
.undefined;
expect(
guardsContextCreator.getInstanceByMetatype(class RandomModule {}),
).to.be.undefined;
});
});
});

View File

@@ -23,7 +23,7 @@ describe('BeforeAppShutdown', () => {
sampleProvider = new SampleProvider();
moduleRef = new Module(SampleModule, new NestContainer());
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule.name);
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule);
moduleWrapperRef.instance = new SampleModule();
moduleRef.addProvider({

View File

@@ -23,7 +23,7 @@ describe('OnApplicationBootstrap', () => {
sampleProvider = new SampleProvider();
moduleRef = new Module(SampleModule, new NestContainer());
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule.name);
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule);
moduleWrapperRef.instance = new SampleModule();
moduleRef.addProvider({

View File

@@ -23,7 +23,7 @@ describe('OnApplicationShutdown', () => {
sampleProvider = new SampleProvider();
moduleRef = new Module(SampleModule, new NestContainer());
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule.name);
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule);
moduleWrapperRef.instance = new SampleModule();
moduleRef.addProvider({

View File

@@ -23,7 +23,7 @@ describe('OnModuleDestroy', () => {
sampleProvider = new SampleProvider();
moduleRef = new Module(SampleModule, new NestContainer());
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule.name);
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule);
moduleWrapperRef.instance = new SampleModule();
moduleRef.addProvider({

View File

@@ -23,7 +23,7 @@ describe('OnModuleInit', () => {
sampleProvider = new SampleProvider();
moduleRef = new Module(SampleModule, new NestContainer());
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule.name);
const moduleWrapperRef = moduleRef.getProviderByKey(SampleModule);
moduleWrapperRef.instance = new SampleModule();
moduleRef.addProvider({

View File

@@ -39,18 +39,21 @@ describe('Injector', () => {
moduleDeps = new Module(DependencyTwo, new NestContainer());
mainTest = new InstanceWrapper({
name: 'MainTest',
token: 'MainTest',
metatype: MainTest,
instance: Object.create(MainTest.prototype),
isResolved: false,
});
depOne = new InstanceWrapper({
name: DependencyOne,
token: DependencyOne,
metatype: DependencyOne,
instance: Object.create(DependencyOne.prototype),
isResolved: false,
});
depTwo = new InstanceWrapper({
name: DependencyTwo,
token: DependencyTwo,
metatype: DependencyTwo,
instance: Object.create(DependencyOne.prototype),
isResolved: false,
@@ -110,7 +113,6 @@ describe('Injector', () => {
});
it('should return undefined when metatype is resolved', async () => {
const value = 'test';
const result = await injector.loadInstance(
new InstanceWrapper({
name: 'MainTestResolved',
@@ -136,6 +138,7 @@ describe('Injector', () => {
moduleDeps = new Module(Test, new NestContainer());
test = new InstanceWrapper({
name: 'Test',
token: 'Test',
metatype: Test,
instance: null,
isResolved: false,
@@ -694,10 +697,10 @@ describe('Injector', () => {
const module = await container.addModule(moduleCtor, []);
module.addProvider({
name: 'TestClass',
provide: TestClass,
useClass: TestClass,
});
const instance = await injector.loadPerContext(
new TestClass(),
module,

View File

@@ -37,10 +37,12 @@ describe('InstanceLoader', () => {
const providerWrapper: InstanceWrapper = {
instance: null,
metatype: TestProvider,
token: 'TestProvider',
} as any;
const routeWrapper: InstanceWrapper = {
instance: null,
metatype: TestRoute,
token: 'TestRoute',
} as any;
module.providers.set('TestProvider', providerWrapper);
@@ -78,6 +80,7 @@ describe('InstanceLoader', () => {
instance: null,
metatype: TestProvider,
name: 'TestProvider',
token: 'TestProvider',
});
module.providers.set('TestProvider', testComp);
@@ -109,6 +112,7 @@ describe('InstanceLoader', () => {
};
const wrapper = new InstanceWrapper({
name: 'TestRoute',
token: 'TestRoute',
instance: null,
metatype: TestRoute,
});
@@ -144,6 +148,7 @@ describe('InstanceLoader', () => {
instance: null,
metatype: TestProvider,
name: 'TestProvider',
token: 'TestProvider',
});
module.injectables.set('TestProvider', testComp);

View File

@@ -36,9 +36,10 @@ describe('Module', () => {
module.addController(Test);
expect(
setSpy.calledWith(
'Test',
Test,
new InstanceWrapper({
host: module,
token: Test,
name: 'Test',
scope: Scope.REQUEST,
metatype: Test,
@@ -57,10 +58,11 @@ describe('Module', () => {
module.addInjectable(TestProvider, TestModule);
expect(
setSpy.calledWith(
'TestProvider',
TestProvider,
new InstanceWrapper({
host: module,
name: 'TestProvider',
token: TestProvider,
scope: undefined,
metatype: TestProvider,
instance: null,
@@ -87,10 +89,11 @@ describe('Module', () => {
module.addProvider(TestProvider);
expect(
setSpy.calledWith(
'TestProvider',
TestProvider,
new InstanceWrapper({
host: module,
name: 'TestProvider',
token: TestProvider,
scope: undefined,
metatype: TestProvider,
instance: null,
@@ -152,7 +155,7 @@ describe('Module', () => {
describe('addCustomClass', () => {
const type = { name: 'TypeTest' };
const provider = { provide: type, useClass: type, name: 'test' };
const provider = { provide: type, useClass: type };
let setSpy;
beforeEach(() => {
@@ -164,10 +167,11 @@ describe('Module', () => {
module.addCustomClass(provider as any, (module as any)._providers);
expect(
setSpy.calledWith(
provider.name,
provider.provide,
new InstanceWrapper({
host: module,
name: provider.name,
token: type as any,
name: provider.provide.name,
scope: undefined,
metatype: type as any,
instance: null,
@@ -181,8 +185,7 @@ describe('Module', () => {
describe('addCustomValue', () => {
let setSpy;
const value = () => ({});
const name = 'test';
const provider = { provide: value, name, useValue: value };
const provider = { provide: value, useValue: value };
beforeEach(() => {
const collection = new Map();
@@ -194,10 +197,11 @@ describe('Module', () => {
module.addCustomValue(provider as any, (module as any)._providers);
expect(
setSpy.calledWith(
name,
provider.provide,
new InstanceWrapper({
host: module,
name,
token: provider.provide,
name: provider.provide.name,
scope: Scope.DEFAULT,
metatype: null,
instance: value,
@@ -212,7 +216,7 @@ describe('Module', () => {
describe('addCustomFactory', () => {
const type = { name: 'TypeTest' };
const inject = [1, 2, 3];
const provider = { provide: type, useFactory: type, name: 'test', inject };
const provider = { provide: type, useFactory: type, inject };
let setSpy;
beforeEach(() => {
@@ -222,12 +226,14 @@ describe('Module', () => {
});
it('should store provider', () => {
module.addCustomFactory(provider as any, (module as any)._providers);
expect(
setSpy.calledWith(
provider.name,
provider.provide,
new InstanceWrapper({
host: module,
name: provider.name,
token: provider.provide as any,
name: provider.provide.name,
scope: undefined,
metatype: type as any,
instance: null,
@@ -241,7 +247,7 @@ describe('Module', () => {
describe('addCustomUseExisting', () => {
const type = { name: 'TypeTest' };
const provider = { provide: type, useExisting: type, name: 'test' };
const provider = { provide: type, useExisting: type };
let setSpy;
beforeEach(() => {
@@ -251,13 +257,17 @@ describe('Module', () => {
});
it('should store provider', () => {
module.addCustomUseExisting(provider as any, (module as any)._providers);
const factoryFn = (module as any)._providers.get(provider.name).metatype;
const factoryFn = (module as any)._providers.get(provider.provide)
.metatype;
const token = provider.provide as any;
expect(
setSpy.calledWith(
provider.name,
token,
new InstanceWrapper({
host: module,
name: provider.name,
token,
name: provider.provide.name,
metatype: factoryFn,
instance: null,
inject: [provider.useExisting as any],
@@ -477,7 +487,7 @@ describe('Module', () => {
describe('otherwise', () => {
it('should return instance wrapper', () => {
module.addProvider(TestProvider);
expect(module.getProviderByKey('TestProvider')).to.not.be.undefined;
expect(module.getProviderByKey(TestProvider)).to.not.be.undefined;
});
});
});

View File

@@ -74,16 +74,22 @@ export class MicroservicesModule {
}
public bindListeners(
controllers: Map<string, InstanceWrapper<Controller>>,
controllers: Map<string | symbol | Function, InstanceWrapper<Controller>>,
server: Server & CustomTransportStrategy,
module: string,
moduleName: string,
) {
controllers.forEach(wrapper =>
this.listenersController.registerPatternHandlers(wrapper, server, module),
this.listenersController.registerPatternHandlers(
wrapper,
server,
moduleName,
),
);
}
public bindClients(items: Map<string, InstanceWrapper<unknown>>) {
public bindClients(
items: Map<string | symbol | Function, InstanceWrapper<unknown>>,
) {
items.forEach(({ instance, isNotMetatype }) => {
!isNotMetatype &&
this.listenersController.assignClientsToProperties(instance);

View File

@@ -5,6 +5,7 @@ import { GuardsContextCreator } from '@nestjs/core/guards/guards-context-creator
import { loadAdapter } from '@nestjs/core/helpers/load-adapter';
import { NestContainer } from '@nestjs/core/injector/container';
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
import { InstanceToken } from '@nestjs/core/injector/module';
import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer';
import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator';
import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer';
@@ -51,7 +52,7 @@ export class SocketModule<HttpServer = any> {
}
public combineAllGateways(
providers: Map<string, InstanceWrapper<Injectable>>,
providers: Map<InstanceToken, InstanceWrapper<Injectable>>,
moduleName: string,
) {
iterate(providers.values())