Skip to content

Commit

Permalink
Merge pull request #9534 from nestjs/feat/module-utils
Browse files Browse the repository at this point in the history
feat(core): add configurable module builder, module utils
  • Loading branch information
kamilmysliwiec committed May 17, 2022
2 parents 19b55e9 + 4d62166 commit fc95619
Show file tree
Hide file tree
Showing 24 changed files with 794 additions and 139 deletions.
17 changes: 17 additions & 0 deletions integration/module-utils/src/integration.module-definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { IntegrationModuleOptions } from './interfaces/integration-module-options.interface';

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<IntegrationModuleOptions>()
.setClassMethodName('forRoot')
.setFactoryMethodName('construct')
.setExtras(
{
isGlobal: true,
},
(definition, extras) => ({
...definition,
global: extras.isGlobal,
}),
)
.build();
16 changes: 16 additions & 0 deletions integration/module-utils/src/integration.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Inject, Module } from '@nestjs/common';
import {
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
} from './integration.module-definition';
import { IntegrationModuleOptions } from './interfaces/integration-module-options.interface';

@Module({})
export class IntegrationModule extends ConfigurableModuleClass {
constructor(
@Inject(MODULE_OPTIONS_TOKEN)
public readonly options: IntegrationModuleOptions,
) {
super();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IntegrationModuleOptions {
url: string;
secure?: boolean;
}
47 changes: 47 additions & 0 deletions integration/module-utils/test/integration-module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import { IntegrationModule } from '../src/integration.module';

describe('Module utils (ConfigurableModuleBuilder)', () => {
it('should auto-generate "forRoot" method', async () => {
const moduleRef = await Test.createTestingModule({
imports: [
IntegrationModule.forRoot({
isGlobal: true,
url: 'test_url',
secure: false,
}),
],
}).compile();

const integrationModule = moduleRef.get(IntegrationModule);

expect(integrationModule.options).to.deep.equal({
url: 'test_url',
secure: false,
});
});

it('should auto-generate "forRootAsync" method', async () => {
const moduleRef = await Test.createTestingModule({
imports: [
IntegrationModule.forRootAsync({
isGlobal: true,
useFactory: () => {
return {
url: 'test_url',
secure: false,
};
},
}),
],
}).compile();

const integrationModule = moduleRef.get(IntegrationModule);

expect(integrationModule.options).to.deep.equal({
url: 'test_url',
secure: false,
});
});
});
22 changes: 22 additions & 0 deletions integration/module-utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": true,
"allowJs": true,
"outDir": "./dist"
},
"include": [
"src/**/*",
"e2e/**/*"
],
"exclude": [
"node_modules",
]
}
1 change: 0 additions & 1 deletion packages/common/cache/cache.constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const CACHE_MANAGER = 'CACHE_MANAGER';
export const CACHE_MODULE_OPTIONS = 'CACHE_MODULE_OPTIONS';
export const CACHE_KEY_METADATA = 'cache_module:cache_key';
export const CACHE_TTL_METADATA = 'cache_module:cache_ttl';
12 changes: 12 additions & 0 deletions packages/common/cache/cache.module-definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ConfigurableModuleBuilder } from '../module-utils';
import {
CacheModuleOptions,
CacheOptionsFactory,
} from './interfaces/cache-module.interface';

export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<CacheModuleOptions>({
moduleName: 'Cache',
})
.setFactoryMethodName('createCacheOptions' as keyof CacheOptionsFactory)
.build();
53 changes: 8 additions & 45 deletions packages/common/cache/cache.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Module } from '../decorators';
import { DynamicModule, Provider } from '../interfaces';
import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from './cache.constants';
import { DynamicModule } from '../interfaces';
import { CACHE_MANAGER } from './cache.constants';
import { ConfigurableModuleClass } from './cache.module-definition';
import { createCacheManager } from './cache.providers';
import {
CacheModuleAsyncOptions,
CacheModuleOptions,
CacheOptionsFactory,
} from './interfaces/cache-module.interface';

/**
Expand All @@ -19,7 +19,7 @@ import {
providers: [createCacheManager()],
exports: [CACHE_MANAGER],
})
export class CacheModule {
export class CacheModule extends ConfigurableModuleClass {
/**
* Configure the cache manager statically.
*
Expand All @@ -31,9 +31,8 @@ export class CacheModule {
options: CacheModuleOptions<StoreConfig> = {} as any,
): DynamicModule {
return {
module: CacheModule,
global: options.isGlobal,
providers: [{ provide: CACHE_MODULE_OPTIONS, useValue: options }],
...super.register(options),
};
}

Expand All @@ -48,47 +47,11 @@ export class CacheModule {
static registerAsync<
StoreConfig extends Record<any, any> = Record<string, any>,
>(options: CacheModuleAsyncOptions<StoreConfig>): DynamicModule {
const moduleDefinition = super.registerAsync(options);
return {
module: CacheModule,
global: options.isGlobal,
imports: options.imports,
providers: [
...this.createAsyncProviders<StoreConfig>(options),
...(options.extraProviders || []),
],
};
}

private static createAsyncProviders<StoreConfig extends Record<any, any>>(
options: CacheModuleAsyncOptions<StoreConfig>,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}
return [
this.createAsyncOptionsProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
}

private static createAsyncOptionsProvider<
StoreConfig extends Record<any, any>,
>(options: CacheModuleAsyncOptions<StoreConfig>): Provider {
if (options.useFactory) {
return {
provide: CACHE_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
}
return {
provide: CACHE_MODULE_OPTIONS,
useFactory: async (optionsFactory: CacheOptionsFactory<StoreConfig>) =>
optionsFactory.createCacheOptions(),
inject: [options.useExisting || options.useClass],
...moduleDefinition,
providers: moduleDefinition.providers.concat(options.extraProviders),
};
}
}
5 changes: 3 additions & 2 deletions packages/common/cache/cache.providers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Provider } from '../interfaces';
import { loadPackage } from '../utils/load-package.util';
import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from './cache.constants';
import { CACHE_MANAGER } from './cache.constants';
import { MODULE_OPTIONS_TOKEN } from './cache.module-definition';
import { defaultCacheOptions } from './default-options';
import { CacheManagerOptions } from './interfaces/cache-manager.interface';

Expand Down Expand Up @@ -31,6 +32,6 @@ export function createCacheManager(): Provider {
...(options || {}),
});
},
inject: [CACHE_MODULE_OPTIONS],
inject: [MODULE_OPTIONS_TOKEN],
};
}
11 changes: 9 additions & 2 deletions packages/common/cache/interfaces/cache-module.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ModuleMetadata, Provider, Type } from '../../interfaces';
import { Provider, Type } from '../../interfaces';
import { ConfigurableModuleAsyncOptions } from '../../module-utils';
import { CacheManagerOptions } from './cache-manager.interface';

export type CacheModuleOptions<
Expand Down Expand Up @@ -39,7 +40,10 @@ export interface CacheOptionsFactory<
*/
export interface CacheModuleAsyncOptions<
StoreConfig extends Record<any, any> = Record<string, any>,
> extends Pick<ModuleMetadata, 'imports'> {
> extends ConfigurableModuleAsyncOptions<
CacheModuleOptions<StoreConfig>,
keyof CacheOptionsFactory
> {
/**
* Injection token resolving to an existing provider. The provider must implement
* the `CacheOptionsFactory` interface.
Expand All @@ -63,6 +67,9 @@ export interface CacheModuleAsyncOptions<
* Dependencies that a Factory may inject.
*/
inject?: any[];
/**
* Extra providers to be registered within a scope of this module.
*/
extraProviders?: Provider[];
/**
* If "true', register `CacheModule` as a global module.
Expand Down
2 changes: 0 additions & 2 deletions packages/common/http/http.constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export const AXIOS_INSTANCE_TOKEN = 'AXIOS_INSTANCE_TOKEN';
export const HTTP_MODULE_ID = 'HTTP_MODULE_ID';
export const HTTP_MODULE_OPTIONS = 'HTTP_MODULE_OPTIONS';
23 changes: 23 additions & 0 deletions packages/common/http/http.module-definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Provider } from '../interfaces';
import { ConfigurableModuleBuilder } from '../module-utils';
import { HttpModuleOptions } from './interfaces';

export const {
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
ASYNC_OPTIONS_TYPE,
} = new ConfigurableModuleBuilder<HttpModuleOptions>({
moduleName: 'Http',
alwaysTransient: true,
})
.setFactoryMethodName('createHttpOptions')
.setExtras<{ extraProviders?: Provider[] }>(
{
extraProviders: [],
},
(definition, extras) => ({
...definition,
providers: definition.providers.concat(extras?.extraProviders),
}),
)
.build();
82 changes: 11 additions & 71 deletions packages/common/http/http.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import Axios from 'axios';
import { Module } from '../decorators/modules/module.decorator';
import { DynamicModule, Provider } from '../interfaces';
import { randomStringGenerator } from '../utils/random-string-generator.util';
import { DynamicModule } from '../interfaces';
import { AXIOS_INSTANCE_TOKEN } from './http.constants';
import {
AXIOS_INSTANCE_TOKEN,
HTTP_MODULE_ID,
HTTP_MODULE_OPTIONS,
} from './http.constants';
ASYNC_OPTIONS_TYPE,
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
} from './http.module-definition';
import { HttpService } from './http.service';
import {
HttpModuleAsyncOptions,
HttpModuleOptions,
HttpModuleOptionsFactory,
} from './interfaces';
import { HttpModuleOptions } from './interfaces';

/**
* @deprecated "HttpModule" (from the "@nestjs/common" package) is deprecated and will be removed in the next major release. Please, use the "@nestjs/axios" package instead.
Expand All @@ -27,73 +23,17 @@ import {
],
exports: [HttpService],
})
export class HttpModule {
static register(config: HttpModuleOptions): DynamicModule {
return {
module: HttpModule,
providers: [
{
provide: AXIOS_INSTANCE_TOKEN,
useValue: Axios.create(config),
},
{
provide: HTTP_MODULE_ID,
useValue: randomStringGenerator(),
},
],
};
}

static registerAsync(options: HttpModuleAsyncOptions): DynamicModule {
export class HttpModule extends ConfigurableModuleClass {
static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {
return {
module: HttpModule,
imports: options.imports,
...super.registerAsync(options),
providers: [
...this.createAsyncProviders(options),
{
provide: AXIOS_INSTANCE_TOKEN,
useFactory: (config: HttpModuleOptions) => Axios.create(config),
inject: [HTTP_MODULE_OPTIONS],
inject: [MODULE_OPTIONS_TOKEN],
},
{
provide: HTTP_MODULE_ID,
useValue: randomStringGenerator(),
},
...(options.extraProviders || []),
],
};
}

private static createAsyncProviders(
options: HttpModuleAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}
return [
this.createAsyncOptionsProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
}

private static createAsyncOptionsProvider(
options: HttpModuleAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: HTTP_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
}
return {
provide: HTTP_MODULE_OPTIONS,
useFactory: async (optionsFactory: HttpModuleOptionsFactory) =>
optionsFactory.createHttpOptions(),
inject: [options.useExisting || options.useClass],
};
}
}

0 comments on commit fc95619

Please sign in to comment.