Skip to content

Commit be6d90a

Browse files
feature(@nestjs/core) enable deffered dynamic modules
1 parent 7023905 commit be6d90a

11 files changed

Lines changed: 198 additions & 108 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { INestApplication } from '@nestjs/common';
2+
import { Test } from '@nestjs/testing';
3+
import * as request from 'supertest';
4+
import { AsyncApplicationModule } from './../src/app-async.module';
5+
6+
describe('TypeOrm (async configuration)', () => {
7+
let server;
8+
let app: INestApplication;
9+
10+
beforeEach(async () => {
11+
const module = await Test.createTestingModule({
12+
imports: [AsyncApplicationModule],
13+
}).compile();
14+
15+
app = module.createNestApplication();
16+
server = app.getHttpServer();
17+
await app.init();
18+
});
19+
20+
it(`should return created entity`, () => {
21+
return request(server)
22+
.post('/photo')
23+
.expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
24+
});
25+
26+
afterEach(async () => {
27+
await app.close();
28+
});
29+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Module } from '@nestjs/common';
2+
import { DatabaseModule } from './database.module';
3+
import { PhotoModule } from './photo/photo.module';
4+
5+
@Module({
6+
imports: [
7+
DatabaseModule.forRoot(),
8+
PhotoModule,
9+
],
10+
})
11+
export class AsyncApplicationModule {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { DynamicModule, Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { Photo } from './photo/photo.entity';
4+
5+
@Module({})
6+
export class DatabaseModule {
7+
static async forRoot(): Promise<DynamicModule> {
8+
await new Promise((resolve) => setTimeout(resolve, 1000));
9+
return {
10+
module: DatabaseModule,
11+
imports: [
12+
TypeOrmModule.forRoot({
13+
type: 'mysql',
14+
host: 'localhost',
15+
port: 3306,
16+
username: 'root',
17+
password: 'root',
18+
database: 'test',
19+
entities: [Photo],
20+
synchronize: true,
21+
keepConnectionAlive: true,
22+
retryAttempts: 2,
23+
retryDelay: 1000,
24+
}),
25+
],
26+
};
27+
}
28+
}
Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
1-
import { NestModule } from './nest-module.interface';
2-
import { Controller } from '../controllers/controller.interface';
3-
import { DynamicModule } from './dynamic-module.interface';
41
import { Type } from '../type.interface';
5-
import { Provider } from './provider.interface';
2+
import { DynamicModule } from './dynamic-module.interface';
63
import { ForwardReference } from './forward-reference.interface';
4+
import { Provider } from './provider.interface';
75

86
export interface ModuleMetadata {
9-
imports?: Array<Type<any> | DynamicModule | ForwardReference>;
7+
imports?: Array<
8+
Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference
9+
>;
1010
controllers?: Type<any>[];
1111
providers?: Provider[];
12-
exports?: Array<DynamicModule | string | Provider | ForwardReference>;
12+
exports?: Array<
13+
| DynamicModule
14+
| Promise<DynamicModule>
15+
| string
16+
| Provider
17+
| ForwardReference
18+
>;
1319
/** @deprecated */
14-
modules?: Array<Type<any> | DynamicModule | ForwardReference>;
20+
modules?: Array<
21+
Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference
22+
>;
1523
/** @deprecated */
1624
components?: Provider[];
1725
}

packages/core/injector/compiler.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Type, DynamicModule } from '@nestjs/common/interfaces';
1+
import { DynamicModule, Type } from '@nestjs/common/interfaces';
22
import { ModuleTokenFactory } from './module-token-factory';
33

44
export interface ModuleFactory {
@@ -10,21 +10,22 @@ export interface ModuleFactory {
1010
export class ModuleCompiler {
1111
private readonly moduleTokenFactory = new ModuleTokenFactory();
1212

13-
public compile(
14-
metatype: Type<any> | DynamicModule,
13+
public async compile(
14+
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
1515
scope: Type<any>[],
16-
): ModuleFactory {
17-
const { type, dynamicMetadata } = this.extractMetadata(metatype);
16+
): Promise<ModuleFactory> {
17+
const { type, dynamicMetadata } = await this.extractMetadata(metatype);
1818
const token = this.moduleTokenFactory.create(type, scope, dynamicMetadata);
1919
return { type, dynamicMetadata, token };
2020
}
2121

22-
public extractMetadata(
23-
metatype: Type<any> | DynamicModule,
24-
): {
22+
public async extractMetadata(
23+
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
24+
): Promise<{
2525
type: Type<any>;
2626
dynamicMetadata?: Partial<DynamicModule> | undefined;
27-
} {
27+
}> {
28+
metatype = this.isDefferedModule(metatype) ? await metatype : metatype;
2829
if (!this.isDynamicModule(metatype)) {
2930
return { type: metatype };
3031
}
@@ -37,4 +38,10 @@ export class ModuleCompiler {
3738
): module is DynamicModule {
3839
return !!(module as DynamicModule).module;
3940
}
41+
42+
public isDefferedModule(
43+
module: Type<any> | DynamicModule | Promise<DynamicModule>,
44+
): module is Promise<DynamicModule> {
45+
return module && module instanceof Promise;
46+
}
4047
}

packages/core/injector/container.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ export class NestContainer {
3535
return this.applicationRef;
3636
}
3737

38-
public addModule(metatype: Type<any> | DynamicModule, scope: Type<any>[]) {
38+
public async addModule(
39+
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
40+
scope: Type<any>[],
41+
) {
3942
if (!metatype) {
4043
throw new InvalidModuleException(scope);
4144
}
42-
const { type, dynamicMetadata, token } = this.moduleCompiler.compile(
45+
const { type, dynamicMetadata, token } = await this.moduleCompiler.compile(
4346
metatype,
4447
scope,
4548
);
@@ -87,7 +90,7 @@ export class NestContainer {
8790
return this.modules;
8891
}
8992

90-
public addRelatedModule(
93+
public async addRelatedModule(
9194
relatedModule: Type<any> | DynamicModule,
9295
token: string,
9396
) {
@@ -97,9 +100,10 @@ export class NestContainer {
97100
const parent = module.metatype;
98101

99102
const scope = [].concat(module.scope, parent);
100-
const {
101-
token: relatedModuleToken,
102-
} = this.moduleCompiler.compile(relatedModule, scope);
103+
const { token: relatedModuleToken } = await this.moduleCompiler.compile(
104+
relatedModule,
105+
scope,
106+
);
103107
const related = this.modules.get(relatedModuleToken);
104108
module.addRelatedModule(related);
105109
}

packages/core/nest-factory.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
1-
import { DependenciesScanner } from './scanner';
2-
import { InstanceLoader } from './injector/instance-loader';
3-
import { NestContainer } from './injector/container';
4-
import { ExceptionsZone } from './errors/exceptions-zone';
5-
import { Logger } from '@nestjs/common/services/logger.service';
6-
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
7-
import { messages } from './constants';
8-
import { NestApplication } from './nest-application';
9-
import { isFunction } from '@nestjs/common/utils/shared.utils';
10-
import { ExpressFactory } from './adapters/express-factory';
111
import {
2+
HttpServer,
123
INestApplication,
13-
INestMicroservice,
144
INestApplicationContext,
15-
HttpServer,
5+
INestMicroservice,
166
} from '@nestjs/common';
17-
import { MetadataScanner } from './metadata-scanner';
18-
import { NestApplicationContext } from './nest-application-context';
19-
import { HttpsOptions } from '@nestjs/common/interfaces/external/https-options.interface';
20-
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
7+
import { MicroserviceOptions } from '@nestjs/common/interfaces/microservices/microservice-configuration.interface';
218
import { NestMicroserviceOptions } from '@nestjs/common/interfaces/microservices/nest-microservice-options.interface';
22-
import { ApplicationConfig } from './application-config';
23-
import { ExpressAdapter } from './adapters/express-adapter';
9+
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
10+
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
2411
import { INestExpressApplication } from '@nestjs/common/interfaces/nest-express-application.interface';
25-
import { FastifyAdapter } from './adapters/fastify-adapter';
2612
import { INestFastifyApplication } from '@nestjs/common/interfaces/nest-fastify-application.interface';
27-
import { MicroserviceOptions } from '@nestjs/common/interfaces/microservices/microservice-configuration.interface';
13+
import { Logger } from '@nestjs/common/services/logger.service';
2814
import { loadPackage } from '@nestjs/common/utils/load-package.util';
15+
import { isFunction } from '@nestjs/common/utils/shared.utils';
16+
import { ExpressAdapter } from './adapters/express-adapter';
17+
import { ExpressFactory } from './adapters/express-factory';
18+
import { FastifyAdapter } from './adapters/fastify-adapter';
19+
import { ApplicationConfig } from './application-config';
20+
import { messages } from './constants';
21+
import { ExceptionsZone } from './errors/exceptions-zone';
22+
import { NestContainer } from './injector/container';
23+
import { InstanceLoader } from './injector/instance-loader';
24+
import { MetadataScanner } from './metadata-scanner';
25+
import { NestApplication } from './nest-application';
26+
import { NestApplicationContext } from './nest-application-context';
27+
import { DependenciesScanner } from './scanner';
2928

3029
export class NestFactoryStatic {
3130
private readonly logger = new Logger('NestFactory', true);
@@ -149,7 +148,7 @@ export class NestFactoryStatic {
149148
try {
150149
this.logger.log(messages.APPLICATION_START);
151150
await ExceptionsZone.asyncRun(async () => {
152-
dependenciesScanner.scan(module);
151+
await dependenciesScanner.scan(module);
153152
await instanceLoader.createInstancesOfDependencies();
154153
dependenciesScanner.applyApplicationProviders();
155154
});

packages/core/scanner.ts

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
import 'reflect-metadata';
2-
import { NestContainer } from './injector/container';
3-
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
4-
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
1+
import { DynamicModule } from '@nestjs/common';
52
import {
6-
metadata,
7-
GATEWAY_MIDDLEWARES,
83
EXCEPTION_FILTERS_METADATA,
4+
GATEWAY_MIDDLEWARES,
95
GUARDS_METADATA,
106
INTERCEPTORS_METADATA,
7+
metadata,
118
PIPES_METADATA,
129
ROUTE_ARGS_METADATA,
1310
} from '@nestjs/common/constants';
11+
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
12+
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
1413
import { Type } from '@nestjs/common/interfaces/type.interface';
15-
import { MetadataScanner } from '../core/metadata-scanner';
16-
import { DynamicModule } from '@nestjs/common';
17-
import { ApplicationConfig } from './application-config';
1814
import {
15+
isFunction,
1916
isNil,
2017
isUndefined,
21-
isFunction,
2218
} from '@nestjs/common/utils/shared.utils';
23-
import { APP_INTERCEPTOR, APP_PIPE, APP_GUARD, APP_FILTER } from './constants';
19+
import 'reflect-metadata';
20+
import { MetadataScanner } from '../core/metadata-scanner';
21+
import { ApplicationConfig } from './application-config';
22+
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from './constants';
2423
import { CircularDependencyException } from './errors/exceptions/circular-dependency.exception';
24+
import { NestContainer } from './injector/container';
2525

2626
interface ApplicationProviderWrapper {
2727
moduleToken: string;
@@ -36,43 +36,43 @@ export class DependenciesScanner {
3636
private readonly applicationConfig = new ApplicationConfig(),
3737
) {}
3838

39-
public scan(module: Type<any>) {
40-
this.scanForModules(module);
41-
this.scanModulesForDependencies();
39+
public async scan(module: Type<any>) {
40+
await this.scanForModules(module);
41+
await this.scanModulesForDependencies();
4242
this.container.bindGlobalScope();
4343
}
4444

45-
public scanForModules(
45+
public async scanForModules(
4646
module: Type<any> | DynamicModule,
4747
scope: Type<any>[] = [],
4848
) {
49-
this.storeModule(module, scope);
49+
await this.storeModule(module, scope);
5050

5151
const modules = this.reflectMetadata(module, metadata.MODULES);
52-
modules.map(innerModule => {
53-
this.scanForModules(innerModule, [].concat(scope, module));
54-
});
52+
for (const innerModule of modules) {
53+
await this.scanForModules(innerModule, [].concat(scope, module));
54+
}
5555
}
5656

57-
public storeModule(module: any, scope: Type<any>[]) {
57+
public async storeModule(module: any, scope: Type<any>[]) {
5858
if (module && module.forwardRef) {
59-
return this.container.addModule(module.forwardRef(), scope);
59+
return await this.container.addModule(module.forwardRef(), scope);
6060
}
61-
this.container.addModule(module, scope);
61+
await this.container.addModule(module, scope);
6262
}
6363

64-
public scanModulesForDependencies() {
64+
public async scanModulesForDependencies() {
6565
const modules = this.container.getModules();
6666

67-
modules.forEach(({ metatype }, token) => {
68-
this.reflectRelatedModules(metatype, token, metatype.name);
67+
for (const [token, { metatype }] of modules) {
68+
await this.reflectRelatedModules(metatype, token, metatype.name);
6969
this.reflectComponents(metatype, token);
7070
this.reflectControllers(metatype, token);
7171
this.reflectExports(metatype, token);
72-
});
72+
}
7373
}
7474

75-
public reflectRelatedModules(
75+
public async reflectRelatedModules(
7676
module: Type<any>,
7777
token: string,
7878
context: string,
@@ -88,7 +88,9 @@ export class DependenciesScanner {
8888
metadata.IMPORTS as 'imports',
8989
),
9090
];
91-
modules.map(related => this.storeRelatedModule(related, token, context));
91+
for (const related of modules) {
92+
await this.storeRelatedModule(related, token, context);
93+
}
9294
}
9395

9496
public reflectComponents(module: Type<any>, token: string) {
@@ -214,14 +216,18 @@ export class DependenciesScanner {
214216
return descriptor ? Reflect.getMetadata(key, descriptor.value) : undefined;
215217
}
216218

217-
public storeRelatedModule(related: any, token: string, context: string) {
219+
public async storeRelatedModule(
220+
related: any,
221+
token: string,
222+
context: string,
223+
) {
218224
if (isUndefined(related)) {
219225
throw new CircularDependencyException(context);
220226
}
221227
if (related && related.forwardRef) {
222-
return this.container.addRelatedModule(related.forwardRef(), token);
228+
return await this.container.addRelatedModule(related.forwardRef(), token);
223229
}
224-
this.container.addRelatedModule(related, token);
230+
await this.container.addRelatedModule(related, token);
225231
}
226232

227233
public storeComponent(component, token: string) {

0 commit comments

Comments
 (0)