mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
feat(@nestjs/core): support useExisting provider #2415
This commit is contained in:
@@ -6,7 +6,8 @@ export type Provider<T = any> =
|
||||
| Type<any>
|
||||
| ClassProvider<T>
|
||||
| ValueProvider<T>
|
||||
| FactoryProvider<T>;
|
||||
| FactoryProvider<T>
|
||||
| ExistingProvider<T>;
|
||||
|
||||
export interface ClassProvider<T = any> {
|
||||
provide: string | symbol | Type<any> | Abstract<any> | Function;
|
||||
@@ -25,3 +26,8 @@ export interface FactoryProvider<T = any> {
|
||||
inject?: Array<Type<any> | string | symbol | Abstract<any> | Function>;
|
||||
scope?: Scope;
|
||||
}
|
||||
|
||||
export interface ExistingProvider<T = any> {
|
||||
provide: string | symbol | Type<any> | Abstract<any> | Function;
|
||||
useExisting: any;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
NestModule,
|
||||
Provider,
|
||||
ValueProvider,
|
||||
ExistingProvider,
|
||||
} from '@nestjs/common/interfaces';
|
||||
import { Type } from '@nestjs/common/interfaces/type.interface';
|
||||
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
|
||||
@@ -39,7 +40,7 @@ export class Module {
|
||||
private readonly _controllers = new Map<
|
||||
string,
|
||||
InstanceWrapper<Controller>
|
||||
>();
|
||||
>();
|
||||
private readonly _exports = new Set<string | symbol>();
|
||||
|
||||
constructor(
|
||||
@@ -187,14 +188,14 @@ export class Module {
|
||||
|
||||
public isCustomProvider(
|
||||
provider: Provider,
|
||||
): provider is ClassProvider | FactoryProvider | ValueProvider {
|
||||
): provider is ClassProvider | FactoryProvider | ValueProvider | ExistingProvider {
|
||||
return !isNil(
|
||||
(provider as ClassProvider | FactoryProvider | ValueProvider).provide,
|
||||
(provider as ClassProvider | FactoryProvider | ValueProvider | ExistingProvider).provide,
|
||||
);
|
||||
}
|
||||
|
||||
public addCustomProvider(
|
||||
provider: (ClassProvider | FactoryProvider | ValueProvider) & ProviderName,
|
||||
provider: (ClassProvider | FactoryProvider | ValueProvider | ExistingProvider) & ProviderName,
|
||||
collection: Map<string, any>,
|
||||
): string {
|
||||
const name = this.getProviderStaticToken(provider.provide) as string;
|
||||
@@ -208,6 +209,8 @@ export class Module {
|
||||
this.addCustomValue(provider, collection);
|
||||
} else if (this.isCustomFactory(provider)) {
|
||||
this.addCustomFactory(provider, collection);
|
||||
} else if (this.isCustomUseExisting(provider)) {
|
||||
this.addCustomUseExisting(provider, collection);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -224,6 +227,10 @@ export class Module {
|
||||
return !isUndefined((provider as FactoryProvider).useFactory);
|
||||
}
|
||||
|
||||
public isCustomUseExisting(provider: any): provider is ExistingProvider {
|
||||
return !isUndefined((provider as ExistingProvider).useExisting);
|
||||
}
|
||||
|
||||
public isDynamicModule(exported: any): exported is DynamicModule {
|
||||
return exported && exported.module;
|
||||
}
|
||||
@@ -283,6 +290,24 @@ export class Module {
|
||||
);
|
||||
}
|
||||
|
||||
public addCustomUseExisting(
|
||||
provider: ExistingProvider & ProviderName,
|
||||
collection: Map<string, InstanceWrapper>,
|
||||
) {
|
||||
const { name, useExisting } = provider;
|
||||
collection.set(
|
||||
name as string,
|
||||
new InstanceWrapper({
|
||||
name,
|
||||
metatype: ((instance) => instance) as any,
|
||||
instance: null,
|
||||
isResolved: false,
|
||||
inject: [useExisting],
|
||||
host: this,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public addExportedProvider(
|
||||
provider: Provider & ProviderName | string | symbol | DynamicModule,
|
||||
) {
|
||||
@@ -301,7 +326,7 @@ export class Module {
|
||||
}
|
||||
|
||||
public addCustomExportedProvider(
|
||||
provider: FactoryProvider | ValueProvider | ClassProvider,
|
||||
provider: FactoryProvider | ValueProvider | ClassProvider | ExistingProvider,
|
||||
) {
|
||||
const provide = provider.provide;
|
||||
if (isString(provide) || isSymbol(provide)) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
ClassProvider,
|
||||
FactoryProvider,
|
||||
ValueProvider,
|
||||
ExistingProvider,
|
||||
} from '@nestjs/common/interfaces';
|
||||
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
|
||||
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
|
||||
@@ -256,7 +257,11 @@ export class DependenciesScanner {
|
||||
|
||||
public isCustomProvider(
|
||||
provider: Provider,
|
||||
): provider is ClassProvider | ValueProvider | FactoryProvider {
|
||||
): provider is
|
||||
| ClassProvider
|
||||
| ValueProvider
|
||||
| FactoryProvider
|
||||
| ExistingProvider {
|
||||
return provider && !isNil((provider as any).provide);
|
||||
}
|
||||
|
||||
@@ -267,8 +272,11 @@ export class DependenciesScanner {
|
||||
}
|
||||
const applyProvidersMap = this.getApplyProvidersMap();
|
||||
const providersKeys = Object.keys(applyProvidersMap);
|
||||
const type = (provider as ClassProvider | ValueProvider | FactoryProvider)
|
||||
.provide;
|
||||
const type = (provider as
|
||||
| ClassProvider
|
||||
| ValueProvider
|
||||
| FactoryProvider
|
||||
| ExistingProvider).provide;
|
||||
|
||||
if (!providersKeys.includes(type as string)) {
|
||||
return this.container.addProvider(provider as any, token);
|
||||
|
||||
@@ -140,6 +140,16 @@ describe('Module', () => {
|
||||
expect((addCustomFactory as sinon.SinonSpy).called).to.be.true;
|
||||
});
|
||||
|
||||
it('should call "addCustomUseExisting" when "useExisting" property exists', () => {
|
||||
const addCustomUseExisting = sinon.spy();
|
||||
module.addCustomUseExisting = addCustomUseExisting;
|
||||
|
||||
const provider = { provide: 'test', useExisting: () => null };
|
||||
|
||||
module.addCustomUseExisting(provider as any, new Map());
|
||||
expect((addCustomUseExisting as sinon.SinonSpy).called).to.be.true;
|
||||
});
|
||||
|
||||
describe('addCustomClass', () => {
|
||||
const type = { name: 'TypeTest' };
|
||||
const provider = { provide: type, useClass: type, name: 'test' };
|
||||
@@ -229,6 +239,36 @@ describe('Module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('addCustomUseExisting', () => {
|
||||
const type = { name: 'TypeTest' };
|
||||
const provider = { provide: type, useExisting: type, name: 'test' };
|
||||
|
||||
let setSpy;
|
||||
beforeEach(() => {
|
||||
const collection = new Map();
|
||||
setSpy = sinon.spy(collection, 'set');
|
||||
(module as any)._providers = collection;
|
||||
});
|
||||
it('should store provider', () => {
|
||||
module.addCustomUseExisting(provider as any, (module as any)._providers);
|
||||
const factoryFn = (module as any)._providers.get(provider.name).metatype;
|
||||
expect(
|
||||
setSpy.calledWith(
|
||||
provider.name,
|
||||
new InstanceWrapper({
|
||||
host: module,
|
||||
name: provider.name,
|
||||
metatype: factoryFn,
|
||||
instance: null,
|
||||
inject: [provider.useExisting as any],
|
||||
isResolved: false,
|
||||
}),
|
||||
),
|
||||
).to.be.true;
|
||||
expect(factoryFn(provider.useExisting)).to.be.eql(type);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when get instance', () => {
|
||||
describe('when metatype does not exists in providers collection', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user