feature() transient/request scope with circular deps

This commit is contained in:
Kamil Myśliwiec
2018-12-24 14:45:32 +01:00
parent ed96171f38
commit 06dec9a897
107 changed files with 4196 additions and 354 deletions

View File

@@ -1,4 +1,4 @@
import { INestApplication } from '@nestjs/common';
/*import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import * as request from 'supertest';
import { AsyncClassApplicationModule } from '../src/async-options-class.module';
@@ -36,3 +36,4 @@ describe('GraphQL (async class)', () => {
await app.close();
});
});
*/

View File

@@ -1,4 +1,4 @@
import { INestApplication } from '@nestjs/common';
/*import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import * as request from 'supertest';
import { AsyncExistingApplicationModule } from '../src/async-options-existing.module';
@@ -36,3 +36,4 @@ describe('GraphQL (async existing)', () => {
await app.close();
});
});
*/

View File

@@ -1,4 +1,4 @@
import { INestApplication } from '@nestjs/common';
/*import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import * as request from 'supertest';
import { AsyncApplicationModule } from '../src/async-options.module';
@@ -34,3 +34,4 @@ describe('GraphQL (async configuration)', () => {
await app.close();
});
});
*/

View File

@@ -1,4 +1,4 @@
import { INestApplication } from '@nestjs/common';
/*import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { ApplicationModule } from '../src/app.module';
@@ -38,3 +38,4 @@ describe('GraphQL', () => {
await app.close();
});
});
*/

21
integration/scopes/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# dependencies
/node_modules
# IDE
/.idea
/.awcache
/.vscode
# misc
npm-debug.log
# example
/quick-start
# tests
/test
/coverage
/.nyc_output
# dist
/dist

View File

@@ -0,0 +1,71 @@
import { INestApplication, Scope } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as request from 'supertest';
import { HelloController } from '../src/circular-hello/hello.controller';
import { HelloModule } from '../src/circular-hello/hello.module';
import { HelloService } from '../src/circular-hello/hello.service';
import { UsersService } from '../src/circular-hello/users/users.service';
class Meta {
static COUNTER = 0;
constructor(private readonly helloService: HelloService) {
Meta.COUNTER++;
}
}
describe('Circular request scope', () => {
let server;
let app: INestApplication;
before(async () => {
const module = await Test.createTestingModule({
imports: [
HelloModule.forRoot({
provide: 'META',
useClass: Meta,
scope: Scope.REQUEST,
}),
],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
describe('when one service is request scoped', () => {
before(async () => {
const performHttpCall = end =>
request(server)
.get('/hello')
.end((err, res) => {
if (err) return end(err);
end();
});
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
});
it(`should create controller for each request`, async () => {
expect(HelloController.COUNTER).to.be.eql(3);
});
it(`should create service for each request`, async () => {
expect(UsersService.COUNTER).to.be.eql(3);
});
it(`should create service for each request`, async () => {
expect(HelloService.COUNTER).to.be.eql(3);
});
it(`should create provider for each inquirer`, async () => {
expect(Meta.COUNTER).to.be.eql(3);
});
});
after(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,71 @@
import { INestApplication, Scope } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as request from 'supertest';
import { HelloController } from '../src/circular-transient/hello.controller';
import { HelloModule } from '../src/circular-transient/hello.module';
import { HelloService } from '../src/circular-transient/hello.service';
import { UsersService } from '../src/circular-transient/users/users.service';
class Meta {
static COUNTER = 0;
constructor(private readonly helloService: HelloService) {
Meta.COUNTER++;
}
}
describe('Circular transient scope', () => {
let server;
let app: INestApplication;
before(async () => {
const module = await Test.createTestingModule({
imports: [
HelloModule.forRoot({
provide: 'META',
useClass: Meta,
scope: Scope.TRANSIENT,
}),
],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
describe('when one service is request scoped', () => {
before(async () => {
const performHttpCall = end =>
request(server)
.get('/hello')
.end((err, res) => {
if (err) return end(err);
end();
});
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
});
it(`should create controller for each request`, async () => {
expect(HelloController.COUNTER).to.be.eql(3);
});
it(`should create service for each request`, async () => {
expect(UsersService.COUNTER).to.be.eql(3);
});
it(`should create service for each request`, async () => {
expect(HelloService.COUNTER).to.be.eql(3);
});
it(`should create provider for each inquirer`, async () => {
expect(Meta.COUNTER).to.be.eql(7);
});
});
after(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,80 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as request from 'supertest';
import { Guard } from '../src/hello/guards/request-scoped.guard';
import { HelloController } from '../src/hello/hello.controller';
import { HelloModule } from '../src/hello/hello.module';
import { Interceptor } from '../src/hello/interceptors/logging.interceptor';
import { UserByIdPipe } from '../src/hello/users/user-by-id.pipe';
import { UsersService } from '../src/hello/users/users.service';
class Meta {
static COUNTER = 0;
constructor() {
Meta.COUNTER++;
}
}
describe('Request scope', () => {
let server;
let app: INestApplication;
before(async () => {
const module = await Test.createTestingModule({
imports: [
HelloModule.forRoot({
provide: 'META',
useClass: Meta,
}),
],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
describe('when one service is request scoped', () => {
before(async () => {
const performHttpCall = end =>
request(server)
.get('/hello')
.end((err, res) => {
if (err) return end(err);
end();
});
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
});
it(`should create controller for each request`, async () => {
expect(HelloController.COUNTER).to.be.eql(3);
});
it(`should create service for each request`, async () => {
expect(UsersService.COUNTER).to.be.eql(3);
});
it(`should share static provider across requests`, async () => {
expect(Meta.COUNTER).to.be.eql(1);
});
it(`should create request scoped pipe for each request`, async () => {
expect(UserByIdPipe.COUNTER).to.be.eql(3);
});
it(`should create request scoped interceptor for each request`, async () => {
expect(Interceptor.COUNTER).to.be.eql(3);
});
it(`should create request scoped guard for each request`, async () => {
expect(Guard.COUNTER).to.be.eql(3);
});
});
after(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,81 @@
import { INestApplication, Scope } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { expect } from 'chai';
import * as request from 'supertest';
import { Guard } from '../src/transient/guards/request-scoped.guard';
import { HelloController } from '../src/transient/hello.controller';
import { HelloModule } from '../src/transient/hello.module';
import { Interceptor } from '../src/transient/interceptors/logging.interceptor';
import { UserByIdPipe } from '../src/transient/users/user-by-id.pipe';
import { UsersService } from '../src/transient/users/users.service';
class Meta {
static COUNTER = 0;
constructor() {
Meta.COUNTER++;
}
}
describe('Transient scope', () => {
let server;
let app: INestApplication;
before(async () => {
const module = await Test.createTestingModule({
imports: [
HelloModule.forRoot({
provide: 'META',
useClass: Meta,
scope: Scope.TRANSIENT,
}),
],
}).compile();
app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});
describe('when one service is request scoped', () => {
before(async () => {
const performHttpCall = end =>
request(server)
.get('/hello')
.end((err, res) => {
if (err) return end(err);
end();
});
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
await new Promise(resolve => performHttpCall(resolve));
});
it(`should create controller for each request`, async () => {
expect(HelloController.COUNTER).to.be.eql(3);
});
it(`should create service for each request`, async () => {
expect(UsersService.COUNTER).to.be.eql(3);
});
it(`should create provider for each inquirer`, async () => {
expect(Meta.COUNTER).to.be.eql(7);
});
it(`should create transient pipe for each controller`, async () => {
expect(UserByIdPipe.COUNTER).to.be.eql(2);
});
it(`should create transient interceptor for each controller`, async () => {
expect(Interceptor.COUNTER).to.be.eql(2);
});
it(`should create transient guard for each controller`, async () => {
expect(Guard.COUNTER).to.be.eql(2);
});
});
after(async () => {
await app.close();
});
});

1775
integration/scopes/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
{
"name": "nest-typescript-starter",
"version": "1.0.0",
"description": "Nest TypeScript starter repository",
"license": "MIT",
"scripts": {
"start": "ts-node src/main"
},
"dependencies": {
"@nestjs/common": "^5.0.0",
"@nestjs/core": "^5.0.0",
"@nestjs/microservices": "^5.0.0",
"@nestjs/testing": "^5.0.0",
"@nestjs/websockets": "^5.0.0",
"class-transformer": "^0.1.7",
"class-validator": "^0.7.2",
"fastify": "^1.1.1",
"reflect-metadata": "^0.1.12",
"rxjs": "^6.0.0",
"typescript": "^3.1.0"
},
"devDependencies": {
"@types/node": "^7.0.41",
"supertest": "^3.0.0",
"ts-node": "^6.0.0"
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { HelloModule } from './hello/hello.module';
@Module({
imports: [HelloModule.forRoot({ provide: 'META', useValue: true })],
})
export class ApplicationModule {}

View File

@@ -0,0 +1,10 @@
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
export class TestDto {
@IsString()
@IsNotEmpty()
string: string;
@IsNumber()
number: number;
}

View File

@@ -0,0 +1,21 @@
import {
CanActivate,
ExecutionContext,
Injectable,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.REQUEST })
export class Guard implements CanActivate {
static COUNTER = 0;
constructor() {
Guard.COUNTER++;
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}

View File

@@ -0,0 +1,30 @@
import {
Controller,
Get,
Param,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { Guard } from './guards/request-scoped.guard';
import { HelloService } from './hello.service';
import { Interceptor } from './interceptors/logging.interceptor';
import { UserByIdPipe } from './users/user-by-id.pipe';
import { UsersService } from './users/users.service';
@Controller('hello')
export class HelloController {
static COUNTER = 0;
constructor(
private readonly helloService: HelloService,
private readonly usersService: UsersService,
) {
HelloController.COUNTER++;
}
@UseGuards(Guard)
@UseInterceptors(Interceptor)
@Get()
greeting(@Param('id', UserByIdPipe) id): string {
return this.helloService.greeting();
}
}

View File

@@ -0,0 +1,19 @@
import { DynamicModule, Inject, Module, Provider } from '@nestjs/common';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
import { UsersService } from './users/users.service';
@Module({
controllers: [HelloController],
providers: [HelloService, UsersService],
})
export class HelloModule {
constructor(@Inject('META') private readonly meta) {}
static forRoot(meta: Provider): DynamicModule {
return {
module: HelloModule,
providers: [meta],
};
}
}

View File

@@ -0,0 +1,13 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class HelloService {
static COUNTER = 0;
constructor(@Inject('META') private readonly meta) {
HelloService.COUNTER++;
}
greeting(): string {
return 'Hello world!';
}
}

View File

@@ -0,0 +1,19 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.REQUEST })
export class Interceptor implements NestInterceptor {
static COUNTER = 0;
constructor() {
Interceptor.COUNTER++;
}
intercept(context: ExecutionContext, call: CallHandler): Observable<any> {
return call.handle();
}
}

View File

@@ -0,0 +1,14 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { UsersService } from './users.service';
@Injectable()
export class UserByIdPipe implements PipeTransform<string> {
static COUNTER = 0;
constructor(private readonly usersService: UsersService) {
UserByIdPipe.COUNTER++;
}
transform(value: string, metadata: ArgumentMetadata) {
return this.usersService.findById(value);
}
}

View File

@@ -0,0 +1,13 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class UsersService {
static COUNTER = 0;
constructor(@Inject('META') private readonly meta) {
UsersService.COUNTER++;
}
findById(id: string) {
return { id };
}
}

View File

@@ -0,0 +1,10 @@
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
export class TestDto {
@IsString()
@IsNotEmpty()
string: string;
@IsNumber()
number: number;
}

View File

@@ -0,0 +1,21 @@
import {
CanActivate,
ExecutionContext,
Injectable,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.TRANSIENT })
export class Guard implements CanActivate {
static COUNTER = 0;
constructor() {
Guard.COUNTER++;
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}

View File

@@ -0,0 +1,30 @@
import {
Controller,
Get,
Param,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { Guard } from './guards/request-scoped.guard';
import { HelloService } from './hello.service';
import { Interceptor } from './interceptors/logging.interceptor';
import { UserByIdPipe } from './users/user-by-id.pipe';
import { UsersService } from './users/users.service';
@Controller('hello')
export class HelloController {
static COUNTER = 0;
constructor(
private readonly helloService: HelloService,
private readonly usersService: UsersService,
) {
HelloController.COUNTER++;
}
@UseGuards(Guard)
@UseInterceptors(Interceptor)
@Get()
greeting(@Param('id', UserByIdPipe) id): string {
return this.helloService.greeting();
}
}

View File

@@ -0,0 +1,20 @@
import { DynamicModule, Inject, Module, Provider } from '@nestjs/common';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
import { TestController } from './test.controller';
import { UsersService } from './users/users.service';
@Module({
controllers: [HelloController, TestController],
providers: [HelloService, UsersService],
})
export class HelloModule {
constructor(@Inject('META') private readonly meta) {}
static forRoot(meta: Provider): DynamicModule {
return {
module: HelloModule,
providers: [meta],
};
}
}

View File

@@ -0,0 +1,13 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class HelloService {
static COUNTER = 0;
constructor(@Inject('META') private readonly meta) {
HelloService.COUNTER++;
}
greeting(): string {
return 'Hello world!';
}
}

View File

@@ -0,0 +1,19 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.TRANSIENT })
export class Interceptor implements NestInterceptor {
static COUNTER = 0;
constructor() {
Interceptor.COUNTER++;
}
intercept(context: ExecutionContext, call: CallHandler): Observable<any> {
return call.handle();
}
}

View File

@@ -0,0 +1,20 @@
import {
Controller,
Get,
Param,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { Guard } from './guards/request-scoped.guard';
import { Interceptor } from './interceptors/logging.interceptor';
import { UserByIdPipe } from './users/user-by-id.pipe';
@Controller('test')
export class TestController {
@UseGuards(Guard)
@UseInterceptors(Interceptor)
@Get()
greeting(@Param('id', UserByIdPipe) id): string {
return 'hey';
}
}

View File

@@ -0,0 +1,18 @@
import {
ArgumentMetadata,
Injectable,
PipeTransform,
Scope,
} from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class UserByIdPipe implements PipeTransform<string> {
static COUNTER = 0;
constructor() {
UserByIdPipe.COUNTER++;
}
transform(value: string, metadata: ArgumentMetadata) {
return value;
}
}

View File

@@ -0,0 +1,13 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class UsersService {
static COUNTER = 0;
constructor(@Inject('META') private readonly meta) {
UsersService.COUNTER++;
}
findById(id: string) {
return { id };
}
}

View File

@@ -0,0 +1,10 @@
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
export class TestDto {
@IsString()
@IsNotEmpty()
string: string;
@IsNumber()
number: number;
}

View File

@@ -0,0 +1,21 @@
import {
CanActivate,
ExecutionContext,
Injectable,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.REQUEST })
export class Guard implements CanActivate {
static COUNTER = 0;
constructor() {
Guard.COUNTER++;
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}

View File

@@ -0,0 +1,30 @@
import {
Controller,
Get,
Param,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { Guard } from './guards/request-scoped.guard';
import { HelloService } from './hello.service';
import { Interceptor } from './interceptors/logging.interceptor';
import { UserByIdPipe } from './users/user-by-id.pipe';
import { UsersService } from './users/users.service';
@Controller('hello')
export class HelloController {
static COUNTER = 0;
constructor(
private readonly helloService: HelloService,
private readonly usersService: UsersService,
) {
HelloController.COUNTER++;
}
@UseGuards(Guard)
@UseInterceptors(Interceptor)
@Get()
greeting(@Param('id', UserByIdPipe) id): string {
return this.helloService.greeting();
}
}

View File

@@ -0,0 +1,19 @@
import { DynamicModule, Inject, Module, Provider } from '@nestjs/common';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
import { UsersService } from './users/users.service';
@Module({
controllers: [HelloController],
providers: [HelloService, UsersService],
})
export class HelloModule {
constructor(@Inject('META') private readonly meta) {}
static forRoot(meta: Provider): DynamicModule {
return {
module: HelloModule,
providers: [meta],
};
}
}

View File

@@ -0,0 +1,10 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class HelloService {
constructor(@Inject('META') private readonly meta) {}
greeting(): string {
return 'Hello world!';
}
}

View File

@@ -0,0 +1,19 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.REQUEST })
export class Interceptor implements NestInterceptor {
static COUNTER = 0;
constructor() {
Interceptor.COUNTER++;
}
intercept(context: ExecutionContext, call: CallHandler): Observable<any> {
return call.handle();
}
}

View File

@@ -0,0 +1,14 @@
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { UsersService } from './users.service';
@Injectable()
export class UserByIdPipe implements PipeTransform<string> {
static COUNTER = 0;
constructor(private readonly usersService: UsersService) {
UserByIdPipe.COUNTER++;
}
transform(value: string, metadata: ArgumentMetadata) {
return this.usersService.findById(value);
}
}

View File

@@ -0,0 +1,13 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class UsersService {
static COUNTER = 0;
constructor(@Inject('META') private readonly meta) {
UsersService.COUNTER++;
}
findById(id: string) {
return { id };
}
}

View File

@@ -0,0 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
await app.listen(3000);
}
bootstrap();

View File

@@ -0,0 +1,10 @@
import { IsString, IsNotEmpty, IsNumber } from 'class-validator';
export class TestDto {
@IsString()
@IsNotEmpty()
string: string;
@IsNumber()
number: number;
}

View File

@@ -0,0 +1,21 @@
import {
CanActivate,
ExecutionContext,
Injectable,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.TRANSIENT })
export class Guard implements CanActivate {
static COUNTER = 0;
constructor() {
Guard.COUNTER++;
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}

View File

@@ -0,0 +1,30 @@
import {
Controller,
Get,
Param,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { Guard } from './guards/request-scoped.guard';
import { HelloService } from './hello.service';
import { Interceptor } from './interceptors/logging.interceptor';
import { UserByIdPipe } from './users/user-by-id.pipe';
import { UsersService } from './users/users.service';
@Controller('hello')
export class HelloController {
static COUNTER = 0;
constructor(
private readonly helloService: HelloService,
private readonly usersService: UsersService,
) {
HelloController.COUNTER++;
}
@UseGuards(Guard)
@UseInterceptors(Interceptor)
@Get()
greeting(@Param('id', UserByIdPipe) id): string {
return this.helloService.greeting();
}
}

View File

@@ -0,0 +1,20 @@
import { DynamicModule, Inject, Module, Provider } from '@nestjs/common';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
import { TestController } from './test.controller';
import { UsersService } from './users/users.service';
@Module({
controllers: [HelloController, TestController],
providers: [HelloService, UsersService],
})
export class HelloModule {
constructor(@Inject('META') private readonly meta) {}
static forRoot(meta: Provider): DynamicModule {
return {
module: HelloModule,
providers: [meta],
};
}
}

View File

@@ -0,0 +1,10 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class HelloService {
constructor(@Inject('META') private readonly meta) {}
greeting(): string {
return 'Hello world!';
}
}

View File

@@ -0,0 +1,19 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
Scope,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable({ scope: Scope.TRANSIENT })
export class Interceptor implements NestInterceptor {
static COUNTER = 0;
constructor() {
Interceptor.COUNTER++;
}
intercept(context: ExecutionContext, call: CallHandler): Observable<any> {
return call.handle();
}
}

View File

@@ -0,0 +1,20 @@
import {
Controller,
Get,
Param,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { Guard } from './guards/request-scoped.guard';
import { Interceptor } from './interceptors/logging.interceptor';
import { UserByIdPipe } from './users/user-by-id.pipe';
@Controller('test')
export class TestController {
@UseGuards(Guard)
@UseInterceptors(Interceptor)
@Get()
greeting(@Param('id', UserByIdPipe) id): string {
return 'hey';
}
}

View File

@@ -0,0 +1,18 @@
import {
ArgumentMetadata,
Injectable,
PipeTransform,
Scope,
} from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class UserByIdPipe implements PipeTransform<string> {
static COUNTER = 0;
constructor() {
UserByIdPipe.COUNTER++;
}
transform(value: string, metadata: ArgumentMetadata) {
return value;
}
}

View File

@@ -0,0 +1,13 @@
import { Inject, Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class UsersService {
static COUNTER = 0;
constructor(@Inject('META') private readonly meta) {
UsersService.COUNTER++;
}
findById(id: string) {
return { id };
}
}

View File

@@ -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",
]
}

View File

@@ -0,0 +1,53 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {
"no-unused-expression": true
},
"rules": {
"eofline": false,
"quotemark": [
true,
"single"
],
"ordered-imports": [
false
],
"max-line-length": [
150
],
"member-ordering": [
false
],
"curly": false,
"interface-name": [
false
],
"array-type": [
false
],
"member-access": [
false
],
"no-empty-interface": false,
"no-empty": false,
"arrow-parens": false,
"object-literal-sort-keys": false,
"no-unused-expression": false,
"max-classes-per-file": [
false
],
"variable-name": [
false
],
"one-line": [
false
],
"one-variable-per-declaration": [
false
]
},
"rulesDirectory": []
}

View File

@@ -1,8 +1,7 @@
export enum Scope {
DEFAULT,
TRANSIENT,
REQUEST,
/** @experimental */
LAZY_ASYNC,
}
export interface ScopeOptions {

View File

@@ -0,0 +1,35 @@
import { expect } from 'chai';
import { createHttpExceptionBody } from '../../utils/http-exception-body.util';
describe('createHttpExceptionBody', () => {
describe('when object has been passed', () => {
it('should return expected object', () => {
const object = {
message: 'test',
};
expect(createHttpExceptionBody(object)).to.be.eql(object);
});
});
describe('when string has been passed', () => {
it('should return expected object', () => {
const message = 'test';
const status = 500;
const error = 'error';
expect(createHttpExceptionBody(message, error, status)).to.be.eql({
message,
error,
statusCode: status,
});
});
});
describe('when nil has been passed', () => {
it('should return expected object', () => {
const status = 500;
const error = 'error';
expect(createHttpExceptionBody(null, error, status)).to.be.eql({
error,
statusCode: status,
});
});
});
});

View File

@@ -0,0 +1,8 @@
import { expect } from 'chai';
import { randomStringGenerator } from '../../utils/random-string-generator.util';
describe('randomStringGenerator', () => {
it('should generate random string', () => {
expect(randomStringGenerator()).to.be.string;
});
});

View File

@@ -2,8 +2,8 @@ import { isObject } from './shared.utils';
export const createHttpExceptionBody = (
message: object | string,
error: string,
statusCode: number,
error?: string,
statusCode?: number,
) => {
if (!message) {
return { statusCode, error };

View File

@@ -1,9 +1,9 @@
import {
PipeTransform,
WebSocketAdapter,
CanActivate,
ExceptionFilter,
NestInterceptor,
CanActivate,
PipeTransform,
WebSocketAdapter,
} from '@nestjs/common';
import { ConfigurationProvider } from '@nestjs/common/interfaces/configuration-provider.interface';

View File

@@ -18,6 +18,7 @@ export class BaseExceptionFilterContext extends ContextCreator {
public createConcreteContext<T extends any[], R extends any[]>(
metadata: T,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): R {
if (isEmpty(metadata)) {
return [] as R;
@@ -26,7 +27,8 @@ export class BaseExceptionFilterContext extends ContextCreator {
.filter(
instance => instance && (isFunction(instance.catch) || instance.name),
)
.map(filter => this.getFilterInstance(filter, contextId))
.map(filter => this.getFilterInstance(filter, contextId, inquirerId))
.filter(item => !!item)
.map(instance => ({
func: instance.catch.bind(instance),
exceptionMetatypes: this.reflectCatchExceptions(instance),
@@ -37,16 +39,20 @@ export class BaseExceptionFilterContext extends ContextCreator {
public getFilterInstance(
filter: Function | ExceptionFilter,
contextId = STATIC_CONTEXT,
) {
inquirerId?: string,
): ExceptionFilter | null {
const isObject = (filter as ExceptionFilter).catch;
if (isObject) {
return filter;
return filter as ExceptionFilter;
}
const instanceWrapper = this.getInstanceByMetatype(filter);
if (!instanceWrapper) {
return null;
}
const instanceHost = instanceWrapper.getInstanceByContextId(contextId);
const instanceHost = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
return instanceHost && instanceHost.instance;
}

View File

@@ -10,21 +10,21 @@ import {
} from '@nestjs/common';
import { isObject } from '@nestjs/common/utils/shared.utils';
import { MESSAGES } from '../constants';
import { ApplicationReferenceHost } from './../helpers/application-ref-host';
import { HttpAdapterHost } from '../helpers';
export class BaseExceptionFilter<T = any> implements ExceptionFilter<T> {
private static readonly logger = new Logger('ExceptionsHandler');
@Optional()
@Inject()
protected readonly applicationRefHost?: ApplicationReferenceHost;
protected readonly httpAdapterHost?: HttpAdapterHost;
constructor(protected readonly applicationRef?: HttpServer) {}
catch(exception: T, host: ArgumentsHost) {
const applicationRef =
this.applicationRef ||
(this.applicationRefHost && this.applicationRefHost.applicationRef);
(this.httpAdapterHost && this.httpAdapterHost.httpAdapter);
if (!(exception instanceof HttpException)) {
const body = {

View File

@@ -24,21 +24,29 @@ export class GuardsContextCreator extends ContextCreator {
callback: (...args: any[]) => any,
module: string,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): CanActivate[] {
this.moduleContext = module;
return this.createContext(instance, callback, GUARDS_METADATA);
return this.createContext(
instance,
callback,
GUARDS_METADATA,
contextId,
inquirerId,
);
}
public createConcreteContext<T extends any[], R extends any[]>(
metadata: T,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): R {
if (isEmpty(metadata)) {
return [] as R;
}
return iterate(metadata)
.filter((guard: any) => guard && (guard.name || guard.canActivate))
.map(guard => this.getGuardInstance(guard, contextId))
.map(guard => this.getGuardInstance(guard, contextId, inquirerId))
.filter((guard: CanActivate) => guard && isFunction(guard.canActivate))
.toArray() as R;
}
@@ -46,16 +54,20 @@ export class GuardsContextCreator extends ContextCreator {
public getGuardInstance(
guard: Function | CanActivate,
contextId = STATIC_CONTEXT,
) {
inquirerId?: string,
): CanActivate | null {
const isObject = (guard as CanActivate).canActivate;
if (isObject) {
return guard;
return guard as CanActivate;
}
const instanceWrapper = this.getInstanceByMetatype(guard);
if (!instanceWrapper) {
return null;
}
const instanceHost = instanceWrapper.getInstanceByContextId(contextId);
const instanceHost = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
return instanceHost && instanceHost.instance;
}

View File

@@ -1,13 +0,0 @@
import { HttpServer } from '@nestjs/common';
export class ApplicationReferenceHost<T extends HttpServer = any> {
private _applicationRef: T;
set applicationRef(applicationRef: T) {
this._applicationRef = applicationRef;
}
get applicationRef(): T | undefined {
return this._applicationRef;
}
}

View File

@@ -6,6 +6,7 @@ export abstract class ContextCreator {
public abstract createConcreteContext<T extends any[], R extends any[]>(
metadata: T,
contextId?: ContextId,
inquirerId?: string,
): R;
public getGlobalMetadata?<T extends any[]>(): T;
@@ -14,6 +15,7 @@ export abstract class ContextCreator {
callback: (...args: any[]) => any,
metadataKey: string,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): R {
const globalMetadata =
this.getGlobalMetadata && this.getGlobalMetadata<T>();
@@ -23,9 +25,14 @@ export abstract class ContextCreator {
...this.createConcreteContext<T, R>(
globalMetadata || ([] as T),
contextId,
inquirerId,
),
...this.createConcreteContext<T, R>(classMetadata, contextId, inquirerId),
...this.createConcreteContext<T, R>(
methodMetadata,
contextId,
inquirerId,
),
...this.createConcreteContext<T, R>(classMetadata, contextId),
...this.createConcreteContext<T, R>(methodMetadata, contextId),
] as R;
}

View File

@@ -0,0 +1,13 @@
import { ContextId } from '../injector/instance-wrapper';
export function createContextId(): ContextId {
/**
* We are generating random identifier to track asynchronous
* execution context. An identifier does not have to be neither unique
* nor unpredictable because WeakMap uses objects as keys (reference comparison).
* Thus, even though identifier number might be equal, WeakMap would properly
* associate asynchronous context with its internal map values using object reference.
* Object is automatically removed once request has been processed (closure).
*/
return { id: Math.random() };
}

View File

@@ -1,11 +1,17 @@
import { Controller } from '@nestjs/common/interfaces';
import { ContextId } from './../injector/instance-wrapper';
import { ParamProperties } from './context-utils';
export const HANDLER_METADATA_SYMBOL = Symbol.for('handler_metadata:cache');
export interface HandlerMetadata {
argsLength: number;
paramsOptions: (ParamProperties & { metatype?: any })[];
paramtypes: any[];
getParamsMetadata: (
moduleKey: string,
contextId?: ContextId,
inquirerId?: string,
) => (ParamProperties & { metatype?: any })[];
fnHandleResponse: <TResult, TResponse>(
result: TResult,
res: TResponse,

View File

@@ -0,0 +1,13 @@
import { AbstractHttpAdapter } from '../adapters/http-adapter';
export class HttpAdapterHost<T extends AbstractHttpAdapter = any> {
private _httpAdapter: T;
set httpAdapter(httpAdapter: T) {
this._httpAdapter = httpAdapter;
}
get httpAdapter(): T | undefined {
return this._httpAdapter;
}
}

View File

@@ -1 +1 @@
export * from './application-ref-host';
export * from './http-adapter-host';

View File

@@ -16,4 +16,5 @@ export * from './middleware';
export * from './nest-application';
export * from './nest-application-context';
export { NestFactory } from './nest-factory';
export * from './router';
export * from './services';

View File

@@ -6,10 +6,11 @@ import { ApplicationConfig } from '../application-config';
import { CircularDependencyException } from '../errors/exceptions/circular-dependency.exception';
import { InvalidModuleException } from '../errors/exceptions/invalid-module.exception';
import { UnknownModuleException } from '../errors/exceptions/unknown-module.exception';
import { ApplicationReferenceHost } from '../helpers/application-ref-host';
import { ExternalContextCreator } from '../helpers/external-context-creator';
import { Reflector } from '../services';
import { HttpAdapterHost } from '../helpers/http-adapter-host';
import { ModuleCompiler } from './compiler';
import { InternalCoreModule } from './internal-core-module';
import { InternalProvidersStorage } from './internal-providers-storage';
import { Module } from './module';
import { ModulesContainer } from './modules-container';
@@ -21,11 +22,8 @@ export class NestContainer {
string,
Partial<DynamicModule>
>();
private readonly reflector = new Reflector();
private readonly applicationRefHost = new ApplicationReferenceHost();
private externalContextCreator: ExternalContextCreator;
private modulesContainer: ModulesContainer;
private applicationRef: any;
private readonly internalProvidersStorage = new InternalProvidersStorage();
private internalCoreModule: Module;
constructor(
private readonly _applicationConfig: ApplicationConfig = undefined,
@@ -35,23 +33,24 @@ export class NestContainer {
return this._applicationConfig;
}
public setApplicationRef(applicationRef: any) {
this.applicationRef = applicationRef;
public setHttpAdapter(httpAdapter: any) {
this.internalProvidersStorage.httpAdapter = httpAdapter;
if (!this.applicationRefHost) {
if (!this.internalProvidersStorage.httpAdapterHost) {
return;
}
this.applicationRefHost.applicationRef = applicationRef;
const host = this.internalProvidersStorage.httpAdapterHost;
host.httpAdapter = httpAdapter;
}
public getApplicationRef() {
return this.applicationRef;
public getHttpAdapterRef() {
return this.internalProvidersStorage.httpAdapter;
}
public async addModule(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
scope: Type<any>[],
) {
): Promise<Module> {
if (!metatype) {
throw new InvalidModuleException(scope);
}
@@ -67,6 +66,8 @@ export class NestContainer {
this.addDynamicMetadata(token, dynamicMetadata, [].concat(scope, type));
this.isGlobalModule(type) && this.addGlobalModule(module);
return module;
}
public addDynamicMetadata(
@@ -103,7 +104,11 @@ export class NestContainer {
}
public getModuleByKey(moduleKey: string): Module {
return this.modulesContainer.get(moduleKey);
return this.modules.get(moduleKey);
}
public getInternalCoreModuleRef(): Module | undefined {
return this.internalCoreModule;
}
public async addImport(
@@ -198,25 +203,24 @@ export class NestContainer {
return [];
}
public getReflector(): Reflector {
return this.reflector;
public createCoreModule(): DynamicModule {
return InternalCoreModule.register([
{
provide: ExternalContextCreator,
useValue: ExternalContextCreator.fromContainer(this),
},
{
provide: ModulesContainer,
useValue: this.modules,
},
{
provide: HttpAdapterHost,
useValue: this.internalProvidersStorage.httpAdapterHost,
},
]);
}
public getExternalContextCreator(): ExternalContextCreator {
if (!this.externalContextCreator) {
this.externalContextCreator = ExternalContextCreator.fromContainer(this);
}
return this.externalContextCreator;
}
public getApplicationRefHost(): ApplicationReferenceHost {
return this.applicationRefHost;
}
public getModulesContainer(): ModulesContainer {
if (!this.modulesContainer) {
this.modulesContainer = this.getModules();
}
return this.modulesContainer;
public registerCoreModuleRef(moduleRef: Module) {
this.internalCoreModule = moduleRef;
}
}

View File

@@ -1,3 +1,3 @@
export * from './container';
export * from './module-ref';
export * from './modules-container';
export * from './tokens';

View File

@@ -22,6 +22,7 @@ import {
ContextId,
InstancePerContext,
InstanceWrapper,
PropertyMetadata,
} from './instance-wrapper';
import { Module } from './module';
@@ -70,6 +71,7 @@ export class Injector {
collection: Map<string, InstanceWrapper>,
module: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const { metatype } = wrapper;
const targetWrapper = collection.get(metatype.name);
@@ -87,6 +89,7 @@ export class Injector {
null,
loadInstance,
contextId,
inquirer,
);
}
@@ -101,13 +104,16 @@ export class Injector {
controllers,
module,
contextId,
wrapper,
);
await this.loadEnhancersPerContext(wrapper, module, contextId, wrapper);
}
public async loadInjectable(
wrapper: InstanceWrapper<Controller>,
module: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const injectables = module.injectables;
await this.loadInstance<Controller>(
@@ -115,6 +121,7 @@ export class Injector {
injectables,
module,
contextId,
inquirer,
);
}
@@ -122,35 +129,36 @@ export class Injector {
wrapper: InstanceWrapper<Injectable>,
module: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const providers = module.providers;
await this.loadInstance<Injectable>(wrapper, providers, module, contextId);
await this.loadInstance<Injectable>(
wrapper,
providers,
module,
contextId,
inquirer,
);
await this.loadEnhancersPerContext(wrapper, module, contextId, wrapper);
}
public loadPrototype<T>(
{ metatype, name }: InstanceWrapper<T>,
{ name }: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper<T>>,
contextId = STATIC_CONTEXT,
) {
if (!collection) {
return null;
return;
}
const target = collection.get(name);
const instanceHost = target.getInstanceByContextId(contextId);
if (
instanceHost.isResolved ||
!isNil(target.inject) ||
!metatype.prototype
) {
return null;
}
collection.set(
name,
new InstanceWrapper({
const instance = target.createPrototype(contextId);
if (instance) {
const wrapper = new InstanceWrapper({
...target,
instance: Object.create(metatype.prototype),
}),
);
instance,
});
collection.set(name, wrapper);
}
}
public applyDoneHook<T>(wrapper: InstancePerContext<T>): () => void {
@@ -167,8 +175,10 @@ export class Injector {
collection: Map<string, InstanceWrapper>,
module: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const instanceHost = wrapper.getInstanceByContextId(contextId);
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = wrapper.getInstanceByContextId(contextId, inquirerId);
if (instanceHost.isPending) {
return instanceHost.donePromise;
}
@@ -188,12 +198,14 @@ export class Injector {
module,
inject,
contextId,
wrapper,
);
const instance = await this.instantiateClass(
instances,
wrapper,
targetWrapper,
contextId,
inquirer,
);
this.applyProperties(instance, properties);
done();
@@ -204,6 +216,7 @@ export class Injector {
inject,
callback,
contextId,
wrapper,
);
}
@@ -213,20 +226,14 @@ export class Injector {
inject: InjectorDependency[],
callback: (args: any[]) => void,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const inquirerId = this.getInquirerId(inquirer);
const metadata = wrapper.getCtorMetadata();
if (metadata) {
const dependenciesHosts = await Promise.all(
metadata.map(async item =>
this.resolveComponentHost(item.host, item, contextId),
),
);
const deps = dependenciesHosts.map(
item => item.getInstanceByContextId(contextId).instance,
);
if (metadata && contextId !== STATIC_CONTEXT) {
const deps = await this.loadCtorMetadata(metadata, contextId, inquirer);
return callback(deps);
}
const dependencies = isNil(inject)
? this.reflectConstructorParams(wrapper.metatype)
: inject;
@@ -235,8 +242,7 @@ export class Injector {
: [];
let isResolved = true;
const findOneParam = async (param, index) => {
const resolveParam = async (param, index) => {
try {
const paramWrapper = await this.resolveSingleParam<T>(
wrapper,
@@ -244,8 +250,12 @@ export class Injector {
{ index, dependencies },
module,
contextId,
inquirer,
);
const instanceHost = paramWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
const instanceHost = paramWrapper.getInstanceByContextId(contextId);
if (!instanceHost.isResolved && !paramWrapper.forwardRef) {
isResolved = false;
}
@@ -259,7 +269,7 @@ export class Injector {
return undefined;
}
};
const instances = await Promise.all(dependencies.map(findOneParam));
const instances = await Promise.all(dependencies.map(resolveParam));
isResolved && (await callback(instances));
}
@@ -285,6 +295,7 @@ export class Injector {
dependencyContext: InjectorDependencyContext,
module: Module,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
if (isUndefined(param)) {
throw new UndefinedDependencyException(
@@ -300,6 +311,7 @@ export class Injector {
dependencyContext,
wrapper,
contextId,
inquirer,
);
}
@@ -320,6 +332,7 @@ export class Injector {
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<InstanceWrapper> {
const providers = module.providers;
const instanceWrapper = await this.lookupComponent(
@@ -328,23 +341,53 @@ export class Injector {
{ ...dependencyContext, name },
wrapper,
contextId,
inquirer,
);
return this.resolveComponentHost(
module,
instanceWrapper,
contextId,
inquirer,
);
return this.resolveComponentHost(module, instanceWrapper, contextId);
}
public async resolveComponentHost<T>(
module: Module,
instanceWrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<InstanceWrapper> {
const instanceHost = instanceWrapper.getInstanceByContextId(contextId);
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
await this.loadProvider(instanceWrapper, module, contextId);
await this.loadProvider(instanceWrapper, module, contextId, inquirer);
} else if (
!instanceHost.isResolved &&
instanceWrapper.forwardRef &&
(contextId !== STATIC_CONTEXT || !!inquirerId)
) {
/**
* When circular dependency has been detected between
* either request/transient providers, we have to asynchronously
* resolve instance host for a specific contextId or inquirer, to ensure
* that eventual lazily created instance will be merged with the prototype
* instantiated beforehand.
*/
instanceHost.donePromise &&
instanceHost.donePromise.then(() =>
this.loadProvider(instanceWrapper, module, contextId, inquirer),
);
}
if (instanceWrapper.async) {
const host = instanceWrapper.getInstanceByContextId(contextId);
const host = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
host.instance = await host.instance;
instanceWrapper.setInstanceByContextId(contextId, host);
instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
}
return instanceWrapper;
}
@@ -355,6 +398,7 @@ export class Injector {
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<InstanceWrapper<T>> {
const { name } = dependencyContext;
const scanInExports = () =>
@@ -363,8 +407,8 @@ export class Injector {
module,
wrapper,
contextId,
inquirer,
);
return providers.has(name) ? providers.get(name) : scanInExports();
}
@@ -373,6 +417,7 @@ export class Injector {
module: Module,
wrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
) {
const instanceWrapper = await this.lookupComponentInImports(
module,
@@ -380,6 +425,7 @@ export class Injector {
wrapper,
[],
contextId,
inquirer,
);
if (isNil(instanceWrapper)) {
throw new UnknownDependenciesException(
@@ -397,10 +443,11 @@ export class Injector {
wrapper: InstanceWrapper,
moduleRegistry: any[] = [],
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<any> {
let instanceWrapperRef: InstanceWrapper = null;
const imports: Set<Module> = module.imports || new Set();
const imports = module.imports || new Set<Module>();
const children = [...imports.values()].filter(item => item);
for (const relatedModule of children) {
@@ -416,6 +463,7 @@ export class Injector {
wrapper,
moduleRegistry,
contextId,
inquirer,
);
if (instanceRef) {
return instanceRef;
@@ -424,9 +472,18 @@ export class Injector {
}
instanceWrapperRef = providers.get(name);
const instanceHost = instanceWrapperRef.getInstanceByContextId(contextId);
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = instanceWrapperRef.getInstanceByContextId(
contextId,
inquirerId,
);
if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
await this.loadProvider(instanceWrapperRef, relatedModule, contextId);
await this.loadProvider(
instanceWrapperRef,
relatedModule,
contextId,
wrapper,
);
break;
}
}
@@ -438,23 +495,14 @@ export class Injector {
module: Module,
inject?: InjectorDependency[],
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<PropertyDependency[]> {
if (!isNil(inject)) {
return [];
}
const metadata = wrapper.getPropertiesMetadata();
if (metadata) {
const dependenciesHosts = await Promise.all(
metadata.map(async ({ wrapper: item, key }) => ({
key,
host: await this.resolveComponentHost(item.host, item, contextId),
})),
);
return dependenciesHosts.map(({ key, host }) => ({
key,
name: key,
instance: host.getInstanceByContextId(contextId).instance,
}));
if (metadata && contextId !== STATIC_CONTEXT) {
return this.loadPropertiesMetadata(metadata, contextId, inquirer);
}
const properties = this.reflectProperties(wrapper.metatype);
const instances = await Promise.all(
@@ -470,12 +518,18 @@ export class Injector {
dependencyContext,
module,
contextId,
inquirer,
);
if (!paramWrapper) {
return undefined;
}
wrapper.addPropertiesMetadata(item.key, paramWrapper);
const instanceHost = paramWrapper.getInstanceByContextId(contextId);
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = paramWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
return instanceHost.instance;
} catch (err) {
if (!item.isOptional) {
@@ -520,16 +574,24 @@ export class Injector {
wrapper: InstanceWrapper,
targetMetatype: InstanceWrapper,
contextId = STATIC_CONTEXT,
inquirer?: InstanceWrapper,
): Promise<T> {
const { metatype, inject } = wrapper;
const instanceHost = targetMetatype.getInstanceByContextId(contextId);
const isDependencyTreeStatic = wrapper.isDependencyTreeStatic();
const isInContext =
(isDependencyTreeStatic && contextId === STATIC_CONTEXT) ||
(!isDependencyTreeStatic && contextId !== STATIC_CONTEXT);
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = targetMetatype.getInstanceByContextId(
contextId,
inquirerId,
);
const isStatic = wrapper.isStatic(contextId, inquirer);
const isInRequestScope = wrapper.isInRequestScope(contextId, inquirer);
const isLazyTransient = wrapper.isLazyTransient(contextId, inquirer);
const isInContext = isStatic || isInRequestScope || isLazyTransient;
if (isNil(inject) && isInContext) {
const targetInstance = wrapper.getInstanceByContextId(contextId);
const targetInstance = wrapper.getInstanceByContextId(
contextId,
inquirerId,
);
targetInstance.instance = wrapper.forwardRef
? Object.assign(targetInstance.instance, new metatype(...instances))
@@ -544,7 +606,7 @@ export class Injector {
return instanceHost.instance;
}
async loadPerContext<T = any>(
public async loadPerContext<T = any>(
instance: T,
module: Module,
collection: Map<string, InstanceWrapper>,
@@ -553,21 +615,66 @@ export class Injector {
const wrapper = collection.get(
instance.constructor && instance.constructor.name,
);
await this.loadInstance(wrapper, collection, module, ctx);
await this.loadEnhancersPerContext(wrapper, module, ctx);
await this.loadInstance(wrapper, collection, module, ctx, wrapper);
await this.loadEnhancersPerContext(wrapper, module, ctx, wrapper);
const host = wrapper.getInstanceByContextId(ctx);
return host && (host.instance as T);
}
async loadEnhancersPerContext(
public async loadEnhancersPerContext(
wrapper: InstanceWrapper,
module: Module,
ctx: ContextId,
inquirer?: InstanceWrapper,
) {
const enhancers = wrapper.getEnhancersMetadata();
const enhancers = wrapper.getEnhancersMetadata() || [];
const loadEnhancer = (item: InstanceWrapper) =>
this.loadInstance(item, module.injectables, module, ctx);
this.loadInstance(item, module.injectables, module, ctx, inquirer);
await Promise.all(enhancers.map(loadEnhancer));
}
public async loadCtorMetadata(
metadata: InstanceWrapper<any>[],
contextId: ContextId,
inquirer?: InstanceWrapper,
): Promise<any[]> {
const hosts = await Promise.all(
metadata.map(async item =>
this.resolveComponentHost(item.host, item, contextId, inquirer),
),
);
const inquirerId = this.getInquirerId(inquirer);
return hosts.map(
item => item.getInstanceByContextId(contextId, inquirerId).instance,
);
}
public async loadPropertiesMetadata(
metadata: PropertyMetadata[],
contextId: ContextId,
inquirer?: InstanceWrapper,
): Promise<PropertyDependency[]> {
const dependenciesHosts = await Promise.all(
metadata.map(async ({ wrapper: item, key }) => ({
key,
host: await this.resolveComponentHost(
item.host,
item,
contextId,
inquirer,
),
})),
);
const inquirerId = this.getInquirerId(inquirer);
return dependenciesHosts.map(({ key, host }) => ({
key,
name: key,
instance: host.getInstanceByContextId(contextId, inquirerId).instance,
}));
}
private getInquirerId(inquirer: InstanceWrapper | undefined): string {
return inquirer && inquirer.id;
}
}

View File

@@ -1,8 +1,11 @@
import { Scope, Type } from '@nestjs/common';
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
import { STATIC_CONTEXT } from './constants';
import { Module } from './module';
export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
export interface ContextId {
readonly id: number;
@@ -36,13 +39,23 @@ export class InstanceWrapper<T = any> {
private readonly values = new WeakMap<ContextId, InstancePerContext<T>>();
private readonly [INSTANCE_METADATA_SYMBOL]: InstanceMetadataStore = {};
private readonly [INSTANCE_ID_SYMBOL]: string;
private transientMap?:
| Map<string, WeakMap<ContextId, InstancePerContext<T>>>
| undefined;
private isTreeStatic: boolean | undefined;
constructor(
metadata: Partial<InstanceWrapper<T>> & Partial<InstancePerContext<T>> = {},
) {
this[INSTANCE_ID_SYMBOL] = randomStringGenerator();
this.initialize(metadata);
}
get id(): string {
return this[INSTANCE_ID_SYMBOL];
}
set instance(value: T) {
this.values.set(STATIC_CONTEXT, { instance: value });
}
@@ -56,29 +69,74 @@ export class InstanceWrapper<T = any> {
return !this.metatype;
}
getInstanceByContextId(contextId: ContextId): InstancePerContext<T> {
get isTransient(): boolean {
return this.scope === Scope.TRANSIENT;
}
public getInstanceByContextId(
contextId: ContextId,
inquirerId?: string,
): InstancePerContext<T> {
if (this.scope === Scope.TRANSIENT && inquirerId) {
return this.getInstanceByInquirerId(contextId, inquirerId);
}
const instancePerContext = this.values.get(contextId);
return instancePerContext
? instancePerContext
: this.cloneStaticInstance(contextId);
}
setInstanceByContextId(contextId: ContextId, value: InstancePerContext<T>) {
public getInstanceByInquirerId(
contextId: ContextId,
inquirerId: string,
): InstancePerContext<T> {
let collectionPerContext = this.transientMap.get(inquirerId);
if (!collectionPerContext) {
collectionPerContext = new WeakMap();
this.transientMap.set(inquirerId, collectionPerContext);
}
const instancePerContext = collectionPerContext.get(contextId);
return instancePerContext
? instancePerContext
: this.cloneTransientInstance(contextId, inquirerId);
}
public setInstanceByContextId(
contextId: ContextId,
value: InstancePerContext<T>,
inquirerId?: string,
) {
if (this.scope === Scope.TRANSIENT && inquirerId) {
return this.setInstanceByInquirerId(contextId, inquirerId, value);
}
this.values.set(contextId, value);
}
addCtorMetadata(index: number, wrapper: InstanceWrapper) {
public setInstanceByInquirerId(
contextId: ContextId,
inquirerId: string,
value: InstancePerContext<T>,
) {
let collection = this.transientMap.get(inquirerId);
if (!collection) {
collection = new WeakMap();
this.transientMap.set(inquirerId, collection);
}
collection.set(contextId, value);
}
public addCtorMetadata(index: number, wrapper: InstanceWrapper) {
if (!this[INSTANCE_METADATA_SYMBOL].dependencies) {
this[INSTANCE_METADATA_SYMBOL].dependencies = [];
}
this[INSTANCE_METADATA_SYMBOL].dependencies[index] = wrapper;
}
getCtorMetadata(): InstanceWrapper[] {
public getCtorMetadata(): InstanceWrapper[] {
return this[INSTANCE_METADATA_SYMBOL].dependencies;
}
addPropertiesMetadata(key: string, wrapper: InstanceWrapper) {
public addPropertiesMetadata(key: string, wrapper: InstanceWrapper) {
if (!this[INSTANCE_METADATA_SYMBOL].properties) {
this[INSTANCE_METADATA_SYMBOL].properties = [];
}
@@ -88,44 +146,163 @@ export class InstanceWrapper<T = any> {
});
}
getPropertiesMetadata(): PropertyMetadata[] {
public getPropertiesMetadata(): PropertyMetadata[] {
return this[INSTANCE_METADATA_SYMBOL].properties;
}
addEnhancerMetadata(wrapper: InstanceWrapper) {
public addEnhancerMetadata(wrapper: InstanceWrapper) {
if (!this[INSTANCE_METADATA_SYMBOL].enhancers) {
this[INSTANCE_METADATA_SYMBOL].enhancers = [];
}
this[INSTANCE_METADATA_SYMBOL].enhancers.push(wrapper);
}
getEnhancersMetadata(): InstanceWrapper[] {
public getEnhancersMetadata(): InstanceWrapper[] {
return this[INSTANCE_METADATA_SYMBOL].enhancers;
}
isDependencyTreeStatic(): boolean {
if (this.scope === Scope.REQUEST) {
return false;
public isDependencyTreeStatic(lookupRegistry: string[] = []): boolean {
if (!isUndefined(this.isTreeStatic)) {
return this.isTreeStatic;
}
if (this.scope === Scope.REQUEST) {
this.isTreeStatic = false;
return this.isTreeStatic;
}
if (lookupRegistry.includes(this[INSTANCE_ID_SYMBOL])) {
return true;
}
lookupRegistry = lookupRegistry.concat(this[INSTANCE_ID_SYMBOL]);
const { dependencies, properties, enhancers } = this[
INSTANCE_METADATA_SYMBOL
];
let isStatic =
(dependencies && this.isWrapperStatic(dependencies)) || !dependencies;
(dependencies &&
this.isWrapperListStatic(dependencies, lookupRegistry)) ||
!dependencies;
if (!properties || !isStatic) {
return isStatic;
if (!isStatic || !(properties || enhancers)) {
this.isTreeStatic = isStatic;
return this.isTreeStatic;
}
const propertiesHosts = properties.map(item => item.wrapper);
isStatic = isStatic && this.isWrapperStatic(propertiesHosts);
if (!enhancers || !isStatic) {
return isStatic;
const propertiesHosts = (properties || []).map(item => item.wrapper);
isStatic =
isStatic && this.isWrapperListStatic(propertiesHosts, lookupRegistry);
if (!isStatic || !enhancers) {
this.isTreeStatic = isStatic;
return this.isTreeStatic;
}
return this.isWrapperStatic(enhancers);
this.isTreeStatic = this.isWrapperListStatic(enhancers, lookupRegistry);
return this.isTreeStatic;
}
private isWrapperStatic(tree: InstanceWrapper[]) {
return tree.every((item: InstanceWrapper) => item.isDependencyTreeStatic());
public cloneStaticInstance(contextId: ContextId): InstancePerContext<T> {
const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
if (this.isDependencyTreeStatic()) {
return staticInstance;
}
const instancePerContext: InstancePerContext<T> = {
...staticInstance,
instance: undefined,
isResolved: false,
isPending: false,
};
if (this.isNewable()) {
instancePerContext.instance = Object.create(this.metatype.prototype);
}
this.setInstanceByContextId(contextId, instancePerContext);
return instancePerContext;
}
public cloneTransientInstance(
contextId: ContextId,
inquirerId: string,
): InstancePerContext<T> {
const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
const instancePerContext: InstancePerContext<T> = {
...staticInstance,
instance: undefined,
isResolved: false,
isPending: false,
};
if (this.isNewable()) {
instancePerContext.instance = Object.create(this.metatype.prototype);
}
this.setInstanceByInquirerId(contextId, inquirerId, instancePerContext);
return instancePerContext;
}
public createPrototype(contextId: ContextId) {
const host = this.getInstanceByContextId(contextId);
if (!this.isNewable() || host.isResolved) {
return;
}
return Object.create(this.metatype.prototype);
}
public isInRequestScope(
contextId: ContextId,
inquirer?: InstanceWrapper | undefined,
): boolean {
const isDependencyTreeStatic = this.isDependencyTreeStatic();
return ((!isDependencyTreeStatic &&
contextId !== STATIC_CONTEXT &&
(!this.isTransient || (this.isTransient && inquirer))) as any) as boolean;
}
public isLazyTransient(
contextId: ContextId,
inquirer: InstanceWrapper | undefined,
): boolean {
const isInquirerRequestScoped =
inquirer && inquirer.scope === Scope.REQUEST;
return (
this.isDependencyTreeStatic() &&
contextId !== STATIC_CONTEXT &&
this.isTransient &&
isInquirerRequestScoped
);
}
public isStatic(
contextId: ContextId,
inquirer: InstanceWrapper | undefined,
): boolean {
const isInquirerRequestScoped =
inquirer && inquirer.scope === Scope.REQUEST;
const isStaticTransient = this.isTransient && !isInquirerRequestScoped;
return (
this.isDependencyTreeStatic() &&
contextId === STATIC_CONTEXT &&
(!this.isTransient || (isStaticTransient && !!inquirer))
);
}
public getStaticTransientInstances() {
if (!this.transientMap) {
return [];
}
const instances = [...this.transientMap.values()];
return instances
.map(item => item.get(STATIC_CONTEXT))
.filter(item => !!item);
}
private isNewable(): boolean {
return isNil(this.inject) && this.metatype && this.metatype.prototype;
}
private isWrapperListStatic(
tree: InstanceWrapper[],
lookupRegistry: string[],
): boolean {
return tree.every((item: InstanceWrapper) =>
item.isDependencyTreeStatic(lookupRegistry),
);
}
private initialize(
@@ -138,20 +315,6 @@ export class InstanceWrapper<T = any> {
instance,
isResolved,
});
}
private cloneStaticInstance(contextId: ContextId): InstancePerContext<T> {
const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
if (this.isDependencyTreeStatic()) {
return staticInstance;
}
const instancePerContext: InstancePerContext<T> = {
...staticInstance,
instance: undefined,
isResolved: false,
isPending: false,
};
this.setInstanceByContextId(contextId, instancePerContext);
return instancePerContext;
this.scope === Scope.TRANSIENT && (this.transientMap = new Map());
}
}

View File

@@ -0,0 +1,19 @@
import { DynamicModule, Global, Module } from '@nestjs/common';
import { ValueProvider } from '@nestjs/common/interfaces';
import { requestProvider } from './../router/request/request-providers';
import { Reflector } from './../services';
@Global()
@Module({
providers: [Reflector, requestProvider],
exports: [Reflector, requestProvider],
})
export class InternalCoreModule {
static register(providers: ValueProvider[]): DynamicModule {
return {
module: InternalCoreModule,
providers: [...providers],
exports: [...providers.map(item => item.provide)],
};
}
}

View File

@@ -0,0 +1,19 @@
import { HttpAdapterHost } from '../helpers';
import { AbstractHttpAdapter } from './../adapters';
export class InternalProvidersStorage {
private readonly _httpAdapterHost = new HttpAdapterHost();
private _httpAdapter: AbstractHttpAdapter;
get httpAdapterHost(): HttpAdapterHost {
return this._httpAdapterHost;
}
get httpAdapter(): AbstractHttpAdapter {
return this._httpAdapter;
}
set httpAdapter(httpAdapter: AbstractHttpAdapter) {
this._httpAdapter = httpAdapter;
}
}

View File

@@ -31,7 +31,7 @@ export abstract class ModuleRef {
module: Module,
): Promise<T> {
const wrapper = new InstanceWrapper({
name: type.name,
name: type && type.name,
metatype: type,
instance: undefined,
isResolved: false,

View File

@@ -18,14 +18,9 @@ import {
import { InvalidClassException } from '../errors/exceptions/invalid-class.exception';
import { RuntimeException } from '../errors/exceptions/runtime.exception';
import { UnknownExportException } from '../errors/exceptions/unknown-export.exception';
import { ApplicationReferenceHost } from '../helpers/application-ref-host';
import { ExternalContextCreator } from '../helpers/external-context-creator';
import { Reflector } from '../services/reflector.service';
import { NestContainer } from './container';
import { InstanceWrapper } from './instance-wrapper';
import { ModuleRef } from './module-ref';
import { ModulesContainer } from './modules-container';
import { HTTP_SERVER_REF } from './tokens';
export interface CustomProvider {
provide: any;
@@ -76,7 +71,7 @@ export class Module {
return this._scope;
}
get providers(): Map<string, InstanceWrapper<Injectable>> {
get providers(): Map<any, InstanceWrapper<Injectable>> {
return this._providers;
}
@@ -132,11 +127,6 @@ export class Module {
public addCoreProviders(container: NestContainer) {
this.addModuleAsProvider();
this.addModuleRef();
this.addReflector(container.getReflector());
this.addApplicationRef(container.getApplicationRef());
this.addExternalContextCreator(container.getExternalContextCreator());
this.addModulesContainer(container.getModulesContainer());
this.addApplicationRefHost(container.getApplicationRefHost());
}
public addModuleRef() {
@@ -166,73 +156,6 @@ export class Module {
);
}
public addReflector(reflector: Reflector) {
this._providers.set(
Reflector.name,
new InstanceWrapper({
name: Reflector.name,
metatype: Reflector,
isResolved: true,
instance: reflector,
host: this,
}),
);
}
public addApplicationRef(applicationRef: any) {
this._providers.set(
HTTP_SERVER_REF,
new InstanceWrapper({
name: HTTP_SERVER_REF,
metatype: {} as any,
isResolved: true,
instance: applicationRef || {},
host: this,
}),
);
}
public addExternalContextCreator(
externalContextCreator: ExternalContextCreator,
) {
this._providers.set(
ExternalContextCreator.name,
new InstanceWrapper({
name: ExternalContextCreator.name,
metatype: ExternalContextCreator,
isResolved: true,
instance: externalContextCreator,
host: this,
}),
);
}
public addModulesContainer(modulesContainer: ModulesContainer) {
this._providers.set(
ModulesContainer.name,
new InstanceWrapper({
name: ModulesContainer.name,
metatype: ModulesContainer,
isResolved: true,
instance: modulesContainer,
host: this,
}),
);
}
public addApplicationRefHost(applicationRefHost: ApplicationReferenceHost) {
this._providers.set(
ApplicationReferenceHost.name,
new InstanceWrapper({
name: ApplicationReferenceHost.name,
metatype: ApplicationReferenceHost,
isResolved: true,
instance: applicationRefHost,
host: this,
}),
);
}
public addInjectable<T extends Injectable>(
injectable: Type<T>,
host?: Type<T>,
@@ -444,6 +367,10 @@ export class Module {
});
}
public getProviderByKey<T = any>(name: string | symbol): InstanceWrapper<T> {
return this._providers.get(name) as InstanceWrapper<T>;
}
public createModuleReferenceType(): any {
const self = this;
return class extends ModuleRef {

View File

@@ -1 +0,0 @@
export const HTTP_SERVER_REF = 'HTTP_SERVER_REF';

View File

@@ -23,6 +23,7 @@ export class InterceptorsContextCreator extends ContextCreator {
callback: (...args: any[]) => any,
module: string,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): NestInterceptor[] {
this.moduleContext = module;
return this.createContext(
@@ -30,12 +31,14 @@ export class InterceptorsContextCreator extends ContextCreator {
callback,
INTERCEPTORS_METADATA,
contextId,
inquirerId,
);
}
public createConcreteContext<T extends any[], R extends any[]>(
metadata: T,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): R {
if (isEmpty(metadata)) {
return [] as R;
@@ -45,7 +48,9 @@ export class InterceptorsContextCreator extends ContextCreator {
(interceptor: any) =>
interceptor && (interceptor.name || interceptor.intercept),
)
.map(interceptor => this.getInterceptorInstance(interceptor, contextId))
.map(interceptor =>
this.getInterceptorInstance(interceptor, contextId, inquirerId),
)
.filter(
(interceptor: NestInterceptor) =>
interceptor && isFunction(interceptor.intercept),
@@ -56,16 +61,20 @@ export class InterceptorsContextCreator extends ContextCreator {
public getInterceptorInstance(
interceptor: Function | NestInterceptor,
contextId = STATIC_CONTEXT,
) {
inquirerId?: string,
): NestInterceptor | null {
const isObject = (interceptor as NestInterceptor).intercept;
if (isObject) {
return interceptor;
return interceptor as NestInterceptor;
}
const instanceWrapper = this.getInstanceByMetatype(interceptor);
if (!instanceWrapper) {
return null;
}
const instanceHost = instanceWrapper.getInstanceByContextId(contextId);
const instanceHost = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
return instanceHost && instanceHost.instance;
}

View File

@@ -38,7 +38,7 @@ export class MiddlewareModule {
config: ApplicationConfig,
injector: Injector,
) {
const appRef = container.getApplicationRef();
const appRef = container.getHttpAdapterRef();
this.routerExceptionFilter = new RouterExceptionFilters(
container,
config,

View File

@@ -8,6 +8,7 @@ import {
} from '@nestjs/common';
import { Type } from '@nestjs/common/interfaces/type.interface';
import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
import { InstanceWrapper } from 'injector/instance-wrapper';
import iterate from 'iterare';
import { UnknownModuleException } from './errors/exceptions/unknown-module.exception';
import { NestContainer } from './injector/container';
@@ -85,15 +86,25 @@ export class NestApplicationContext implements INestApplicationContext {
// Lifecycle hook has to be called once all classes are properly initialized
const [_, { instance: moduleClassInstance }] = providers.shift();
const instances = [...module.controllers, ...providers];
await Promise.all(
iterate(instances)
.filter(([key, wrapper]) => wrapper.isDependencyTreeStatic())
.map(([key, { instance }]) => instance)
const callOperator = (list: any) =>
list
.filter(instance => !isNil(instance))
.filter(this.hasOnModuleInitHook)
.map(async instance => (instance as OnModuleInit).onModuleInit()),
.map(async instance => (instance as OnModuleInit).onModuleInit());
await Promise.all(
callOperator(
iterate(instances)
.filter(
([key, wrapper]) =>
wrapper.isDependencyTreeStatic() && !wrapper.isTransient,
)
.map(([key, { instance }]) => instance),
),
);
const transientInstances = this.getTransientInstances(instances);
await Promise.all(callOperator(iterate(transientInstances)));
if (moduleClassInstance && this.hasOnModuleInitHook(moduleClassInstance)) {
await (moduleClassInstance as OnModuleInit).onModuleInit();
}
@@ -116,15 +127,25 @@ export class NestApplicationContext implements INestApplicationContext {
// Lifecycle hook has to be called once all classes are properly destroyed
const [_, { instance: moduleClassInstance }] = providers.shift();
const instances = [...module.controllers, ...providers];
await Promise.all(
iterate(instances)
.filter(([key, wrapper]) => wrapper.isDependencyTreeStatic())
.map(([key, { instance }]) => instance)
const callOperator = (list: any) =>
list
.filter(instance => !isNil(instance))
.filter(this.hasOnModuleDestroyHook)
.map(async instance => (instance as OnModuleDestroy).onModuleDestroy()),
.map(async instance => (instance as OnModuleDestroy).onModuleDestroy());
await Promise.all(
callOperator(
iterate(instances)
.filter(
([key, wrapper]) =>
wrapper.isDependencyTreeStatic() && !wrapper.isTransient,
)
.map(([key, { instance }]) => instance),
),
);
const transientInstances = this.getTransientInstances(instances);
await Promise.all(callOperator(iterate(transientInstances)));
if (
moduleClassInstance &&
this.hasOnModuleDestroyHook(moduleClassInstance)
@@ -149,16 +170,27 @@ export class NestApplicationContext implements INestApplicationContext {
const [_, { instance: moduleClassInstance }] = providers.shift();
const instances = [...module.controllers, ...providers];
await Promise.all(
iterate(instances)
.filter(([key, wrapper]) => wrapper.isDependencyTreeStatic())
.map(([key, { instance }]) => instance)
const callOperator = (list: any) =>
list
.filter(instance => !isNil(instance))
.filter(this.hasOnAppBotstrapHook)
.map(async instance =>
(instance as OnApplicationBootstrap).onApplicationBootstrap(),
),
);
await Promise.all(
callOperator(
iterate(instances)
.filter(
([key, wrapper]) =>
wrapper.isDependencyTreeStatic() && !wrapper.isTransient,
)
.map(([key, { instance }]) => instance),
),
);
const transientInstances = this.getTransientInstances(instances);
await Promise.all(callOperator(iterate(transientInstances)));
if (moduleClassInstance && this.hasOnAppBotstrapHook(moduleClassInstance)) {
await (moduleClassInstance as OnApplicationBootstrap).onApplicationBootstrap();
}
@@ -187,4 +219,16 @@ export class NestApplicationContext implements INestApplicationContext {
TResult
>(metatypeOrToken, contextModule);
}
private getTransientInstances(
instances: [string, InstanceWrapper][],
): InstanceWrapper[] {
return iterate(instances)
.filter(([key, wrapper]) => wrapper.isDependencyTreeStatic())
.map(([key, wrapper]) => wrapper.getStaticTransientInstances())
.flatten()
.filter(item => !!item)
.map(({ instance }: any) => instance)
.toArray() as InstanceWrapper[];
}
}

View File

@@ -78,11 +78,11 @@ export class NestFactoryStatic {
'@nestjs/microservices',
'NestFactory',
);
const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
this.applyLogger(options);
await this.initialize(module, container, applicationConfig);
return this.createNestInstance<INestMicroservice>(
new NestMicroservice(container, options, applicationConfig),
@@ -129,7 +129,7 @@ export class NestFactoryStatic {
new MetadataScanner(),
config,
);
container.setApplicationRef(httpServer);
container.setHttpAdapter(httpServer);
try {
this.logger.log(MESSAGES.APPLICATION_START);
await ExceptionsZone.asyncRun(async () => {

View File

@@ -27,21 +27,29 @@ export class PipesContextCreator extends ContextCreator {
callback: (...args: any[]) => any,
module: string,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): Transform<any>[] {
this.moduleContext = module;
return this.createContext(instance, callback, PIPES_METADATA, contextId);
return this.createContext(
instance,
callback,
PIPES_METADATA,
contextId,
inquirerId,
);
}
public createConcreteContext<T extends any[], R extends any[]>(
metadata: T,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): R {
if (isEmpty(metadata)) {
return [] as R;
}
return iterate(metadata)
.filter((pipe: any) => pipe && (pipe.name || pipe.transform))
.map(pipe => this.getPipeInstance(pipe, contextId))
.map(pipe => this.getPipeInstance(pipe, contextId, inquirerId))
.filter(pipe => pipe && pipe.transform && isFunction(pipe.transform))
.map(pipe => pipe.transform.bind(pipe))
.toArray() as R;
@@ -50,16 +58,20 @@ export class PipesContextCreator extends ContextCreator {
public getPipeInstance(
pipe: Function | PipeTransform,
contextId = STATIC_CONTEXT,
) {
inquirerId?: string,
): PipeTransform | null {
const isObject = (pipe as PipeTransform).transform;
if (isObject) {
return pipe;
return pipe as PipeTransform;
}
const instanceWrapper = this.getInstanceByMetatype(pipe as Function);
if (!instanceWrapper) {
return null;
}
const instanceHost = instanceWrapper.getInstanceByContextId(contextId);
const instanceHost = instanceWrapper.getInstanceByContextId(
contextId,
inquirerId,
);
return instanceHost && instanceHost.instance;
}

View File

@@ -0,0 +1 @@
export * from './request';

View File

@@ -8,5 +8,6 @@ export interface ExceptionsFilter {
callback: Function,
module: string,
contextId?: ContextId,
inquirerId?: string,
): ExceptionsHandler;
}

View File

@@ -0,0 +1 @@
export * from './request-constants';

View File

@@ -0,0 +1 @@
export const REQUEST = Symbol('REQUEST');

View File

@@ -0,0 +1,9 @@
import { Provider, Scope } from '@nestjs/common';
import { REQUEST } from './request-constants';
const noop = () => {};
export const requestProvider: Provider = {
provide: REQUEST,
scope: Scope.REQUEST,
useFactory: noop,
};

View File

@@ -23,6 +23,7 @@ export class RouterExceptionFilters extends BaseExceptionFilterContext {
callback: RouterProxyCallback,
module: string,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): ExceptionsHandler {
this.moduleContext = module;
@@ -32,6 +33,7 @@ export class RouterExceptionFilters extends BaseExceptionFilterContext {
callback,
EXCEPTION_FILTERS_METADATA,
contextId,
inquirerId,
);
if (isEmpty(filters)) {
return exceptionHandler;

View File

@@ -76,31 +76,38 @@ export class RouterExecutionContext {
module: string,
requestMethod: RequestMethod,
contextId = STATIC_CONTEXT,
inquirerId?: string,
) {
const { argsLength, paramsOptions, fnHandleResponse } = this.getMetadata(
instance,
callback,
methodName,
module,
requestMethod,
const {
argsLength,
fnHandleResponse,
paramtypes,
getParamsMetadata,
} = this.getMetadata(instance, callback, methodName, module, requestMethod);
const paramsOptions = this.contextUtils.mergeParamsMetatypes(
getParamsMetadata(module, contextId, inquirerId),
paramtypes,
);
const pipes = this.pipesContextCreator.create(
instance,
callback,
module,
contextId,
inquirerId,
);
const guards = this.guardsContextCreator.create(
instance,
callback,
module,
contextId,
inquirerId,
);
const interceptors = this.interceptorsContextCreator.create(
instance,
callback,
module,
contextId,
inquirerId,
);
const fnCanActivate = this.createGuardsFn(guards, instance, callback);
@@ -159,15 +166,24 @@ export class RouterExecutionContext {
methodName,
);
const httpCode = this.reflectHttpStatusCode(callback);
const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module);
const getParamsMetadata = (
moduleKey: string,
contextId = STATIC_CONTEXT,
inquirerId?: string,
) =>
this.exchangeKeysForValues(
keys,
metadata,
moduleKey,
contextId,
inquirerId,
);
const paramsMetadata = getParamsMetadata(module);
const isResponseHandled = paramsMetadata.some(
({ type }) =>
type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT,
);
const paramsOptions = this.contextUtils.mergeParamsMetatypes(
paramsMetadata,
paramtypes,
);
const httpStatusCode = httpCode
? httpCode
: this.responseController.getStatusByMethod(requestMethod);
@@ -179,8 +195,9 @@ export class RouterExecutionContext {
);
const handlerMetadata: HandlerMetadata = {
argsLength,
paramsOptions,
fnHandleResponse,
paramtypes,
getParamsMetadata,
};
this.handlerMetadataStorage.set(instance, methodName, handlerMetadata);
return handlerMetadata;
@@ -204,12 +221,16 @@ export class RouterExecutionContext {
keys: string[],
metadata: RouteParamsMetadata,
moduleContext: string,
contextId = STATIC_CONTEXT,
inquirerId?: string,
): ParamProperties[] {
this.pipesContextCreator.setModuleContext(moduleContext);
return keys.map(key => {
const { index, data, pipes: pipesCollection } = metadata[key];
const pipes = this.pipesContextCreator.createConcreteContext(
pipesCollection,
contextId,
inquirerId,
);
const type = this.contextUtils.mapParamType(key);

View File

@@ -9,18 +9,20 @@ import { ApplicationConfig } from '../application-config';
import { UnknownRequestMappingException } from '../errors/exceptions/unknown-request-mapping.exception';
import { GuardsConsumer } from '../guards/guards-consumer';
import { GuardsContextCreator } from '../guards/guards-context-creator';
import { createContextId } from '../helpers/context-id-factory';
import { ROUTE_MAPPED_MESSAGE } from '../helpers/messages';
import { RouterMethodFactory } from '../helpers/router-method-factory';
import { STATIC_CONTEXT } from '../injector/constants';
import { NestContainer } from '../injector/container';
import { Injector } from '../injector/injector';
import { InstanceWrapper } from '../injector/instance-wrapper';
import { ContextId, InstanceWrapper } from '../injector/instance-wrapper';
import { InterceptorsConsumer } from '../interceptors/interceptors-consumer';
import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator';
import { MetadataScanner } from '../metadata-scanner';
import { PipesConsumer } from '../pipes/pipes-consumer';
import { PipesContextCreator } from '../pipes/pipes-context-creator';
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
import { REQUEST } from './request';
import { RouteParamsFactory } from './route-params-factory';
import { RouterExecutionContext } from './router-execution-context';
import { RouterProxy, RouterProxyCallback } from './router-proxy';
@@ -53,7 +55,7 @@ export class RouterExplorer {
new GuardsConsumer(),
new InterceptorsContextCreator(container, config),
new InterceptorsConsumer(),
container.getApplicationRef(),
container.getHttpAdapterRef(),
);
}
@@ -176,7 +178,9 @@ export class RouterExplorer {
res: TResponse,
next: Function,
) => {
const contextId = { id: 1 }; // asyncId
const contextId = createContextId();
this.registerRequestProvider(req, contextId);
const contextInstance = await this.injector.loadPerContext(
instance,
module,
@@ -190,6 +194,7 @@ export class RouterExplorer {
moduleKey,
requestMethod,
contextId,
instanceWrapper.id,
)(req, res, next);
},
);
@@ -212,6 +217,7 @@ export class RouterExplorer {
module: string,
requestMethod: RequestMethod,
contextId = STATIC_CONTEXT,
inquirerId?: string,
) {
const executionContext = this.executionContextCreator.create(
instance,
@@ -220,13 +226,25 @@ export class RouterExplorer {
module,
requestMethod,
contextId,
inquirerId,
);
const exceptionFilter = this.exceptionsFilter.create(
instance,
callback,
module,
contextId,
inquirerId,
);
return this.routerProxy.createProxy(executionContext, exceptionFilter);
}
private registerRequestProvider<T = any>(request: T, contextId: ContextId) {
const coreModuleRef = this.container.getInternalCoreModuleRef();
const wrapper = coreModuleRef.getProviderByKey(REQUEST);
wrapper.setInstanceByContextId(contextId, {
instance: request,
isResolved: true,
});
}
}

View File

@@ -28,7 +28,7 @@ export class RoutesResolver implements Resolver {
this.routerExceptionsFilter = new RouterExceptionFilters(
container,
config,
container.getApplicationRef(),
container.getHttpAdapterRef(),
);
this.routerBuilder = new RouterExplorer(
new MetadataScanner(),
@@ -73,7 +73,7 @@ export class RoutesResolver implements Resolver {
}
public registerNotFoundHandler() {
const applicationRef = this.container.getApplicationRef();
const applicationRef = this.container.getHttpAdapterRef();
const callback = <TRequest, TResponse>(req: TRequest, res: TResponse) => {
const method = applicationRef.getRequestMethod(req);
const url = applicationRef.getRequestUrl(req);
@@ -100,7 +100,7 @@ export class RoutesResolver implements Resolver {
undefined,
);
const proxy = this.routerProxy.createExceptionLayerProxy(callback, handler);
const applicationRef = this.container.getApplicationRef();
const applicationRef = this.container.getHttpAdapterRef();
applicationRef.setErrorHandler && applicationRef.setErrorHandler(proxy);
}

View File

@@ -25,6 +25,7 @@ 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';
import { Module } from './injector/module';
import { MetadataScanner } from './metadata-scanner';
interface ApplicationProviderWrapper {
@@ -35,6 +36,7 @@ interface ApplicationProviderWrapper {
export class DependenciesScanner {
private readonly applicationProvidersApplyMap: ApplicationProviderWrapper[] = [];
constructor(
private readonly container: NestContainer,
private readonly metadataScanner: MetadataScanner,
@@ -42,6 +44,7 @@ export class DependenciesScanner {
) {}
public async scan(module: Type<any>) {
await this.registerCoreModule();
await this.scanForModules(module);
await this.scanModulesForDependencies();
this.container.bindGlobalScope();
@@ -51,8 +54,8 @@ export class DependenciesScanner {
module: ForwardReference | Type<any> | DynamicModule,
scope: Type<any>[] = [],
ctxRegistry: (ForwardReference | DynamicModule | Type<any>)[] = [],
) {
await this.insertModule(module, scope);
): Promise<Module> {
const moduleInstance = await this.insertModule(module, scope);
ctxRegistry.push(module);
if (this.isForwardReference(module)) {
@@ -78,13 +81,14 @@ export class DependenciesScanner {
ctxRegistry,
);
}
return moduleInstance;
}
public async insertModule(module: any, scope: Type<any>[]) {
public async insertModule(module: any, scope: Type<any>[]): Promise<Module> {
if (module && module.forwardRef) {
return this.container.addModule(module.forwardRef(), scope);
}
await this.container.addModule(module, scope);
return this.container.addModule(module, scope);
}
public async scanModulesForDependencies() {
@@ -301,6 +305,12 @@ export class DependenciesScanner {
return Reflect.getMetadata(metadataKey, metatype) || [];
}
public async registerCoreModule() {
const module = this.container.createCoreModule();
const instance = await this.scanForModules(module);
this.container.registerCoreModuleRef(instance);
}
public applyApplicationProviders() {
const applyProvidersMap = this.getApplyProvidersMap();
this.applicationProvidersApplyMap.forEach(

View File

@@ -6,7 +6,7 @@ import { AbstractHttpAdapter } from '../../adapters';
import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception';
import { ExceptionsHandler } from '../../exceptions/exceptions-handler';
import { ExecutionContextHost } from '../../helpers/execution-context-host';
import { NoopHttpAdapter } from './../utils/noop-adapter';
import { NoopHttpAdapter } from './../utils/noop-adapter.spec';
describe('ExceptionsHandler', () => {
let adapter: AbstractHttpAdapter;

View File

@@ -1,12 +1,12 @@
import { expect } from 'chai';
import { ApplicationReferenceHost } from './../../helpers/application-ref-host';
import { HttpAdapterHost } from '../../helpers/http-adapter-host';
describe('ApplicationReferenceHost', () => {
const applicationRefHost = new ApplicationReferenceHost();
describe('HttpAdapterHost', () => {
const applicationRefHost = new HttpAdapterHost();
it('should wrap application reference', () => {
const ref = {};
applicationRefHost.applicationRef = ref;
applicationRefHost.httpAdapter = ref;
expect(applicationRefHost.applicationRef).to.be.eql(ref);
expect(applicationRefHost.httpAdapter).to.be.eql(ref);
});
});

View File

@@ -5,6 +5,8 @@ import { Global } from '../../../common/index';
import { CircularDependencyException } from '../../errors/exceptions/circular-dependency.exception';
import { UnknownModuleException } from '../../errors/exceptions/unknown-module.exception';
import { NestContainer } from '../../injector/container';
import { InternalCoreModule } from '../../injector/internal-core-module';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
describe('NestContainer', () => {
let container: NestContainer;
@@ -173,4 +175,46 @@ describe('NestContainer', () => {
});
});
});
describe('get applicationConfig', () => {
it('should return ApplicationConfig instance', () => {
expect(container.applicationConfig).to.be.eql(
(container as any)._applicationConfig,
);
});
});
describe('setHttpAdapter', () => {
it('should set http adapter', () => {
const httpAdapter = new NoopHttpAdapter({});
container.setHttpAdapter(httpAdapter);
const internalStorage = (container as any).internalProvidersStorage;
expect(internalStorage.httpAdapter).to.be.eql(httpAdapter);
});
});
describe('getModuleByKey', () => {
it('should return module by passed key', () => {
const key = 'test';
const value = {};
container.getModules().set(key, value as any);
expect(container.getModuleByKey(key)).to.be.eql(value);
});
});
describe('createCoreModule', () => {
it('should create InternalCoreModule', () => {
expect(container.createCoreModule().module).to.be.eql(InternalCoreModule);
});
});
describe('registerCoreModuleRef', () => {
it('should register core module ref', () => {
const ref = {} as any;
container.registerCoreModuleRef(ref);
expect((container as any).internalCoreModule).to.be.eql(ref);
});
});
});

View File

@@ -150,25 +150,28 @@ describe('Injector', () => {
);
});
it('should return null when collection is nil', () => {
it('should return undefined when collection is nil', () => {
const result = injector.loadPrototype(test, null);
expect(result).to.be.null;
expect(result).to.be.undefined;
});
it('should return null when target isResolved', () => {
it('should return undefined when target isResolved', () => {
const collection = {
get: () => ({ getInstanceByContextId: () => ({ isResolved: true }) }),
get: () => ({
getInstanceByContextId: () => ({ isResolved: true }),
createPrototype: () => {},
}),
};
const result = injector.loadPrototype(test, collection as any);
expect(result).to.be.null;
expect(result).to.be.undefined;
});
it('should return null when "inject" is not nil', () => {
it('should return undefined when "inject" is not nil', () => {
const collection = {
get: () => new InstanceWrapper({ inject: [] }),
};
const result = injector.loadPrototype(test, collection as any);
expect(result).to.be.null;
expect(result).to.be.undefined;
});
});
@@ -236,7 +239,7 @@ describe('Injector', () => {
it('should call "loadInstance" with expected arguments', async () => {
const module = { controllers: [] };
const wrapper = { test: 'test' };
const wrapper = { test: 'test', getEnhancersMetadata: () => [] };
await injector.loadController(wrapper as any, module as any);
expect(loadInstance.calledWith(wrapper, module.controllers, module)).to.be
@@ -605,4 +608,126 @@ describe('Injector', () => {
});
});
});
describe('instantiateClass', () => {
class TestClass {}
describe('when context is static', () => {
it('should instantiate class', async () => {
const wrapper = new InstanceWrapper({ metatype: TestClass });
await injector.instantiateClass([], wrapper, wrapper, STATIC_CONTEXT);
expect(wrapper.instance).to.not.be.undefined;
expect(wrapper.instance).to.be.instanceOf(TestClass);
});
it('should call factory', async () => {
const wrapper = new InstanceWrapper({
inject: [],
metatype: (() => ({})) as any,
});
await injector.instantiateClass([], wrapper, wrapper, STATIC_CONTEXT);
expect(wrapper.instance).to.not.be.undefined;
});
});
describe('when context is not static', () => {
it('should not instantiate class', async () => {
const ctx = { id: 3 };
const wrapper = new InstanceWrapper({ metatype: TestClass });
await injector.instantiateClass([], wrapper, wrapper, ctx);
expect(wrapper.instance).to.be.undefined;
expect(wrapper.getInstanceByContextId(ctx).isResolved).to.be.true;
});
it('should not call factory', async () => {
const wrapper = new InstanceWrapper({
inject: [],
metatype: sinon.spy() as any,
});
await injector.instantiateClass([], wrapper, wrapper, { id: 2 });
expect(wrapper.instance).to.be.undefined;
expect((wrapper.metatype as any).called).to.be.false;
});
});
});
describe('loadPerContext', () => {
class TestClass {}
it('should load instance per context id', async () => {
const container = new NestContainer();
const moduleCtor = class TestModule {};
const ctx = STATIC_CONTEXT;
const module = await container.addModule(moduleCtor, []);
module.addProvider({
name: 'TestClass',
provide: TestClass,
useClass: TestClass,
});
const instance = await injector.loadPerContext(
new TestClass(),
module,
module.providers,
ctx,
);
expect(instance).to.be.instanceOf(TestClass);
});
});
describe('loadEnhancersPerContext', () => {
it('should load enhancers per context id', async () => {
const wrapper = new InstanceWrapper();
wrapper.addEnhancerMetadata(new InstanceWrapper());
wrapper.addEnhancerMetadata(new InstanceWrapper());
const loadInstanceStub = sinon
.stub(injector, 'loadInstance')
.callsFake(() => ({}));
await injector.loadEnhancersPerContext(
wrapper,
new Module(class {}, [], new NestContainer()),
STATIC_CONTEXT,
);
expect(loadInstanceStub.calledTwice).to.be.true;
});
});
describe('loadCtorMetadata', () => {
it('should resolve ctor metadata', async () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(0, new InstanceWrapper());
wrapper.addCtorMetadata(1, new InstanceWrapper());
const resolveComponentHostStub = sinon
.stub(injector, 'resolveComponentHost')
.callsFake(() => new InstanceWrapper());
await injector.loadCtorMetadata(
wrapper.getCtorMetadata(),
STATIC_CONTEXT,
);
expect(resolveComponentHostStub.calledTwice).to.be.true;
});
});
describe('loadPropertiesMetadata', () => {
it('should resolve properties metadata', async () => {
const wrapper = new InstanceWrapper();
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
wrapper.addPropertiesMetadata('key2', new InstanceWrapper());
const resolveComponentHostStub = sinon
.stub(injector, 'resolveComponentHost')
.callsFake(() => new InstanceWrapper());
await injector.loadPropertiesMetadata(
wrapper.getPropertiesMetadata(),
STATIC_CONTEXT,
);
expect(resolveComponentHostStub.calledTwice).to.be.true;
});
});
});

View File

@@ -0,0 +1,325 @@
import { Scope } from '@nestjs/common';
import { expect } from 'chai';
import * as sinon from 'sinon';
import { STATIC_CONTEXT } from '../../injector/constants';
import { InstanceWrapper } from '../../injector/instance-wrapper';
class TestClass {}
describe('InstanceWrapper', () => {
describe('initialize', () => {
const partial = {
name: 'test',
metatype: TestClass,
scope: Scope.DEFAULT,
instance: new TestClass(),
};
it('should assign partial', () => {
const instance = new InstanceWrapper(partial);
expect(instance.name).to.be.eql(partial.name);
expect(instance.scope).to.be.eql(partial.scope);
expect(instance.metatype).to.be.eql(partial.metatype);
});
it('should set intance by context id', () => {
const instance = new InstanceWrapper(partial);
expect(
instance.getInstanceByContextId(STATIC_CONTEXT).instance,
).to.be.eql(partial.instance);
});
});
describe('isDependencyTreeStatic', () => {
describe('when request scoped', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
});
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
describe('when statically scoped', () => {
describe('dependencies', () => {
describe('when each is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(0, new InstanceWrapper());
expect(wrapper.isDependencyTreeStatic()).to.be.true;
});
});
describe('when one is not static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addCtorMetadata(0, new InstanceWrapper());
wrapper.addCtorMetadata(
1,
new InstanceWrapper({
scope: Scope.REQUEST,
}),
);
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
});
describe('properties', () => {
describe('when each is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper();
wrapper.addPropertiesMetadata('key1', new InstanceWrapper());
wrapper.addPropertiesMetadata('key2', new InstanceWrapper());
expect(wrapper.isDependencyTreeStatic()).to.be.true;
});
});
describe('when one is not static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addPropertiesMetadata(
'key1',
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addPropertiesMetadata('key2', new InstanceWrapper());
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
});
describe('enhancers', () => {
describe('when each is static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper();
wrapper.addEnhancerMetadata(new InstanceWrapper());
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeStatic()).to.be.true;
});
});
describe('when one is not static', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper();
wrapper.addEnhancerMetadata(
new InstanceWrapper({ scope: Scope.REQUEST }),
);
wrapper.addEnhancerMetadata(new InstanceWrapper());
expect(wrapper.isDependencyTreeStatic()).to.be.false;
});
});
});
});
});
describe('isNotMetatype', () => {
describe('when metatype is nil', () => {
it('should return true', () => {
const instance = new InstanceWrapper({ metatype: null });
expect(instance.isNotMetatype).to.be.true;
});
});
describe('when metatype is not nil', () => {
it('should return false', () => {
const instance = new InstanceWrapper({ metatype: TestClass });
expect(instance.isNotMetatype).to.be.false;
});
});
});
describe('addEnhancerMetadata', () => {
it('should add enhancers metadata', () => {
const instance = new InstanceWrapper();
const enhancers = [new InstanceWrapper()];
instance.addEnhancerMetadata(enhancers[0]);
expect(instance.getEnhancersMetadata()).to.be.eql(enhancers);
});
});
describe('when set instance has been called', () => {
it('should set static context value', () => {
const instance = { test: true };
const wrapper = new InstanceWrapper();
wrapper.instance = instance;
expect(wrapper.getInstanceByContextId(STATIC_CONTEXT).instance).to.be.eql(
instance,
);
});
});
describe('cloneStaticInstance', () => {
describe('when wrapper is static', () => {
it('should return static instance', () => {
const instance = { test: true };
const wrapper = new InstanceWrapper({ instance });
expect(wrapper.cloneStaticInstance({ id: 0 }).instance).to.be.eql(
instance,
);
});
});
describe('when wrapper is not static', () => {
it('should clone instance by context id', () => {
const instance = { test: true };
const wrapper = new InstanceWrapper({ instance, scope: Scope.REQUEST });
expect(wrapper.cloneStaticInstance({ id: 0 }).instance).to.be.undefined;
});
});
});
describe('getInstanceByContextId', () => {
describe('when transient and inquirer has been passed', () => {
it('should call "getInstanceByInquirerId"', () => {
const wrapper = new InstanceWrapper({
scope: Scope.TRANSIENT,
});
const getInstanceByInquirerIdSpy = sinon.spy(
wrapper,
'getInstanceByInquirerId',
);
wrapper.getInstanceByContextId(STATIC_CONTEXT, 'inquirerId');
expect(getInstanceByInquirerIdSpy.called).to.be.true;
});
});
});
describe('setInstanceByContextId', () => {
describe('when transient and inquirer has been passed', () => {
it('should call "setInstanceByInquirerId"', () => {
const wrapper = new InstanceWrapper({
scope: Scope.TRANSIENT,
});
const setInstanceByInquirerIdSpy = sinon.spy(
wrapper,
'setInstanceByInquirerId',
);
wrapper.setInstanceByContextId(
STATIC_CONTEXT,
{ instance: {} },
'inquirerId',
);
expect(setInstanceByInquirerIdSpy.called).to.be.true;
});
});
});
describe('isInRequestScope', () => {
describe('when tree and context are not static and is not transient', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
});
expect(wrapper.isInRequestScope({ id: 3 })).to.be.true;
});
});
describe('otherwise', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.TRANSIENT,
});
expect(wrapper.isInRequestScope({ id: 3 })).to.be.false;
const wrapper2 = new InstanceWrapper({
scope: Scope.REQUEST,
});
expect(wrapper2.isInRequestScope(STATIC_CONTEXT)).to.be.false;
});
});
});
describe('isLazyTransient', () => {
describe('when inquirer is request scoped and context is not static and is transient', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.TRANSIENT,
});
expect(
wrapper.isLazyTransient(
{ id: 3 },
new InstanceWrapper({
scope: Scope.REQUEST,
}),
),
).to.be.true;
});
});
describe('otherwise', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.TRANSIENT,
});
expect(wrapper.isLazyTransient({ id: 3 }, new InstanceWrapper())).to.be
.false;
const wrapper2 = new InstanceWrapper({
scope: Scope.REQUEST,
});
expect(
wrapper2.isLazyTransient(
STATIC_CONTEXT,
new InstanceWrapper({
scope: Scope.TRANSIENT,
}),
),
).to.be.false;
});
});
});
describe('isStatic', () => {
describe('when inquirer is not request scoped and context and tree are static', () => {
it('should return true', () => {
const wrapper = new InstanceWrapper({
scope: Scope.DEFAULT,
});
expect(
wrapper.isStatic(
STATIC_CONTEXT,
new InstanceWrapper({
scope: Scope.DEFAULT,
}),
),
).to.be.true;
});
});
describe('otherwise', () => {
it('should return false', () => {
const wrapper = new InstanceWrapper({
scope: Scope.REQUEST,
});
expect(wrapper.isStatic({ id: 3 }, new InstanceWrapper())).to.be.false;
const wrapper2 = new InstanceWrapper({
scope: Scope.TRANSIENT,
});
expect(
wrapper2.isStatic(
STATIC_CONTEXT,
new InstanceWrapper({
scope: Scope.REQUEST,
}),
),
).to.be.false;
});
});
});
describe('getStaticTransientInstances', () => {
describe('when instance is not transient', () => {
it('should return an empty array', () => {
const wrapper = new InstanceWrapper({
scope: Scope.DEFAULT,
});
expect(wrapper.getStaticTransientInstances()).to.be.eql([]);
});
});
describe('when instance is transient', () => {
it('should return all static instances', () => {
const wrapper = new InstanceWrapper({
scope: Scope.TRANSIENT,
});
const instanceHost = {
instance: {},
};
wrapper.setInstanceByInquirerId(STATIC_CONTEXT, 'test', instanceHost);
expect(wrapper.getStaticTransientInstances()).to.be.eql([instanceHost]);
});
});
});
});

View File

@@ -277,10 +277,12 @@ describe('Module', () => {
});
});
describe('relatedModules', () => {
describe('imports', () => {
it('should return relatedModules', () => {
const test = ['test'];
(module as any)._imports = test;
expect(module.imports).to.be.eql(test);
expect(module.relatedModules).to.be.eql(test);
});
});
@@ -297,7 +299,9 @@ describe('Module', () => {
it('should return controllers', () => {
const test = ['test'];
(module as any)._controllers = test;
expect(module.controllers).to.be.eql(test);
expect(module.routes).to.be.eql(test);
});
});
@@ -305,10 +309,21 @@ describe('Module', () => {
it('should return exports', () => {
const test = ['test'];
(module as any)._exports = test;
expect(module.exports).to.be.eql(test);
});
});
describe('providers', () => {
it('should return providers', () => {
const test = ['test'];
(module as any)._providers = test;
expect(module.providers).to.be.eql(test);
expect(module.components).to.be.eql(test);
});
});
describe('createModuleReferenceType', () => {
let moduleRef;

View File

@@ -15,7 +15,7 @@ import { MiddlewareBuilder } from '../../middleware/builder';
import { MiddlewareContainer } from '../../middleware/container';
import { MiddlewareModule } from '../../middleware/middleware-module';
import { RouterExceptionFilters } from '../../router/router-exception-filters';
import { NoopHttpAdapter } from '../utils/noop-adapter';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
describe('MiddlewareModule', () => {
let middlewareModule: MiddlewareModule;

View File

@@ -5,7 +5,7 @@ import { UseFilters } from '../../../common/decorators/core/exception-filters.de
import { ApplicationConfig } from '../../application-config';
import { NestContainer } from '../../injector/container';
import { RouterExceptionFilters } from '../../router/router-exception-filters';
import { NoopHttpAdapter } from '../utils/noop-adapter';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
describe('RouterExceptionFilters', () => {
let moduleName: string;

View File

@@ -14,7 +14,7 @@ import { PipesConsumer } from '../../pipes/pipes-consumer';
import { PipesContextCreator } from '../../pipes/pipes-context-creator';
import { RouteParamsFactory } from '../../router/route-params-factory';
import { RouterExecutionContext } from '../../router/router-execution-context';
import { NoopHttpAdapter } from '../utils/noop-adapter';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
describe('RouterExecutionContext', () => {
let contextCreator: RouterExecutionContext;

View File

@@ -1,28 +1,33 @@
import * as sinon from 'sinon';
import { expect } from 'chai';
import { RouterExplorer } from '../../router/router-explorer';
import * as sinon from 'sinon';
import { Controller } from '../../../common/decorators/core/controller.decorator';
import { RequestMapping } from '../../../common/decorators/http/request-mapping.decorator';
import {
All,
Get,
Post,
} from '../../../common/decorators/http/request-mapping.decorator';
import { RequestMethod } from '../../../common/enums/request-method.enum';
import { MetadataScanner } from '../../metadata-scanner';
import { NestContainer } from '../../injector/container';
import { MetadataScanner } from '../../metadata-scanner';
import { RouterExplorer } from '../../router/router-explorer';
describe('RouterExplorer', () => {
@Controller('global')
class TestRoute {
@RequestMapping({ path: 'test' })
@Get('test')
public getTest() {}
@RequestMapping({ path: 'test', method: RequestMethod.POST })
@Post('test')
public postTest() {}
@RequestMapping({ path: 'another-test', method: RequestMethod.ALL })
@All('another-test')
public anotherTest() {}
}
let routerBuilder: RouterExplorer;
beforeEach(() => {
routerBuilder = new RouterExplorer(new MetadataScanner(), new NestContainer());
routerBuilder = new RouterExplorer(
new MetadataScanner(),
new NestContainer(),
);
});
describe('scanForPaths', () => {
it('should method return expected list of route paths', () => {

View File

@@ -3,7 +3,7 @@ import * as sinon from 'sinon';
import { HttpException } from '../../../common/exceptions/http.exception';
import { ExceptionsHandler } from '../../exceptions/exceptions-handler';
import { RouterProxy } from '../../router/router-proxy';
import { NoopHttpAdapter } from '../utils/noop-adapter';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
describe('RouterProxy', () => {
let routerProxy: RouterProxy;

View File

@@ -4,7 +4,7 @@ import { of } from 'rxjs';
import * as sinon from 'sinon';
import { RequestMethod } from '../../../common';
import { RouterResponseController } from '../../router/router-response-controller';
import { NoopHttpAdapter } from '../utils/noop-adapter';
import { NoopHttpAdapter } from '../utils/noop-adapter.spec';
describe('RouterResponseController', () => {
let adapter: NoopHttpAdapter;

Some files were not shown because too many files have changed in this diff Show More