Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add configurable module builder, module utils #9534

Merged
merged 5 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions integration/module-utils/src/integration.module-definition.ts
@@ -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
@@ -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();
}
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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],
};
}
}