feat(@nestjs/core): support useExisting provider #2415

This commit is contained in:
likui
2019-05-31 11:13:14 +08:00
parent 343fb310ce
commit 0ff1612111
4 changed files with 88 additions and 9 deletions

View File

@@ -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;
}

View File

@@ -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)) {

View File

@@ -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);

View File

@@ -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(() => {