Skip to content

Commit

Permalink
feat(module): add option to make AMQP module available globally
Browse files Browse the repository at this point in the history
  • Loading branch information
tahubu committed Apr 13, 2021
1 parent 1816233 commit d92dcc5
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 4 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ Instead of `useFactory` you can use `useClass` or `useExisting` to set module op
#### Module options

The module options object needs to be added to the `forRoot()` or `forRootAsync()` static method. The possible options can be these:
* **isGlobal**?: A boolean value. If this property is `true`, then you can skip the import with`.forFeature()` method in the feature
modules, because the public services will be available in all modules which imports the root module. Default value is
`false`. (You can read more about it on [Nest modules page](https://docs.nestjs.com/modules#global-modules))
* **throwExceptionOnConnectionError**?: A boolean value. If it's `true` then AMQPModule will throw forward the exception which occurs
during the connection creation. Default value is `false`.
* **acceptValidationNullObjectException**?: A boolean value. If it's `true` then AMQPModule will accept the message when a
Expand All @@ -138,7 +141,8 @@ First basic example:
imports: [
QueueModule.forRoot(
'amqp://user:password@localhost:5672',
{
{
isGlobal: true,
throwExceptionOnConnectionError: true,
connectionOptions: {
transport: 'tcp'
Expand All @@ -155,6 +159,9 @@ Second example with asynchronous configuration:
@Module({
imports: [
QueueModule.forRootAsync({
// in case of `QueueModule.forRootAsync`, isGlobal property goes here and
// not into the object returned by 'useFactory' or 'useClass' or 'useExisting'
isGlobal: true,
imports: [ConfigModule],
useFactory: (configService: ConfigService): QueueModuleOptions => ({
connectionUri: configService.get<string>('AMQP_CONNECTION_URI'),
Expand Down
1 change: 1 addition & 0 deletions src/interface/amqp-connection-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ConnectionOptions } from 'rhea-promise';
* @publicApi
*/
export type QueueModuleOptions = {
isGlobal?: boolean;
throwExceptionOnConnectionError?: boolean;
acceptValidationNullObjectException?: boolean;
connectionUri?: string;
Expand Down
1 change: 1 addition & 0 deletions src/interface/queue-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface QueueModuleOptionsFactory {
}

export interface QueueModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
isGlobal?: boolean;
useExisting?: Type<QueueModuleOptionsFactory>;
useClass?: Type<QueueModuleOptionsFactory>;
useFactory?: (...args: any[]) => Promise<QueueModuleOptions> | QueueModuleOptions;
Expand Down
45 changes: 45 additions & 0 deletions src/queue.module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ describe('QueueModule', () => {
})
class TestQueueConfigModule {}

@Injectable()
class TestGlobalFeatureService {
constructor(public readonly queueService: QueueService) {}
}

@Module({
providers: [TestGlobalFeatureService],
exports: [TestGlobalFeatureService],
})
class TestGlobalFeatureModule {}

afterEach(async () => {
await module.close();
(QueueModule as any).moduleDefinition.imports = [];
Expand Down Expand Up @@ -158,4 +169,38 @@ describe('QueueModule', () => {
expect(amqpService.getModuleOptions()).toEqual({ connectionUri });
});
});

describe('make to global module', () => {
it(`should be global module with .forRoot() import`, async () => {
module = await Test.createTestingModule({
imports: [QueueModule.forRoot(connectionUri, { isGlobal: true }), TestGlobalFeatureModule],
}).compile();
const testGlobalFeatureService = module.get<TestGlobalFeatureService>(TestGlobalFeatureService);

expect(testGlobalFeatureService.queueService).toBeDefined();
});

it(`should be global module with .forRootAsync() import`, async () => {
module = await Test.createTestingModule({
imports: [
QueueModule.forRootAsync({
isGlobal: true,
useFactory: () => ({ connectionUri }),
}),
TestGlobalFeatureModule,
],
}).compile();
const testGlobalFeatureService = module.get<TestGlobalFeatureService>(TestGlobalFeatureService);

expect(testGlobalFeatureService.queueService).toBeDefined();
});

it(`should use .forFeature() when not global module`, async () => {
const moduleBuilder = Test.createTestingModule({
imports: [QueueModule.forRoot(connectionUri, { isGlobal: false }), TestGlobalFeatureModule],
});

await expect(moduleBuilder.compile()).rejects.toThrow(/Nest can't resolve dependencies of the TestGlobalFeatureService/);
});
});
});
8 changes: 5 additions & 3 deletions src/queue.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Logger } from './util';
@Module({})
export class QueueModule implements OnModuleInit, OnModuleDestroy {
private static readonly moduleDefinition: DynamicModule = {
global: false,
module: QueueModule,
providers: [AMQPService, QueueService, MetadataScanner, ListenerExplorer, ObjectValidatorService],
exports: [QueueService],
Expand All @@ -20,12 +21,12 @@ export class QueueModule implements OnModuleInit, OnModuleDestroy {
public static forRoot(options: QueueModuleOptions): DynamicModule;
public static forRoot(connectionUri: string, options?: Omit<QueueModuleOptions, 'connectionUri'>): DynamicModule;
public static forRoot(connectionUri: string | QueueModuleOptions, options?: Omit<QueueModuleOptions, 'connectionUri'>): DynamicModule {
const queueModuleOptionsProvider = QueueModule.getQueueModuleOptionsProvider(
typeof connectionUri === 'string' ? { ...options, connectionUri } : connectionUri,
);
const moduleOptions = typeof connectionUri === 'string' ? { ...options, connectionUri } : connectionUri;
const queueModuleOptionsProvider = QueueModule.getQueueModuleOptionsProvider(moduleOptions);
const connectionProvider = QueueModule.getConnectionProvider();

Object.assign(QueueModule.moduleDefinition, {
global: !!moduleOptions.isGlobal,
providers: [queueModuleOptionsProvider, ...QueueModule.moduleDefinition.providers, connectionProvider],
});

Expand All @@ -37,6 +38,7 @@ export class QueueModule implements OnModuleInit, OnModuleDestroy {
const asyncProviders = this.createAsyncProviders(options);

Object.assign(QueueModule.moduleDefinition, {
global: !!options.isGlobal,
imports: options.imports,
providers: [...asyncProviders, ...QueueModule.moduleDefinition.providers, connectionProvider],
});
Expand Down

0 comments on commit d92dcc5

Please sign in to comment.