Update to final version

This commit is contained in:
kamil.mysliwiec
2017-05-01 14:04:25 +02:00
parent b0e8d5f125
commit e1fcf64d22
187 changed files with 2590 additions and 1122 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
// Place your settings in this file to overwrite default and user settings.
{
}

View File

@@ -1,4 +1,4 @@
import { Controller } from '../../../src/common/utils/controller.decorator';
import { Controller } from '../../../src/common/utils/decorators/controller.decorator';
import { MessagePattern } from '../../../src/microservices/utils/pattern.decorator';
@Controller()

View File

@@ -1,4 +1,4 @@
import { Module } from '../../../src/common/utils/module.decorator';
import { Module } from '../../../src/common/utils/decorators/module.decorator';
import { MathController } from './math.controller';
@Module({

View File

@@ -4,6 +4,6 @@ import { ClientController } from './client/client.controller';
@Module({
modules: [ UsersModule ],
controllers: [ ClientController ]
controllers: [ ClientController ],
})
export class ApplicationModule {}

View File

@@ -1,19 +1,20 @@
import { Controller } from '../../../src/common/utils/controller.decorator';
import { Controller } from '../../../src/common/utils/decorators/controller.decorator';
import { Client } from '../../../src/microservices/utils/client.decorator';
import { RequestMapping } from '../../../src/common/utils/request-mapping.decorator';
import { RequestMapping } from '../../../src/common/utils/decorators/request-mapping.decorator';
import { ClientProxy } from '../../../src/microservices/client/client-proxy';
import { Observable } from 'rxjs';
import { Transport } from '../../../src/common/enums/transport.enum';
import 'rxjs/add/operator/catch';
const MicroserviceClient = { transport: Transport.TCP, port: 5667 };
@Controller()
export class ClientController {
@Client({ transport: Transport.TCP, port: 5667 })
client: ClientProxy;
@Client(MicroserviceClient)
public client: ClientProxy;
@RequestMapping({ path: 'client' })
sendMessage(req, res) {
public sendMessage(req, res) {
const pattern = { command: 'add' };
const data = [ 1, 2, 3, 4, 5 ];

View File

@@ -1,8 +1,8 @@
import { Module } from '../../../src/common/utils/module.decorator';
import { Module } from '../../../src/common/utils/decorators/module.decorator';
import { ChatGateway } from '../users/chat.gateway';
@Module({
components: [ ChatGateway ],
exports: [ ChatGateway ]
exports: [ ChatGateway ],
})
export class SharedModule {}

View File

@@ -7,18 +7,17 @@ import { NestMiddleware } from '../../../src/core/middlewares/interfaces/nest-mi
export class AuthMiddleware implements NestMiddleware {
constructor(private usersService: UsersService) {}
resolve() {
public resolve() {
return (req, res, next) => {
const userName = req.headers['x-access-token'];
const username = req.headers['x-access-token'];
const users = this.usersService.getUsers();
const user = users.find((user) => user.name === userName);
const user = users.find(({ name }) => name === username);
if (!user) {
throw new HttpException('User not found.', 401);
}
req.user = user;
next();
}
};
}
}

View File

@@ -1,21 +1,21 @@
import { WebSocketGateway } from '../../../src/websockets/utils/socket-gateway.decorator';
import { SubscribeMessage } from '../../../src/websockets/utils/subscribe-message.decorator';
import { Subject } from 'rxjs/Subject';
import { WebSocketServer } from '../../../src/websockets/utils/gateway-server.decorator';
import { OnGatewayInit, OnGatewayConnection } from '../../../src/websockets/index';
import { ChatMiddleware } from './chat.middleware';
@WebSocketGateway({ port: 2000 })
export class ChatGateway {
private msg$ = new Subject<any>();
@WebSocketGateway({
port: 2000,
middlewares: [ ChatMiddleware ],
})
export class ChatGateway implements OnGatewayInit, OnGatewayConnection {
@WebSocketServer() private server;
@WebSocketServer() server;
public afterInit(server) {}
public handleConnection(client) {}
get msgStream() {
return this.msg$.asObservable();
@SubscribeMessage('event')
public onMessage(client, data) {
client.emit('event', data);
}
@SubscribeMessage({ value: 'message' })
onMessage(client, data) {
this.msg$.next({ client, data });
}
}

View File

@@ -0,0 +1,13 @@
import { GatewayMiddleware } from '../../../src/websockets/interfaces/gateway-middleware.interface';
import { UsersService } from './users.service';
import { Middleware } from '../../../src/index';
@Middleware()
export class ChatMiddleware implements GatewayMiddleware {
public resolve(): (socket, next) => void {
return (socket, next) => {
console.log('Authorization...');
next();
};
}
}

View File

@@ -1,16 +0,0 @@
import { Component } from '../../../src/common/utils/component.decorator';
import { ChatGateway } from './chat.gateway';
@Component()
export class ChatService {
constructor(private chatGateway: ChatGateway) {
const stream$ = this.chatGateway.msgStream;
stream$.subscribe(this.storeMessage.bind(this));
}
storeMessage(data) {
// store data
}
}

View File

@@ -0,0 +1,14 @@
import { ExceptionFilter } from '../../../src/common/interfaces/exception-filter.interface';
import { Catch } from '../../../src/common/utils/decorators/catch.decorator';
import { UsersService } from './users.service';
export class CustomException {}
@Catch(CustomException)
export class CustomExceptionFilter implements ExceptionFilter {
public catch(exception, response) {
response.status(500).json({
message: 'Custom exception message.',
});
}
}

View File

@@ -1,11 +0,0 @@
import { Component } from '../../../src/common/utils/component.decorator';
@Component()
export class NotificationService {
storeNotification(data) {
const notification = this.mapDataToNotification(data);
// store notification
}
private mapDataToNotification(msg) {}
}

View File

@@ -1,34 +1,34 @@
import { UsersService } from "./users.service";
import { RequestMethod, Controller, RequestMapping } from "./../../../src/";
import { ModuleRef } from '../../../src/core/injector/module-ref';
import { UsersModule } from './users.module';
import { Get, Post } from '../../../src/common/utils/request-mapping.decorator';
import * as express from 'express';
import { UsersService } from './users.service';
import { Controller, Response, Body, Param } from './../../../src/';
import { Get, Post } from '../../../src/common/utils/decorators/request-mapping.decorator';
import { HttpStatus } from '../../../src/common/index';
import { ExceptionFilters } from '../../../src/common/utils/decorators/exception-filters.decorator';
import { CustomExceptionFilter } from './exception.filter';
@Controller({ path: 'users' })
@Controller('users')
@ExceptionFilters(CustomExceptionFilter)
export class UsersController {
constructor(
private usersService: UsersService,
private moduleRef: ModuleRef) {}
private usersService: UsersService) {}
@Get()
async getAllUsers(req, res) {
public async getAllUsers(@Response() res: express.Response) {
const users = await this.usersService.getAllUsers();
res.status(200).json(users);
res.status(HttpStatus.OK).json(users);
}
@Get('/:id')
async getUser(req, res) {
const user = await this.usersService.getUser(req.params.id);
res.status(200).json(user);
public async getUser(@Response() res: express.Response, @Param('id') id) {
const user = await this.usersService.getUser(id);
res.status(HttpStatus.OK).json(user);
}
@Post()
async addUser(req, res) {
const msg = await this.usersService.getUser(req.body.user);
res.status(201).json(msg);
public async addUser(@Response() res: express.Response, @Body('user') user) {
const msg = await this.usersService.getUser(user);
res.status(HttpStatus.CREATED).json(msg);
}
}

View File

@@ -1,25 +1,16 @@
import { Module } from './../../../src/';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { MiddlewareBuilder } from '../../../src/core/middlewares/builder';
import { AuthMiddleware } from './auth.middleware';
import { NestModule, MiddlewaresConsumer } from '../../../src/common/index';
import { ChatGateway } from './chat.gateway';
@Module({
controllers: [ UsersController ],
components: [
UsersService
],
components: [ UsersService, ChatGateway ],
})
export class UsersModule {
getContext() {
return 'Test';
}
configure(builder: MiddlewareBuilder) {
builder.apply(AuthMiddleware)
.with('admin')
.forRoutes(UsersController);
export class UsersModule implements NestModule {
public configure(consumer: MiddlewaresConsumer) {
consumer.apply(AuthMiddleware).forRoutes(UsersController);
}
}

View File

@@ -1,34 +1,32 @@
import { Component } from "./../../../src/";
import { Component } from './../../../src/';
import { HttpException } from '../../../src/core/exceptions/http-exception';
@Component()
export class UsersService {
private users = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Alice Caeiro" },
{ id: 3, name: "Who Knows" },
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Alice Caeiro' },
{ id: 3, name: 'Who Knows' },
];
getUsers() {
public getUsers() {
return this.users;
}
getAllUsers() {
public getAllUsers() {
return Promise.resolve(this.users);
}
getUser(id: string) {
public getUser(id: string) {
const user = this.users.find((user) => user.id === +id);
if (!user) {
throw new HttpException("User not found", 404);
throw new HttpException('User not found', 404);
}
return Promise.resolve(user);
}
addUser(user) {
public addUser(user) {
this.users.push(user);
return Promise.resolve();
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "nest.js",
"version": "1.0.0-rc4",
"version": "1.0.0",
"description": "Modern, fast, powerful node.js web framework",
"main": "index.js",
"scripts": {
@@ -18,7 +18,13 @@
"src/**/*.ts"
],
"exclude": [
"node_modules/"
"node_modules/",
"src/*.ts",
"src/**/*.spec.ts",
"src/common/services/logger.service.ts",
"src/microservices/exceptions/",
"src/core/adapters/*.ts",
"src/websockets/adapters/*.ts"
],
"extension": [
".ts"
@@ -47,10 +53,12 @@
},
"devDependencies": {
"@types/chai": "^3.4.34",
"@types/express": "^4.0.35",
"@types/mocha": "^2.2.38",
"@types/node": "^7.0.5",
"@types/redis": "^0.12.36",
"@types/sinon": "^1.16.36",
"@types/socket.io": "^1.4.29",
"awesome-typescript-loader": "^3.0.0-beta.18",
"chai": "^3.5.0",
"concurrently": "^3.4.0",

View File

@@ -7,5 +7,9 @@ export const metadata = {
export const PATH_METADATA = 'path';
export const PARAMTYPES_METADATA = 'design:paramtypes';
export const SELF_PARAMS_METADATA = 'self:paramtypes';
export const SELF_DECLARED_DEPS_METADATA = 'self:paramtypes';
export const METHOD_METADATA = 'method';
export const ROUTE_ARGS_METADATA = '__routeArguments__';
export const EXCEPTION_FILTERS_METADATA = '__exceptionFilters__';
export const FILTER_CATCH_EXCEPTIONS = '__filterCatchExceptions__';

View File

@@ -0,0 +1,45 @@
export enum HttpStatus {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
AMBIGUOUS = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
UNPROCESSABLE_ENTITY = 422,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
}

View File

@@ -1,2 +1,3 @@
export * from './request-method.enum';
export * from './transport.enum';
export * from './http-status.enum';

View File

@@ -0,0 +1,4 @@
export enum NestEnvironment {
RUN,
TEST,
}

View File

@@ -1,4 +0,0 @@
export enum NestMode {
RUN,
TEST
}

View File

@@ -3,5 +3,5 @@ export enum RequestMethod {
POST,
PUT,
DELETE,
ALL
ALL,
}

View File

@@ -0,0 +1,10 @@
export enum RouteParamtypes {
REQUEST,
RESPONSE,
NEXT,
BODY,
QUERY,
PARAM,
HEADERS,
SESSION,
}

View File

@@ -1,4 +1,4 @@
export enum Transport {
TCP,
REDIS
REDIS,
}

View File

@@ -1,3 +1,10 @@
export * from './utils';
export * from './enums';
export { NestModule } from './interfaces';
export {
NestModule,
INestApplication,
INestMicroservice,
MiddlewareConfigProxy,
MiddlewaresConsumer,
OnModuleInit,
} from './interfaces';

View File

@@ -0,0 +1,7 @@
import { ExceptionFilter } from './exception-filter.interface';
import { Metatype } from './metatype.interface';
export interface ExceptionFilterMetadata {
func: ExceptionFilter['catch'];
exceptionMetatypes: Metatype<any>[];
}

View File

@@ -0,0 +1,3 @@
export interface ExceptionFilter {
catch(exception, response);
}

View File

@@ -6,3 +6,8 @@ export * from './injectable.interface';
export * from './controller-metadata.interface';
export * from './module-metadata.interface';
export * from './metatype.interface';
export * from './nest-application.interface';
export * from './nest-microservice.interface';
export * from './middlewares-consumer.interface';
export * from './on-init.interface';
export * from './middleware-config-proxy.interface';

View File

@@ -0,0 +1,7 @@
import { MiddlewaresConsumer } from './middlewares-consumer.interface';
import { RequestMappingMetadata } from './request-mapping-metadata.interface';
export interface MiddlewareConfigProxy {
with: (...data) => MiddlewareConfigProxy;
forRoutes: (...routes) => MiddlewaresConsumer;
}

View File

@@ -0,0 +1,6 @@
import { Metatype } from './metatype.interface';
import { MiddlewareConfigProxy } from './middleware-config-proxy.interface';
export interface MiddlewaresConsumer {
apply(metatypes: Metatype<any> | Array<Metatype<any>>): MiddlewareConfigProxy;
}

View File

@@ -2,8 +2,8 @@ import { NestModule } from './nest-module.interface';
import { Controller } from './controller.interface';
export interface ModuleMetadata {
modules?: NestModule[],
components?: any[],
controllers?: Controller[] | any[],
exports?: any[],
modules?: NestModule[];
components?: any[];
controllers?: Controller[] | any[];
exports?: any[];
}

View File

@@ -0,0 +1,3 @@
export interface INestApplication {
listen(port: number, callback?: () => void);
}

View File

@@ -0,0 +1,3 @@
export interface INestMicroservice {
listen(callback: () => void);
}

View File

@@ -1,5 +1,6 @@
import { MiddlewareBuilder } from '../../core/middlewares/builder';
import { MiddlewaresConsumer } from './middlewares-consumer.interface';
export interface NestModule {
configure?: (router: MiddlewareBuilder) => MiddlewareBuilder;
configure?: (consumer: MiddlewaresConsumer) => MiddlewaresConsumer | void;
}

View File

@@ -0,0 +1,3 @@
export interface OnModuleInit {
onModuleInit();
}

View File

@@ -1,6 +1,6 @@
import { RequestMethod } from '../enums/request-method.enum';
export interface RequestMappingMetadata {
path?: string,
method?: RequestMethod
path?: string;
method?: RequestMethod;
}

View File

@@ -1,33 +1,33 @@
import { NestMode } from '../enums/nest-mode.enum';
import { NestEnvironment } from '../enums/nest-environment.enum';
declare var process;
declare const process;
import * as clc from 'cli-color';
export class Logger {
private static mode = NestMode.RUN;
private static mode = NestEnvironment.RUN;
private readonly yellow = clc.xterm(3);
constructor(private context: string) {}
static setMode(mode: NestMode) {
public static setMode(mode: NestEnvironment) {
this.mode = mode;
}
log(message: string) {
public log(message: string) {
this.logMessage(message, clc.green);
}
error(message: string, trace = '') {
public error(message: string, trace = '') {
this.logMessage(message, clc.red);
this.printStackTrace(trace);
}
warn(message: string) {
public warn(message: string) {
this.logMessage(message, clc.yellow);
}
private logMessage(message: string, color: Function) {
if (Logger.mode === NestMode.TEST) { return; }
private logMessage(message: string, color: (msg: string) => string) {
if (Logger.mode === NestEnvironment.TEST) return;
process.stdout.write(color(`[Nest] ${process.pid} - `));
process.stdout.write(`${new Date(Date.now()).toLocaleString()} `);
@@ -37,7 +37,7 @@ export class Logger {
}
private printStackTrace(trace: string) {
if (Logger.mode === NestMode.TEST) { return; }
if (Logger.mode === NestEnvironment.TEST) return;
process.stdout.write(trace);
process.stdout.write(`\n`);

View File

@@ -5,12 +5,12 @@ import { BindResolveMiddlewareValues } from '../../utils/bind-resolve-values.uti
import { NestMiddleware } from '../../../core/middlewares/interfaces/nest-middleware.interface';
describe('BindResolveMiddlewareValues', () => {
let type,
arg1 = 3,
let type;
const arg1 = 3,
arg2 = 4;
class Test implements NestMiddleware {
resolve(a, b) {
public resolve(a, b) {
return () => [a, b];
}
}
@@ -24,6 +24,6 @@ describe('BindResolveMiddlewareValues', () => {
expect(hof()).to.deep.equal([arg1, arg2]);
});
it('should set name of metatype', () => {
expect(type.name).to.eq((<any>Test).name + JSON.stringify([ arg1, arg2 ]));
expect(type.name).to.eq((Test as any).name + JSON.stringify([ arg1, arg2 ]));
});
});

View File

@@ -0,0 +1,16 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { Catch } from '../../utils/decorators/catch.decorator';
import { FILTER_CATCH_EXCEPTIONS } from '../../constants';
describe('@Catch', () => {
const exceptions = [ 'exception', 'exception2' ];
@Catch(...exceptions) class Test {}
it('should enhance class with expected exceptions array', () => {
const metadata = Reflect.getMetadata(FILTER_CATCH_EXCEPTIONS, Test);
expect(metadata).to.be.eql(exceptions);
});
});

View File

@@ -1,6 +1,6 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { Component } from '../../utils/component.decorator';
import { Component } from '../../utils/decorators/component.decorator';
describe('@Component', () => {
@@ -11,7 +11,7 @@ describe('@Component', () => {
test: string) {}
}
it('should enhance transport with "design:paramtypes" metadata', () => {
it('should enhance component with "design:paramtypes" metadata', () => {
const constructorParams = Reflect.getMetadata('design:paramtypes', TestComponent);
expect(constructorParams[0]).to.be.eql(Number);

View File

@@ -1,6 +1,6 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { Controller } from '../../utils/controller.decorator';
import { Controller } from '../../utils/decorators/controller.decorator';
describe('@Controller', () => {
const props = {
@@ -11,7 +11,7 @@ describe('@Controller', () => {
@Controller() class EmptyDecorator {}
@Controller({}) class AnotherTest {}
it('should enhance transport with expected path metadata', () => {
it('should enhance controller with expected path metadata', () => {
const path = Reflect.getMetadata('path', Test);
expect(path).to.be.eql(props.path);
});

View File

@@ -1,16 +1,22 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { Dependencies } from '../../utils/dependencies.decorator';
import { Dependencies } from '../../utils/decorators/dependencies.decorator';
import { PARAMTYPES_METADATA } from '../../constants';
describe('@Dependencies', () => {
const deps = [ 'test', 'test2' ];
const dep = 'test', dep2 = 'test2';
const deps = [ dep, dep2 ];
@Dependencies(deps) class Test {}
@Dependencies(dep, dep2) class Test2 {}
it('should enhance class with expected dependencies array', () => {
const metadata = Reflect.getMetadata(PARAMTYPES_METADATA, Test);
expect(metadata).to.be.eql(deps);
});
it('should makes passed array flatten', () => {
const metadata = Reflect.getMetadata(PARAMTYPES_METADATA, Test2);
expect(metadata).to.be.eql([dep, dep2]);
});
});

View File

@@ -0,0 +1,16 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { EXCEPTION_FILTERS_METADATA } from '../../constants';
import { ExceptionFilters } from '../../utils/decorators/exception-filters.decorator';
describe('@ExceptionFilters', () => {
const filters = [ 'exception', 'exception2' ];
@ExceptionFilters(...filters) class Test {}
it('should enhance class with expected exception filters array', () => {
const metadata = Reflect.getMetadata(EXCEPTION_FILTERS_METADATA, Test);
expect(metadata).to.be.eql(filters);
});
});

View File

@@ -0,0 +1,26 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { SELF_DECLARED_DEPS_METADATA } from '../../constants';
import { Inject } from '../../index';
describe('@Inject', () => {
const opaqueToken = () => ({});
class Test {
constructor(
@Inject('test') param,
@Inject('test2') param2,
@Inject(opaqueToken) param3) {}
}
it('should enhance class with expected constructor params metadata', () => {
const metadata = Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, Test);
const expectedMetadata = [
{ index: 2, param: opaqueToken.name },
{ index: 1, param: 'test2' },
{ index: 0, param: 'test' },
];
expect(metadata).to.be.eql(expectedMetadata);
});
});

View File

@@ -3,7 +3,8 @@ import { expect } from 'chai';
import { MergeWithValues } from '../../utils/merge-with-values.util';
describe('MergeWithValues', () => {
let type, data = { test: [ 1, 2, 3 ] };
let type;
const data = { test: [ 1, 2, 3 ] };
class Test {}
beforeEach(() => {

View File

@@ -1,6 +1,6 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { Module } from '../../utils/module.decorator';
import { Module } from '../../utils/decorators/module.decorator';
import { InvalidModuleConfigException } from '../../../errors/exceptions/invalid-module-config.exception';
describe('@Module', () => {
@@ -14,7 +14,7 @@ describe('@Module', () => {
@Module(moduleProps)
class TestModule {}
it('should enhance transport with expected module metadata', () => {
it('should enhance class with expected module metadata', () => {
const modules = Reflect.getMetadata('modules', TestModule);
const components = Reflect.getMetadata('components', TestModule);
const exports = Reflect.getMetadata('exports', TestModule);
@@ -29,7 +29,7 @@ describe('@Module', () => {
it('should throw exception when module properties are invalid', () => {
const invalidProps = {
...moduleProps,
test: []
test: [],
};
expect(Module.bind(null, invalidProps)).to.throw(InvalidModuleConfigException);

View File

@@ -1,18 +1,18 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { RequestMapping } from '../../utils/request-mapping.decorator';
import { RequestMapping } from '../../utils/decorators/request-mapping.decorator';
import { RequestMethod } from '../../enums/request-method.enum';
describe('@RequestMapping', () => {
const requestProps = {
path: 'test',
method: RequestMethod.ALL
method: RequestMethod.ALL,
};
it('should enhance transport with expected request metadata', () => {
it('should enhance class with expected request metadata', () => {
class Test {
@RequestMapping(requestProps)
static test() {}
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
@@ -25,7 +25,7 @@ describe('@RequestMapping', () => {
it('should set request method on GET by default', () => {
class Test {
@RequestMapping({ path: '' })
static test() {}
public static test() {}
}
const method = Reflect.getMetadata('method', Test.test);
@@ -35,7 +35,7 @@ describe('@RequestMapping', () => {
it('should set path on "/" by default', () => {
class Test {
@RequestMapping({})
static test() {}
public static test() {}
}
const method = Reflect.getMetadata('path', Test.test);

View File

@@ -0,0 +1,159 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { RequestMethod } from '../../enums/request-method.enum';
import { Get, Post, Delete, All, Put } from '../../index';
describe('@Get', () => {
const requestPath = 'test';
const requestProps = {
path: requestPath,
method: RequestMethod.GET,
};
it('should enhance class with expected request metadata', () => {
class Test {
@Get(requestPath)
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
const method = Reflect.getMetadata('method', Test.test);
expect(method).to.be.eql(requestProps.method);
expect(path).to.be.eql(requestPath);
});
it('should set path on "/" by default', () => {
class Test {
@Get()
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
expect(path).to.be.eql('/');
});
});
describe('@Post', () => {
const requestPath = 'test';
const requestProps = {
path: requestPath,
method: RequestMethod.POST,
};
it('should enhance class with expected request metadata', () => {
class Test {
@Post(requestPath)
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
const method = Reflect.getMetadata('method', Test.test);
expect(method).to.be.eql(requestProps.method);
expect(path).to.be.eql(requestPath);
});
it('should set path on "/" by default', () => {
class Test {
@Post()
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
expect(path).to.be.eql('/');
});
});
describe('@Delete', () => {
const requestPath = 'test';
const requestProps = {
path: requestPath,
method: RequestMethod.DELETE,
};
it('should enhance class with expected request metadata', () => {
class Test {
@Delete(requestPath)
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
const method = Reflect.getMetadata('method', Test.test);
expect(method).to.be.eql(requestProps.method);
expect(path).to.be.eql(requestPath);
});
it('should set path on "/" by default', () => {
class Test {
@Delete()
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
expect(path).to.be.eql('/');
});
});
describe('@All', () => {
const requestPath = 'test';
const requestProps = {
path: requestPath,
method: RequestMethod.ALL,
};
it('should enhance class with expected request metadata', () => {
class Test {
@All(requestPath)
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
const method = Reflect.getMetadata('method', Test.test);
expect(method).to.be.eql(requestProps.method);
expect(path).to.be.eql(requestPath);
});
it('should set path on "/" by default', () => {
class Test {
@All()
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
expect(path).to.be.eql('/');
});
});
describe('@Put', () => {
const requestPath = 'test';
const requestProps = {
path: requestPath,
method: RequestMethod.PUT,
};
it('should enhance class with expected request metadata', () => {
class Test {
@Put(requestPath)
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
const method = Reflect.getMetadata('method', Test.test);
expect(method).to.be.eql(requestProps.method);
expect(path).to.be.eql(requestPath);
});
it('should set path on "/" by default', () => {
class Test {
@Put()
public static test() {}
}
const path = Reflect.getMetadata('path', Test.test);
expect(path).to.be.eql('/');
});
});

View File

@@ -2,14 +2,14 @@ import { Constructor } from './merge-with-values.util';
import { NestMiddleware } from '../../core/middlewares/interfaces/nest-middleware.interface';
export const BindResolveMiddlewareValues = <T extends Constructor<NestMiddleware>>(data: Array<any>) => {
return (metatype: T): any => {
const type = class extends metatype {
resolve() {
return (Metatype: T): any => {
const type = class extends Metatype {
public resolve() {
return super.resolve(...data);
}
};
const token = metatype.name + JSON.stringify(data);
const token = Metatype.name + JSON.stringify(data);
Object.defineProperty(type, 'name', { value: token });
return type;
}
};
};

View File

@@ -1,15 +0,0 @@
import 'reflect-metadata';
import { ControllerMetadata } from '../interfaces/controller-metadata.interface';
import { isUndefined } from './shared.utils';
import { PATH_METADATA } from '../constants';
const defaultMetadata = { [PATH_METADATA]: '/' };
export const Controller = (metadata: ControllerMetadata = defaultMetadata): ClassDecorator => {
if (isUndefined(metadata[PATH_METADATA])) {
metadata[PATH_METADATA] = '/';
}
return (target: Object) => {
Reflect.defineMetadata(PATH_METADATA, metadata[PATH_METADATA], target);
}
};

View File

@@ -0,0 +1,8 @@
import 'reflect-metadata';
import { FILTER_CATCH_EXCEPTIONS } from '../../constants';
export const Catch = (...exceptions): ClassDecorator => {
return (target: object) => {
Reflect.defineMetadata(FILTER_CATCH_EXCEPTIONS, exceptions, target);
};
};

View File

@@ -1,3 +1,3 @@
export const Component = (): ClassDecorator => {
return (target: Object) => {}
return (target: object) => {};
};

View File

@@ -0,0 +1,13 @@
import 'reflect-metadata';
import { ControllerMetadata } from '../../interfaces/controller-metadata.interface';
import { isUndefined, isObject } from '../shared.utils';
import { PATH_METADATA } from '../../constants';
export const Controller = (metadata?: ControllerMetadata | string): ClassDecorator => {
let path = isObject(metadata) ? metadata[PATH_METADATA] : metadata;
path = isUndefined(path) ? '/' : path;
return (target: object) => {
Reflect.defineMetadata(PATH_METADATA, path, target);
};
};

View File

@@ -0,0 +1,14 @@
import 'reflect-metadata';
import { PARAMTYPES_METADATA } from '../../constants';
const flatten = (arr) => {
const flat = [].concat(...arr);
return flat.some(Array.isArray) ? flatten(flat) : flat;
};
export const Dependencies = (...metadata): ClassDecorator => {
const flattenDeps = flatten(metadata);
return (target: object) => {
Reflect.defineMetadata(PARAMTYPES_METADATA, flattenDeps, target);
};
};

View File

@@ -0,0 +1,8 @@
import 'reflect-metadata';
import { EXCEPTION_FILTERS_METADATA } from '../../constants';
export const ExceptionFilters = (...filters): ClassDecorator => {
return (target: object) => {
Reflect.defineMetadata(EXCEPTION_FILTERS_METADATA, filters, target);
};
};

View File

@@ -0,0 +1,13 @@
import 'reflect-metadata';
import { SELF_DECLARED_DEPS_METADATA } from '../../constants';
import { isFunction } from '../shared.utils';
export const Inject = (param): ParameterDecorator => {
return (target, key, index) => {
const args = Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
const type = isFunction(param) ? param.name : param;
args.push({ index, param: type });
Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, args, target);
};
};

View File

@@ -0,0 +1,34 @@
import 'reflect-metadata';
import { ModuleMetadata } from '../../interfaces/module-metadata.interface';
import { InvalidModuleConfigException } from '../../../errors/exceptions/invalid-module-config.exception';
import { metadata } from '../../constants';
const metadataKeys = [
metadata.MODULES,
metadata.EXPORTS,
metadata.COMPONENTS,
metadata.CONTROLLERS,
];
const validateKeys = (keys: string[]) => {
const isKeyValid = (key) => metadataKeys.findIndex(k => k === key) < 0;
const validateKey = (key) => {
if (isKeyValid(key)) {
throw new InvalidModuleConfigException(key);
}
};
keys.forEach(validateKey);
};
export const Module = (props: ModuleMetadata): ClassDecorator => {
const propsKeys = Object.keys(props);
validateKeys(propsKeys);
return (target: object) => {
for (const property in props) {
if (props.hasOwnProperty(property)) {
Reflect.defineMetadata(property, props[property], target);
}
}
};
};

View File

@@ -1,11 +1,11 @@
import 'reflect-metadata';
import { RequestMappingMetadata } from '../interfaces/request-mapping-metadata.interface';
import { RequestMethod } from '../enums/request-method.enum';
import { PATH_METADATA, METHOD_METADATA } from '../constants';
import { RequestMappingMetadata } from '../../interfaces/request-mapping-metadata.interface';
import { RequestMethod } from '../../enums/request-method.enum';
import { PATH_METADATA, METHOD_METADATA } from '../../constants';
const defaultMetadata = {
[PATH_METADATA]: '/',
[METHOD_METADATA]: RequestMethod.GET
[METHOD_METADATA]: RequestMethod.GET,
};
export const RequestMapping = (metadata: RequestMappingMetadata = defaultMetadata): MethodDecorator => {
@@ -16,13 +16,13 @@ export const RequestMapping = (metadata: RequestMappingMetadata = defaultMetadat
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value);
Reflect.defineMetadata(METHOD_METADATA, requestMethod, descriptor.value);
return descriptor;
}
};
};
const createMappingDecorator = (method: RequestMethod) => (path?: string): MethodDecorator => {
return RequestMapping({
[PATH_METADATA]: path,
[METHOD_METADATA]: method
[METHOD_METADATA]: method,
});
};

View File

@@ -0,0 +1,41 @@
import 'reflect-metadata';
import { ROUTE_ARGS_METADATA } from '../../constants';
import { RouteParamtypes } from '../../enums/route-paramtypes.enum';
export type ParamData = object | string | number;
export interface RouteParamsMetadata {
[prop: number]: {
index: number;
data?: ParamData;
};
}
const assignMetadata = (
args: RouteParamsMetadata,
paramtype: RouteParamtypes,
index: number,
data?: ParamData) => ({
...args,
[paramtype]: { index, data },
});
const createRouteParamDecorator = (paramtype: RouteParamtypes) => {
return (data?: ParamData): ParameterDecorator => (target, key, index) => {
const args = Reflect.getMetadata(ROUTE_ARGS_METADATA, target, key) || {};
Reflect.defineMetadata(
ROUTE_ARGS_METADATA,
assignMetadata(args, paramtype, index, data),
target,
key,
);
};
};
export const Request: () => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.REQUEST);
export const Response: () => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.RESPONSE);
export const Next: () => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.NEXT);
export const Query: (property?: string) => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.QUERY);
export const Body: (property?: string) => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.BODY);
export const Param: (property?: string) => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.PARAM);
export const Session: () => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.SESSION);
export const Headers: (property?: string) => ParameterDecorator = createRouteParamDecorator(RouteParamtypes.HEADERS);

View File

@@ -1,8 +0,0 @@
import 'reflect-metadata';
import { PARAMTYPES_METADATA } from '../constants';
export const Dependencies = (metadata: any[]): ClassDecorator => {
return (target: Object) => {
Reflect.defineMetadata(PARAMTYPES_METADATA, metadata, target);
}
};

View File

@@ -1,8 +1,10 @@
export * from './request-mapping.decorator';
export * from './controller.decorator';
export * from './component.decorator';
export * from './module.decorator';
export * from './dependencies.decorator';
export * from './inject.decorator';
export { Component as Middleware } from './component.decorator';
export * from './merge-with-values.util';
export * from './decorators/request-mapping.decorator';
export * from './decorators/controller.decorator';
export * from './decorators/component.decorator';
export * from './decorators/module.decorator';
export * from './decorators/dependencies.decorator';
export * from './decorators/inject.decorator';
export { Component as Middleware } from './decorators/component.decorator';
export * from './decorators/route-params.decorator';
export * from './decorators/catch.decorator';
export * from './decorators/exception-filters.decorator';

View File

@@ -1,13 +0,0 @@
import 'reflect-metadata';
import { SELF_PARAMS_METADATA } from '../constants';
import { isFunction } from './shared.utils';
export const Inject = (param): ParameterDecorator => {
return (target, key, index) => {
const selfArgs = Reflect.getMetadata(SELF_PARAMS_METADATA, target) || [];
const type = isFunction(param) ? param.name : param;
selfArgs.push({ index, param: type });
Reflect.defineMetadata(SELF_PARAMS_METADATA, selfArgs, target);
}
};

View File

@@ -1,19 +1,19 @@
import 'reflect-metadata';
export interface Constructor<T> {
new(...args: any[]): T
new(...args: any[]): T;
}
export const MergeWithValues = <T extends Constructor<{}>>(data: { [param: string]: any }) => {
return (metatype: T): any => {
const type = class extends metatype {
return (Metatype: T): any => {
const Type = class extends Metatype {
constructor(...args) {
super(...args);
}
};
const token = metatype.name + JSON.stringify(data);
Object.defineProperty(type, 'name', { value: token });
Object.assign(type.prototype, data);
return type;
}
const token = Metatype.name + JSON.stringify(data);
Object.defineProperty(Type, 'name', { value: token });
Object.assign(Type.prototype, data);
return Type;
};
};

View File

@@ -1,22 +0,0 @@
import 'reflect-metadata';
import { ModuleMetadata } from '../interfaces/module-metadata.interface';
import { InvalidModuleConfigException } from '../../errors/exceptions/invalid-module-config.exception';
import { metadata } from '../constants';
export const Module = (props: ModuleMetadata): ClassDecorator => {
const propsKeys = Object.keys(props);
const acceptableParams = [ metadata.MODULES, metadata.EXPORTS, metadata.COMPONENTS, metadata.CONTROLLERS ];
propsKeys.forEach((prop) => {
if (acceptableParams.findIndex((param) => param === prop) < 0) {
throw new InvalidModuleConfigException(prop);
}
});
return (target: Object) => {
for (let property in props) {
if (props.hasOwnProperty(property)) {
Reflect.defineMetadata(property, props[property], target);
}
}
}
};

View File

@@ -1,5 +1,8 @@
export const isUndefined = (obj): boolean => typeof obj === 'undefined';
export const isUndefined = (obj): obj is undefined => typeof obj === 'undefined';
export const isFunction = (fn): boolean => typeof fn === 'function';
export const isObject = (fn): fn is object => typeof fn === 'object';
export const isString = (fn): fn is string => typeof fn === 'string';
export const isConstructor = (fn): boolean => fn === 'constructor';
export const validatePath = (path): string => (path.charAt(0) !== '/') ? '/' + path : path;
export const isNil = (obj): boolean => isUndefined(obj) || obj === null;
export const isEmpty = (array): boolean => !(array && array.length > 0);

View File

@@ -1,12 +1,11 @@
import * as express from 'express';
export class ExpressAdapter {
static create(): any {
public static create(): any {
return express();
}
static createRouter(): any {
public static createRouter(): any {
return express.Router();
}
}

View File

@@ -1,5 +1,5 @@
export const messages = {
APPLICATION_START: `Starting Nest application...`,
APPLICATION_READY: `Nest application is ready!`,
UNKOWN_EXCEPTION_MESSAGE: 'Unkown exception'
UNKOWN_EXCEPTION_MESSAGE: 'Unkown exception',
};

View File

@@ -1,11 +1,17 @@
import { HttpException } from './http-exception';
import { messages } from '../constants';
import { Logger } from '../../common/services/logger.service';
import { ExceptionFilterMetadata } from '../../common/interfaces/exception-filter-metadata.interface';
import { isEmpty, isObject } from '../../common/utils/shared.utils';
import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception';
export class ExceptionsHandler {
private readonly logger = new Logger(ExceptionsHandler.name);
private filters: ExceptionFilterMetadata[] = [];
public next(exception: Error | HttpException | any, response) {
if (this.invokeCustomFilters(exception, response)) return;
next(exception: Error | HttpException, response) {
if (!(exception instanceof HttpException)) {
response.status(500).json({ message: messages.UNKOWN_EXCEPTION_MESSAGE });
@@ -13,9 +19,28 @@ export class ExceptionsHandler {
return;
}
response.status(exception.getStatus()).json({
message: exception.getMessage()
});
const res = exception.getResponse();
const message = isObject(res) ? res : ({ message: res });
response.status(exception.getStatus()).json(message);
}
public setCustomFilters(filters: ExceptionFilterMetadata[]) {
if (!Array.isArray(filters)) {
throw new InvalidExceptionFilterException();
}
this.filters = filters;
}
public invokeCustomFilters(exception, response): boolean {
if (isEmpty(this.filters)) return false;
const filter = this.filters.find(({ exceptionMetatypes, func }) => {
const hasMetatype = !!exceptionMetatypes.find(
ExceptionMetatype => exception instanceof ExceptionMetatype,
);
return hasMetatype;
});
filter && filter.func(exception, response);
return !!filter;
}
}

View File

@@ -1,14 +1,13 @@
export class HttpException {
constructor(
private readonly message: string,
private readonly response: string | object,
private readonly status: number) {}
getMessage() {
return this.message;
public getResponse(): string | object {
return this.response;
}
getStatus() {
public getStatus(): number {
return this.status;
}
}

View File

@@ -1,10 +1,5 @@
import { RequestMethod } from '../../common/enums/request-method.enum';
export const getModuleInitMessage =
(module: string) => `${module} dependencies initialized`;
export const getRouteMappedMessage =
(path: string, method) => `Mapped {${path}, ${RequestMethod[method]}} route`;
export const getControllerMappingMessage =
(name: string) => `${name}:`;
export const ModuleInitMessage = (module: string) => `${module} dependencies initialized`;
export const RouteMappedMessage = (path: string, method) => `Mapped {${path}, ${RequestMethod[method]}} route`;
export const ControllerMappingMessage = (name: string) => `${name}:`;

View File

@@ -1,7 +1,7 @@
import { RequestMethod } from '../../common/enums/request-method.enum';
export class RouterMethodFactory {
get(target, requestMethod: RequestMethod) {
public get(target, requestMethod: RequestMethod) {
switch (requestMethod) {
case RequestMethod.POST: return target.post;
case RequestMethod.ALL: return target.all;

View File

@@ -1,3 +1,4 @@
export { HttpException } from './exceptions/http-exception';
export { NestMiddleware, MiddlewareConfiguration } from './middlewares/interfaces';
export { MiddlewareBuilder } from './middlewares/builder';
export { ModuleRef } from './injector/module-ref';

View File

@@ -7,18 +7,18 @@ import { Metatype } from '../../common/interfaces/metatype.interface';
export class NestContainer {
private readonly modules = new Map<string, Module>();
addModule(metatype: NestModuleMetatype) {
if (this.modules.has(metatype.name)) { return; }
public addModule(metatype: NestModuleMetatype) {
if (this.modules.has(metatype.name)) return;
this.modules.set(metatype.name, new Module(metatype));
}
getModules(): Map<string, Module> {
public getModules(): Map<string, Module> {
return this.modules;
}
addRelatedModule(relatedModule: NestModuleMetatype, target: NestModuleMetatype) {
if (!this.modules.has(target.name)) { return; }
public addRelatedModule(relatedModule: NestModuleMetatype, target: NestModuleMetatype) {
if (!this.modules.has(target.name)) return;
const module = this.modules.get(target.name);
const related = this.modules.get(relatedModule.name);
@@ -26,34 +26,31 @@ export class NestContainer {
module.addRelatedModule(related);
}
addComponent(component: Metatype<Injectable>, metatype: NestModuleMetatype) {
public addComponent(component: Metatype<Injectable>, metatype: NestModuleMetatype) {
if (!this.modules.has(metatype.name)) {
throw new UnkownModuleException();
}
const module = this.modules.get(metatype.name);
module.addComponent(component);
}
addExportedComponent(exportedComponent: Metatype<Injectable>, metatype: NestModuleMetatype) {
public addExportedComponent(exportedComponent: Metatype<Injectable>, metatype: NestModuleMetatype) {
if (!this.modules.has(metatype.name)) {
throw new UnkownModuleException();
}
const module = this.modules.get(metatype.name);
module.addExportedComponent(exportedComponent);
}
addController(controller: Metatype<Controller>, metatype: NestModuleMetatype) {
public addController(controller: Metatype<Controller>, metatype: NestModuleMetatype) {
if (!this.modules.has(metatype.name)) {
throw new UnkownModuleException();
}
const module = this.modules.get(metatype.name);
module.addRoute(controller);
}
clear() {
public clear() {
this.modules.clear();
}

View File

@@ -8,37 +8,36 @@ import { Controller } from '../../common/interfaces/controller.interface';
import { Injectable } from '../../common/interfaces/injectable.interface';
import { MiddlewareWrapper } from '../middlewares/container';
import { isUndefined, isNil, isFunction } from '../../common/utils/shared.utils';
import { PARAMTYPES_METADATA, SELF_PARAMS_METADATA } from '../../common/constants';
import { PARAMTYPES_METADATA, SELF_DECLARED_DEPS_METADATA } from '../../common/constants';
export class Injector {
loadInstanceOfMiddleware(
public loadInstanceOfMiddleware(
wrapper: MiddlewareWrapper,
collection: Map<string, MiddlewareWrapper>,
module: Module) {
const { metatype } = wrapper;
const currentFetchedMiddleware = collection.get(metatype.name);
if (currentFetchedMiddleware.instance !== null) return;
const currentMetatype = collection.get(metatype.name);
if (currentMetatype.instance !== null) return;
this.resolveConstructorParams(<any>wrapper, module, null, (argsInstances) => {
this.resolveConstructorParams(wrapper as any, module, null, argsInstances => {
collection.set(metatype.name, {
instance: new metatype(...argsInstances),
metatype
})
metatype,
});
});
}
loadInstanceOfRoute(wrapper: InstanceWrapper<Controller>, module: Module) {
public loadInstanceOfRoute(wrapper: InstanceWrapper<Controller>, module: Module) {
const routes = module.routes;
this.loadInstance<Controller>(wrapper, routes, module);
}
loadPrototypeOfInstance<T>({ metatype, name }: InstanceWrapper<T>, collection: Map<string, InstanceWrapper<T>>) {
if (!collection) { return; }
public loadPrototypeOfInstance<T>({ metatype, name }: InstanceWrapper<T>, collection: Map<string, InstanceWrapper<T>>) {
if (!collection) return;
const target = collection.get(name);
if (target.isResolved || !isNil(target.inject)) { return; }
if (target.isResolved || !isNil(target.inject)) return;
collection.set(name, {
...collection.get(name),
@@ -46,40 +45,44 @@ export class Injector {
});
}
loadInstanceOfComponent(wrapper: InstanceWrapper<Injectable>, module: Module) {
public loadInstanceOfComponent(wrapper: InstanceWrapper<Injectable>, module: Module) {
const components = module.components;
this.loadInstance<Injectable>(wrapper, components, module);
}
loadInstance<T>(wrapper: InstanceWrapper<T>, collection, module: Module) {
public loadInstance<T>(wrapper: InstanceWrapper<T>, collection, module: Module) {
const { metatype, name, inject } = wrapper;
const currentFetchedInstance = collection.get(name);
if (isUndefined(currentFetchedInstance)) {
const currentMetatype = collection.get(name);
if (isUndefined(currentMetatype)) {
throw new RuntimeException('');
}
if (currentFetchedInstance.isResolved) return;
this.resolveConstructorParams<T>(wrapper, module, inject, (argsInstances) => {
if (currentMetatype.isResolved) return;
this.resolveConstructorParams<T>(wrapper, module, inject, argsInstances => {
if (isNil(inject)) {
currentFetchedInstance.instance = Object.assign(
currentFetchedInstance.instance,
currentMetatype.instance = Object.assign(
currentMetatype.instance,
new metatype(...argsInstances),
);
} else {
currentMetatype.instance = currentMetatype.metatype(...argsInstances);
}
else {
currentFetchedInstance.instance = currentFetchedInstance.metatype(...argsInstances);
}
currentFetchedInstance.isResolved = true;
currentMetatype.isResolved = true;
});
}
resolveConstructorParams<T>(wrapper: InstanceWrapper<T>, module: Module, inject: any[], callback: Function) {
public resolveConstructorParams<T>(
wrapper: InstanceWrapper<T>,
module: Module,
inject: any[],
callback: (args) => void) {
const args = isNil(inject) ? this.reflectConstructorParams(wrapper.metatype) : inject;
const instances = args.map(param => this.resolveSingleParam<T>(wrapper, param, module));
callback(instances);
}
reflectConstructorParams<T>(type: Metatype<T>): any[] {
public reflectConstructorParams<T>(type: Metatype<T>): any[] {
const paramtypes = Reflect.getMetadata(PARAMTYPES_METADATA, type) || [];
const selfParams = this.reflectSelfParams<T>(type);
@@ -87,37 +90,32 @@ export class Injector {
return paramtypes;
}
reflectSelfParams<T>(type: Metatype<T>): any[] {
return Reflect.getMetadata(SELF_PARAMS_METADATA, type) || [];
public reflectSelfParams<T>(type: Metatype<T>): any[] {
return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
}
resolveSingleParam<T>(wrapper: InstanceWrapper<T>, param: Metatype<any> | string | symbol, module: Module) {
public resolveSingleParam<T>(wrapper: InstanceWrapper<T>, param: Metatype<any> | string | symbol, module: Module) {
if (isUndefined(param)) {
throw new RuntimeException();
}
return this.resolveComponentInstance<T>(
module,
isFunction(param) ? (<Metatype<any>>param).name : param,
wrapper
isFunction(param) ? (param as Metatype<any>).name : param,
wrapper,
);
}
resolveComponentInstance<T>(module: Module, name: any, wrapper: InstanceWrapper<T>) {
public resolveComponentInstance<T>(module: Module, name: any, wrapper: InstanceWrapper<T>) {
const components = module.components;
const instanceWrapper = this.scanForComponent<T>(components, name, module, wrapper);
const instanceWrapper = this.scanForComponent(components, name, module, wrapper);
if (isNil(instanceWrapper.instance)) {
if (!instanceWrapper.isResolved) {
this.loadInstanceOfComponent(components.get(name), module);
}
return instanceWrapper.instance;
}
scanForComponent<T>(
components: Map<string, any>,
name: any,
module: Module,
{ metatype }) {
public scanForComponent(components: Map<string, any>, name: any, module: Module, { metatype }) {
if (components.has(name)) {
return components.get(name);
}
@@ -129,13 +127,13 @@ export class Injector {
return instanceWrapper;
}
scanForComponentInRelatedModules(module: Module, name: any) {
public scanForComponentInRelatedModules(module: Module, name: any) {
const relatedModules = module.relatedModules || [];
let component = null;
(<Array<any>>relatedModules).forEach((relatedModule) => {
(relatedModules as any[]).forEach((relatedModule) => {
const { components, exports } = relatedModule;
if (!exports.has(name) || !components.has(name)) { return; }
if (!exports.has(name) || !components.has(name)) return;
component = components.get(name);
if (!component.isResolved) {

View File

@@ -4,7 +4,9 @@ import { Injectable } from '../../common/interfaces/injectable.interface';
import { Controller } from '../../common/interfaces/controller.interface';
import { Module } from './module';
import { Logger } from '../../common/services/logger.service';
import { getModuleInitMessage } from '../helpers/messages';
import { ModuleInitMessage } from '../helpers/messages';
import { isUndefined, isNil } from '../../common/utils/shared.utils';
import { OnModuleInit } from '../../common/interfaces/index';
export class InstanceLoader {
private injector = new Injector();
@@ -12,7 +14,7 @@ export class InstanceLoader {
constructor(private container: NestContainer) {}
createInstancesOfDependencies() {
public createInstancesOfDependencies() {
const modules = this.container.getModules();
this.createPrototypes(modules);
@@ -30,8 +32,10 @@ export class InstanceLoader {
modules.forEach((module, name) => {
this.createInstancesOfComponents(module);
this.createInstancesOfRoutes(module);
this.logger.log(getModuleInitMessage(name));
})
this.callModuleInitHook(module);
this.logger.log(ModuleInitMessage(name));
});
}
private createPrototypesOfComponents(module: Module) {
@@ -58,4 +62,15 @@ export class InstanceLoader {
});
}
private callModuleInitHook(module: Module) {
const components = [...module.routes, ...module.components];
components.map(([key, {instance}]) => instance)
.filter((instance) => !isNil(instance))
.filter(this.hasOnModuleInitHook)
.forEach((instance) => (instance as OnModuleInit).onModuleInit());
}
private hasOnModuleInitHook(instance: Controller | Injectable): instance is OnModuleInit {
return !isUndefined((instance as OnModuleInit).onModuleInit);
}
}

View File

@@ -1 +1,5 @@
export class ModuleRef {}
import { OpaqueToken } from './module';
export abstract class ModuleRef {
public abstract get<T>(type: OpaqueToken): T;
}

View File

@@ -4,24 +4,27 @@ import { UnkownExportException } from '../../errors/exceptions/unkown-export.exc
import { NestModuleMetatype } from '../../common/interfaces/module-metatype.interface';
import { Metatype } from '../../common/interfaces/metatype.interface';
import { ModuleRef } from './module-ref';
import { isFunction, isNil } from '../../common/utils/shared.utils';
import { isFunction, isNil, isUndefined } from '../../common/utils/shared.utils';
import { RuntimeException } from '../../errors/exceptions/runtime.exception';
export type CustomComponent = { provide: any };
export interface CustomComponent {
provide: any;
}
export type OpaqueToken = string | symbol | object | Metatype<any>;
export type CustomClass = CustomComponent & { useClass: Metatype<any> };
export type CustomFactory = CustomComponent & { useFactory: Function, inject?: Metatype<any>[] };
export type CustomFactory = CustomComponent & { useFactory: (...args) => any, inject?: Metatype<any>[] };
export type CustomValue = CustomComponent & { useValue: any };
export type ComponentMetatype = Metatype<Injectable> | CustomFactory | CustomValue | CustomClass;
export class Module {
private _instance: NestModule;
private _relatedModules = new Set<Module>();
private _components = new Map<any, InstanceWrapper<Injectable>>();
private _routes = new Map<string, InstanceWrapper<Controller>>();
private _exports = new Set<string>();
constructor(private _metatype: NestModuleMetatype) {
this._instance = new _metatype();
this.addModuleRef();
this.addModuleAsComponent();
}
get relatedModules(): Set<Module> {
@@ -41,47 +44,72 @@ export class Module {
}
get instance(): NestModule {
return this._instance;
if (!this._components.has(this._metatype.name)) {
throw new RuntimeException();
}
set instance(value: NestModule) {
this._instance = value;
const module = this._components.get(this._metatype.name);
return module.instance;
}
get metatype(): NestModuleMetatype {
return this._metatype;
}
addModuleRef() {
public addModuleRef() {
const moduleRef = this.getModuleRefMetatype(this._components);
this._components.set(ModuleRef.name, {
name: ModuleRef.name,
metatype: ModuleRef,
metatype: ModuleRef as any,
isResolved: true,
instance: new moduleRef,
instance: new moduleRef(),
});
}
addComponent(component: ComponentMetatype) {
if (!isNil((<CustomComponent>component).provide)) {
public addModuleAsComponent() {
this._components.set(this._metatype.name, {
name: this._metatype.name,
metatype: this._metatype,
isResolved: false,
instance: null,
});
}
public addComponent(component: ComponentMetatype) {
if (this.isCustomComponent(component)) {
this.addCustomComponent(component);
return;
}
this._components.set((<Metatype<Injectable>>component).name, {
name: (<Metatype<Injectable>>component).name,
metatype: <Metatype<Injectable>>component,
this._components.set((component as Metatype<Injectable>).name, {
name: (component as Metatype<Injectable>).name,
metatype: component as Metatype<Injectable>,
instance: null,
isResolved: false,
});
}
addCustomComponent(component: ComponentMetatype) {
if ((<CustomClass>component).useClass) this.addCustomClass(<CustomClass>component);
else if((<CustomValue>component).useValue) this.addCustomValue(<CustomValue>component);
else if((<CustomFactory>component).useFactory) this.addCustomFactory(<CustomFactory>component);
public isCustomComponent(component: ComponentMetatype): component is CustomClass | CustomFactory | CustomValue {
return !isNil((component as CustomComponent).provide);
}
addCustomClass(component: CustomClass) {
public addCustomComponent(component: ComponentMetatype) {
if (this.isCustomClass(component)) this.addCustomClass(component);
else if (this.isCustomValue(component)) this.addCustomValue(component);
else if (this.isCustomFactory(component)) this.addCustomFactory(component);
}
public isCustomClass(component): component is CustomClass {
return !isUndefined((component as CustomClass).useClass);
}
public isCustomValue(component): component is CustomValue {
return !isUndefined((component as CustomValue).useValue);
}
public isCustomFactory(component): component is CustomFactory {
return !isUndefined((component as CustomFactory).useFactory);
}
public addCustomClass(component: CustomClass) {
const { provide: metatype, useClass } = component;
this._components.set(metatype.name, {
name: metatype.name,
@@ -91,12 +119,12 @@ export class Module {
});
}
addCustomValue(component: CustomValue) {
public addCustomValue(component: CustomValue) {
const { provide, useValue: value } = component;
const name = isFunction(provide) ? provide.name : provide;
this._components.set(name, {
name: name,
name,
metatype: null,
instance: value,
isResolved: true,
@@ -104,11 +132,11 @@ export class Module {
});
}
addCustomFactory(component: CustomFactory){
public addCustomFactory(component: CustomFactory){
const { provide: name, useFactory: factory, inject } = component;
this._components.set(name, {
name: name,
metatype: <any>factory,
name,
metatype: factory as any,
instance: null,
isResolved: false,
inject: inject || [],
@@ -116,35 +144,35 @@ export class Module {
});
}
addExportedComponent(exportedComponent: Metatype<Injectable>) {
public addExportedComponent(exportedComponent: Metatype<Injectable>) {
if (!this._components.get(exportedComponent.name)) {
throw new UnkownExportException(exportedComponent.name);
}
this._exports.add(exportedComponent.name);
}
addRoute(route: Metatype<Controller>) {
public addRoute(route: Metatype<Controller>) {
this._routes.set(route.name, {
name: (<Metatype<Controller>>route).name,
name: route.name,
metatype: route,
instance: null,
isResolved: false,
});
}
addRelatedModule(relatedModule) {
public addRelatedModule(relatedModule) {
this._relatedModules.add(relatedModule);
}
private getModuleRefMetatype(components) {
return class extends this._metatype {
return class {
private readonly components = components;
get<T>(type: string | symbol | object | Metatype<any>): T {
const name = isFunction(type) ? (<Metatype<any>>type).name : type;
public get<T>(type: OpaqueToken): T {
const name = isFunction(type) ? (type as Metatype<any>).name : type;
const exists = this.components.has(name);
return exists ? <T>this.components.get(name).instance : null;
return exists ? this.components.get(name).instance as T : null;
}
}
}

View File

@@ -0,0 +1,17 @@
import { Injectable } from '../common/interfaces/injectable.interface';
import { isConstructor, isFunction, isNil } from '../common/utils/shared.utils';
export class MetadataScanner {
public scanFromPrototype<T extends Injectable, R>(instance: T, prototype, callback: (name: string) => R): R[] {
return Object.getOwnPropertyNames(prototype)
.filter((method) => {
const descriptor = Object.getOwnPropertyDescriptor(prototype, method);
if (descriptor.set || descriptor.get) {
return false;
}
return !isConstructor(method) && isFunction(prototype[method]);
})
.map(callback)
.filter((metadata) => !isNil(metadata));
}
}

View File

@@ -3,36 +3,25 @@ import { InvalidMiddlewareConfigurationException } from '../../errors/exceptions
import { isUndefined, isNil } from '../../common/utils/shared.utils';
import { BindResolveMiddlewareValues } from '../../common/utils/bind-resolve-values.util';
import { Logger } from '../../common/services/logger.service';
import { Metatype } from '../../common/interfaces/metatype.interface';
import { Metatype, MiddlewaresConsumer } from '../../common/interfaces';
import { MiddlewareConfigProxy } from '../../index';
import { RoutesMapper } from './routes-mapper';
export class MiddlewareBuilder {
private middlewaresCollection = new Set<MiddlewareConfiguration>();
private logger = new Logger(MiddlewareBuilder.name);
export class MiddlewareBuilder implements MiddlewaresConsumer {
private readonly middlewaresCollection = new Set<MiddlewareConfiguration>();
private readonly logger = new Logger(MiddlewareBuilder.name);
apply(metatypes: Metatype<any> | Array<Metatype<any>>): MiddlewareConfigProxy {
let resolveParams = null;
const configProxy = {
with: (...data) => {
resolveParams = data;
return configProxy as MiddlewareConfigProxy;
},
forRoutes: (...routes) => {
const configuration = {
middlewares: this.bindValuesToResolve(metatypes, resolveParams),
forRoutes: routes
};
this.middlewaresCollection.add(<MiddlewareConfiguration>configuration);
return this;
}
};
return configProxy;
constructor(private readonly routesMapper: RoutesMapper) {}
public apply(metatypes: Metatype<any> | Metatype<any>[]): MiddlewareConfigProxy {
return new MiddlewareBuilder.ConfigProxy(this, metatypes);
}
/**
* @deprecated
* Since version RC.6 this method is deprecated. Use apply() instead.
*/
use(configuration: MiddlewareConfiguration) {
public use(configuration: MiddlewareConfiguration) {
this.logger.warn('DEPRECATED! Since version RC.6 `use()` method is deprecated. Use `apply()` instead.');
const { middlewares, forRoutes } = configuration;
@@ -44,20 +33,48 @@ export class MiddlewareBuilder {
return this;
}
build() {
public build() {
return [ ...this.middlewaresCollection ];
}
private bindValuesToResolve(middlewares: Metatype<any> | Array<Metatype<any>>, resolveParams: Array<any>) {
private bindValuesToResolve(middlewares: Metatype<any> | Metatype<any>[], resolveParams: any[]) {
if (isNil(resolveParams)) {
return middlewares;
}
const bindArgs = BindResolveMiddlewareValues(resolveParams);
return [].concat(middlewares).map(bindArgs);
}
private static ConfigProxy = class implements MiddlewareConfigProxy {
private contextArgs = null;
constructor(
private readonly builder: MiddlewareBuilder,
private readonly includedRoutes: Metatype<any> | Metatype<any>[]) {}
public with(...args): this {
this.contextArgs = args;
return this;
}
export interface MiddlewareConfigProxy {
with: (...data) => MiddlewareConfigProxy;
forRoutes: (...routes) => MiddlewareBuilder;
public forRoutes(...routes): MiddlewaresConsumer {
const { middlewaresCollection, bindValuesToResolve, routesMapper } = this.builder;
const forRoutes = this.mapRoutesToFlatList(
routes.map((route) => routesMapper.mapRouteToRouteProps(route),
));
const configuration = {
middlewares: bindValuesToResolve(
this.includedRoutes, this.contextArgs,
),
forRoutes,
};
middlewaresCollection.add(configuration);
return this.builder;
}
private mapRoutesToFlatList(forRoutes) {
return forRoutes.reduce((a, b) => a.concat(b));
}
};
}

View File

@@ -1,46 +1,36 @@
import { MiddlewareConfiguration } from './interfaces/middleware-configuration.interface';
import { NestMiddleware } from './interfaces/nest-middleware.interface';
import { RoutesMapper } from './routes-mapper';
import { Metatype } from '../../common/interfaces/metatype.interface';
export class MiddlewaresContainer {
private readonly middlewares = new Map<string, Map<string, MiddlewareWrapper>>();
private readonly configs = new Map<string, Set<MiddlewareConfiguration>>();
constructor(private routesMapper: RoutesMapper) {}
getMiddlewares(module: string): Map<string, MiddlewareWrapper> {
public getMiddlewares(module: string): Map<string, MiddlewareWrapper> {
return this.middlewares.get(module) || new Map();
}
getConfigs(): Map<string, Set<MiddlewareConfiguration>> {
public getConfigs(): Map<string, Set<MiddlewareConfiguration>> {
return this.configs;
}
addConfig(configList: MiddlewareConfiguration[], module: string) {
public addConfig(configList: MiddlewareConfiguration[], module: string) {
const middlewares = this.getCurrentMiddlewares(module);
const currentConfig = this.getCurrentConfig(module);
(configList || []).map((config) => {
const configurations = configList || [];
configurations.map((config) => {
[].concat(config.middlewares).map((metatype) => {
const token = metatype.name;
middlewares.set(token, {
instance: null,
metatype
metatype,
});
});
config.forRoutes = this.mapRoutesToFlatList(config.forRoutes);
currentConfig.add(config);
});
}
private mapRoutesToFlatList(forRoutes) {
return forRoutes.map((route) => (
this.routesMapper.mapRouteToRouteProps(route)
)).reduce((a, b) => a.concat(b));
}
private getCurrentMiddlewares(module: string) {
if (!this.middlewares.has(module)) {
this.middlewares.set(module, new Map<string, MiddlewareWrapper>());
@@ -57,6 +47,6 @@ export class MiddlewaresContainer {
}
export interface MiddlewareWrapper {
instance: NestMiddleware,
metatype: Metatype<NestMiddleware>
instance: NestMiddleware;
metatype: Metatype<NestMiddleware>;
}

View File

@@ -1,4 +0,0 @@
import { NestMiddleware } from './nest-middleware.interface';
import { Metatype } from '../../../common/interfaces/metatype.interface';
export interface MiddlewareMetatype extends Metatype<NestMiddleware> {}

View File

@@ -12,30 +12,32 @@ import { RoutesMapper } from './routes-mapper';
import { RouterProxy } from '../router/router-proxy';
import { ExceptionsHandler } from '../exceptions/exceptions-handler';
import { Module } from '../injector/module';
import { isUndefined } from 'util';
import { RouterMethodFactory } from '../helpers/router-method-factory';
import { NestMiddleware } from './interfaces/nest-middleware.interface';
import { Metatype } from '../../common/interfaces/metatype.interface';
import { RuntimeException } from '../../errors/exceptions/runtime.exception';
import { isUndefined } from '../../common/utils/shared.utils';
export class MiddlewaresModule {
private static container = new MiddlewaresContainer(new RoutesMapper());
private static routesMapper = new RoutesMapper();
private static container = new MiddlewaresContainer();
private static resolver: MiddlewaresResolver;
private static routerProxy = new RouterProxy(new ExceptionsHandler());
private static exceptionHandler = new ExceptionsHandler();
private static routerProxy = new RouterProxy();
private static routerMethodFactory = new RouterMethodFactory();
static getContainer(): MiddlewaresContainer {
public static getContainer(): MiddlewaresContainer {
return this.container;
}
static setup(container: NestContainer) {
public static setup(container: NestContainer) {
this.resolver = new MiddlewaresResolver(this.container);
const modules = container.getModules();
this.resolveMiddlewares(modules);
}
static resolveMiddlewares(modules: Map<string, Module>) {
public static resolveMiddlewares(modules: Map<string, Module>) {
modules.forEach((module, name) => {
const instance = module.instance;
@@ -44,10 +46,10 @@ export class MiddlewaresModule {
});
}
static loadConfiguration(instance: NestModule, module: string) {
if (!instance.configure) { return; }
public static loadConfiguration(instance: NestModule, module: string) {
if (!instance.configure) return;
const middlewaresBuilder = new MiddlewareBuilder();
const middlewaresBuilder = new MiddlewareBuilder(this.routesMapper);
instance.configure(middlewaresBuilder);
if (!(middlewaresBuilder instanceof MiddlewareBuilder))
return;
@@ -56,7 +58,7 @@ export class MiddlewaresModule {
this.container.addConfig(config, module);
}
static setupMiddlewares(app: Application) {
public static setupMiddlewares(app: Application) {
const configs = this.container.getConfigs();
configs.forEach((moduleConfigs, module: string) => {
@@ -69,7 +71,7 @@ export class MiddlewaresModule {
});
}
static setupRouteMiddleware(
public static setupRouteMiddleware(
route: ControllerMetadata & { method: RequestMethod },
config: MiddlewareConfiguration,
module: string,
@@ -77,15 +79,16 @@ export class MiddlewaresModule {
const { path, method } = route;
[].concat(config.middlewares).map((middlewareMetatype) => {
const middlewares = [].concat(config.middlewares);
middlewares.map((metatype: Metatype<NestMiddleware>) => {
const collection = this.container.getMiddlewares(module);
const middleware: MiddlewareWrapper = collection.get(middlewareMetatype.name);
const middleware = collection.get(metatype.name);
if (isUndefined(middleware)) {
throw new RuntimeException();
}
const instance = middleware.instance;
this.setupHandler(instance, middlewareMetatype, app, method, path);
const { instance } = (middleware as MiddlewareWrapper);
this.setupHandler(instance, metatype, app, method, path);
});
}
@@ -100,7 +103,7 @@ export class MiddlewaresModule {
throw new InvalidMiddlewareException(metatype.name);
}
const router = this.routerMethodFactory.get(app, method).bind(app);
const proxy = this.routerProxy.createProxy(instance.resolve());
const proxy = this.routerProxy.createProxy(instance.resolve(), this.exceptionHandler);
router(path, proxy);
}

View File

@@ -7,12 +7,9 @@ export class MiddlewaresResolver {
constructor(private middlewaresContainer: MiddlewaresContainer) {}
resolveInstances(module: Module, moduleName: string) {
public resolveInstances(module: Module, moduleName: string) {
const middlewares = this.middlewaresContainer.getMiddlewares(moduleName);
middlewares.forEach((wrapper) => {
this.resolveMiddlewareInstance(wrapper, middlewares, module);
});
middlewares.forEach((wrapper) => this.resolveMiddlewareInstance(wrapper, middlewares, module));
}
private resolveMiddlewareInstance(
@@ -23,7 +20,7 @@ export class MiddlewaresResolver {
this.instanceLoader.loadInstanceOfMiddleware(
wrapper,
middlewares,
module
module,
);
}

View File

@@ -1,27 +1,23 @@
import 'reflect-metadata';
import { RouterBuilder } from '../router/router-builder';
import { ExpressRouterExplorer } from '../router/router-explorer';
import { UnkownRequestMappingException } from '../../errors/exceptions/unkown-request-mapping.exception';
import { RequestMethod } from '../../common/enums/request-method.enum';
import { isUndefined, validatePath } from '../../common/utils/shared.utils';
import { PATH_METADATA } from '../../common/constants';
import { MetadataScanner } from '../metadata-scanner';
export class RoutesMapper {
private readonly routerBuilder = new RouterBuilder();
private readonly routerExplorer = new ExpressRouterExplorer(new MetadataScanner());
mapRouteToRouteProps(routeMetatype) {
public mapRouteToRouteProps(routeMetatype) {
const routePath: string = Reflect.getMetadata(PATH_METADATA, routeMetatype);
if (isUndefined(routePath)) {
return [this.mapObjectToRouteProps(routeMetatype)];
}
const paths = this.routerBuilder.scanForPathsFromPrototype(
Object.create(routeMetatype),
routeMetatype.prototype
);
return paths.map((singlePath) => ({
path: this.validateRoutePath(routePath) + this.validateRoutePath(singlePath.path),
method: singlePath.requestMethod
const paths = this.routerExplorer.scanForPaths(Object.create(routeMetatype), routeMetatype.prototype);
return paths.map((route) => ({
path: this.validateRoutePath(routePath) + this.validateRoutePath(route.path),
method: route.requestMethod,
}));
}
@@ -30,15 +26,13 @@ export class RoutesMapper {
if (isUndefined(path)) {
throw new UnkownRequestMappingException();
}
return {
path: this.validateRoutePath(path),
method: (isUndefined(method)) ? RequestMethod.ALL : method
method: isUndefined(method) ? RequestMethod.ALL : method,
};
}
private validateRoutePath(path: string): string {
return validatePath(path);
}
}

View File

@@ -0,0 +1,6 @@
import { Controller } from '../../../common/interfaces/controller.interface';
import { ExceptionsHandler } from '../../exceptions/exceptions-handler';
export interface ExceptionsFilter {
create(instance: Controller, moduleName: string): ExceptionsHandler;
}

View File

@@ -0,0 +1,6 @@
import { Controller } from '../../../common/interfaces/index';
import { Metatype } from '../../../common/interfaces/metatype.interface';
export interface RouterExplorer {
explore(instance: Controller, metatype: Metatype<Controller>, moduleName: string);
}

View File

@@ -0,0 +1,5 @@
import { Application } from 'express';
export interface Resolver {
resolve(express: Application);
}

View File

@@ -0,0 +1,5 @@
import { RouteParamtypes } from '../../../common/enums/route-paramtypes.enum';
export interface IRouteParamsFactory {
exchangeKeyForValue(key: RouteParamtypes, data, { req, res, next });
}

View File

@@ -0,0 +1,18 @@
import { RouteParamtypes } from '../../common/enums/route-paramtypes.enum';
import { IRouteParamsFactory } from './interfaces/route-params-factory.interface';
export class RouteParamsFactory implements IRouteParamsFactory {
public exchangeKeyForValue(key: RouteParamtypes, data, { req, res, next }) {
switch (key) {
case RouteParamtypes.NEXT: return next;
case RouteParamtypes.REQUEST: return req;
case RouteParamtypes.RESPONSE: return res;
case RouteParamtypes.BODY: return data ? req.body[data] : req.body;
case RouteParamtypes.PARAM: return data ? req.params[data] : req.params;
case RouteParamtypes.QUERY: return data ? req.query[data] : req.query;
case RouteParamtypes.HEADERS: return data ? req.headers[data] : req.headers;
case RouteParamtypes.SESSION: return req.session;
default: return null;
}
}
}

View File

@@ -1,100 +0,0 @@
import 'reflect-metadata';
import { Controller } from '../../common/interfaces/controller.interface';
import { RequestMethod } from '../../common/enums/request-method.enum';
import { RouterProxy, RouterProxyCallback } from './router-proxy';
import { UnkownRequestMappingException } from '../../errors/exceptions/unkown-request-mapping.exception';
import { ExpressAdapter } from '../adapters/express-adapter';
import { Metatype } from '../../common/interfaces/metatype.interface';
import { isUndefined, isConstructor, validatePath, isFunction } from '../../common/utils/shared.utils';
import { RouterMethodFactory } from '../helpers/router-method-factory';
import { PATH_METADATA, METHOD_METADATA } from '../../common/constants';
import { Logger } from '../../common/services/logger.service';
import { getRouteMappedMessage } from '../helpers/messages';
export class RouterBuilder {
private readonly routerMethodFactory = new RouterMethodFactory();
private readonly logger = new Logger(RouterBuilder.name);
constructor(
private routerProxy?: RouterProxy,
private expressAdapter?: ExpressAdapter) {}
public build(instance: Controller, metatype: Metatype<Controller>) {
const router = (<any>this.expressAdapter).createRouter();
const path = this.fetchRouterPath(metatype);
const routerPaths = this.scanForPaths(instance);
this.applyPathsToRouterProxy(router, routerPaths);
return { path, router };
}
scanForPaths(instance: Controller): RoutePathProperties[] {
const instancePrototype = Object.getPrototypeOf(instance);
return this.scanForPathsFromPrototype(instance, instancePrototype);
}
scanForPathsFromPrototype(instance: Controller, instancePrototype) {
return Object.getOwnPropertyNames(instancePrototype)
.filter((method) => {
const descriptor = Object.getOwnPropertyDescriptor(instancePrototype, method);
if (descriptor.set || descriptor.get) {
return false;
}
return !isConstructor(method) && isFunction(instancePrototype[method]);
})
.map((methodName) => this.exploreMethodMetadata(instance, instancePrototype, methodName))
.filter((path) => path !== null);
}
exploreMethodMetadata(instance: Controller, instancePrototype, methodName: string): RoutePathProperties {
const callbackMethod = instancePrototype[methodName];
const routePath = Reflect.getMetadata(PATH_METADATA, callbackMethod);
if (isUndefined(routePath)) {
return null;
}
const requestMethod: RequestMethod = Reflect.getMetadata(METHOD_METADATA, callbackMethod);
return {
targetCallback: (<Function>callbackMethod).bind(instance),
path: this.validateRoutePath(routePath),
requestMethod,
};
}
applyPathsToRouterProxy(router, routePaths: RoutePathProperties[]) {
(routePaths || []).map((pathProperties) => {
this.bindMethodToRouterProxy(router, pathProperties);
});
}
private bindMethodToRouterProxy(router, pathProperties: RoutePathProperties) {
const { path, requestMethod, targetCallback } = pathProperties;
const routerMethod = this.routerMethodFactory.get(router, requestMethod).bind(router);
const proxy = this.routerProxy.createProxy(targetCallback);
routerMethod(path, proxy);
this.logger.log(getRouteMappedMessage(path, requestMethod));
}
private fetchRouterPath(metatype: Metatype<Controller>) {
const path = Reflect.getMetadata(PATH_METADATA, metatype);
return this.validateRoutePath(path);
}
private validateRoutePath(path: string): string {
if (isUndefined(path)) {
throw new UnkownRequestMappingException();
}
return validatePath(path);
}
}
export interface RoutePathProperties {
path: string,
requestMethod: RequestMethod,
targetCallback: RouterProxyCallback,
}

View File

@@ -0,0 +1,58 @@
import 'reflect-metadata';
import { Controller } from '../../common/interfaces/controller.interface';
import { ExceptionsHandler } from '../exceptions/exceptions-handler';
import { EXCEPTION_FILTERS_METADATA, FILTER_CATCH_EXCEPTIONS } from '../../common/constants';
import { isEmpty, isFunction } from '../../common/utils/shared.utils';
import { Metatype } from '../../common/interfaces/index';
import { ExceptionFilterMetadata } from '../../common/interfaces/exception-filter-metadata.interface';
import { NestContainer } from '../injector/container';
import { UnkownModuleException } from '../../errors/exceptions/unkown-module.exception';
import { ExceptionFilter } from '../../common/interfaces/exception-filter.interface';
export class RouterExceptionFilters {
constructor(private container: NestContainer) {}
public create(instance: Controller, moduleName: string): ExceptionsHandler {
const exceptionHandler = new ExceptionsHandler();
const filters = this.reflectExceptionFilters(instance);
if (isEmpty(filters)) {
return exceptionHandler;
}
const filtersHooks = this.resolveFiltersMetatypes(filters, moduleName);
exceptionHandler.setCustomFilters(filtersHooks);
return exceptionHandler;
}
public reflectExceptionFilters(instance: Controller): Metatype<any>[] {
const prototype = Object.getPrototypeOf(instance);
return Reflect.getMetadata(EXCEPTION_FILTERS_METADATA, prototype.constructor) || [];
}
public resolveFiltersMetatypes(filters: Metatype<any>[], moduleName: string): ExceptionFilterMetadata[] {
return filters.filter(metatype => isFunction(metatype))
.map(metatype => ({
instance: this.findExceptionsFilterInstance(metatype, moduleName),
metatype,
}))
.filter(({ instance }) => instance.catch && isFunction(instance.catch))
.map(({ instance, metatype }) => ({
func: instance.catch.bind(instance),
exceptionMetatypes: this.reflectCatchExceptions(metatype),
}));
}
public findExceptionsFilterInstance(metatype: Metatype<any>, moduleName: string): ExceptionFilter {
const modules = this.container.getModules();
if (!modules.has(moduleName)) {
throw new UnkownModuleException();
}
const { components } = modules.get(moduleName);
const { instance } = components.get(metatype.name);
return instance as ExceptionFilter;
}
public reflectCatchExceptions(metatype: Metatype<Controller>): Metatype<any>[] {
return Reflect.getMetadata(FILTER_CATCH_EXCEPTIONS, metatype) || [];
}
}

View File

@@ -0,0 +1,51 @@
import 'reflect-metadata';
import { ROUTE_ARGS_METADATA } from '../../common/constants';
import { isUndefined } from '../../common/utils/shared.utils';
import { RouteParamtypes } from '../../common/enums/route-paramtypes.enum';
import { Controller } from '../../common/interfaces';
import { RouteParamsMetadata } from '../../index';
import { IRouteParamsFactory } from './interfaces/route-params-factory.interface';
export interface IndexValuePair {
index: number;
value: any;
}
export class RouterExecutionContext {
constructor(private paramsFactory: IRouteParamsFactory) {}
public create(instance: Controller, callback: (...args) => any) {
const metadata = this.reflectCallbackMetadata(instance, callback);
if (isUndefined(metadata)) {
return callback.bind(instance);
}
const keys = Object.keys(metadata).map(Number);
const argsLength = this.getArgumentsLength(keys, metadata);
const args = this.createNullArray(argsLength);
return (req, res, next) => {
const indexValuePairs = this.exchangeKeysForValues(keys, metadata, { req, res, next });
indexValuePairs.forEach(pair => args[pair.index] = pair.value);
return callback.apply(instance, args);
};
}
public reflectCallbackMetadata(instance: Controller, callback: (...args) => any): RouteParamsMetadata {
return Reflect.getMetadata(ROUTE_ARGS_METADATA, instance, callback.name);
}
public getArgumentsLength(keys: RouteParamtypes[], metadata: RouteParamsMetadata): number {
return Math.max(...keys.map(key => metadata[key].index)) + 1;
}
public createNullArray(length: number): any[] {
return Array.apply(null, { length }).fill(null);
}
public exchangeKeysForValues(keys: RouteParamtypes[], metadata: RouteParamsMetadata, { req, res, next }): IndexValuePair[] {
return keys.map(key => ({
index: metadata[key].index,
value: this.paramsFactory.exchangeKeyForValue(key, metadata[key].data, { req, res, next }),
}));
}
}

View File

@@ -0,0 +1,103 @@
import 'reflect-metadata';
import { Controller } from '../../common/interfaces/controller.interface';
import { RequestMethod } from '../../common/enums/request-method.enum';
import { RouterProxy, RouterProxyCallback } from './router-proxy';
import { UnkownRequestMappingException } from '../../errors/exceptions/unkown-request-mapping.exception';
import { ExpressAdapter } from '../adapters/express-adapter';
import { Metatype } from '../../common/interfaces/metatype.interface';
import { isUndefined, validatePath } from '../../common/utils/shared.utils';
import { RouterMethodFactory } from '../helpers/router-method-factory';
import { PATH_METADATA, METHOD_METADATA } from '../../common/constants';
import { Logger } from '../../common/services/logger.service';
import { RouteMappedMessage } from '../helpers/messages';
import { RouterExecutionContext } from './router-execution-context';
import { ExceptionsFilter } from './interfaces/exceptions-filter.interface';
import { RouteParamsFactory } from './route-params-factory';
import { RouterExplorer } from './interfaces/explorer.inteface';
import { MetadataScanner } from '../metadata-scanner';
export class ExpressRouterExplorer implements RouterExplorer {
private readonly executionContextCreator = new RouterExecutionContext(new RouteParamsFactory());
private readonly routerMethodFactory = new RouterMethodFactory();
private readonly logger = new Logger('RouterExplorer');
constructor(
private metadataScanner?: MetadataScanner,
private routerProxy?: RouterProxy,
private expressAdapter?: ExpressAdapter,
private exceptionsFilter?: ExceptionsFilter) {}
public explore(instance: Controller, metatype: Metatype<Controller>, moduleName: string) {
const router = (this.expressAdapter as any).createRouter();
const path = this.fetchRouterPath(metatype);
const routerPaths = this.scanForPaths(instance);
this.applyPathsToRouterProxy(router, routerPaths, instance, moduleName);
return { path, router };
}
public scanForPaths(instance: Controller, prototype?): RoutePathProperties[] {
const instancePrototype = isUndefined(prototype) ? Object.getPrototypeOf(instance) : prototype;
return this.metadataScanner.scanFromPrototype<Controller, RoutePathProperties>(
instance,
instancePrototype,
(method) => this.exploreMethodMetadata(instance, instancePrototype, method),
);
}
public exploreMethodMetadata(instance: Controller, instancePrototype, methodName: string): RoutePathProperties {
const targetCallback = instancePrototype[methodName];
const routePath = Reflect.getMetadata(PATH_METADATA, targetCallback);
if (isUndefined(routePath)) {
return null;
}
const requestMethod: RequestMethod = Reflect.getMetadata(METHOD_METADATA, targetCallback);
return {
targetCallback,
requestMethod,
path: this.validateRoutePath(routePath),
};
}
public applyPathsToRouterProxy(router, routePaths: RoutePathProperties[], instance: Controller, moduleName: string) {
(routePaths || []).map((pathProperties) => {
const { path, requestMethod } = pathProperties;
this.applyCallbackToRouter(router, pathProperties, instance, moduleName);
this.logger.log(RouteMappedMessage(path, requestMethod));
});
}
private applyCallbackToRouter(router, pathProperties: RoutePathProperties, instance: Controller, moduleName: string) {
const { path, requestMethod, targetCallback } = pathProperties;
const routerMethod = this.routerMethodFactory.get(router, requestMethod).bind(router);
const proxy = this.createCallbackProxy(instance, targetCallback, moduleName);
routerMethod(path, proxy);
}
private createCallbackProxy(instance: Controller, callback: RoutePathProperties['targetCallback'], moduleName: string) {
const executionContext = this.executionContextCreator.create(instance, callback);
const exceptionFilter = this.exceptionsFilter.create(instance, moduleName);
return this.routerProxy.createProxy(executionContext, exceptionFilter);
}
private fetchRouterPath(metatype: Metatype<Controller>) {
const path = Reflect.getMetadata(PATH_METADATA, metatype);
return this.validateRoutePath(path);
}
private validateRoutePath(path: string): string {
if (isUndefined(path)) {
throw new UnkownRequestMappingException();
}
return validatePath(path);
}
}
export interface RoutePathProperties {
path: string;
requestMethod: RequestMethod;
targetCallback: RouterProxyCallback;
}

View File

@@ -1,27 +1,23 @@
import { ExceptionsHandler } from '../exceptions/exceptions-handler';
export type RouterProxyCallback = (req?, res?, next?) => void;
export class RouterProxy {
constructor(private exceptionsHandler: ExceptionsHandler) {}
createProxy(targetCallback: RouterProxyCallback) {
public createProxy(
targetCallback: RouterProxyCallback,
exceptionsHandler: ExceptionsHandler,
) {
return (req, res, next) => {
try {
Promise
.resolve(targetCallback(req, res, next))
Promise.resolve(targetCallback(req, res, next))
.catch((e) => {
this.exceptionsHandler.next(e, res);
exceptionsHandler.next(e, res);
});
}
catch (e) {
this.exceptionsHandler.next(e, res);
}
exceptionsHandler.next(e, res);
}
};
}
}
export interface RouterProxyCallback {
(req?, res?, next?): void;
}

View File

@@ -1,34 +1,45 @@
import { Application } from 'express';
import { NestContainer, InstanceWrapper } from '../injector/container';
import { RouterBuilder } from './router-builder';
import { RouterProxy } from './router-proxy';
import { ExceptionsHandler } from '../exceptions/exceptions-handler';
import { Controller } from '../../common/interfaces/controller.interface';
import { Logger } from '../../common/services/logger.service';
import { getControllerMappingMessage } from '../helpers/messages';
import { ControllerMappingMessage } from '../helpers/messages';
import { Resolver } from './interfaces/resolver.interface';
import { RouterExceptionFilters } from './router-exception-filters';
import { MetadataScanner } from '../metadata-scanner';
import { RouterExplorer } from './interfaces/explorer.inteface';
import { ExpressRouterExplorer } from './router-explorer';
export class RoutesResolver {
export class RoutesResolver implements Resolver {
private readonly logger = new Logger(RoutesResolver.name);
private readonly routerProxy = new RouterProxy(new ExceptionsHandler());
private routerBuilder: RouterBuilder;
private readonly routerProxy = new RouterProxy();
private readonly routerExceptionsFilter: RouterExceptionFilters;
private readonly routerBuilder: RouterExplorer;
constructor(private container: NestContainer, expressAdapter) {
this.routerBuilder = new RouterBuilder(this.routerProxy, expressAdapter);
this.routerExceptionsFilter = new RouterExceptionFilters(container);
this.routerBuilder = new ExpressRouterExplorer(
new MetadataScanner(),
this.routerProxy,
expressAdapter,
this.routerExceptionsFilter,
);
}
resolve(express: Application) {
public resolve(express: Application) {
const modules = this.container.getModules();
modules.forEach(({ routes }) => this.setupRouters(routes, express));
modules.forEach(({ routes }, moduleName) => this.setupRouters(routes, moduleName, express));
}
setupRouters(
public setupRouters(
routes: Map<string, InstanceWrapper<Controller>>,
moduleName: string,
express: Application) {
routes.forEach(({ instance, metatype }) => {
this.logger.log(getControllerMappingMessage(metatype.name));
this.logger.log(ControllerMappingMessage(metatype.name));
const { path, router } = this.routerBuilder.build(instance, metatype);
const { path, router } = this.routerBuilder.explore(instance, metatype, moduleName);
express.use(path, router);
});
}

View File

@@ -2,15 +2,15 @@ import 'reflect-metadata';
import { NestContainer } from './injector/container';
import { Controller } from '../common/interfaces/controller.interface';
import { Injectable } from '../common/interfaces/injectable.interface';
import { metadata } from '../common/constants';
import { metadata, EXCEPTION_FILTERS_METADATA } from '../common/constants';
import { NestModuleMetatype } from '../common/interfaces/module-metatype.interface';
import { Metatype } from '../common/interfaces/metatype.interface';
import { GATEWAY_MIDDLEWARES } from '../websockets/constants';
export class DependenciesScanner {
constructor(private container: NestContainer) {}
scan(module: NestModuleMetatype) {
public scan(module: NestModuleMetatype) {
this.scanForModules(module);
this.scanModulesForDependencies();
}
@@ -18,8 +18,8 @@ export class DependenciesScanner {
private scanForModules(module: NestModuleMetatype) {
this.storeModule(module);
const innerModules = this.reflectMetadata(module, metadata.MODULES);
innerModules.map((module) => this.scanForModules(module));
const importedModules = this.reflectMetadata(module, metadata.MODULES);
importedModules.map((imported) => this.scanForModules(imported));
}
private storeModule(module: NestModuleMetatype) {
@@ -44,12 +44,18 @@ export class DependenciesScanner {
private reflectComponents(module: NestModuleMetatype) {
const components = this.reflectMetadata(module, metadata.COMPONENTS);
components.map((component) => this.storeComponent(component, module));
components.map((component) => {
this.storeComponent(component, module);
this.reflectGatewaysMiddlewares(component, module);
});
}
private reflectControllers(module: NestModuleMetatype) {
const routes = this.reflectMetadata(module, metadata.CONTROLLERS);
routes.map((route) => this.storeRoute(route, module));
routes.map((route) => {
this.storeRoute(route, module);
this.reflectExceptionFilters(route, module);
});
}
private reflectExports(module: NestModuleMetatype) {
@@ -57,6 +63,16 @@ export class DependenciesScanner {
exports.map((exportedComponent) => this.storeExportedComponent(exportedComponent, module));
}
private reflectExceptionFilters(component: Metatype<Injectable>, module: NestModuleMetatype) {
const filters = this.reflectMetadata(component, EXCEPTION_FILTERS_METADATA);
filters.map((filter) => this.storeComponent(filter, module));
}
private reflectGatewaysMiddlewares(component: Metatype<Injectable>, module: NestModuleMetatype) {
const middlewares = this.reflectMetadata(component, GATEWAY_MIDDLEWARES);
middlewares.map((middleware) => this.storeComponent(middleware, module));
}
private storeRelatedModule(related: NestModuleMetatype, module: NestModuleMetatype) {
this.container.addRelatedModule(related, module);
}

View File

@@ -3,7 +3,8 @@ import { expect } from 'chai';
import { ExceptionsHandler } from '../../exceptions/exceptions-handler';
import { HttpException } from '../../exceptions/http-exception';
import { Logger } from '../../../common/services/logger.service';
import { NestMode } from '../../../common/enums/nest-mode.enum';
import { NestEnvironment } from '../../../common/enums/nest-environment.enum';
import { InvalidExceptionFilterException } from '../../../errors/exceptions/invalid-exception-filter.exception';
describe('ExceptionsHandler', () => {
let handler: ExceptionsHandler;
@@ -11,7 +12,7 @@ describe('ExceptionsHandler', () => {
let jsonStub: sinon.SinonStub;
let response;
before(() => Logger.setMode(NestMode.TEST));
before(() => Logger.setMode(NestEnvironment.TEST));
beforeEach(() => {
handler = new ExceptionsHandler();
@@ -20,22 +21,31 @@ describe('ExceptionsHandler', () => {
response = {
status: statusStub,
json: jsonStub
json: jsonStub,
};
response.status.returns(response);
response.json.returns(response);
});
describe('next', () => {
it('should method send expected response status code and message when exception is unknown', () => {
handler.next(new Error(), response);
expect(statusStub.calledWith(500)).to.be.true;
expect(jsonStub.calledWith({ message: 'Unkown exception' })).to.be.true;
});
describe('when exception is instance of HttpException', () => {
it('should method send expected response status code and json object', () => {
const status = 401;
const message = {
custom: 'Unauthorized',
};
handler.next(new HttpException(message, status), response);
it('should method send expected response status code and message when exception is instance of HttpException', () => {
expect(statusStub.calledWith(status)).to.be.true;
expect(jsonStub.calledWith(message)).to.be.true;
});
it('should method send expected response status code and transform message to json', () => {
const status = 401;
const message = 'Unauthorized';
@@ -44,7 +54,73 @@ describe('ExceptionsHandler', () => {
expect(statusStub.calledWith(status)).to.be.true;
expect(jsonStub.calledWith({ message })).to.be.true;
});
});
describe('when "invokeCustomFilters" returns true', () => {
beforeEach(() => {
sinon.stub(handler, 'invokeCustomFilters').returns(true);
});
it('should not call status and json stubs', () => {
expect(statusStub.notCalled).to.be.true;
expect(jsonStub.notCalled).to.be.true;
});
});
});
describe('setCustomFilters', () => {
const filters = [ 'test', 'test2' ];
it('should set custom filters', () => {
handler.setCustomFilters(filters as any);
expect((handler as any).filters).to.be.eql(filters);
});
it('should throws exception when passed argument is not an array', () => {
expect(
() => handler.setCustomFilters(null),
).to.throws(InvalidExceptionFilterException);
});
});
describe('invokeCustomFilters', () => {
describe('when filters array is empty', () => {
it('should returns false', () => {
expect(handler.invokeCustomFilters(null, null)).to.be.false;
});
});
describe('when filters array is not empty', () => {
let filters, funcSpy;
class TestException {}
beforeEach(() => {
funcSpy = sinon.spy();
});
describe('when filter exists in filters array', () => {
beforeEach(() => {
filters = [
{ exceptionMetatypes: [ TestException ], func: funcSpy },
];
(handler as any).filters = filters;
});
it('should call funcSpy', () => {
handler.invokeCustomFilters(new TestException(), null);
expect(funcSpy.notCalled).to.be.false;
});
it('should call funcSpy with exception and response passed as an arguments', () => {
const exception = new TestException();
const res = { foo: 'bar' };
handler.invokeCustomFilters(exception, res);
expect(funcSpy.calledWith(exception, res)).to.be.true;
});
it('should returns true', () => {
expect(handler.invokeCustomFilters(new TestException(), null)).to.be.true;
});
});
describe('when filter does not exists in filters array', () => {
it('should not call funcSpy', () => {
handler.invokeCustomFilters(new TestException(), null);
expect(funcSpy.notCalled).to.be.true;
});
it('should returns false', () => {
expect(handler.invokeCustomFilters(new TestException(), null)).to.be.false;
});
});
});
});
});

View File

@@ -4,7 +4,7 @@ import { RequestMethod } from '../../../common/enums/request-method.enum';
describe('RouterMethodFactory', () => {
let factory: RouterMethodFactory;
let target = {
const target = {
get: () => {},
post: () => {},
all: () => {},

View File

@@ -1,7 +1,7 @@
import { expect } from 'chai';
import * as sinon from 'sinon';
import { NestContainer } from '../../injector/container';
import { Module } from '../../../common/utils/module.decorator';
import { Module } from '../../../common/utils/decorators/module.decorator';
import { UnkownModuleException } from '../../../errors/exceptions/unkown-module.exception';
describe('NestContainer', () => {
@@ -17,7 +17,7 @@ describe('NestContainer', () => {
it('should not add module if already exists in collection', () => {
const modules = new Map();
const setSpy = sinon.spy(modules, 'set');
(container as any)['modules'] = modules;
(container as any).modules = modules;
container.addModule(TestModule);
container.addModule(TestModule);

View File

@@ -2,7 +2,7 @@ import * as sinon from 'sinon';
import { expect } from 'chai';
import { InstanceWrapper } from '../../injector/container';
import { Injector } from '../../injector/injector';
import { Component } from '../../../common/utils/component.decorator';
import { Component } from '../../../common/utils/decorators/component.decorator';
import { RuntimeException } from '../../../errors/exceptions/runtime.exception';
import { Module } from '../../injector/module';
import { UnkownDependenciesException } from '../../../errors/exceptions/unkown-dependencies.exception';
@@ -38,19 +38,19 @@ describe('Injector', () => {
name: 'MainTest',
metatype: MainTest,
instance: Object.create(MainTest.prototype),
isResolved: false
isResolved: false,
};
depOne = {
name: 'DependencyOne',
metatype: DependencyOne,
instance: Object.create(DependencyOne.prototype),
isResolved: false
isResolved: false,
};
depTwo = {
name: 'DependencyTwo',
metatype: DependencyTwo,
instance: Object.create(DependencyOne.prototype),
isResolved: false
isResolved: false,
};
moduleDeps.components.set('MainTest', mainTest);
moduleDeps.components.set('DependencyOne', depOne);
@@ -59,7 +59,7 @@ describe('Injector', () => {
it('should create an instance of component with proper dependencies', () => {
injector.loadInstance(mainTest, moduleDeps.components, moduleDeps);
const { instance } = <InstanceWrapper<MainTest>>(moduleDeps.components.get('MainTest'));
const { instance } = moduleDeps.components.get('MainTest') as InstanceWrapper<MainTest>;
expect(instance.depOne instanceof DependencyOne).to.be.true;
expect(instance.depTwo instanceof DependencyOne).to.be.true;
@@ -68,13 +68,13 @@ describe('Injector', () => {
it('should set "isResolved" property to true after instance initialization', () => {
injector.loadInstance(mainTest, moduleDeps.components, moduleDeps);
const { isResolved } = <InstanceWrapper<MainTest>>(moduleDeps.components.get('MainTest'));
const { isResolved } = moduleDeps.components.get('MainTest') as InstanceWrapper<MainTest>;
expect(isResolved).to.be.true;
});
it('should throw RuntimeException when type is not stored in collection', () => {
expect(
injector.loadInstance.bind(injector, 'Test', moduleDeps.components, moduleDeps)
injector.loadInstance.bind(injector, 'Test', moduleDeps.components, moduleDeps),
).to.throw(RuntimeException);
});
@@ -94,7 +94,7 @@ describe('Injector', () => {
name: 'Test',
metatype: Test,
instance: Object.create(Test.prototype),
isResolved: false
isResolved: false,
};
moduleDeps.components.set('Test', test);
});
@@ -104,7 +104,7 @@ describe('Injector', () => {
instance: Object.create(Test.prototype),
isResolved: false,
metatype: Test,
name: 'Test'
name: 'Test',
};
injector.loadPrototypeOfInstance(test, moduleDeps.components);
expect(moduleDeps.components.get('Test')).to.deep.equal(expectedResult);
@@ -128,24 +128,24 @@ describe('Injector', () => {
it('should call "resolveConstructorParams" when instance is not resolved', () => {
const collection = {
get: (...args) => ({
instance: null
instance: null,
}),
set: (...args) => {}
set: (...args) => {},
};
injector.loadInstanceOfMiddleware(<any>{ metatype: { name: '' }}, <any>collection, null);
injector.loadInstanceOfMiddleware({ metatype: { name: '' }} as any, collection as any, null);
expect(resolveConstructorParams.called).to.be.true;
});
it('should not call "resolveConstructorParams" when instance is not resolved', () => {
const collection = {
get: (...args) => ({
instance: {}
instance: {},
}),
set: (...args) => {}
set: (...args) => {},
};
injector.loadInstanceOfMiddleware(<any>{ metatype: { name: '' }}, <any>collection, null);
injector.loadInstanceOfMiddleware({ metatype: { name: '' }} as any, collection as any, null);
expect(resolveConstructorParams.called).to.be.false;
});
});
@@ -156,46 +156,46 @@ describe('Injector', () => {
beforeEach(() => {
scanForComponentInRelatedModules = sinon.stub();
injector['scanForComponentInRelatedModules'] = scanForComponentInRelatedModules;
(injector as any).scanForComponentInRelatedModules = scanForComponentInRelatedModules;
});
it('should return object from collection if exists', () => {
const instance = { test: 3 };
const collection = {
has: () => true,
get: () => instance
get: () => instance,
};
const result = injector.scanForComponent(<any>collection, metatype.name, null, metatype);
const result = injector.scanForComponent(collection as any, metatype.name, null, metatype);
expect(result).to.be.equal(instance);
});
it('should call "scanForComponentInRelatedModules" when object is not in collection', () => {
scanForComponentInRelatedModules.returns({});
const collection = {
has: () => false
has: () => false,
};
injector.scanForComponent(<any>collection, metatype.name, null, metatype);
injector.scanForComponent(collection as any, metatype.name, null, metatype);
expect(scanForComponentInRelatedModules.called).to.be.true;
});
it('should throw "UnkownDependenciesException" instanceWrapper is null', () => {
scanForComponentInRelatedModules.returns(null);
const collection = {
has: () => false
has: () => false,
};
expect(
() => injector.scanForComponent(<any>collection, metatype.name, null, metatype)
() => injector.scanForComponent(collection as any, metatype.name, null, metatype)
).throws(UnkownDependenciesException);
});
it('should not throw "UnkownDependenciesException" instanceWrapper is not null', () => {
scanForComponentInRelatedModules.returns({});
const collection = {
has: () => false
has: () => false,
};
expect(
() => injector.scanForComponent(<any>collection, metatype.name, null, metatype)
() => injector.scanForComponent(collection as any, metatype.name, null, metatype)
).not.throws(UnkownDependenciesException);
});
@@ -205,16 +205,16 @@ describe('Injector', () => {
let loadInstanceOfComponent: sinon.SinonSpy;
const metatype = { name: 'test' };
const module = {
relatedModules: []
relatedModules: [],
};
beforeEach(() => {
loadInstanceOfComponent = sinon.spy();
injector['loadInstanceOfComponent'] = loadInstanceOfComponent;
(injector as any).loadInstanceOfComponent = loadInstanceOfComponent;
});
it('should return null when there is no related modules', () => {
const result = injector.scanForComponentInRelatedModules(<any>module, null);
const result = injector.scanForComponentInRelatedModules(module as any, null);
expect(result).to.be.eq(null);
});
@@ -226,10 +226,10 @@ describe('Injector', () => {
},
exports: {
has: () => true,
}
}]
},
}],
};
expect(injector.scanForComponentInRelatedModules(<any>module, <any>metatype)).to.be.eq(null);
expect(injector.scanForComponentInRelatedModules(module as any, metatype as any)).to.be.eq(null);
module = {
relatedModules: [{
@@ -238,10 +238,10 @@ describe('Injector', () => {
},
exports: {
has: () => false,
}
}]
},
}],
};
expect(injector.scanForComponentInRelatedModules(<any>module, <any>metatype)).to.be.eq(null);
expect(injector.scanForComponentInRelatedModules(module as any, metatype as any)).to.be.eq(null);
});
it('should call "loadInstanceOfComponent" when component is not resolved', () => {
@@ -250,15 +250,15 @@ describe('Injector', () => {
components: {
has: () => true,
get: () => ({
isResolved: false
})
isResolved: false,
}),
},
exports: {
has: () => true,
}
}]
},
}],
};
injector.scanForComponentInRelatedModules(<any>module, <any>metatype);
injector.scanForComponentInRelatedModules(module as any, metatype as any);
expect(loadInstanceOfComponent.called).to.be.true;
});
@@ -268,15 +268,15 @@ describe('Injector', () => {
components: {
has: () => true,
get: () => ({
isResolved: true
})
isResolved: true,
}),
},
exports: {
has: () => true,
}
}]
},
}],
};
injector.scanForComponentInRelatedModules(<any>module, <any>metatype);
injector.scanForComponentInRelatedModules(module as any, metatype as any);
expect(loadInstanceOfComponent.called).to.be.false;
});

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