Skip to content

Commit

Permalink
feat(@nestjs) reactive microservices, custom transport strategy, webs…
Browse files Browse the repository at this point in the history
…ockets adapter, exception filters breaking change, async pipes feature
  • Loading branch information
kamil.mysliwiec committed May 31, 2017
1 parent 07d7768 commit 9bf3661
Show file tree
Hide file tree
Showing 35 changed files with 527 additions and 125 deletions.
1 change: 0 additions & 1 deletion .github/ISSUE_TEMPLATE.md

This file was deleted.

5 changes: 3 additions & 2 deletions example/modules/users/validator.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { CustomException } from './exception.filter';

@Pipe()
export class ValidatorPipe implements PipeTransform {
public transform(value, metatype, token): any {
return value;
public async transform(value, metadata?) {
console.log(value, metadata);
return Promise.resolve(value);
}
}
14 changes: 0 additions & 14 deletions microservice.js

This file was deleted.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
"start": "concurrently \"npm run build:live\" \"npm run microservice:live\"",
"build:live": "nodemon -e ts --watch src index.js",
"build": "node index.js",
"microservice:live": "nodemon -e ts --watch src microservice.js",
"microservice": "node microservice.js",
"compile": "tsc -p tsconfig.prod.json",
"test": "nyc --require ts-node/register mocha src/**/*.spec.ts --reporter spec",
"coverage": "nyc report --reporter=text-lcov | coveralls",
Expand Down
7 changes: 6 additions & 1 deletion src/common/interfaces/pipe-transform.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Paramtype } from './paramtype.interface';

export type Transform<T> = (value: T, metatype?, type?: Paramtype) => any;
export type Transform<T> = (value: T, metadata?: ArgumentMetadata) => any;

export interface ArgumentMetadata {
type: Paramtype;
metatype?: any;
}

export interface PipeTransform {
transform: Transform<any>;
Expand Down
12 changes: 11 additions & 1 deletion src/common/test/utils/exception-filters.decorator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ import { ExceptionFilters } from '../../utils/decorators/exception-filters.decor
describe('@ExceptionFilters', () => {
const filters = [ 'exception', 'exception2' ];

@ExceptionFilters(...filters) class Test {}
@ExceptionFilters(...filters as any) class Test {}

class TestWithMethod {
@ExceptionFilters(...filters as any)
public static test() {}
}

it('should enhance class with expected exception filters array', () => {
const metadata = Reflect.getMetadata(EXCEPTION_FILTERS_METADATA, Test);
expect(metadata).to.be.eql(filters);
});

it('should enhance method with expected exception filters array', () => {
const metadata = Reflect.getMetadata(EXCEPTION_FILTERS_METADATA, TestWithMethod.test);
expect(metadata).to.be.eql(filters);
});

});
26 changes: 26 additions & 0 deletions src/common/test/utils/use-pipes.decorator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'reflect-metadata';
import { expect } from 'chai';
import { UsePipes } from '../../utils/decorators/use-pipes.decorator';
import { PIPES_METADATA } from './../../constants';

describe('@UsePipes', () => {
const pipes = [ 'pipe1', 'pipe2' ];

@UsePipes(...pipes as any) class Test {}

class TestWithMethod {
@UsePipes(...pipes as any)
public static test() {}
}

it('should enhance class with expected pipes array', () => {
const metadata = Reflect.getMetadata(PIPES_METADATA, Test);
expect(metadata).to.be.eql(pipes);
});

it('should enhance method with expected pipes array', () => {
const metadata = Reflect.getMetadata(PIPES_METADATA, TestWithMethod.test);
expect(metadata).to.be.eql(pipes);
});

});
13 changes: 0 additions & 13 deletions src/core/injector/instance-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export class InstanceLoader {
modules.forEach((module) => {
this.createInstancesOfComponents(module);
this.createInstancesOfRoutes(module);
this.callModuleInitHook(module);

const { name } = module.metatype;
this.logger.log(ModuleInitMessage(name));
Expand Down Expand Up @@ -62,16 +61,4 @@ export class InstanceLoader {
this.injector.loadInstanceOfRoute(wrapper, module);
});
}

private callModuleInitHook(module: Module) {
const components = [...module.routes, ...module.components];
iterate(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);
}
}
12 changes: 8 additions & 4 deletions src/core/middlewares/middlewares-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,20 @@ export class MiddlewaresModule {

public static setupMiddlewares(app: Application) {
const configs = this.container.getConfigs();

configs.forEach((moduleConfigs, module: string) => {
[ ...moduleConfigs ].forEach((config: MiddlewareConfiguration) => {
config.forRoutes.forEach((route: ControllerMetadata & { method: RequestMethod }) => {
this.setupRouteMiddleware(route, config, module, app);
});
this.setupMiddlewareConfig(config, module, app);
});
});
}

public static setupMiddlewareConfig(config: MiddlewareConfiguration, module: string, app: Application) {
const { forRoutes } = config;
forRoutes.forEach((route: ControllerMetadata & { method: RequestMethod }) => {
this.setupRouteMiddleware(route, config, module, app);
});
}

public static setupRouteMiddleware(
route: ControllerMetadata & { method: RequestMethod },
config: MiddlewareConfiguration,
Expand Down
22 changes: 21 additions & 1 deletion src/core/nest-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Logger } from '@nestjs/common/services/logger.service';
import { messages } from './constants';
import { MicroservicesModule } from '@nestjs/microservices/microservices-module';
import { Resolver } from './router/interfaces/resolver.interface';
import { INestApplication, INestMicroservice } from '@nestjs/common';
import { INestApplication, INestMicroservice, OnModuleInit } from '@nestjs/common';
import { ApplicationConfig } from './application-config';
import { validatePath, isNil, isUndefined } from '@nestjs/common/utils/shared.utils';
import { MicroserviceConfiguration } from '@nestjs/microservices';
Expand Down Expand Up @@ -50,6 +50,7 @@ export class NestApplication implements INestApplication {
validatePath(this.config.getGlobalPrefix()),
router,
);
this.callInitHook();
this.logger.log(messages.APPLICATION_READY);
this.isInitialized = true;
}
Expand Down Expand Up @@ -111,6 +112,25 @@ export class NestApplication implements INestApplication {
});
}

private callInitHook() {
const modules = this.container.getModules();
modules.forEach((module) => {
this.callModuleInitHook(module);
});
}

private callModuleInitHook(module: Module) {
const components = [...module.routes, ...module.components];
iterate(components).map(([key, {instance}]) => instance)
.filter((instance) => !isNil(instance))
.filter(this.hasOnModuleInitHook)
.forEach((instance) => (instance as OnModuleInit).onModuleInit());
}

private hasOnModuleInitHook(instance): instance is OnModuleInit {
return !isUndefined((instance as OnModuleInit).onModuleInit);
}

private callDestroyHook() {
const modules = this.container.getModules();
modules.forEach((module) => {
Expand Down
11 changes: 9 additions & 2 deletions src/core/pipes/pipes-consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ import { ParamsTokenFactory } from './../pipes/params-token-factory';
export class PipesConsumer {
private readonly paramsTokenFactory = new ParamsTokenFactory();

public apply(value, metatype, type: RouteParamtypes, transforms: Transform<any>[]) {
public async apply(value, metatype, type: RouteParamtypes, transforms: Transform<any>[]) {
const token = this.paramsTokenFactory.exchangeEnumForString(type);
return transforms.reduce((val, fn) => fn(val, metatype, token), value);
return await transforms.reduce(async (defferedValue, fn) => {
const val = await defferedValue;
const result = fn(val, { metatype, type: token });
if (result instanceof Promise) {
return result;
}
return Promise.resolve(result);
}, Promise.resolve(value));
}
}
4 changes: 2 additions & 2 deletions src/core/pipes/pipes-context-creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ export class PipesContextCreator extends ContextCreator {
if (isUndefined(metadata) || isEmpty(metadata)) {
return [];
}
return iterate(metadata).filter((pipe) => pipe.transform && isFunction(pipe.transform))
return iterate(metadata).filter((pipe) => pipe && pipe.transform && isFunction(pipe.transform))
.map((pipe) => pipe.transform.bind(pipe))
.toArray();
}

public getGlobalMetadata(): PipeTransform[] {
return [];
return this.config.getGlobalPipes();
}
}
9 changes: 7 additions & 2 deletions src/core/router/router-exception-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import { UnknownModuleException } from '../errors/exceptions/unknown-module.exce
import { ExceptionFilter } from '@nestjs/common/interfaces/exceptions/exception-filter.interface';
import { RouterProxyCallback } from './../router/router-proxy';
import { ContextCreator } from './../helpers/context-creator';
import { ApplicationConfig } from './../application-config';

export class RouterExceptionFilters extends ContextCreator {
constructor(private readonly config: ApplicationConfig) {
super();
}

public create(instance: Controller, callback: RouterProxyCallback): ExceptionsHandler {
const exceptionHandler = new ExceptionsHandler();
const filters = this.createContext(instance, callback, EXCEPTION_FILTERS_METADATA);
Expand All @@ -22,8 +27,8 @@ export class RouterExceptionFilters extends ContextCreator {
return exceptionHandler;
}

public getGlobalMetadata<T extends any[]>(): T {
return [] as T;
public getGlobalMetadata(): ExceptionFilter[] {
return this.config.getGlobalFilters();
}

public createConcreteContext(metadata: ExceptionFilter[]): ExceptionFilterMetadata[] {
Expand Down
20 changes: 13 additions & 7 deletions src/core/router/router-execution-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ export class RouterExecutionContext {
const pipes = this.pipesContextCreator.create(instance, callback);
const paramtypes = this.reflectCallbackParamtypes(instance, callback);

return (req, res, next) => {
return async (req, res, next) => {
const paramProperties = this.exchangeKeysForValues(keys, metadata, { req, res, next });
paramProperties.forEach((param) => {
args[param.index] = this.getParamValue(param.value, paramtypes[param.index], param.type, pipes);
});
for (const param of paramProperties) {
const { index, value, type } = param;
args[index] = await this.getParamValue(value, paramtypes[index], type, pipes);
}
return callback.apply(instance, args);
};
}
Expand Down Expand Up @@ -76,13 +77,18 @@ export class RouterExecutionContext {
});
}

public getParamValue<T>(value: T, metatype, paramtype: RouteParamtypes, transforms: Transform<any>[]) {
public async getParamValue<T>(
value: T,
metatype,
paramtype: RouteParamtypes,
transforms: Transform<any>[]): Promise<any> {

if (paramtype === RouteParamtypes.BODY
|| paramtype === RouteParamtypes.QUERY
|| paramtype === RouteParamtypes.PARAM) {

return this.pipesConsumer.apply(value, metatype, paramtype, transforms);
return await this.pipesConsumer.apply(value, metatype, paramtype, transforms);
}
return value;
return Promise.resolve(value);
}
}
5 changes: 3 additions & 2 deletions src/core/router/routes-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import { ApplicationConfig } from './../application-config';
export class RoutesResolver implements Resolver {
private readonly logger = new Logger(RoutesResolver.name);
private readonly routerProxy = new RouterProxy();
private readonly routerExceptionsFilter = new RouterExceptionFilters();
private readonly routerExceptionsFilter: RouterExceptionFilters;
private readonly routerBuilder: RouterExplorer;

constructor(
private readonly container: NestContainer,
expressAdapter,
private readonly expressAdapter,
private readonly config: ApplicationConfig) {

this.routerExceptionsFilter = new RouterExceptionFilters(config);
this.routerBuilder = new ExpressRouterExplorer(
new MetadataScanner(),
this.routerProxy,
Expand Down
34 changes: 34 additions & 0 deletions src/core/test/pipes/params-token-factory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { expect } from 'chai';
import { ParamsTokenFactory } from './../../pipes/params-token-factory';
import { RouteParamtypes } from '../../../common/enums/route-paramtypes.enum';

describe('ParamsTokenFactory', () => {
let factory: ParamsTokenFactory;
beforeEach(() => {
factory = new ParamsTokenFactory();
});
describe('exchangeEnumForString', () => {
describe('when key is', () => {
describe(`RouteParamtypes.BODY`, () => {
it('should returns body object', () => {
expect(factory.exchangeEnumForString(RouteParamtypes.BODY)).to.be.eql('body');
});
});
describe(`RouteParamtypes.QUERY`, () => {
it('should returns query object', () => {
expect(factory.exchangeEnumForString(RouteParamtypes.QUERY)).to.be.eql('query');
});
});
describe(`RouteParamtypes.PARAM`, () => {
it('should returns params object', () => {
expect(factory.exchangeEnumForString(RouteParamtypes.PARAM)).to.be.eql('param');
});
});
describe('not available', () => {
it('should returns null', () => {
expect(factory.exchangeEnumForString(-1)).to.be.eql(null);
});
});
});
});
});
38 changes: 38 additions & 0 deletions src/core/test/pipes/pipes-consumer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as sinon from 'sinon';
import { expect } from 'chai';
import { PipesConsumer } from './../../pipes/pipes-consumer';
import { RouteParamtypes } from './../../../common/enums/route-paramtypes.enum';

describe('PipesConsumer', () => {
let consumer: PipesConsumer;
beforeEach(() => {
consumer = new PipesConsumer();
});
describe('apply', () => {
let value, metatype, type, stringifiedType, transforms;
beforeEach(() => {
value = 0;
metatype = {},
type = RouteParamtypes.QUERY;
stringifiedType = 'query';
transforms = [
sinon.stub().callsFake((val) => val + 1),
sinon.stub().callsFake((val) => Promise.resolve(val + 1)),
sinon.stub().callsFake((val) => val + 1),
];
});
it('should call all transform functions', (done) => {
consumer.apply(value, metatype, type, transforms).then(() => {
expect(transforms.reduce((prev, next) => prev && next.called, true)).to.be.true;
done();
});
});
it('should returns expected result', (done) => {
const expectedResult = 3;
consumer.apply(value, metatype, type, transforms).then((result) => {
expect(result).to.be.eql(expectedResult);
done();
});
});
});
});
Loading

0 comments on commit 9bf3661

Please sign in to comment.