Skip to content

Commit

Permalink
feat(core): Add multi provider option
Browse files Browse the repository at this point in the history
Resolves nestjs#770
  • Loading branch information
BrunnerLivio committed Aug 27, 2019
1 parent d01a798 commit b74a7b4
Show file tree
Hide file tree
Showing 14 changed files with 474 additions and 19 deletions.
63 changes: 63 additions & 0 deletions integration/injector/e2e/multi-provider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { MultiProviderUseValueModule } from '../src/multi-provider/multi-provider-use-value.module';
import { MultiProviderUseFactoryModule } from '../src/multi-provider/multi-provider-use-factory.module';
import { MultiProviderUseClassModule } from '../src/multi-provider/multi-provider-use-class.module';
import { MultiProviderMixedModule } from '../src/multi-provider/multi-provider-mixed.module';
import { MutliProviderExportModule } from '../src/multi-provider/multi-provider-export.module';
import { MixedMultiProviderException } from '@nestjs/core/errors/exceptions/mixed-multi-provider.exception';
import { MultiProviderCircularModule } from '../src/multi-provider/multi-provider-circular.module';

describe('MultiProvider', () => {
it(`should return an array of values when using the "multi" option and "useValue"`, async () => {
const builder = Test.createTestingModule({
imports: [MultiProviderUseValueModule],
});
const app = await builder.compile();
expect(app.get('TEST')).to.deep.eq(['a', 'b']);
});

it(`should return an array of values when using the "multi" option and "useFactory"`, async () => {
const builder = Test.createTestingModule({
imports: [MultiProviderUseFactoryModule],
});
const app = await builder.compile();
expect(app.get('TEST')).to.deep.eq(['a', 'b']);
});

it(`should return an array of values when using the "multi" option and "useClass"`, async () => {
const builder = Test.createTestingModule({
imports: [MultiProviderUseClassModule],
});
const app = await builder.compile();
expect(app.get('TEST')[0].test()).to.deep.eq('a');
expect(app.get('TEST')[1].test()).to.deep.eq('b');
});

it(`should return an array of values when using the "multi" option with exported providers`, async () => {
const builder = Test.createTestingModule({
imports: [MutliProviderExportModule],
});
const app = await builder.compile();
expect(app.get('TEST')).to.deep.eq(['a', 'b', 'c']);
});

it(`should return an array of values when using the "multi" option with exported providers in a circual module`, async () => {
const builder = Test.createTestingModule({
imports: [MultiProviderCircularModule],
});
const app = await builder.compile();
expect(app.get('TEST')).to.deep.eq(['a', 'b']);
});

it(`should throw an error if "mutli" value is mixed with the same token`, async () => {
try {
const builder = Test.createTestingModule({
imports: [MultiProviderMixedModule],
});
await builder.compile();
} catch (err) {
expect(err).to.be.instanceof(MixedMultiProviderException);
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Module, forwardRef } from '@nestjs/common';

const MULTI_TOKEN = 'TEST';

@Module({
imports: [forwardRef(() => MultiProviderCircularModule)],
providers: [
{
provide: MULTI_TOKEN,
multi: true,
useValue: 'a',
},
],
exports: [MULTI_TOKEN],
})
class AModule {}

@Module({
imports: [forwardRef(() => AModule)],
providers: [
{
provide: MULTI_TOKEN,
useValue: 'b',
multi: true,
},
],
exports: [MULTI_TOKEN],
})
export class MultiProviderCircularModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Module } from '@nestjs/common';

const MULTI_TOKEN = 'TEST';
const INTERNAL_PROVIDER_TOKEN = 'INTERNAL_PROVIDER_TOKEN';

@Module({
providers: [
{
provide: INTERNAL_PROVIDER_TOKEN,
useValue: 'a',
},
{
provide: MULTI_TOKEN,
multi: true,
useFactory: arg => arg,
inject: [INTERNAL_PROVIDER_TOKEN],
},
],
exports: [MULTI_TOKEN],
})
class AModule {}

@Module({
imports: [AModule],
providers: [
{
provide: MULTI_TOKEN,
useValue: 'b',
multi: true,
},
{
provide: MULTI_TOKEN,
useValue: 'c',
multi: true,
},
],
})
export class MutliProviderExportModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Module, Inject } from '@nestjs/common';

@Module({
providers: [

{
provide: 'TEST',
useValue: 'a',
multi: true,
},
{
provide: 'TEST',
useValue: 'b',
multi: false,
}],
})
export class MultiProviderMixedModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Module, Inject } from '@nestjs/common';

class Test1 {
test() {
return 'a';
}
}
class Test2 {
constructor(@Inject('B_VALUE') private b: string) { }
test() {
return this.b;
}
}

@Module({
providers: [
{
provide: 'B_VALUE',
useValue: 'b',
},
{
provide: 'TEST',
useClass: Test1,
multi: true,
},
{
provide: 'TEST',
useClass: Test2,
multi: true,
}],
})
export class MultiProviderUseClassModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';

const BProviderToken = 'BProvider';

@Module({
providers: [{
provide: 'TEST',
useFactory: () => 'a',
multi: true,
},
{
provide: BProviderToken,
useValue: 'b',
},
{
provide: 'TEST',
useFactory: (b: string) => b,
inject: [BProviderToken],
multi: true,
}],
})
export class MultiProviderUseFactoryModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import { Module } from '@nestjs/common';

@Module({
providers: [{
provide: 'TEST',
useValue: 'a',
multi: true,
},
{
provide: 'TEST',
useValue: 'b',
multi: true,
}],
})
export class MultiProviderUseValueModule { }
15 changes: 15 additions & 0 deletions packages/common/interfaces/modules/provider.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export interface ClassProvider<T = any> {
* Optional enum defining lifetime of the provider that is injected.
*/
scope?: Scope;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean | undefined;
}

/**
Expand All @@ -71,6 +76,11 @@ export interface ValueProvider<T = any> {
* Instance of a provider to be injected.
*/
useValue: T;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean | undefined;
}

/**
Expand Down Expand Up @@ -110,6 +120,11 @@ export interface FactoryProvider<T = any> {
* Optional enum defining lifetime of the provider that is returned by the Factory function.
*/
scope?: Scope;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean | undefined;
}

/**
Expand Down
31 changes: 31 additions & 0 deletions packages/core/errors/exceptions/mixed-multi-provider.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MIXED_MULTI_PROVIDER_MESSAGE } from '../messages';
import { RuntimeException } from './runtime.exception';

/**
* Errors which should get thrown when the user
* mixes up the `multi` option of a provider
*
* ```typescript
* // Will throw an exception
* @Module({
* providers: [
* {
* useValue: 'eng',
* provide: 'LANG',
* multi: true,
* },
* {
* useValue: 'de',
* provide: 'LANG',
* // Not allowed, because inconsistent value for `multi`
* multi: false,
* },
* ],
* })
* ```
*/
export class MixedMultiProviderException extends RuntimeException {
constructor(name: string | symbol) {
super(MIXED_MULTI_PROVIDER_MESSAGE(name.toString()));
}
}
4 changes: 4 additions & 0 deletions packages/core/errors/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export const UNKNOWN_DEPENDENCIES_MESSAGE = (
return message;
};

export const MIXED_MULTI_PROVIDER_MESSAGE = (
name: string
) => `Mixed multi-provider for ${name}.`;

export const INVALID_MIDDLEWARE_MESSAGE = (
text: TemplateStringsArray,
name: string,
Expand Down
1 change: 1 addition & 0 deletions packages/core/injector/instance-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class InstanceWrapper<T = any> {
public readonly async?: boolean;
public readonly host?: Module;
public readonly scope?: Scope = Scope.DEFAULT;
public readonly multi?: boolean | undefined;
public metatype: Type<T> | Function;
public inject?: (string | symbol | Function | Type<any>)[];
public forwardRef?: boolean;
Expand Down
Loading

0 comments on commit b74a7b4

Please sign in to comment.