Skip to content

Commit

Permalink
feature(@nestjs/core) enable deffered dynamic modules
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jun 24, 2018
1 parent 7023905 commit be6d90a
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 108 deletions.
29 changes: 29 additions & 0 deletions integration/typeorm/e2e/typeorm-async.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { AsyncApplicationModule } from './../src/app-async.module';

describe('TypeOrm (async configuration)', () => {
let server;
let app: INestApplication;

beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [AsyncApplicationModule],
}).compile();

app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});

it(`should return created entity`, () => {
return request(server)
.post('/photo')
.expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
});

afterEach(async () => {
await app.close();
});
});
11 changes: 11 additions & 0 deletions integration/typeorm/src/app-async.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database.module';
import { PhotoModule } from './photo/photo.module';

@Module({
imports: [
DatabaseModule.forRoot(),
PhotoModule,
],
})
export class AsyncApplicationModule {}
28 changes: 28 additions & 0 deletions integration/typeorm/src/database.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DynamicModule, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Photo } from './photo/photo.entity';

@Module({})
export class DatabaseModule {
static async forRoot(): Promise<DynamicModule> {
await new Promise((resolve) => setTimeout(resolve, 1000));
return {
module: DatabaseModule,
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [Photo],
synchronize: true,
keepConnectionAlive: true,
retryAttempts: 2,
retryDelay: 1000,
}),
],
};
}
}
22 changes: 15 additions & 7 deletions packages/common/interfaces/modules/module-metadata.interface.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { NestModule } from './nest-module.interface';
import { Controller } from '../controllers/controller.interface';
import { DynamicModule } from './dynamic-module.interface';
import { Type } from '../type.interface';
import { Provider } from './provider.interface';
import { DynamicModule } from './dynamic-module.interface';
import { ForwardReference } from './forward-reference.interface';
import { Provider } from './provider.interface';

export interface ModuleMetadata {
imports?: Array<Type<any> | DynamicModule | ForwardReference>;
imports?: Array<
Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference
>;
controllers?: Type<any>[];
providers?: Provider[];
exports?: Array<DynamicModule | string | Provider | ForwardReference>;
exports?: Array<
| DynamicModule
| Promise<DynamicModule>
| string
| Provider
| ForwardReference
>;
/** @deprecated */
modules?: Array<Type<any> | DynamicModule | ForwardReference>;
modules?: Array<
Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference
>;
/** @deprecated */
components?: Provider[];
}
25 changes: 16 additions & 9 deletions packages/core/injector/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Type, DynamicModule } from '@nestjs/common/interfaces';
import { DynamicModule, Type } from '@nestjs/common/interfaces';
import { ModuleTokenFactory } from './module-token-factory';

export interface ModuleFactory {
Expand All @@ -10,21 +10,22 @@ export interface ModuleFactory {
export class ModuleCompiler {
private readonly moduleTokenFactory = new ModuleTokenFactory();

public compile(
metatype: Type<any> | DynamicModule,
public async compile(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
scope: Type<any>[],
): ModuleFactory {
const { type, dynamicMetadata } = this.extractMetadata(metatype);
): Promise<ModuleFactory> {
const { type, dynamicMetadata } = await this.extractMetadata(metatype);
const token = this.moduleTokenFactory.create(type, scope, dynamicMetadata);
return { type, dynamicMetadata, token };
}

public extractMetadata(
metatype: Type<any> | DynamicModule,
): {
public async extractMetadata(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
): Promise<{
type: Type<any>;
dynamicMetadata?: Partial<DynamicModule> | undefined;
} {
}> {
metatype = this.isDefferedModule(metatype) ? await metatype : metatype;
if (!this.isDynamicModule(metatype)) {
return { type: metatype };
}
Expand All @@ -37,4 +38,10 @@ export class ModuleCompiler {
): module is DynamicModule {
return !!(module as DynamicModule).module;
}

public isDefferedModule(
module: Type<any> | DynamicModule | Promise<DynamicModule>,
): module is Promise<DynamicModule> {
return module && module instanceof Promise;
}
}
16 changes: 10 additions & 6 deletions packages/core/injector/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ export class NestContainer {
return this.applicationRef;
}

public addModule(metatype: Type<any> | DynamicModule, scope: Type<any>[]) {
public async addModule(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
scope: Type<any>[],
) {
if (!metatype) {
throw new InvalidModuleException(scope);
}
const { type, dynamicMetadata, token } = this.moduleCompiler.compile(
const { type, dynamicMetadata, token } = await this.moduleCompiler.compile(
metatype,
scope,
);
Expand Down Expand Up @@ -87,7 +90,7 @@ export class NestContainer {
return this.modules;
}

public addRelatedModule(
public async addRelatedModule(
relatedModule: Type<any> | DynamicModule,
token: string,
) {
Expand All @@ -97,9 +100,10 @@ export class NestContainer {
const parent = module.metatype;

const scope = [].concat(module.scope, parent);
const {
token: relatedModuleToken,
} = this.moduleCompiler.compile(relatedModule, scope);
const { token: relatedModuleToken } = await this.moduleCompiler.compile(
relatedModule,
scope,
);
const related = this.modules.get(relatedModuleToken);
module.addRelatedModule(related);
}
Expand Down
41 changes: 20 additions & 21 deletions packages/core/nest-factory.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
import { DependenciesScanner } from './scanner';
import { InstanceLoader } from './injector/instance-loader';
import { NestContainer } from './injector/container';
import { ExceptionsZone } from './errors/exceptions-zone';
import { Logger } from '@nestjs/common/services/logger.service';
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
import { messages } from './constants';
import { NestApplication } from './nest-application';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import { ExpressFactory } from './adapters/express-factory';
import {
HttpServer,
INestApplication,
INestMicroservice,
INestApplicationContext,
HttpServer,
INestMicroservice,
} from '@nestjs/common';
import { MetadataScanner } from './metadata-scanner';
import { NestApplicationContext } from './nest-application-context';
import { HttpsOptions } from '@nestjs/common/interfaces/external/https-options.interface';
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
import { MicroserviceOptions } from '@nestjs/common/interfaces/microservices/microservice-configuration.interface';
import { NestMicroserviceOptions } from '@nestjs/common/interfaces/microservices/nest-microservice-options.interface';
import { ApplicationConfig } from './application-config';
import { ExpressAdapter } from './adapters/express-adapter';
import { NestApplicationContextOptions } from '@nestjs/common/interfaces/nest-application-context-options.interface';
import { NestApplicationOptions } from '@nestjs/common/interfaces/nest-application-options.interface';
import { INestExpressApplication } from '@nestjs/common/interfaces/nest-express-application.interface';
import { FastifyAdapter } from './adapters/fastify-adapter';
import { INestFastifyApplication } from '@nestjs/common/interfaces/nest-fastify-application.interface';
import { MicroserviceOptions } from '@nestjs/common/interfaces/microservices/microservice-configuration.interface';
import { Logger } from '@nestjs/common/services/logger.service';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { isFunction } from '@nestjs/common/utils/shared.utils';
import { ExpressAdapter } from './adapters/express-adapter';
import { ExpressFactory } from './adapters/express-factory';
import { FastifyAdapter } from './adapters/fastify-adapter';
import { ApplicationConfig } from './application-config';
import { messages } from './constants';
import { ExceptionsZone } from './errors/exceptions-zone';
import { NestContainer } from './injector/container';
import { InstanceLoader } from './injector/instance-loader';
import { MetadataScanner } from './metadata-scanner';
import { NestApplication } from './nest-application';
import { NestApplicationContext } from './nest-application-context';
import { DependenciesScanner } from './scanner';

export class NestFactoryStatic {
private readonly logger = new Logger('NestFactory', true);
Expand Down Expand Up @@ -149,7 +148,7 @@ export class NestFactoryStatic {
try {
this.logger.log(messages.APPLICATION_START);
await ExceptionsZone.asyncRun(async () => {
dependenciesScanner.scan(module);
await dependenciesScanner.scan(module);
await instanceLoader.createInstancesOfDependencies();
dependenciesScanner.applyApplicationProviders();
});
Expand Down
68 changes: 37 additions & 31 deletions packages/core/scanner.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import 'reflect-metadata';
import { NestContainer } from './injector/container';
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
import { DynamicModule } from '@nestjs/common';
import {
metadata,
GATEWAY_MIDDLEWARES,
EXCEPTION_FILTERS_METADATA,
GATEWAY_MIDDLEWARES,
GUARDS_METADATA,
INTERCEPTORS_METADATA,
metadata,
PIPES_METADATA,
ROUTE_ARGS_METADATA,
} from '@nestjs/common/constants';
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { MetadataScanner } from '../core/metadata-scanner';
import { DynamicModule } from '@nestjs/common';
import { ApplicationConfig } from './application-config';
import {
isFunction,
isNil,
isUndefined,
isFunction,
} from '@nestjs/common/utils/shared.utils';
import { APP_INTERCEPTOR, APP_PIPE, APP_GUARD, APP_FILTER } from './constants';
import 'reflect-metadata';
import { MetadataScanner } from '../core/metadata-scanner';
import { ApplicationConfig } from './application-config';
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from './constants';
import { CircularDependencyException } from './errors/exceptions/circular-dependency.exception';
import { NestContainer } from './injector/container';

interface ApplicationProviderWrapper {
moduleToken: string;
Expand All @@ -36,43 +36,43 @@ export class DependenciesScanner {
private readonly applicationConfig = new ApplicationConfig(),
) {}

public scan(module: Type<any>) {
this.scanForModules(module);
this.scanModulesForDependencies();
public async scan(module: Type<any>) {
await this.scanForModules(module);
await this.scanModulesForDependencies();
this.container.bindGlobalScope();
}

public scanForModules(
public async scanForModules(
module: Type<any> | DynamicModule,
scope: Type<any>[] = [],
) {
this.storeModule(module, scope);
await this.storeModule(module, scope);

const modules = this.reflectMetadata(module, metadata.MODULES);
modules.map(innerModule => {
this.scanForModules(innerModule, [].concat(scope, module));
});
for (const innerModule of modules) {
await this.scanForModules(innerModule, [].concat(scope, module));
}
}

public storeModule(module: any, scope: Type<any>[]) {
public async storeModule(module: any, scope: Type<any>[]) {
if (module && module.forwardRef) {
return this.container.addModule(module.forwardRef(), scope);
return await this.container.addModule(module.forwardRef(), scope);
}
this.container.addModule(module, scope);
await this.container.addModule(module, scope);
}

public scanModulesForDependencies() {
public async scanModulesForDependencies() {
const modules = this.container.getModules();

modules.forEach(({ metatype }, token) => {
this.reflectRelatedModules(metatype, token, metatype.name);
for (const [token, { metatype }] of modules) {
await this.reflectRelatedModules(metatype, token, metatype.name);
this.reflectComponents(metatype, token);
this.reflectControllers(metatype, token);
this.reflectExports(metatype, token);
});
}
}

public reflectRelatedModules(
public async reflectRelatedModules(
module: Type<any>,
token: string,
context: string,
Expand All @@ -88,7 +88,9 @@ export class DependenciesScanner {
metadata.IMPORTS as 'imports',
),
];
modules.map(related => this.storeRelatedModule(related, token, context));
for (const related of modules) {
await this.storeRelatedModule(related, token, context);
}
}

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

public storeRelatedModule(related: any, token: string, context: string) {
public async storeRelatedModule(
related: any,
token: string,
context: string,
) {
if (isUndefined(related)) {
throw new CircularDependencyException(context);
}
if (related && related.forwardRef) {
return this.container.addRelatedModule(related.forwardRef(), token);
return await this.container.addRelatedModule(related.forwardRef(), token);
}
this.container.addRelatedModule(related, token);
await this.container.addRelatedModule(related, token);
}

public storeComponent(component, token: string) {
Expand Down

0 comments on commit be6d90a

Please sign in to comment.