feature(@nestjs) dynamic modules, global scope, bugfixes, improvements

This commit is contained in:
Kamil Myśliwiec
2017-12-15 14:34:10 +01:00
parent 3747abc32a
commit 4795b8f209
81 changed files with 1308 additions and 210 deletions

View File

@@ -1,6 +1,16 @@
## 4.5.0@soon
- **common**: bugfix #286
- **core**: dynamic modules feature
- **core**: global scope feature
- **core**: `ExternalContextCreator` & `ModulesContainer`
- **core**: merge `NestApplicationContext` with `NestApplication`
- **core**: `NotFoundException` is thrown when route is not available #298
- **core**: add `set()` wrapper around native express methods
- **core**: bugfix #281
- **websockets**: bugfix #271
- **microservices**: log RPC exceptions #303
- **microservices**: merge `NestApplicationContext` with `NestMicroservice`
- **microservices**: handle ECONNREFUSED #288
## 4.4.1
- **common**: `ValidationPipe` improvement

View File

@@ -1,13 +1,13 @@
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { CatsModule } from '../../src/modules/cats/cats.module';
import { CatsService } from '../../src/modules/cats/cats.service';
import { INestApplication } from '@nestjs/common';
describe('Cats', () => {
const server = express();
server.use(bodyParser.json());
let server;
let app: INestApplication;
const catsService = { findAll: () => ['test'] };
@@ -15,10 +15,12 @@ describe('Cats', () => {
const module = await Test.createTestingModule({
modules: [CatsModule],
})
.overrideComponent(CatsService).useValue(catsService)
.overrideComponent(CatsService)
.useValue(catsService)
.compile();
const app = module.createNestApplication(server);
server = express();
app = module.createNestApplication(server);
await app.init();
});
@@ -30,4 +32,8 @@ describe('Cats', () => {
data: catsService.findAll(),
});
});
afterAll(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,10 @@
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["src/**/**.entity{.ts,.js}"],
"autoSchemaSync": true
}

View File

@@ -13,6 +13,7 @@
"@nestjs/core": "^4.3.0",
"@nestjs/microservices": "^4.3.0",
"@nestjs/testing": "^4.3.0",
"@nestjs/typeorm": "^1.0.0",
"@nestjs/websockets": "^4.3.0",
"mysql": "^2.14.1",
"redis": "^2.7.1",

View File

@@ -1,7 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhotoModule } from './photo/photo.module';
import { Photo } from './photo/photo.entity';
@Module({
modules: [PhotoModule],
modules: [
TypeOrmModule.forRoot([Photo]),
PhotoModule,
],
})
export class ApplicationModule {}

View File

@@ -1,8 +0,0 @@
import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';
@Module({
components: [...databaseProviders],
exports: [...databaseProviders],
})
export class DatabaseModule {}

View File

@@ -1,19 +0,0 @@
import { createConnection } from 'typeorm';
export const databaseProviders = [
{
provide: 'DbConnectionToken',
useFactory: async () => await createConnection({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [
__dirname + '/../**/**.entity{.ts,.js}',
],
autoSchemaSync: true,
}),
},
];

View File

@@ -0,0 +1,13 @@
import { Controller, Get } from '@nestjs/common';
import { PhotoService } from './photo.service';
import { Photo } from './photo.entity';
@Controller('photo')
export class PhotoController {
constructor(private readonly photoService: PhotoService) {}
@Get()
findAll(): Promise<Photo[]> {
return this.photoService.findAll();
}
}

View File

@@ -1,13 +1,9 @@
import { Module } from '@nestjs/common';
import { DatabaseModule } from '../database/database.module';
import { photoProviders } from './photo.providers';
import { PhotoService } from './photo.service';
import { PhotoController } from './photo.controller';
@Module({
modules: [DatabaseModule],
components: [
...photoProviders,
PhotoService,
],
components: [PhotoService],
controllers: [PhotoController],
})
export class PhotoModule {}

View File

@@ -1,10 +0,0 @@
import { Connection, Repository } from 'typeorm';
import { Photo } from './photo.entity';
export const photoProviders = [
{
provide: 'PhotoRepositoryToken',
useFactory: (connection: Connection) => connection.getRepository(Photo),
inject: ['DbConnectionToken'],
},
];

View File

@@ -1,11 +1,15 @@
import { Component, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';
@Component()
export class PhotoService {
constructor(
@Inject('PhotoRepositoryToken') private readonly photoRepository: Repository<Photo>) {}
@InjectRepository(Photo)
private readonly photoRepository: Repository<Photo>,
) {}
async findAll(): Promise<Photo[]> {
return await this.photoRepository.find();

View File

@@ -12,6 +12,8 @@
true,
"single"
],
"ban-types": false,
"indent": false,
"ordered-imports": [
false
],

View File

@@ -18,9 +18,9 @@
"redis": "^2.7.1",
"reflect-metadata": "^0.1.10",
"rxjs": "^5.4.3",
"sequelize": "^4.12.0",
"sequelize-typescript": "^0.5.0",
"typescript": "^2.4.2"
"sequelize": "^4.28.0",
"sequelize-typescript": "^0.6.1",
"typescript": "^2.6.2"
},
"devDependencies": {
"@types/node": "^7.0.41",

View File

@@ -7,7 +7,7 @@ import { Cat } from './cat.entity';
@Component()
export class CatsService {
constructor(
@Inject('CatsRepository') private readonly catsRepository: typeof Model) {}
@Inject('CatsRepository') private readonly catsRepository: typeof Cat) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const cat = new Cat();

21
examples/12-graphql-apollo/.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

@@ -1,5 +1,5 @@
require('ts-node/register');
require('./example/server');
require('./src/server');

View File

@@ -0,0 +1,26 @@
{
"name": "nest-typescript-starter",
"version": "1.0.0",
"description": "Nest TypeScript starter repository",
"license": "MIT",
"scripts": {
"start": "node index.js",
"prestart:prod": "tsc",
"start:prod": "node dist/server.js"
},
"dependencies": {
"@nestjs/common": "^4.3.0",
"@nestjs/core": "^4.3.0",
"@nestjs/graphql": "^1.0.0",
"apollo-server-express": "^1.2.0",
"graphql": "^0.11.7",
"graphql-tools": "^2.11.0",
"reflect-metadata": "^0.1.10",
"rxjs": "^5.4.3",
"typescript": "^2.4.2"
},
"devDependencies": {
"@types/node": "^7.0.41",
"ts-node": "^3.3.0"
}
}

View File

@@ -0,0 +1,63 @@
import {
Module,
MiddlewaresConsumer,
NestModule,
RequestMethod,
} from '@nestjs/common';
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
import {
makeExecutableSchema,
addMockFunctionsToSchema,
mergeSchemas,
} from 'graphql-tools';
import { GraphQLModule, GraphQLFactory } from '@nestjs/graphql';
import { CatsModule } from './cats/cats.module';
@Module({
modules: [CatsModule, GraphQLModule],
})
export class ApplicationModule {
constructor(private readonly graphQLFactory: GraphQLFactory) {}
configure(consumer: MiddlewaresConsumer) {
const schema = this.createSchema();
consumer
.apply(graphiqlExpress({ endpointURL: '/graphql' }))
.forRoutes({ path: '/graphiql', method: RequestMethod.GET })
.apply(graphqlExpress(req => ({ schema, rootValue: req })))
.forRoutes({ path: '/graphql', method: RequestMethod.ALL });
}
createSchema() {
const typeDefs = this.graphQLFactory.mergeTypesByPaths('./**/*.graphql');
const schema = this.graphQLFactory.createSchema({ typeDefs });
const delegates = this.graphQLFactory.createDelegates();
const { humanSchema, linkTypeDefs } = this.createDelegatedSchema();
return mergeSchemas({
schemas: [schema, humanSchema, linkTypeDefs],
resolvers: delegates,
});
}
createDelegatedSchema() {
const linkTypeDefs = `
extend type Cat {
human: Human
}
`;
const humanSchema = makeExecutableSchema({
typeDefs: `
type Human {
id: ID!
}
type Query {
humanById(id: ID!): Human
}
`,
});
addMockFunctionsToSchema({ schema: humanSchema });
return { humanSchema, linkTypeDefs };
}
}

View File

@@ -0,0 +1,8 @@
import { CanActivate, Guard, ExecutionContext } from '@nestjs/common';
@Guard()
export class CatsGuard implements CanActivate {
canActivate(request: any, context: ExecutionContext): boolean {
return true;
}
}

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CatsResolvers } from './cats.resolvers';
@Module({
components: [CatsService, CatsResolvers],
})
export class CatsModule {}

View File

@@ -0,0 +1,52 @@
import { Component, UseGuards } from '@nestjs/common';
import {
Query,
Mutation,
Resolver,
DelegateProperty,
} from '@nestjs/graphql';
import { Cat } from './interfaces/cat.interface';
import { CatsService } from './cats.service';
import { CatsGuard } from './cats.guard';
import { MergeInfo } from 'graphql-tools/dist/Interfaces';
@Resolver('Cat')
export class CatsResolvers {
constructor(private readonly catsService: CatsService) {}
@Query()
@UseGuards(CatsGuard)
async getCats() {
return await this.catsService.findAll();
}
@Query('cat')
async findOneById(id: number) {
return await this.catsService.findOneById(id);
}
@Mutation('createCat')
async create(cat: Cat) {
await this.catsService.create(cat);
}
@DelegateProperty('human')
findHumansById(cat: Cat) {
return (mergeInfo: MergeInfo) => ({
fragment: `fragment CatFragment on Cat { humanId }`,
resolve(parent, args, context, info) {
const humanId = parent.id;
return mergeInfo.delegate(
'query',
'humanById',
{
id: humanId,
},
context,
info,
);
},
});
}
}

View File

@@ -0,0 +1,21 @@
import { Component } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Component()
export class CatsService {
private readonly cats: Cat[] = [
{ id: 1, name: 'Cat', age: 5 },
];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
findOneById(id: number): Cat {
return this.cats.find((cat) => cat.id === id);
}
}

View File

@@ -0,0 +1,16 @@
type Query {
getCats: [Cat]
cat(id: ID!): Cat
catByHumanId(id: ID!): Cat
}
type Mutation {
createCat(name: String): Cat
}
type Cat {
id: Int
name: String
age: Int
humanId: Int
}

View File

@@ -0,0 +1,5 @@
export interface Cat {
readonly id: number;
readonly name: string;
readonly age: number;
}

View File

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

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/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,54 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {
"no-unused-expression": true
},
"rules": {
"eofline": false,
"quotemark": [
true,
"single"
],
"indent": false,
"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": []
}

21
examples/13-mongo-typeorm/.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,14 @@
require('ts-node/register');
require('./src/server');

View File

@@ -0,0 +1,7 @@
{
"type": "mongodb",
"host": "localhost",
"database": "nest",
"entities": ["src/**/**.entity{.ts,.js}"],
"synchronize": true
}

View File

@@ -0,0 +1,25 @@
{
"name": "nest-typescript-starter",
"version": "1.0.0",
"description": "Nest TypeScript starter repository",
"license": "MIT",
"scripts": {
"start": "node index.js",
"prestart:prod": "tsc",
"start:prod": "node dist/server.js"
},
"dependencies": {
"@nestjs/common": "^4.3.0",
"@nestjs/core": "^4.3.0",
"@nestjs/typeorm": "^1.0.0",
"mongodb": "^2.2.31",
"reflect-metadata": "^0.1.10",
"rxjs": "^5.4.3",
"typeorm": "0.1.0-alpha.40",
"typescript": "^2.4.2"
},
"devDependencies": {
"@types/node": "^7.0.41",
"ts-node": "^3.3.0"
}
}

View File

@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhotoModule } from './photo/photo.module';
import { Photo } from './photo/photo.entity';
@Module({
modules: [
TypeOrmModule.forRoot([Photo]),
PhotoModule,
],
})
export class ApplicationModule {}

View File

@@ -0,0 +1,13 @@
import { Controller, Get } from '@nestjs/common';
import { PhotoService } from './photo.service';
import { Photo } from './photo.entity';
@Controller('photo')
export class PhotoController {
constructor(private readonly photoService: PhotoService) {}
@Get()
findAll(): Promise<Photo[]> {
return this.photoService.findAll();
}
}

View File

@@ -0,0 +1,19 @@
import { Entity, Column, PrimaryGeneratedColumn, ObjectIdColumn, ObjectID } from 'typeorm';
@Entity()
export class Photo {
@ObjectIdColumn()
id: ObjectID;
@Column()
name: string;
@Column()
description: string;
@Column()
filename: string;
@Column()
isPublished: boolean;
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { PhotoService } from './photo.service';
import { PhotoController } from './photo.controller';
@Module({
components: [PhotoService],
controllers: [PhotoController],
})
export class PhotoModule {}

View File

@@ -0,0 +1,17 @@
import { Component, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';
@Component()
export class PhotoService {
constructor(
@InjectRepository(Photo)
private readonly photoRepository: Repository<Photo>,
) {}
async findAll(): Promise<Photo[]> {
return await this.photoRepository.find();
}
}

View File

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

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/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,55 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {
"no-unused-expression": true
},
"rules": {
"eofline": false,
"quotemark": [
true,
"single"
],
"ban-types": false,
"indent": false,
"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": []
}

21
examples/14-mongoose-module/.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,14 @@
require('ts-node/register');
require('./src/server');

View File

@@ -0,0 +1,25 @@
{
"name": "nest-typescript-starter",
"version": "1.0.0",
"description": "Nest TypeScript starter repository",
"license": "MIT",
"scripts": {
"start": "node index.js",
"prestart:prod": "tsc",
"start:prod": "node dist/server.js"
},
"dependencies": {
"@nestjs/common": "^4.3.0",
"@nestjs/core": "^4.3.0",
"@nestjs/mongoose": "^1.0.1",
"mongoose": "^4.11.13",
"reflect-metadata": "^0.1.10",
"rxjs": "^5.4.3",
"typescript": "^2.4.2"
},
"devDependencies": {
"@types/mongoose": "^4.7.23",
"@types/node": "^7.0.41",
"ts-node": "^3.3.0"
}
}

View File

@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsModule } from './cats/cats.module';
import { CatSchema } from './cats/schemas/cat.schema';
@Module({
modules: [
MongooseModule.forRoot('mongodb://localhost/nest', [
{ name: 'Cat', schema: CatSchema },
]),
CatsModule,
],
})
export class ApplicationModule {}

View File

@@ -0,0 +1,19 @@
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
components: [CatsService],
})
export class CatsModule {}

View File

@@ -0,0 +1,21 @@
import { Model } from 'mongoose';
import { Component, Inject } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat } from './interfaces/cat.interface';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatSchema } from './schemas/cat.schema';
@Component()
export class CatsService {
constructor(@InjectModel(CatSchema) private readonly catModel: Model<Cat>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return await createdCat.save();
}
async findAll(): Promise<Cat[]> {
return await this.catModel.find().exec();
}
}

View File

@@ -0,0 +1,5 @@
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}

View File

@@ -0,0 +1,7 @@
import { Document } from 'mongoose';
export interface Cat extends Document {
readonly name: string;
readonly age: number;
readonly breed: string;
}

View File

@@ -0,0 +1,7 @@
import * as mongoose from 'mongoose';
export const CatSchema = new mongoose.Schema({
name: String,
age: Number,
breed: String,
});

View File

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

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/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,54 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {
"no-unused-expression": true
},
"rules": {
"eofline": false,
"quotemark": [
true,
"single"
],
"ban-types": false,
"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

@@ -57,5 +57,7 @@ gulp.task('move', function() {
gulp.dest('examples/09-babel-example/node_modules/@nestjs')
).pipe(
gulp.dest('examples/11-swagger/node_modules/@nestjs')
).pipe(
gulp.dest('examples/12-graphql-apollo/node_modules/@nestjs')
);
});

View File

@@ -4,8 +4,6 @@
"description": "Modern, fast, powerful node.js web framework",
"main": "index.js",
"scripts": {
"start": "node index.js",
"start:live": "nodemon -e ts --watch src index.js",
"test": "nyc --require ts-node/register mocha src/**/*.spec.ts --reporter spec",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"build": "gulp build && gulp move",

View File

@@ -1,4 +1,6 @@
export class HttpException {
private readonly message: string | object;
/**
* The base Nest Application exception, which is handled by the default Exceptions Handler.
* If you throw an exception from your HTTP route handlers, Nest will map them to the appropriate HTTP response and send to the client.
@@ -16,7 +18,9 @@ export class HttpException {
constructor(
private readonly response: string | object,
private readonly status: number,
) {}
) {
this.message = response;
}
public getResponse(): string | object {
return this.response;

View File

@@ -1,10 +1,10 @@
import { MicroserviceConfiguration } from '@nestjs/microservices';
import { INestMicroservice, ExceptionFilter, PipeTransform } from './index';
import { WebSocketAdapter } from './web-socket-adapter.interface';
import { CanActivate } from './can-activate.interface';
import { NestInterceptor } from './nest-interceptor.interface';
import { INestApplicationContext } from './nest-application-context.interface';
export interface INestApplication {
export interface INestApplication extends INestApplicationContext {
/**
* Initializes application. It is not necessary to call this method directly.
*
@@ -20,6 +20,14 @@ export interface INestApplication {
*/
use(...args): void;
/**
* The wrapper function around native `express.set()` method.
* Example `app.set('trust proxy', 'loopback')`
*
* @returns void
*/
set(...args): void;
/**
* Starts the application.
*
@@ -63,7 +71,7 @@ export interface INestApplication {
* @param {MicroserviceConfiguration} config Microservice configuration objet
* @returns INestMicroservice
*/
connectMicroservice(config: MicroserviceConfiguration): INestMicroservice;
connectMicroservice(config): INestMicroservice;
/**
* Returns array of the connected microservices to the NestApplication.

View File

@@ -1,11 +1,17 @@
import { MessageMappingProperties } from '@nestjs/websockets/gateway-metadata-explorer';
import { Observable } from 'rxjs/Observable';
export interface WebSocketAdapter {
create(port: number);
createWithNamespace?(port: number, namespace: string);
createWithNamespace?(port: number, namespace: string, server?);
bindClientConnect(server, callback: (...args) => void);
bindClientDisconnect?(client, callback: (...args) => void);
bindMessageHandlers(client, handler: MessageMappingProperties[], process: (data) => Observable<any>);
bindMessageHandlers(
client,
handler: {
message: string;
callback: (...args) => Observable<any> | Promise<any> | void;
}[],
process: (data) => Observable<any>,
);
bindMiddleware?(server, middleware: (socket, next) => void);
}

View File

@@ -0,0 +1,76 @@
import 'reflect-metadata';
import { GuardsContextCreator } from './../guards/guards-context-creator';
import { GuardsConsumer } from './../guards/guards-consumer';
import { InterceptorsContextCreator } from './../interceptors/interceptors-context-creator';
import { InterceptorsConsumer } from './../interceptors/interceptors-consumer';
import { Controller } from '@nestjs/common/interfaces';
import { FORBIDDEN_MESSAGE } from '../guards/constants';
import { HttpStatus, HttpException } from '@nestjs/common';
import { Module } from './../injector/module';
import { ModulesContainer } from './../injector/modules-container';
export class ExternalContextCreator {
constructor(
private readonly guardsContextCreator: GuardsContextCreator,
private readonly guardsConsumer: GuardsConsumer,
private readonly interceptorsContextCreator: InterceptorsContextCreator,
private readonly interceptorsConsumer: InterceptorsConsumer,
private readonly modulesContainer: ModulesContainer,
) {}
public create(
instance: Controller,
callback: (...args) => any,
methodName: string,
) {
const module = this.findContextModuleName(instance.constructor);
const guards = this.guardsContextCreator.create(instance, callback, module);
const interceptors = this.interceptorsContextCreator.create(
instance,
callback,
module,
);
return async (...args) => {
const [req] = args;
const canActivate = await this.guardsConsumer.tryActivate(
guards,
req,
instance,
callback,
);
if (!canActivate) {
throw new HttpException(FORBIDDEN_MESSAGE, HttpStatus.FORBIDDEN);
}
const handler = () => callback.apply(instance, args);
return await this.interceptorsConsumer.intercept(
interceptors,
req,
instance,
callback,
handler,
);
};
}
public findContextModuleName(constructor: Function): string {
const className = constructor.name;
if (!className) {
return '';
}
for (const [key, module] of [...this.modulesContainer.entries()]) {
if (this.findComponentByClassName(module, className)) {
return key;
}
}
return '';
}
public findComponentByClassName(module: Module, className: string): boolean {
const { components } = module;
const hasComponent = [...components.keys()].find(
component => component === className,
);
return !!hasComponent;
}
}

View File

@@ -10,10 +10,11 @@ import { UnknownModuleException } from '../errors/exceptions/unknown-module.exce
import { ModuleTokenFactory } from './module-token-factory';
import { InvalidModuleException } from './../errors/exceptions/invalid-module.exception';
import { DynamicModule } from '@nestjs/common';
import { ModulesContainer } from './modules-container';
export class NestContainer {
private readonly globalModules = new Set<Module>();
private readonly modules = new Map<string, Module>();
private readonly modules = new ModulesContainer();
private readonly dynamicModulesMetadata = new Map<string, Partial<DynamicModule>>();
private readonly moduleTokenFactory = new ModuleTokenFactory();
@@ -26,7 +27,7 @@ export class NestContainer {
if (this.modules.has(token)) {
return;
}
const module = new Module(type, scope);
const module = new Module(type, scope, this);
this.modules.set(token, module);
this.addDynamicMetadata(token, dynamicMetadata);
@@ -62,7 +63,7 @@ export class NestContainer {
this.globalModules.add(module);
}
public getModules(): Map<string, Module> {
public getModules(): ModulesContainer {
return this.modules;
}

View File

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

View File

@@ -1,4 +1,4 @@
import { InstanceWrapper } from './container';
import { InstanceWrapper, NestContainer } from './container';
import { Injectable, Controller, NestModule } from '@nestjs/common/interfaces';
import { UnknownExportException } from '../errors/exceptions/unknown-export.exception';
import { NestModuleMetatype } from '@nestjs/common/interfaces/modules/module-metatype.interface';
@@ -7,6 +7,12 @@ import { ModuleRef } from './module-ref';
import { isFunction, isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
import { RuntimeException } from '../errors/exceptions/runtime.exception';
import { Reflector } from '../services/reflector.service';
import { ExternalContextCreator } from './../helpers/external-context-creator';
import { GuardsContextCreator } from './../guards/guards-context-creator';
import { InterceptorsContextCreator } from './../interceptors/interceptors-context-creator';
import { InterceptorsConsumer } from './../interceptors/interceptors-consumer';
import { GuardsConsumer } from './../guards/guards-consumer';
import { ModulesContainer } from './modules-container';
export interface CustomComponent {
provide: any;
@@ -27,9 +33,10 @@ export class Module {
constructor(
private _metatype: NestModuleMetatype,
private _scope: NestModuleMetatype[]) {
this.addCoreInjectables();
private _scope: NestModuleMetatype[],
container: NestContainer,
) {
this.addCoreInjectables(container);
}
get scope(): NestModuleMetatype[] {
@@ -68,10 +75,12 @@ export class Module {
return this._metatype;
}
public addCoreInjectables() {
public addCoreInjectables(container: NestContainer) {
this.addModuleRef();
this.addModuleAsComponent();
this.addReflector();
this.addExternalContextCreator(container);
this.addModulesContainer(container);
}
public addModuleRef() {
@@ -102,6 +111,30 @@ export class Module {
});
}
public addExternalContextCreator(container: NestContainer) {
this._components.set(ExternalContextCreator.name, {
name: ExternalContextCreator.name,
metatype: ExternalContextCreator,
isResolved: true,
instance: new ExternalContextCreator(
new GuardsContextCreator(container),
new GuardsConsumer(),
new InterceptorsContextCreator(container),
new InterceptorsConsumer(),
container.getModules(),
),
});
}
public addModulesContainer(container: NestContainer) {
this._components.set(ModulesContainer.name, {
name: ModulesContainer.name,
metatype: ModulesContainer,
isResolved: true,
instance: container.getModules(),
});
}
public addInjectable(injectable: Metatype<Injectable>) {
if (this.isCustomProvider(injectable)) {
return this.addCustomProvider(injectable, this._injectables);

View File

@@ -0,0 +1,3 @@
import { Module } from './module';
export class ModulesContainer extends Map<string, Module> {}

View File

@@ -12,7 +12,8 @@ export class NestApplicationContext implements INestApplicationContext {
constructor(
protected readonly container: NestContainer,
private readonly scope: NestModuleMetatype[],
private readonly contextModule) {}
protected contextModule,
) {}
public select<T>(module: Metatype<T>): INestApplicationContext {
const modules = this.container.getModules();

View File

@@ -24,13 +24,14 @@ import { Resolver } from './router/interfaces/resolver.interface';
import { RoutesResolver } from './router/routes-resolver';
import { MicroservicesPackageNotFoundException } from './errors/exceptions/microservices-package-not-found.exception';
import { MiddlewaresContainer } from './middlewares/container';
import { NestApplicationContext } from './nest-application-context';
const { SocketModule } = optional('@nestjs/websockets/socket-module') || {} as any;
const { MicroservicesModule } = optional('@nestjs/microservices/microservices-module') || {} as any;
const { NestMicroservice } = optional('@nestjs/microservices/nest-microservice') || {} as any;
const { IoAdapter } = optional('@nestjs/websockets/adapters/io-adapter') || {} as any;
export class NestApplication implements INestApplication {
export class NestApplication extends NestApplicationContext implements INestApplication {
private readonly logger = new Logger(NestApplication.name, true);
private readonly middlewaresModule = new MiddlewaresModule();
private readonly middlewaresContainer = new MiddlewaresContainer();
@@ -47,11 +48,11 @@ export class NestApplication implements INestApplication {
private readonly microservices = [];
private isInitialized = false;
constructor(
private readonly container: NestContainer,
private readonly express,
) {
this.setupParserMiddlewares();
constructor(container: NestContainer, private readonly express) {
super(container, [], null);
const modules = this.container.getModules().values();
this.contextModule = modules.next().value;
this.httpServer = http.createServer(express);
const ioAdapter = IoAdapter ? new IoAdapter(this.httpServer) : null;
@@ -61,11 +62,6 @@ export class NestApplication implements INestApplication {
);
}
public setupParserMiddlewares() {
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: true }));
}
public async setupModules() {
this.socketModule && this.socketModule.setup(this.container, this.config);
@@ -81,6 +77,8 @@ export class NestApplication implements INestApplication {
}
public async init() {
this.setupParserMiddlewares();
await this.setupModules();
await this.setupRouter();
@@ -89,6 +87,22 @@ export class NestApplication implements INestApplication {
this.isInitialized = true;
}
public setupParserMiddlewares() {
const parserMiddlewares = {
jsonParser: bodyParser.json(),
urlencodedParser: bodyParser.urlencoded({ extended: true }),
};
Object.keys(parserMiddlewares)
.filter((parser) => !this.isMiddlewareApplied(this.express, parser))
.forEach((parserKey) => this.express.use(parserMiddlewares[parserKey]));
}
public isMiddlewareApplied(app, name: string): boolean {
return !!app._router && !!app._router.stack.filter(
(layer) => layer && layer.handle && layer.handle.name === name,
).length;
}
public async setupRouter() {
const router = ExpressAdapter.createRouter();
await this.setupMiddlewares(router);
@@ -128,6 +142,10 @@ export class NestApplication implements INestApplication {
this.express.use(...args);
}
public set(...args) {
this.express.set(...args);
}
public async listen(port: number, callback?: () => void);
public async listen(port: number, hostname: string, callback?: () => void);
public async listen(port: number, ...args) {

View File

@@ -1,6 +1,5 @@
import { Observable } from 'rxjs/Observable';
import { RequestMethod, HttpStatus } from '@nestjs/common';
import { isNil, isObject } from '@nestjs/common/utils/shared.utils';
import { isNil, isObject, isFunction } from '@nestjs/common/utils/shared.utils';
import 'rxjs/add/operator/toPromise';
export class RouterResponseController {
@@ -18,7 +17,7 @@ export class RouterResponseController {
if (resultOrDeffered instanceof Promise) {
return await resultOrDeffered;
}
else if (resultOrDeffered instanceof Observable) {
else if (resultOrDeffered && isFunction(resultOrDeffered.subscribe)) {
return await resultOrDeffered.toPromise();
}
return resultOrDeffered;

View File

@@ -10,6 +10,7 @@ import { MetadataScanner } from '../metadata-scanner';
import { RouterExplorer } from './interfaces/explorer.inteface';
import { ExpressRouterExplorer } from './router-explorer';
import { ApplicationConfig } from './../application-config';
import { NotFoundException } from '@nestjs/common';
export class RoutesResolver implements Resolver {
private readonly logger = new Logger(RoutesResolver.name, true);
@@ -32,6 +33,9 @@ export class RoutesResolver implements Resolver {
public resolve(express: Application) {
const modules = this.container.getModules();
modules.forEach(({ routes }, moduleName) => this.setupRouters(routes, moduleName, express));
this.setupNotFoundHandler(express);
this.setupExceptionHandler(express);
}
public setupRouters(
@@ -48,7 +52,15 @@ export class RoutesResolver implements Resolver {
const router = this.routerBuilder.explore(instance, metatype, moduleName);
express.use(path, router);
});
this.setupExceptionHandler(express);
}
public setupNotFoundHandler(express: Application) {
const callback = (req, res) => {
throw new NotFoundException(`Cannot ${req.method} ${req.url}`);
};
const exceptionHandler = this.routerExceptionsFilter.create({}, callback as any);
const proxy = this.routerProxy.createProxy(callback, exceptionHandler);
express.use(proxy);
}
public setupExceptionHandler(express: Application) {

View File

@@ -0,0 +1,135 @@
import * as sinon from 'sinon';
import { expect } from 'chai';
import { GuardsConsumer } from '../../guards/guards-consumer';
import { GuardsContextCreator } from '../../guards/guards-context-creator';
import { ModulesContainer } from '../../injector/modules-container';
import { NestContainer } from '../../injector/container';
import { InterceptorsContextCreator } from '../../interceptors/interceptors-context-creator';
import { InterceptorsConsumer } from '../../interceptors/interceptors-consumer';
import { ExternalContextCreator } from '../../helpers/external-context-creator';
describe('ExternalContextCreator', () => {
let contextCreator: ExternalContextCreator;
let callback;
let applySpy: sinon.SinonSpy;
let bindSpy: sinon.SinonSpy;
let guardsConsumer: GuardsConsumer;
beforeEach(() => {
callback = {
bind: () => ({}),
apply: () => ({}),
};
bindSpy = sinon.spy(callback, 'bind');
applySpy = sinon.spy(callback, 'apply');
guardsConsumer = new GuardsConsumer();
contextCreator = new ExternalContextCreator(
new GuardsContextCreator(new NestContainer()),
guardsConsumer,
new InterceptorsContextCreator(new NestContainer()),
new InterceptorsConsumer(),
new ModulesContainer(),
);
});
describe('create', () => {
it('should call "findContextModuleName" with expected argument', done => {
const findContextModuleNameSpy = sinon.spy(
contextCreator,
'findContextModuleName',
);
contextCreator.create({ foo: 'bar' }, callback as any, '');
expect(findContextModuleNameSpy.called).to.be.true;
done();
});
describe('returns proxy function', () => {
let proxyContext;
let instance;
beforeEach(() => {
instance = { foo: 'bar' };
proxyContext = contextCreator.create(instance, callback as any, '');
});
it('should be a function', () => {
expect(proxyContext).to.be.a('function');
});
describe('when proxy function called', () => {
describe('when can not activate', () => {
it('should throw exception when "tryActivate" returns false', () => {
sinon.stub(guardsConsumer, 'tryActivate', () => false);
expect(proxyContext(1, 2, 3)).to.eventually.throw();
});
});
describe('when can activate', () => {
it('should apply context and args', async () => {
const args = [1, 2, 3];
sinon.stub(guardsConsumer, 'tryActivate', () => true);
await proxyContext(...args);
expect(applySpy.calledWith(instance, args)).to.be.true;
});
});
});
});
});
describe('findContextModuleName', () => {
describe('when constructor name is undefined', () => {
it('should return empty string', () => {
expect(contextCreator.findContextModuleName({} as any)).to.be.eql('');
});
});
describe('when component exists', () => {
it('should return module key', () => {
const modules = new Map();
const componentKey = 'test';
const moduleKey = 'key';
modules.set(moduleKey, {});
(contextCreator as any).modulesContainer = modules;
sinon.stub(contextCreator, 'findComponentByClassName', () => true);
expect(
contextCreator.findContextModuleName({ name: componentKey } as any),
).to.be.eql(moduleKey);
});
});
describe('when component does not exists', () => {
it('should return empty string', () => {
sinon.stub(contextCreator, 'findComponentByClassName', () => false);
expect(contextCreator.findContextModuleName({} as any)).to.be.eql('');
});
});
});
describe('findComponentByClassName', () => {
describe('when component exists', () => {
it('should return true', () => {
const components = new Map();
const key = 'test';
components.set(key, key);
expect(
contextCreator.findComponentByClassName(
{
components,
} as any,
key,
),
).to.be.true;
});
});
describe('when component does not exists', () => {
it('should return false', () => {
const components = new Map();
const key = 'test';
expect(
contextCreator.findComponentByClassName(
{
components,
} as any,
key,
),
).to.be.false;
});
});
});
});

View File

@@ -1,6 +1,6 @@
import * as sinon from 'sinon';
import { expect } from 'chai';
import { InstanceWrapper } from '../../injector/container';
import { InstanceWrapper, NestContainer } from '../../injector/container';
import { Injector } from '../../injector/injector';
import { Component } from '../../../common/decorators/core/component.decorator';
import { RuntimeException } from '../../errors/exceptions/runtime.exception';
@@ -8,7 +8,7 @@ import { Module } from '../../injector/module';
import { UnknownDependenciesException } from '../../errors/exceptions/unknown-dependencies.exception';
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import { UndefinedDependencyException } from "../../errors/exceptions/undefined-dependency.exception";
import { UndefinedDependencyException } from '../../errors/exceptions/undefined-dependency.exception';
chai.use(chaiAsPromised);
describe('Injector', () => {
@@ -37,7 +37,7 @@ describe('Injector', () => {
let mainTest, depOne, depTwo;
beforeEach(() => {
moduleDeps = new Module(DependencyTwo as any, []);
moduleDeps = new Module(DependencyTwo as any, [], new NestContainer());
mainTest = {
name: 'MainTest',
metatype: MainTest,
@@ -117,7 +117,7 @@ describe('Injector', () => {
let test;
beforeEach(() => {
moduleDeps = new Module(Test as any, []);
moduleDeps = new Module(Test as any, [], new NestContainer());
test = {
name: 'Test',
metatype: Test,

View File

@@ -5,6 +5,7 @@ import { UnknownExportException } from '../../errors/exceptions/unknown-export.e
import { Module } from '../../injector/module';
import { Component } from '../../../common/decorators/core/component.decorator';
import { RuntimeException } from '../../errors/exceptions/runtime.exception';
import { NestContainer } from '../../injector/container';
describe('Module', () => {
let module: Module;
@@ -13,7 +14,7 @@ describe('Module', () => {
@Component() class TestComponent {}
beforeEach(() => {
module = new Module(TestModule as any, []);
module = new Module(TestModule as any, [], new NestContainer());
});
it('should add route', () => {

View File

@@ -63,7 +63,7 @@ describe('RoutesResolver', () => {
modules.set('TestModule2', { routes });
const spy = sinon.stub(routesResolver, 'setupRouters').callsFake(() => undefined);
routesResolver.resolve({} as any);
routesResolver.resolve({ use: sinon.spy() } as any);
expect(spy.calledTwice).to.be.true;
});

View File

@@ -23,7 +23,7 @@ export class ClientRedis extends ClientProxy {
protected sendSingleMessage(msg, callback: (...args) => any) {
if (!this.pub || !this.sub) {
this.init();
this.init(callback);
}
const pattern = JSON.stringify(msg.pattern);
const responseCallback = (channel, message) => {
@@ -56,19 +56,24 @@ export class ClientRedis extends ClientProxy {
this.sub && this.sub.quit();
}
public init() {
public init(callback: (...args) => any) {
this.pub = this.createClient();
this.sub = this.createClient();
this.handleErrors(this.pub);
this.handleErrors(this.sub);
this.handleErrors(this.pub, callback);
this.handleErrors(this.sub, callback);
}
public createClient(): redis.RedisClient {
return redis.createClient({ url: this.url });
}
public handleErrors(stream) {
stream.on(ERROR_EVENT, (err) => this.logger.error(err));
public handleErrors(stream, callback: (...args) => any) {
stream.on(ERROR_EVENT, (err) => {
if (err.code === 'ECONNREFUSED') {
callback(err, null);
}
this.logger.error(err);
});
}
}

View File

@@ -24,12 +24,13 @@ export class ClientTCP extends ClientProxy {
this.host = host || DEFAULT_HOST;
}
public init(): Promise<{}> {
public init(callback: (...args) => any): Promise<{}> {
this.socket = this.createSocket();
return new Promise((resolve) => {
this.bindEvents(this.socket, callback);
this.socket.on(CONNECT_EVENT, () => {
this.isConnected = true;
this.bindEvents(this.socket);
resolve(this.socket);
});
this.socket.connect(this.port, this.host);
@@ -45,7 +46,7 @@ export class ClientTCP extends ClientProxy {
sendMessage(this.socket);
return Promise.resolve();
}
const socket = await this.init();
const socket = await this.init(callback);
sendMessage(socket);
}
@@ -64,15 +65,18 @@ export class ClientTCP extends ClientProxy {
}
public close() {
if (this.socket) {
this.socket.end();
this.socket && this.socket.end();
this.isConnected = false;
this.socket = null;
}
}
public bindEvents(socket) {
socket.on(ERROR_EVENT, (err) => this.logger.error(err));
public bindEvents(socket, callback: (...args) => any) {
socket.on(ERROR_EVENT, (err) => {
if (err.code === 'ECONNREFUSED') {
callback(err, null);
}
this.logger.error(err);
});
socket.on(CLOSE_EVENT, () => {
this.isConnected = false;
this.socket = null;

View File

@@ -8,6 +8,7 @@ import { RpcExceptionFilterMetadata } from '@nestjs/common/interfaces/exceptions
import 'rxjs/add/observable/throw';
export class RpcExceptionsHandler {
private static readonly logger = new Logger(RpcExceptionsHandler.name);
private filters: RpcExceptionFilterMetadata[] = [];
public handle(exception: Error | RpcException | any): Observable<any> {
@@ -18,10 +19,18 @@ export class RpcExceptionsHandler {
const status = 'error';
if (!(exception instanceof RpcException)) {
const message = messages.UNKNOWN_EXCEPTION_MESSAGE;
const isError = isObject(exception) && (exception as Error).message;
const loggerArgs = isError
? [(exception as Error).message, (exception as Error).stack]
: [exception];
const logger = RpcExceptionsHandler.logger;
logger.error.apply(logger, loggerArgs);
return Observable.throw({ status, message });
}
const res = exception.getError();
const message = isObject(res) ? res : ({ status, message: res });
const message = isObject(res) ? res : { status, message: res };
return Observable.throw(message);
}

View File

@@ -153,7 +153,7 @@ describe('ClientRedis', () => {
beforeEach(() => {
createClientSpy = sinon.spy(client, 'createClient');
handleErrorsSpy = sinon.spy(client, 'handleErrors');
client.init();
client.init(sinon.spy());
});
afterEach(() => {
createClientSpy.restore();

View File

@@ -16,7 +16,7 @@ export class IoAdapter implements WebSocketAdapter {
return this.createIOServer(port);
}
public createWithNamespace(port: number, namespace: string) {
public createWithNamespace(port: number, namespace: string, server?) {
return this.createIOServer(port).of(namespace);
}

View File

@@ -7,12 +7,17 @@ export class SocketsContainer {
return this.observableServers;
}
public getServer(namespace: string, port: number): ObservableSocketServer {
return this.observableServers.get(`${namespace}:${port}`);
public getServerByPort(port: number): ObservableSocketServer {
return this.observableServers.get(`${port}`);
}
public addServer(namespace: string, port: number, server: ObservableSocketServer) {
this.observableServers.set(`${namespace}:${port}`, server);
this.observableServers.set(
namespace
? `${namespace}:${port}`
: `${port}`,
server,
);
}
public clear() {

View File

@@ -10,24 +10,36 @@ export class SocketServerProvider {
private readonly applicationConfig: ApplicationConfig) {}
public scanForSocketServer(namespace: string, port: number): ObservableSocketServer {
const observableServer = this.socketsContainer.getServer(namespace, port);
return observableServer ? observableServer : this.createSocketServer(namespace, port);
const observableServer = this.socketsContainer.getServerByPort(port);
return observableServer
? this.createWithNamespace(namespace, port, observableServer)
: this.createSocketServer(namespace, port);
}
private createSocketServer(namespace: string, port: number) {
const server = this.getServerOfNamespace(namespace, port);
private createSocketServer(namespace: string, port: number): ObservableSocketServer {
const adapter = this.applicationConfig.getIoAdapter();
const server = adapter.create(port);
const observableSocket = ObservableSocket.create(server);
this.socketsContainer.addServer(namespace, port, observableSocket);
return observableSocket;
this.socketsContainer.addServer(null, port, observableSocket);
return this.createWithNamespace(namespace, port, observableSocket);
}
private getServerOfNamespace(namespace: string, port: number) {
private createWithNamespace(namespace: string, port: number, observableSocket: ObservableSocketServer): ObservableSocketServer {
const adapter = this.applicationConfig.getIoAdapter();
if (namespace && adapter.createWithNamespace) {
return adapter.createWithNamespace(port, this.validateNamespace(namespace));
if (!namespace || !adapter.createWithNamespace) {
return observableSocket;
}
return adapter.create(port);
const namespaceServer = this.getServerOfNamespace(namespace, port, observableSocket.server);
const observableNamespaceSocket = ObservableSocket.create(namespaceServer);
this.socketsContainer.addServer(namespace, port, observableNamespaceSocket);
return observableNamespaceSocket;
}
private getServerOfNamespace(namespace: string, port: number, server) {
const adapter = this.applicationConfig.getIoAdapter();
return adapter.createWithNamespace(port, this.validateNamespace(namespace), server);
}
private validateNamespace(namespace: string): string {

View File

@@ -21,7 +21,7 @@ describe('SocketsContainer', () => {
});
describe('getSocketServer', () => {
it(`should call "observableServers" get method with expected arguments`, () => {
instance.getServer(namespace, port);
instance.getServerByPort(port);
expect(getSpy.calledWith({ namespace, port }));
});
});

View File

@@ -28,15 +28,15 @@ describe('SocketServerProvider', () => {
});
it(`should returns stored server`, () => {
const server = { test: 'test' };
mockContainer.expects('getServer').returns(server);
mockContainer.expects('getServerByPort').returns(server);
const result = instance.scanForSocketServer(namespace, port);
const result = instance.scanForSocketServer(null, port);
expect(createSocketServerSpy.called).to.be.false;
expect(result).to.eq(server);
});
it(`should call "createSocketServer" when server is not stored already`, () => {
mockContainer.expects('getServer').returns(null);
mockContainer.expects('getServerByPort').returns(null);
instance.scanForSocketServer(namespace, port);
expect(createSocketServerSpy.called).to.be.true;