mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
chore() merge latest changes (resolve conflicts)
This commit is contained in:
@@ -73,6 +73,15 @@ describe('NATS transport', () => {
|
||||
.expect(200, 'true');
|
||||
});
|
||||
|
||||
it(`/GET (exception)`, () => {
|
||||
return request(server)
|
||||
.get('/exception')
|
||||
.expect(200, {
|
||||
message: 'test',
|
||||
status: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Body, Controller, HttpCode, Post, Query } from '@nestjs/common';
|
||||
import { Body, Controller, Get, HttpCode, Post, Query } from '@nestjs/common';
|
||||
import {
|
||||
Client,
|
||||
ClientProxy,
|
||||
MessagePattern,
|
||||
RpcException,
|
||||
Transport,
|
||||
} from '@nestjs/microservices';
|
||||
import { from, Observable, of } from 'rxjs';
|
||||
import { scan } from 'rxjs/operators';
|
||||
import { from, Observable, of, throwError } from 'rxjs';
|
||||
import { catchError, scan } from 'rxjs/operators';
|
||||
|
||||
@Controller()
|
||||
export class NatsController {
|
||||
@@ -71,4 +72,16 @@ export class NatsController {
|
||||
streaming(data: number[]): Observable<number> {
|
||||
return from(data);
|
||||
}
|
||||
|
||||
@Get('exception')
|
||||
async getError() {
|
||||
return await this.client
|
||||
.send<number>('exception', {})
|
||||
.pipe(catchError(err => of(err)));
|
||||
}
|
||||
|
||||
@MessagePattern('exception')
|
||||
throwError(): Observable<number> {
|
||||
return throwError(new RpcException('test'));
|
||||
}
|
||||
}
|
||||
|
||||
35
integration/websockets/e2e/error-gateway.spec.ts
Normal file
35
integration/websockets/e2e/error-gateway.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { expect } from 'chai';
|
||||
import * as io from 'socket.io-client';
|
||||
import { ErrorGateway } from '../src/error.gateway';
|
||||
|
||||
describe('ErrorGateway', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const testingModule = await Test.createTestingModule({
|
||||
providers: [ErrorGateway],
|
||||
}).compile();
|
||||
app = await testingModule.createNestApplication();
|
||||
await app.listenAsync(3000);
|
||||
});
|
||||
|
||||
it(`should handle error`, async () => {
|
||||
const ws = io.connect('http://localhost:8080');
|
||||
ws.emit('push', {
|
||||
test: 'test',
|
||||
});
|
||||
await new Promise(resolve =>
|
||||
ws.on('exception', data => {
|
||||
expect(data).to.be.eql({
|
||||
status: 'error',
|
||||
message: 'test',
|
||||
});
|
||||
resolve();
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => app.close());
|
||||
});
|
||||
14
integration/websockets/src/error.gateway.ts
Normal file
14
integration/websockets/src/error.gateway.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
SubscribeMessage,
|
||||
WebSocketGateway,
|
||||
WsException,
|
||||
} from '@nestjs/websockets';
|
||||
import { throwError } from 'rxjs';
|
||||
|
||||
@WebSocketGateway(8080)
|
||||
export class ErrorGateway {
|
||||
@SubscribeMessage('push')
|
||||
onPush(client, data) {
|
||||
return throwError(new WsException('test'));
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "5.4.1"
|
||||
"version": "5.5.0"
|
||||
}
|
||||
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -4982,9 +4982,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify-plugin": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-1.2.1.tgz",
|
||||
"integrity": "sha512-TLmXpp3PEsD3MFSV3FBiH70hkalvgZ8Qmg3hr1FcJpZFaPwxoWUDoFdXZrguY+2gBVQgkFtrM47aWL5lNHqg+Q==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-1.3.0.tgz",
|
||||
"integrity": "sha512-3Tv+3ptWUbxgd2dRbZ57c3wOPDqJf+X5cSSN20YTOG9KI9YL13/PEZhEu66E3Yd/YDPWx8uZtC+axfxI+24qLA==",
|
||||
"requires": {
|
||||
"semver": "^5.5.0"
|
||||
}
|
||||
@@ -15733,9 +15733,9 @@
|
||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.1.tgz",
|
||||
"integrity": "sha512-jw7P2z/h6aPT4AENXDGjcfHTu5CSqzsbZc6YlUIebTyBAq8XaKp78x7VcSh30xwSCcsu5irZkYZUSFP1MrAMbg==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz",
|
||||
"integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
|
||||
51
package.json
51
package.json
@@ -5,30 +5,21 @@
|
||||
"scripts": {
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"precommit": "lint-staged",
|
||||
"test":
|
||||
"nyc --require ts-node/register mocha packages/**/*.spec.ts --reporter spec --require 'node_modules/reflect-metadata/Reflect.js'",
|
||||
"integration-test":
|
||||
"mocha integration/**/*.spec.ts --reporter spec --require ts-node/register --require 'node_modules/reflect-metadata/Reflect.js'",
|
||||
"lint":
|
||||
"tslint -p tsconfig.json -c tslint.json \"packages/**/*.ts\" -e \"*.spec.ts\"",
|
||||
"format":
|
||||
"prettier **/**/*.ts --ignore-path ./.prettierignore --write && git status",
|
||||
"test": "nyc --require ts-node/register mocha packages/**/*.spec.ts --reporter spec --require 'node_modules/reflect-metadata/Reflect.js'",
|
||||
"integration-test": "mocha integration/**/*.spec.ts --reporter spec --require ts-node/register --require 'node_modules/reflect-metadata/Reflect.js'",
|
||||
"lint": "tslint -p tsconfig.json -c tslint.json \"packages/**/*.ts\" -e \"*.spec.ts\"",
|
||||
"format": "prettier **/**/*.ts --ignore-path ./.prettierignore --write && git status",
|
||||
"clean": "gulp clean:bundle",
|
||||
"build": "npm run clean && gulp build",
|
||||
"prebuild:dev": "rm -rf node_modules/@nestjs",
|
||||
"build:dev": "gulp build --dist node_modules/@nestjs && gulp move",
|
||||
"postinstall": "opencollective",
|
||||
"prerelease": "gulp copy-misc && gulp build --dist node_modules/@nestjs",
|
||||
"publish":
|
||||
"npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --force-publish --exact -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:rc":
|
||||
"npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --npm-tag=rc -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:next":
|
||||
"npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --npm-tag=next --skip-git -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:beta":
|
||||
"npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --npm-tag=beta -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:test":
|
||||
"npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --force-publish --npm-tag=test --skip-git -m \"chore(@nestjs) publish %s release\""
|
||||
"publish": "npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --force-publish --exact -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:rc": "npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --npm-tag=rc -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:next": "npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --npm-tag=next --skip-git -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:beta": "npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --npm-tag=beta -m \"chore(@nestjs) publish %s release\"",
|
||||
"publish:test": "npm run prerelease && npm run build && ./node_modules/.bin/lerna publish --force-publish --npm-tag=test --skip-git -m \"chore(@nestjs) publish %s release\""
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9.0"
|
||||
@@ -132,7 +123,7 @@
|
||||
"supertest": "^3.0.0",
|
||||
"ts-node": "^6.0.0",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^3.1.6"
|
||||
"typescript": "^3.2.2"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
@@ -142,7 +133,9 @@
|
||||
}
|
||||
},
|
||||
"nyc": {
|
||||
"include": ["packages/**/*.ts"],
|
||||
"include": [
|
||||
"packages/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules/",
|
||||
"packages/**/*.spec.ts",
|
||||
@@ -161,13 +154,23 @@
|
||||
"packages/common/serializer/**/*",
|
||||
"packages/common/services/logger.service.ts"
|
||||
],
|
||||
"extension": [".ts"],
|
||||
"require": ["ts-node/register"],
|
||||
"reporter": ["text-summary", "html"],
|
||||
"extension": [
|
||||
".ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
],
|
||||
"reporter": [
|
||||
"text-summary",
|
||||
"html"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"instrument": true
|
||||
},
|
||||
"lint-staged": {
|
||||
"packages/**/*.{ts,json}": ["npm run format", "git add"]
|
||||
"packages/**/*.{ts,json}": [
|
||||
"npm run format",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
8
packages/common/cache/cache.module.ts
vendored
8
packages/common/cache/cache.module.ts
vendored
@@ -24,7 +24,10 @@ export class CacheModule {
|
||||
return {
|
||||
module: CacheModule,
|
||||
imports: options.imports,
|
||||
providers: this.createAsyncProviders(options),
|
||||
providers: [
|
||||
...this.createAsyncProviders(options),
|
||||
...(options.extraProviders || []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,7 +58,8 @@ export class CacheModule {
|
||||
}
|
||||
return {
|
||||
provide: CACHE_MODULE_OPTIONS,
|
||||
useFactory: async (optionsFactory: CacheOptionsFactory) => optionsFactory.createCacheOptions(),
|
||||
useFactory: async (optionsFactory: CacheOptionsFactory) =>
|
||||
optionsFactory.createCacheOptions(),
|
||||
inject: [options.useExisting || options.useClass],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Observable, of } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { Inject, Injectable, Optional } from '../../decorators';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
HttpServer,
|
||||
NestInterceptor,
|
||||
@@ -28,22 +29,22 @@ export class CacheInterceptor implements NestInterceptor {
|
||||
|
||||
async intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
next: CallHandler,
|
||||
): Promise<Observable<any>> {
|
||||
const key = this.trackBy(context);
|
||||
if (!key) {
|
||||
return call$;
|
||||
return next.handle();
|
||||
}
|
||||
try {
|
||||
const value = await this.cacheManager.get(key);
|
||||
if (value) {
|
||||
return of(value);
|
||||
}
|
||||
return call$.pipe(
|
||||
tap((response: any) => this.cacheManager.set(key, response)),
|
||||
);
|
||||
return next
|
||||
.handle()
|
||||
.pipe(tap(response => this.cacheManager.set(key, response)));
|
||||
} catch {
|
||||
return call$;
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModuleMetadata, Type } from '../../interfaces';
|
||||
import { ModuleMetadata, Provider, Type } from '../../interfaces';
|
||||
import { CacheManagerOptions } from './cache-manager.interface';
|
||||
|
||||
export interface CacheModuleOptions extends CacheManagerOptions {
|
||||
@@ -17,4 +17,5 @@ export interface CacheModuleAsyncOptions
|
||||
...args: any[]
|
||||
) => Promise<CacheModuleOptions> | CacheModuleOptions;
|
||||
inject?: any[];
|
||||
extraProviders?: Provider[];
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@ export function Inject<T = any>(token?: T) {
|
||||
token && isFunction(token) ? ((token as any) as Function).name : token;
|
||||
|
||||
if (!isUndefined(index)) {
|
||||
const dependencies =
|
||||
let dependencies =
|
||||
Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, target) || [];
|
||||
|
||||
dependencies.push({ index, param: type });
|
||||
dependencies = [...dependencies, { index, param: type }];
|
||||
Reflect.defineMetadata(SELF_DECLARED_DEPS_METADATA, dependencies, target);
|
||||
return;
|
||||
}
|
||||
const properties =
|
||||
let properties =
|
||||
Reflect.getMetadata(PROPERTY_DEPS_METADATA, target.constructor) || [];
|
||||
|
||||
properties.push({ key, type });
|
||||
properties = [...properties, { key, type }];
|
||||
Reflect.defineMetadata(
|
||||
PROPERTY_DEPS_METADATA,
|
||||
properties,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export const AXIOS_INSTANCE_TOKEN = 'AXIOS_INSTANCE_TOKEN';
|
||||
export const HTTP_MODULE_ID = 'HTTP_MODULE_ID';
|
||||
export const HTTP_MODULE_OPTIONS = 'HTTP_MODULE_OPTIONS';
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import Axios, { AxiosRequestConfig } from 'axios';
|
||||
import Axios from 'axios';
|
||||
import { Module } from '../decorators/modules/module.decorator';
|
||||
import { DynamicModule } from '../interfaces';
|
||||
import { DynamicModule, Provider } from '../interfaces';
|
||||
import { randomStringGenerator } from '../utils/random-string-generator.util';
|
||||
import { AXIOS_INSTANCE_TOKEN, HTTP_MODULE_ID } from './http.constants';
|
||||
import {
|
||||
AXIOS_INSTANCE_TOKEN,
|
||||
HTTP_MODULE_ID,
|
||||
HTTP_MODULE_OPTIONS,
|
||||
} from './http.constants';
|
||||
import { HttpService } from './http.service';
|
||||
import {
|
||||
HttpModuleAsyncOptions,
|
||||
HttpModuleOptions,
|
||||
HttpModuleOptionsFactory,
|
||||
} from './interfaces';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
@@ -16,7 +25,7 @@ import { HttpService } from './http.service';
|
||||
exports: [HttpService],
|
||||
})
|
||||
export class HttpModule {
|
||||
static register(config: AxiosRequestConfig): DynamicModule {
|
||||
static register(config: HttpModuleOptions): DynamicModule {
|
||||
return {
|
||||
module: HttpModule,
|
||||
providers: [
|
||||
@@ -31,4 +40,52 @@ export class HttpModule {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
static registerAsync(options: HttpModuleAsyncOptions): DynamicModule {
|
||||
return {
|
||||
module: HttpModule,
|
||||
imports: options.imports,
|
||||
providers: [
|
||||
...this.createAsyncProviders(options),
|
||||
{
|
||||
provide: HTTP_MODULE_ID,
|
||||
useValue: randomStringGenerator(),
|
||||
},
|
||||
...(options.extraProviders || []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private static createAsyncProviders(
|
||||
options: HttpModuleAsyncOptions,
|
||||
): Provider[] {
|
||||
if (options.useExisting || options.useFactory) {
|
||||
return [this.createAsyncOptionsProvider(options)];
|
||||
}
|
||||
return [
|
||||
this.createAsyncOptionsProvider(options),
|
||||
{
|
||||
provide: options.useClass,
|
||||
useClass: options.useClass,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private static createAsyncOptionsProvider(
|
||||
options: HttpModuleAsyncOptions,
|
||||
): Provider {
|
||||
if (options.useFactory) {
|
||||
return {
|
||||
provide: HTTP_MODULE_OPTIONS,
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject || [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
provide: HTTP_MODULE_OPTIONS,
|
||||
useFactory: async (optionsFactory: HttpModuleOptionsFactory) =>
|
||||
optionsFactory.createHttpOptions(),
|
||||
inject: [options.useExisting || options.useClass],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './http.module';
|
||||
export * from './http.service';
|
||||
export * from './interfaces';
|
||||
|
||||
19
packages/common/http/interfaces/http-module.interface.ts
Normal file
19
packages/common/http/interfaces/http-module.interface.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { ModuleMetadata, Provider, Type } from '../../interfaces';
|
||||
|
||||
export interface HttpModuleOptions extends AxiosRequestConfig {}
|
||||
|
||||
export interface HttpModuleOptionsFactory {
|
||||
createHttpOptions(): Promise<HttpModuleOptions> | HttpModuleOptions;
|
||||
}
|
||||
|
||||
export interface HttpModuleAsyncOptions
|
||||
extends Pick<ModuleMetadata, 'imports'> {
|
||||
useExisting?: Type<HttpModuleOptions>;
|
||||
useClass?: Type<HttpModuleOptions>;
|
||||
useFactory?: (
|
||||
...args: any[]
|
||||
) => Promise<HttpModuleOptions> | HttpModuleOptions;
|
||||
inject?: any[];
|
||||
extraProviders?: Provider[];
|
||||
}
|
||||
1
packages/common/http/interfaces/index.ts
Normal file
1
packages/common/http/interfaces/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './http-module.interface';
|
||||
@@ -14,6 +14,7 @@ export * from './http';
|
||||
export {
|
||||
ArgumentMetadata,
|
||||
ArgumentsHost,
|
||||
CallHandler,
|
||||
CanActivate,
|
||||
DynamicModule,
|
||||
ExceptionFilter,
|
||||
@@ -36,6 +37,7 @@ export {
|
||||
Provider,
|
||||
RpcExceptionFilter,
|
||||
Type,
|
||||
ValidationError,
|
||||
WebSocketAdapter,
|
||||
WsExceptionFilter,
|
||||
} from './interfaces';
|
||||
|
||||
28
packages/common/interfaces/external/validation-error.interface.ts
vendored
Normal file
28
packages/common/interfaces/external/validation-error.interface.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Validation error description.
|
||||
* @see https://github.com/typestack/class-validator
|
||||
*/
|
||||
export interface ValidationError {
|
||||
/**
|
||||
* Object that was validated.
|
||||
*/
|
||||
target: Object;
|
||||
/**
|
||||
* Object's property that haven't pass validation.
|
||||
*/
|
||||
property: string;
|
||||
/**
|
||||
* Value that haven't pass a validation.
|
||||
*/
|
||||
value: any;
|
||||
/**
|
||||
* Constraints that failed validation with error messages.
|
||||
*/
|
||||
constraints: {
|
||||
[type: string]: string;
|
||||
};
|
||||
/**
|
||||
* Contains all nested validation errors of the property.
|
||||
*/
|
||||
children: ValidationError[];
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { ExecutionContext } from './execution-context.interface';
|
||||
|
||||
export interface CallHandler<T = any> {
|
||||
handle(): Observable<T>;
|
||||
}
|
||||
|
||||
export interface NestInterceptor<T = any, R = any> {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<T>,
|
||||
next: CallHandler<T>,
|
||||
): Observable<R> | Promise<Observable<R>>;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export * from './controllers/controller.interface';
|
||||
export * from './exceptions/exception-filter.interface';
|
||||
export * from './exceptions/rpc-exception-filter.interface';
|
||||
export * from './exceptions/ws-exception-filter.interface';
|
||||
export * from './external/validation-error.interface';
|
||||
export * from './features/arguments-host.interface';
|
||||
export * from './features/can-activate.interface';
|
||||
export * from './features/custom-route-param-factory.interface';
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface NatsOptions {
|
||||
reconnectTimeWait?: number;
|
||||
servers?: string[];
|
||||
tls?: any;
|
||||
queue?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "@nestjs/common",
|
||||
"version": "5.4.1",
|
||||
"description":
|
||||
"Nest - modern, fast, powerful node.js web framework (@common)",
|
||||
"version": "5.5.0",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@common)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Optional } from '../decorators';
|
||||
import { Injectable } from '../decorators/core';
|
||||
import { ArgumentMetadata, BadRequestException } from '../index';
|
||||
import {
|
||||
ArgumentMetadata,
|
||||
BadRequestException,
|
||||
ValidationError,
|
||||
} from '../index';
|
||||
import { ValidatorOptions } from '../interfaces/external/validator-options.interface';
|
||||
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
|
||||
import { loadPackage } from '../utils/load-package.util';
|
||||
@@ -9,6 +13,7 @@ import { isNil } from '../utils/shared.utils';
|
||||
export interface ValidationPipeOptions extends ValidatorOptions {
|
||||
transform?: boolean;
|
||||
disableErrorMessages?: boolean;
|
||||
exceptionFactory?: (errors: ValidationError[]) => any;
|
||||
}
|
||||
|
||||
let classValidator: any = {};
|
||||
@@ -19,6 +24,7 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
protected isTransformEnabled: boolean;
|
||||
protected isDetailedOutputDisabled?: boolean;
|
||||
protected validatorOptions: ValidatorOptions;
|
||||
protected exceptionFactory: (errors: ValidationError[]) => any;
|
||||
|
||||
constructor(@Optional() options?: ValidationPipeOptions) {
|
||||
options = options || {};
|
||||
@@ -26,6 +32,12 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
this.isTransformEnabled = !!transform;
|
||||
this.validatorOptions = validatorOptions;
|
||||
this.isDetailedOutputDisabled = disableErrorMessages;
|
||||
this.exceptionFactory =
|
||||
options.exceptionFactory ||
|
||||
(errors =>
|
||||
new BadRequestException(
|
||||
this.isDetailedOutputDisabled ? undefined : errors,
|
||||
));
|
||||
|
||||
const loadPkg = (pkg: any) => loadPackage(pkg, 'ValidationPipe');
|
||||
classValidator = loadPkg('class-validator');
|
||||
@@ -43,9 +55,7 @@ export class ValidationPipe implements PipeTransform<any> {
|
||||
);
|
||||
const errors = await classValidator.validate(entity, this.validatorOptions);
|
||||
if (errors.length > 0) {
|
||||
throw new BadRequestException(
|
||||
this.isDetailedOutputDisabled ? undefined : errors,
|
||||
);
|
||||
throw this.exceptionFactory(errors);
|
||||
}
|
||||
return this.isTransformEnabled
|
||||
? entity
|
||||
|
||||
@@ -4,7 +4,11 @@ import { Inject, Injectable } from '../decorators/core';
|
||||
import { ClassTransformOptions } from '../interfaces/external/class-transform-options.interface';
|
||||
import { loadPackage } from '../utils/load-package.util';
|
||||
import { isObject } from '../utils/shared.utils';
|
||||
import { ExecutionContext, NestInterceptor } from './../interfaces';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
NestInterceptor,
|
||||
} from './../interfaces';
|
||||
import { CLASS_SERIALIZER_OPTIONS } from './class-serializer.constants';
|
||||
|
||||
let classTransformer: any = {};
|
||||
@@ -26,16 +30,15 @@ export class ClassSerializerInterceptor implements NestInterceptor {
|
||||
classTransformer = loadPkg('class-transformer');
|
||||
}
|
||||
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const options = this.getContextOptions(context);
|
||||
return call$.pipe(
|
||||
map((res: PlainLiteralObject | Array<PlainLiteralObject>) =>
|
||||
this.serialize(res, options),
|
||||
),
|
||||
);
|
||||
return next
|
||||
.handle()
|
||||
.pipe(
|
||||
map((res: PlainLiteralObject | Array<PlainLiteralObject>) =>
|
||||
this.serialize(res, options),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
serialize(
|
||||
|
||||
@@ -86,8 +86,7 @@ export class Logger implements LoggerService {
|
||||
if (Logger.contextEnvironment === NestEnvironment.TEST) {
|
||||
return;
|
||||
}
|
||||
const output =
|
||||
message && isObject(message) ? JSON.stringify(message, null, 2) : message;
|
||||
const output = isObject(message) ? JSON.stringify(message, null, 2) : message;
|
||||
process.stdout.write(color(`[Nest] ${process.pid} - `));
|
||||
process.stdout.write(`${new Date(Date.now()).toLocaleString()} `);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('Shared utils', () => {
|
||||
});
|
||||
it('should returns false when object is not function', () => {
|
||||
expect(isFunction(null)).to.be.false;
|
||||
expect(isFunction(undefined)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('isObject', () => {
|
||||
@@ -33,6 +34,8 @@ describe('Shared utils', () => {
|
||||
});
|
||||
it('should returns false when object is not object', () => {
|
||||
expect(isObject(3)).to.be.false;
|
||||
expect(isObject(null)).to.be.false;
|
||||
expect(isObject(undefined)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('isString', () => {
|
||||
@@ -41,6 +44,8 @@ describe('Shared utils', () => {
|
||||
});
|
||||
it('should returns false when object is not string', () => {
|
||||
expect(isString(false)).to.be.false;
|
||||
expect(isString(null)).to.be.false;
|
||||
expect(isString(undefined)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('isConstructor', () => {
|
||||
@@ -58,6 +63,11 @@ describe('Shared utils', () => {
|
||||
it('should returns same path', () => {
|
||||
expect(validatePath('/nope')).to.be.eql('/nope');
|
||||
});
|
||||
it('should returns empty path', () => {
|
||||
expect(validatePath('')).to.be.eql('');
|
||||
expect(validatePath(null)).to.be.eql('');
|
||||
expect(validatePath(undefined)).to.be.eql('');
|
||||
});
|
||||
});
|
||||
describe('isNil', () => {
|
||||
it('should returns true when obj is undefined or null', () => {
|
||||
@@ -72,6 +82,7 @@ describe('Shared utils', () => {
|
||||
it('should returns true when array is empty or not exists', () => {
|
||||
expect(isEmpty([])).to.be.true;
|
||||
expect(isEmpty(null)).to.be.true;
|
||||
expect(isEmpty(undefined)).to.be.true;
|
||||
});
|
||||
it('should returns false when array is not empty', () => {
|
||||
expect(isEmpty([1, 2])).to.be.false;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { isObject } from './shared.utils';
|
||||
|
||||
export const createHttpExceptionBody = (
|
||||
message: any,
|
||||
message: object | string,
|
||||
error: string,
|
||||
statusCode: number,
|
||||
) => (message ? { statusCode, error, message } : { statusCode, error });
|
||||
) => {
|
||||
if (!message) {
|
||||
return { statusCode, error };
|
||||
}
|
||||
return isObject(message) ? message : { statusCode, error, message };
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
export const isUndefined = (obj: any): obj is undefined =>
|
||||
typeof obj === 'undefined';
|
||||
export const isObject = (fn: any): fn is object =>
|
||||
!isNil(fn) && typeof fn === 'object';
|
||||
export const validatePath = (path?: string): string =>
|
||||
path ? (path.charAt(0) !== '/' ? '/' + path : path) : '';
|
||||
export const isFunction = (fn: any): boolean => typeof fn === 'function';
|
||||
export const isObject = (fn: any): fn is object => typeof fn === 'object';
|
||||
export const isString = (fn: any): fn is string => typeof fn === 'string';
|
||||
export const isConstructor = (fn: any): boolean => fn === 'constructor';
|
||||
export const validatePath = (path: string): string =>
|
||||
path.charAt(0) !== '/' ? '/' + path : path;
|
||||
export const isNil = (obj: any): boolean => isUndefined(obj) || obj === null;
|
||||
export const isEmpty = (array: any): boolean => !(array && array.length > 0);
|
||||
export const isSymbol = (fn: any): fn is symbol => typeof fn === 'symbol';
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Module } from '../injector/module';
|
||||
* Returns the name of an instance
|
||||
* @param instance The instance which should get the name from
|
||||
*/
|
||||
const getInstanceName = (instance: any) =>
|
||||
const getInstanceName = (instance: unknown) =>
|
||||
instance && (instance as Type<any>).name;
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ export class BaseExceptionFilterContext extends ContextCreator {
|
||||
public createConcreteContext<T extends any[], R extends any[]>(
|
||||
metadata: T,
|
||||
): R {
|
||||
if (isUndefined(metadata) || isEmpty(metadata)) {
|
||||
if (isEmpty(metadata)) {
|
||||
return [] as R;
|
||||
}
|
||||
return iterate(metadata)
|
||||
|
||||
@@ -2,11 +2,7 @@ import { CanActivate } from '@nestjs/common';
|
||||
import { GUARDS_METADATA } from '@nestjs/common/constants';
|
||||
import { Controller } from '@nestjs/common/interfaces';
|
||||
import { ConfigurationProvider } from '@nestjs/common/interfaces/configuration-provider.interface';
|
||||
import {
|
||||
isEmpty,
|
||||
isFunction,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils';
|
||||
import iterate from 'iterare';
|
||||
import { ContextCreator } from '../helpers/context-creator';
|
||||
import { NestContainer } from '../injector/container';
|
||||
@@ -33,7 +29,7 @@ export class GuardsContextCreator extends ContextCreator {
|
||||
public createConcreteContext<T extends any[], R extends any[]>(
|
||||
metadata: T,
|
||||
): R {
|
||||
if (isUndefined(metadata) || isEmpty(metadata)) {
|
||||
if (isEmpty(metadata)) {
|
||||
return [] as R;
|
||||
}
|
||||
return iterate(metadata)
|
||||
@@ -65,7 +61,8 @@ export class GuardsContextCreator extends ContextCreator {
|
||||
if (!module) {
|
||||
return undefined;
|
||||
}
|
||||
return module.injectables.get(guard.name);
|
||||
const injectables = module.injectables;
|
||||
return injectables.get(guard.name);
|
||||
}
|
||||
|
||||
public getGlobalMetadata<T extends any[]>(): T {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenException, ParamData } from '@nestjs/common';
|
||||
import { CUSTOM_ROUTE_AGRS_METADATA } from '@nestjs/common/constants';
|
||||
import { Controller, Transform } from '@nestjs/common/interfaces';
|
||||
import { isFunction, isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
import { isFunction } from '@nestjs/common/utils/shared.utils';
|
||||
import { FORBIDDEN_MESSAGE } from '../guards/constants';
|
||||
import { GuardsConsumer } from '../guards/guards-consumer';
|
||||
import { GuardsContextCreator } from '../guards/guards-context-creator';
|
||||
@@ -169,7 +169,7 @@ export class ExternalContextCreator {
|
||||
factory: (...args: any[]) => void,
|
||||
data: any,
|
||||
): (...args: any[]) => any {
|
||||
return !isUndefined(factory) && isFunction(factory)
|
||||
return isFunction(factory)
|
||||
? (...args: any[]) => factory(data, args)
|
||||
: () => null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { NestInterceptor } from '@nestjs/common';
|
||||
import { Controller } from '@nestjs/common/interfaces';
|
||||
import { CallHandler, Controller } from '@nestjs/common/interfaces';
|
||||
import { isEmpty } from '@nestjs/common/utils/shared.utils';
|
||||
import { defer, from as fromPromise, Observable } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { mergeAll, switchMap } from 'rxjs/operators';
|
||||
import { ExecutionContextHost } from '../helpers/execution-context.host';
|
||||
|
||||
export class InterceptorsConsumer {
|
||||
@@ -18,20 +18,17 @@ export class InterceptorsConsumer {
|
||||
}
|
||||
const context = this.createContext(args, instance, callback);
|
||||
const start$ = defer(() => this.transformDeffered(next));
|
||||
/***
|
||||
const nextFn = (i: number) => async () => {
|
||||
if (i <= interceptors.length) {
|
||||
|
||||
const nextFn = (i = 0) => async () => {
|
||||
if (i >= interceptors.length) {
|
||||
return start$;
|
||||
}
|
||||
return await interceptors[i].intercept(context, nextFn(i + 1) as any);
|
||||
const handler: CallHandler = {
|
||||
handle: () => fromPromise(nextFn(i + 1)()).pipe(mergeAll()),
|
||||
};
|
||||
return interceptors[i].intercept(context, handler);
|
||||
};
|
||||
*/
|
||||
const result$ = await interceptors.reduce(
|
||||
async (stream$, interceptor) =>
|
||||
interceptor.intercept(context, await stream$),
|
||||
Promise.resolve(start$),
|
||||
);
|
||||
return result$.toPromise();
|
||||
return nextFn()();
|
||||
}
|
||||
|
||||
public createContext(
|
||||
|
||||
@@ -32,7 +32,7 @@ export class InterceptorsContextCreator extends ContextCreator {
|
||||
public createConcreteContext<T extends any[], R extends any[]>(
|
||||
metadata: T,
|
||||
): R {
|
||||
if (isUndefined(metadata) || isEmpty(metadata)) {
|
||||
if (isEmpty(metadata)) {
|
||||
return [] as R;
|
||||
}
|
||||
return iterate(metadata)
|
||||
|
||||
@@ -198,7 +198,7 @@ export class MiddlewareModule {
|
||||
) {
|
||||
const proxy = this.routerProxy.createProxy(middleware, exceptionsHandler);
|
||||
const prefix = this.config.getGlobalPrefix();
|
||||
const basePath = prefix ? validatePath(prefix) : '';
|
||||
const basePath = validatePath(prefix);
|
||||
router(basePath + path, proxy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export class NestApplication extends NestApplicationContext
|
||||
await this.registerMiddleware(this.httpAdapter);
|
||||
|
||||
const prefix = this.config.getGlobalPrefix();
|
||||
const basePath = prefix ? validatePath(prefix) : '';
|
||||
const basePath = validatePath(prefix);
|
||||
this.routesResolver.resolve(this.httpAdapter, basePath);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/core",
|
||||
"version": "5.4.1",
|
||||
"version": "5.5.0",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@core)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -36,7 +36,7 @@ export class PipesContextCreator extends ContextCreator {
|
||||
public createConcreteContext<T extends any[], R extends any[]>(
|
||||
metadata: T,
|
||||
): R {
|
||||
if (isUndefined(metadata) || isEmpty(metadata)) {
|
||||
if (isEmpty(metadata)) {
|
||||
return [] as R;
|
||||
}
|
||||
return iterate(metadata)
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
isEmpty,
|
||||
isFunction,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from '@nestjs/common/utils/shared.utils';
|
||||
import { FORBIDDEN_MESSAGE } from '../guards/constants';
|
||||
import { GuardsConsumer } from '../guards/guards-consumer';
|
||||
@@ -191,7 +190,7 @@ export class RouterExecutionContext {
|
||||
factory: (...args: any[]) => void,
|
||||
data: any,
|
||||
): (...args: any[]) => any {
|
||||
return !isUndefined(factory) && isFunction(factory)
|
||||
return isFunction(factory)
|
||||
? (req, res, next) => factory(data, req)
|
||||
: () => null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { of } from 'rxjs';
|
||||
import * as sinon from 'sinon';
|
||||
import { InterceptorsConsumer } from '../../interceptors/interceptors-consumer';
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
|
||||
|
||||
describe('InterceptorsConsumer', () => {
|
||||
let consumer: InterceptorsConsumer;
|
||||
@@ -11,10 +10,12 @@ describe('InterceptorsConsumer', () => {
|
||||
consumer = new InterceptorsConsumer();
|
||||
interceptors = [
|
||||
{
|
||||
intercept: sinon.stub().returns(of(true)),
|
||||
intercept: sinon.stub().callsFake((ctx, handler) => handler.handle()),
|
||||
},
|
||||
{
|
||||
intercept: sinon.stub().returns(of(true)),
|
||||
intercept: sinon
|
||||
.stub()
|
||||
.callsFake(async (ctx, handler) => handler.handle()),
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -73,18 +74,18 @@ describe('InterceptorsConsumer', () => {
|
||||
it('should return Observable', async () => {
|
||||
const val = 3;
|
||||
const next = async () => val;
|
||||
expect(
|
||||
await consumer.transformDeffered(next).toPromise(),
|
||||
).to.be.eql(val);
|
||||
expect(await consumer.transformDeffered(next).toPromise()).to.be.eql(
|
||||
val,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('when next() result is Promise', () => {
|
||||
it('should return Observable', async () => {
|
||||
const val = 3;
|
||||
const next = async () => val;
|
||||
expect(
|
||||
await consumer.transformDeffered(next).toPromise(),
|
||||
).to.be.eql(val);
|
||||
expect(await consumer.transformDeffered(next).toPromise()).to.be.eql(
|
||||
val,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('when next() result is Observable', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common/services/logger.service';
|
||||
import { loadPackage } from '@nestjs/common/utils/load-package.util';
|
||||
import { isObject } from '@nestjs/common/utils/shared.utils';
|
||||
import { Observable } from 'rxjs';
|
||||
import { GRPC_DEFAULT_URL } from '../constants';
|
||||
import { GRPC_DEFAULT_PROTO_LOADER, GRPC_DEFAULT_URL } from '../constants';
|
||||
import { InvalidGrpcPackageException } from '../errors/invalid-grpc-package.exception';
|
||||
import { InvalidGrpcServiceException } from '../errors/invalid-grpc-service.exception';
|
||||
import { InvalidProtoDefinitionException } from '../errors/invalid-proto-definition.exception';
|
||||
@@ -24,11 +24,12 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
|
||||
this.url =
|
||||
this.getOptionsProp<GrpcOptions>(options, 'url') || GRPC_DEFAULT_URL;
|
||||
|
||||
const protoLoader =
|
||||
this.getOptionsProp<GrpcOptions>(options, 'protoLoader') ||
|
||||
GRPC_DEFAULT_PROTO_LOADER;
|
||||
|
||||
grpcPackage = loadPackage('grpc', ClientGrpcProxy.name);
|
||||
grpcProtoLoaderPackage = loadPackage(
|
||||
'@grpc/proto-loader',
|
||||
ClientGrpcProxy.name,
|
||||
);
|
||||
grpcProtoLoaderPackage = loadPackage(protoLoader, ClientGrpcProxy.name);
|
||||
this.grpcClient = this.createClient();
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,6 @@ export abstract class ClientProxy {
|
||||
}
|
||||
|
||||
protected normalizePattern<T = any>(pattern: T): string {
|
||||
return pattern && isString(pattern) ? pattern : JSON.stringify(pattern);
|
||||
return isString(pattern) ? pattern : JSON.stringify(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,3 +25,5 @@ export const RQM_DEFAULT_QUEUE = 'default';
|
||||
export const RQM_DEFAULT_PREFETCH_COUNT = 0;
|
||||
export const RQM_DEFAULT_IS_GLOBAL_PREFETCH_COUNT = false;
|
||||
export const RQM_DEFAULT_QUEUE_OPTIONS = {};
|
||||
|
||||
export const GRPC_DEFAULT_PROTO_LOADER = '@grpc/proto-loader';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { isFunction } from '@nestjs/common/utils/shared.utils';
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
|
||||
import { Observable } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { RpcExceptionsHandler } from '../exceptions/rpc-exceptions-handler';
|
||||
|
||||
export class RpcProxy {
|
||||
@@ -9,11 +11,30 @@ export class RpcProxy {
|
||||
): (...args: any[]) => Promise<Observable<any>> {
|
||||
return async (...args: any[]) => {
|
||||
try {
|
||||
return await targetCallback(...args);
|
||||
} catch (e) {
|
||||
const host = new ExecutionContextHost(args);
|
||||
return exceptionsHandler.handle(e, host);
|
||||
const result = await targetCallback(...args);
|
||||
return !this.isObservable(result)
|
||||
? result
|
||||
: result.pipe(
|
||||
catchError(error =>
|
||||
this.handleError(exceptionsHandler, args, error),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
return this.handleError(exceptionsHandler, args, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleError<T>(
|
||||
exceptionsHandler: RpcExceptionsHandler,
|
||||
args: any[],
|
||||
error: T,
|
||||
): Observable<any> {
|
||||
const host = new ExecutionContextHost(args);
|
||||
return exceptionsHandler.handle(error, host);
|
||||
}
|
||||
|
||||
isObservable(result: any): boolean {
|
||||
return result && isFunction(result.subscribe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export * from './client-grpc.interface';
|
||||
export * from './client-metadata.interface';
|
||||
export * from './microservice-configuration.interface';
|
||||
export * from './pattern-metadata.interface';
|
||||
export * from './custom-transport-strategy.interface';
|
||||
export * from './message-handlers.interface';
|
||||
export * from './closeable.interface';
|
||||
export * from './custom-transport-strategy.interface';
|
||||
export * from './message-handler.interface';
|
||||
export * from './microservice-configuration.interface';
|
||||
export * from './packet.interface';
|
||||
export * from './client-grpc.interface';
|
||||
export * from './pattern-metadata.interface';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export type MessageHandler<TInput = any, TResult = any> = (
|
||||
data: TInput,
|
||||
) => Promise<Observable<TResult>>;
|
||||
@@ -24,6 +24,7 @@ export interface GrpcOptions {
|
||||
credentials?: any;
|
||||
protoPath: string;
|
||||
package: string;
|
||||
protoLoader?: string;
|
||||
loader?: {
|
||||
keepCase?: boolean;
|
||||
alternateCommentMode?: boolean;
|
||||
|
||||
@@ -10,6 +10,16 @@ import {
|
||||
import { ClientOptions } from './interfaces/client-metadata.interface';
|
||||
import { PatternMetadata } from './interfaces/pattern-metadata.interface';
|
||||
|
||||
export interface ClientProperties {
|
||||
property: string;
|
||||
metadata: ClientOptions;
|
||||
}
|
||||
|
||||
export interface PatternProperties {
|
||||
pattern: PatternMetadata;
|
||||
targetCallback: (...args: any[]) => any;
|
||||
}
|
||||
|
||||
export class ListenerMetadataExplorer {
|
||||
constructor(private readonly metadataScanner: MetadataScanner) {}
|
||||
|
||||
@@ -63,13 +73,3 @@ export class ListenerMetadataExplorer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ClientProperties {
|
||||
property: string;
|
||||
metadata: ClientOptions;
|
||||
}
|
||||
|
||||
export interface PatternProperties {
|
||||
pattern: PatternMetadata;
|
||||
targetCallback: (...args: any[]) => any;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/microservices",
|
||||
"version": "5.4.1",
|
||||
"version": "5.5.0",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@microservices)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { fromEvent } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { CANCEL_EVENT, GRPC_DEFAULT_URL } from '../constants';
|
||||
import {
|
||||
CANCEL_EVENT,
|
||||
GRPC_DEFAULT_PROTO_LOADER,
|
||||
GRPC_DEFAULT_URL,
|
||||
} from '../constants';
|
||||
import { InvalidGrpcPackageException } from '../errors/invalid-grpc-package.exception';
|
||||
import { InvalidProtoDefinitionException } from '../errors/invalid-proto-definition.exception';
|
||||
import { CustomTransportStrategy } from '../interfaces';
|
||||
@@ -28,11 +32,12 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
this.url =
|
||||
this.getOptionsProp<GrpcOptions>(options, 'url') || GRPC_DEFAULT_URL;
|
||||
|
||||
const protoLoader =
|
||||
this.getOptionsProp<GrpcOptions>(options, 'protoLoader') ||
|
||||
GRPC_DEFAULT_PROTO_LOADER;
|
||||
|
||||
grpcPackage = this.loadPackage('grpc', ServerGrpc.name);
|
||||
grpcProtoLoaderPackage = this.loadPackage(
|
||||
'@grpc/proto-loader',
|
||||
ServerGrpc.name,
|
||||
);
|
||||
grpcProtoLoaderPackage = this.loadPackage(protoLoader, ServerGrpc.name);
|
||||
}
|
||||
|
||||
public async listen(callback: () => void) {
|
||||
@@ -75,9 +80,9 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
|
||||
|
||||
// tslint:disable-next-line:forin
|
||||
for (const methodName in grpcService.prototype) {
|
||||
const methodHandler = this.messageHandlers[
|
||||
this.createPattern(name, methodName)
|
||||
];
|
||||
const methodHandler = this.getHandlerByPattern(
|
||||
this.createPattern(name, methodName),
|
||||
);
|
||||
if (!methodHandler) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
|
||||
|
||||
public bindEvents(mqttClient: MqttClient) {
|
||||
mqttClient.on(MESSAGE_EVENT, this.getMessageHandler(mqttClient).bind(this));
|
||||
const registeredPatterns = Object.keys(this.messageHandlers);
|
||||
const registeredPatterns = [...this.messageHandlers.keys()];
|
||||
registeredPatterns.forEach(pattern =>
|
||||
mqttClient.subscribe(this.getAckQueueName(pattern)),
|
||||
);
|
||||
@@ -56,7 +56,7 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
|
||||
return mqttPackage.connect(this.url, this.options as MqttOptions);
|
||||
}
|
||||
|
||||
public getMessageHandler(pub: MqttClient): any {
|
||||
public getMessageHandler(pub: MqttClient): Function {
|
||||
return async (channel: string, buffer: Buffer) =>
|
||||
this.handleMessage(channel, buffer, pub);
|
||||
}
|
||||
@@ -69,12 +69,12 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
|
||||
const packet = this.deserialize(buffer.toString());
|
||||
const pattern = channel.replace(/_ack$/, '');
|
||||
const publish = this.getPublisher(pub, pattern, packet.id);
|
||||
const status = 'error';
|
||||
const handler = this.getHandlerByPattern(pattern);
|
||||
|
||||
if (!this.messageHandlers[pattern]) {
|
||||
if (!handler) {
|
||||
const status = 'error';
|
||||
return publish({ id: packet.id, status, err: NO_PATTERN_MESSAGE });
|
||||
}
|
||||
const handler = this.messageHandlers[pattern];
|
||||
const response$ = this.transformToObservable(
|
||||
await handler(packet.data),
|
||||
) as Observable<any>;
|
||||
|
||||
@@ -41,20 +41,20 @@ export class ServerNats extends Server implements CustomTransportStrategy {
|
||||
|
||||
public bindEvents(client: Client) {
|
||||
const queue = this.getOptionsProp<NatsOptions>(this.options, 'queue');
|
||||
const subscribe = (channel: string) => {
|
||||
if (queue) {
|
||||
return client.subscribe(
|
||||
channel,
|
||||
{ queue },
|
||||
this.getMessageHandler(channel, client).bind(this),
|
||||
);
|
||||
}
|
||||
client.subscribe(
|
||||
channel,
|
||||
this.getMessageHandler(channel, client).bind(this),
|
||||
);
|
||||
};
|
||||
const registeredPatterns = Object.keys(this.messageHandlers);
|
||||
const subscribe = queue
|
||||
? (channel: string) =>
|
||||
client.subscribe(
|
||||
channel,
|
||||
{ queue },
|
||||
this.getMessageHandler(channel, client).bind(this),
|
||||
)
|
||||
: (channel: string) =>
|
||||
client.subscribe(
|
||||
channel,
|
||||
this.getMessageHandler(channel, client).bind(this),
|
||||
);
|
||||
|
||||
const registeredPatterns = [...this.messageHandlers.keys()];
|
||||
registeredPatterns.forEach(channel => subscribe(channel));
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export class ServerNats extends Server implements CustomTransportStrategy {
|
||||
});
|
||||
}
|
||||
|
||||
public getMessageHandler(channel: string, client: Client) {
|
||||
public getMessageHandler(channel: string, client: Client): Function {
|
||||
return async (buffer: ReadPacket & PacketId, replyTo: string) =>
|
||||
this.handleMessage(channel, buffer, client, replyTo);
|
||||
}
|
||||
@@ -84,12 +84,11 @@ export class ServerNats extends Server implements CustomTransportStrategy {
|
||||
replyTo: string,
|
||||
) {
|
||||
const publish = this.getPublisher(client, replyTo, message.id);
|
||||
const status = 'error';
|
||||
|
||||
if (!this.messageHandlers[channel]) {
|
||||
const handler = this.getHandlerByPattern(channel);
|
||||
if (!handler) {
|
||||
const status = 'error';
|
||||
return publish({ id: message.id, status, err: NO_PATTERN_MESSAGE });
|
||||
}
|
||||
const handler = this.messageHandlers[channel];
|
||||
const response$ = this.transformToObservable(
|
||||
await handler(message.data),
|
||||
) as Observable<any>;
|
||||
|
||||
@@ -51,7 +51,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
|
||||
public bindEvents(subClient: RedisClient, pubClient: RedisClient) {
|
||||
subClient.on(MESSAGE_EVENT, this.getMessageHandler(pubClient).bind(this));
|
||||
const subscribePatterns = Object.keys(this.messageHandlers);
|
||||
const subscribePatterns = [...this.messageHandlers.keys()];
|
||||
subscribePatterns.forEach(pattern =>
|
||||
subClient.subscribe(this.getAckQueueName(pattern)),
|
||||
);
|
||||
@@ -83,12 +83,12 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
|
||||
const packet = this.deserialize(buffer);
|
||||
const pattern = channel.replace(/_ack$/, '');
|
||||
const publish = this.getPublisher(pub, pattern, packet.id);
|
||||
const status = 'error';
|
||||
const handler = this.getHandlerByPattern(pattern);
|
||||
|
||||
if (!this.messageHandlers[pattern]) {
|
||||
if (!handler) {
|
||||
const status = 'error';
|
||||
return publish({ id: packet.id, status, err: NO_PATTERN_MESSAGE });
|
||||
}
|
||||
const handler = this.messageHandlers[pattern];
|
||||
const response$ = this.transformToObservable(
|
||||
await handler(packet.data),
|
||||
) as Observable<any>;
|
||||
|
||||
@@ -53,16 +53,16 @@ export class ServerTCP extends Server implements CustomTransportStrategy {
|
||||
const pattern = !isString(packet.pattern)
|
||||
? JSON.stringify(packet.pattern)
|
||||
: packet.pattern;
|
||||
const status = 'error';
|
||||
const handler = this.getHandlerByPattern(pattern);
|
||||
|
||||
if (!this.messageHandlers[pattern]) {
|
||||
if (!handler) {
|
||||
const status = 'error';
|
||||
return socket.sendMessage({
|
||||
id: packet.id,
|
||||
status,
|
||||
err: NO_PATTERN_MESSAGE,
|
||||
});
|
||||
}
|
||||
const handler = this.messageHandlers[pattern];
|
||||
const response$ = this.transformToObservable(
|
||||
await handler(packet.data),
|
||||
) as Observable<any>;
|
||||
|
||||
@@ -9,11 +9,14 @@ import {
|
||||
Subscription,
|
||||
} from 'rxjs';
|
||||
import { catchError, finalize } from 'rxjs/operators';
|
||||
import { MicroserviceOptions, WritePacket } from '../interfaces';
|
||||
import { MessageHandlers } from '../interfaces/message-handlers.interface';
|
||||
import {
|
||||
MessageHandler,
|
||||
MicroserviceOptions,
|
||||
WritePacket,
|
||||
} from '../interfaces';
|
||||
|
||||
export abstract class Server {
|
||||
protected readonly messageHandlers: MessageHandlers = {};
|
||||
protected readonly messageHandlers = new Map<string, MessageHandler>();
|
||||
protected readonly logger = new Logger(Server.name);
|
||||
|
||||
public addHandler(
|
||||
@@ -21,17 +24,17 @@ export abstract class Server {
|
||||
callback: <T>(data: T) => Promise<Observable<any>>,
|
||||
) {
|
||||
const key = isString(pattern) ? pattern : JSON.stringify(pattern);
|
||||
this.messageHandlers[key] = callback;
|
||||
this.messageHandlers.set(key, callback);
|
||||
}
|
||||
|
||||
public getHandlers(): MessageHandlers {
|
||||
public getHandlers(): Map<string, MessageHandler> {
|
||||
return this.messageHandlers;
|
||||
}
|
||||
|
||||
public getHandlerByPattern(
|
||||
pattern: string,
|
||||
): <T>(data: T) => Promise<Observable<any>> | null {
|
||||
return this.messageHandlers[pattern] ? this.messageHandlers[pattern] : null;
|
||||
public getHandlerByPattern(pattern: string): MessageHandler | null {
|
||||
return this.messageHandlers.has(pattern)
|
||||
? this.messageHandlers.get(pattern)
|
||||
: null;
|
||||
}
|
||||
|
||||
public send(
|
||||
@@ -70,7 +73,7 @@ export abstract class Server {
|
||||
this.logger.error(error);
|
||||
}
|
||||
|
||||
protected loadPackage(name: string, ctx: string) {
|
||||
protected loadPackage<T = any>(name: string, ctx: string): T {
|
||||
return loadPackage(name, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import * as sinon from 'sinon';
|
||||
import { RpcProxy } from '../../context/rpc-proxy';
|
||||
import { RpcExceptionsHandler } from '../../exceptions/rpc-exceptions-handler';
|
||||
import { RpcException } from '../../exceptions/rpc-exception';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { RpcExceptionsHandler } from '../../exceptions/rpc-exceptions-handler';
|
||||
|
||||
describe('RpcProxy', () => {
|
||||
let routerProxy: RpcProxy;
|
||||
@@ -18,10 +18,7 @@ describe('RpcProxy', () => {
|
||||
|
||||
describe('create', () => {
|
||||
it('should method return thunk', async () => {
|
||||
const proxy = await routerProxy.create(
|
||||
async data => of(true),
|
||||
handler,
|
||||
);
|
||||
const proxy = await routerProxy.create(async data => of(true), handler);
|
||||
expect(typeof proxy === 'function').to.be.true;
|
||||
});
|
||||
|
||||
@@ -33,5 +30,26 @@ describe('RpcProxy', () => {
|
||||
await proxy(null);
|
||||
expectation.verify();
|
||||
});
|
||||
|
||||
it('should attach "catchError" operator when observable was returned', async () => {
|
||||
const expectation = handlerMock.expects('handle').once();
|
||||
const proxy = routerProxy.create(async (client, data) => {
|
||||
return throwError(new RpcException('test'));
|
||||
}, handler);
|
||||
(await proxy(null, null)).subscribe(null, () => expectation.verify());
|
||||
});
|
||||
});
|
||||
|
||||
describe('isObservable', () => {
|
||||
describe('when observable', () => {
|
||||
it('should return true', () => {
|
||||
expect(routerProxy.isObservable(of('test'))).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('when not observable', () => {
|
||||
it('should return false', () => {
|
||||
expect(routerProxy.isObservable({})).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,10 +80,12 @@ describe('ServerGrpc', () => {
|
||||
|
||||
describe('createService', () => {
|
||||
it('should call "createServiceMethod"', async () => {
|
||||
const handlers = {
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
const handlers = objectToMap({
|
||||
test: null,
|
||||
test2: () => ({}),
|
||||
};
|
||||
});
|
||||
sinon
|
||||
.stub(server, 'createPattern')
|
||||
.onFirstCall()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
import { NO_PATTERN_MESSAGE } from '../../constants';
|
||||
import { ServerMqtt } from '../../server/server-mqtt';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
describe('ServerMqtt', () => {
|
||||
let server: ServerMqtt;
|
||||
@@ -59,9 +58,12 @@ describe('ServerMqtt', () => {
|
||||
it('should subscribe each acknowledge patterns', () => {
|
||||
const pattern = 'test';
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[pattern]: handler,
|
||||
};
|
||||
});
|
||||
server.bindEvents(mqttClient);
|
||||
|
||||
const expectedPattern = 'test_ack';
|
||||
@@ -111,9 +113,12 @@ describe('ServerMqtt', () => {
|
||||
});
|
||||
it(`should call handler with expected arguments`, () => {
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[channel]: handler,
|
||||
};
|
||||
});
|
||||
|
||||
server.handleMessage(
|
||||
channel,
|
||||
|
||||
@@ -52,12 +52,16 @@ describe('ServerNats', () => {
|
||||
subscribe: subscribeSpy,
|
||||
};
|
||||
});
|
||||
it('should subscribe each acknowledge patterns', () => {
|
||||
it('should subscribe to each acknowledge patterns', () => {
|
||||
const pattern = 'test';
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[pattern]: handler,
|
||||
};
|
||||
});
|
||||
server.bindEvents(natsClient);
|
||||
expect(subscribeSpy.calledWith(pattern)).to.be.true;
|
||||
});
|
||||
@@ -104,9 +108,12 @@ describe('ServerNats', () => {
|
||||
});
|
||||
it(`should call handler with expected arguments`, () => {
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[channel]: handler,
|
||||
};
|
||||
});
|
||||
|
||||
server.handleMessage(channel, { pattern: '', data, id: '2' }, null, '');
|
||||
expect(handler.calledWith(data)).to.be.true;
|
||||
|
||||
@@ -66,9 +66,12 @@ describe('ServerRedis', () => {
|
||||
it('should subscribe each acknowledge patterns', () => {
|
||||
const pattern = 'test';
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[pattern]: handler,
|
||||
};
|
||||
});
|
||||
server.bindEvents(sub, null);
|
||||
|
||||
const expectedPattern = 'test_ack';
|
||||
@@ -104,9 +107,12 @@ describe('ServerRedis', () => {
|
||||
});
|
||||
it(`should call handler with expected arguments`, () => {
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[channel]: handler,
|
||||
};
|
||||
});
|
||||
|
||||
server.handleMessage(channel, {}, null);
|
||||
expect(handler.calledWith(data)).to.be.true;
|
||||
|
||||
@@ -93,9 +93,12 @@ describe('ServerRMQ', () => {
|
||||
});
|
||||
it('should call handler if exists in handlers object', async () => {
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[JSON.stringify(pattern)]: handler as any,
|
||||
};
|
||||
});
|
||||
await server.handleMessage(msg);
|
||||
expect(handler.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
@@ -68,9 +68,12 @@ describe('ServerTCP', () => {
|
||||
});
|
||||
it('should call handler if exists in handlers object', () => {
|
||||
const handler = sinon.spy();
|
||||
(server as any).messageHandlers = {
|
||||
const objectToMap = obj =>
|
||||
new Map(Object.keys(obj).map(key => [key, obj[key]]) as any);
|
||||
|
||||
(server as any).messageHandlers = objectToMap({
|
||||
[msg.pattern]: handler as any,
|
||||
};
|
||||
});
|
||||
server.handleMessage(socket, msg);
|
||||
expect(handler.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { Server } from '../../server/server';
|
||||
import { Observable, of, throwError as _throw } from 'rxjs';
|
||||
import * as sinon from 'sinon';
|
||||
import { Server } from '../../server/server';
|
||||
|
||||
class TestServer extends Server {
|
||||
public listen(callback: () => void) {}
|
||||
@@ -18,13 +18,13 @@ describe('Server', () => {
|
||||
server.addHandler(pattern, callback as any);
|
||||
|
||||
const handlers = server.getHandlers();
|
||||
expect(handlers[JSON.stringify(pattern)]).to.equal(callback);
|
||||
expect(handlers.get(JSON.stringify(pattern))).to.equal(callback);
|
||||
});
|
||||
|
||||
it(`should add handler as string pattern key`, () => {
|
||||
server.addHandler(pattern.test, callback as any);
|
||||
const handlers = server.getHandlers();
|
||||
expect(handlers[pattern.test]).to.equal(callback);
|
||||
expect(handlers.get(pattern.test)).to.equal(callback);
|
||||
});
|
||||
});
|
||||
describe('send', () => {
|
||||
@@ -96,17 +96,17 @@ describe('Server', () => {
|
||||
describe('getHandlerByPattern', () => {
|
||||
describe('when handler exists', () => {
|
||||
it('should return expected handler', () => {
|
||||
const pattern = 'pattern';
|
||||
const channel = 'pattern';
|
||||
const expectedResult = {};
|
||||
(server as any).messageHandlers[pattern] = expectedResult;
|
||||
expect(server.getHandlerByPattern(pattern)).to.be.eql(expectedResult);
|
||||
const handlers = new Map([[channel, expectedResult]]);
|
||||
(server as any).messageHandlers = handlers;
|
||||
expect(server.getHandlerByPattern(channel)).to.be.eql(expectedResult);
|
||||
});
|
||||
});
|
||||
describe('when handler does not exists', () => {
|
||||
it('should return null', () => {
|
||||
const pattern = 'test';
|
||||
const expectedResult = null;
|
||||
expect(server.getHandlerByPattern(pattern)).to.be.eql(null);
|
||||
const channel = 'test';
|
||||
expect(server.getHandlerByPattern(channel)).to.be.eql(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
mixin,
|
||||
@@ -38,7 +39,7 @@ export function FileFieldsInterceptor(
|
||||
|
||||
async intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
next: CallHandler,
|
||||
): Promise<Observable<any>> {
|
||||
const ctx = context.switchToHttp();
|
||||
|
||||
@@ -55,7 +56,7 @@ export function FileFieldsInterceptor(
|
||||
},
|
||||
),
|
||||
);
|
||||
return call$;
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
const Interceptor = mixin(MixinInterceptor);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
mixin,
|
||||
@@ -35,7 +36,7 @@ export function FileInterceptor(
|
||||
|
||||
async intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
next: CallHandler,
|
||||
): Promise<Observable<any>> {
|
||||
const ctx = context.switchToHttp();
|
||||
|
||||
@@ -52,7 +53,7 @@ export function FileInterceptor(
|
||||
},
|
||||
),
|
||||
);
|
||||
return call$;
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
const Interceptor = mixin(MixinInterceptor);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
mixin,
|
||||
@@ -36,7 +37,7 @@ export function FilesInterceptor(
|
||||
|
||||
async intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
next: CallHandler,
|
||||
): Promise<Observable<any>> {
|
||||
const ctx = context.switchToHttp();
|
||||
|
||||
@@ -53,7 +54,7 @@ export function FilesInterceptor(
|
||||
},
|
||||
),
|
||||
);
|
||||
return call$;
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
const Interceptor = mixin(MixinInterceptor);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CallHandler } from '@nestjs/common';
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
|
||||
import { expect } from 'chai';
|
||||
import { of } from 'rxjs';
|
||||
@@ -13,9 +14,11 @@ describe('FileFieldsInterceptor', () => {
|
||||
expect(targetClass.prototype.intercept).to.not.be.undefined;
|
||||
});
|
||||
describe('intercept', () => {
|
||||
let stream$;
|
||||
let handler: CallHandler;
|
||||
beforeEach(() => {
|
||||
stream$ = of('test');
|
||||
handler = {
|
||||
handle: () => of('test'),
|
||||
};
|
||||
});
|
||||
it('should call object with expected params', async () => {
|
||||
const fieldName1 = 'file';
|
||||
@@ -33,7 +36,7 @@ describe('FileFieldsInterceptor', () => {
|
||||
.stub((target as any).multer, 'fields')
|
||||
.returns(callback);
|
||||
|
||||
await target.intercept(new ExecutionContextHost([]), stream$);
|
||||
await target.intercept(new ExecutionContextHost([]), handler);
|
||||
|
||||
expect(fieldsSpy.called).to.be.true;
|
||||
expect(fieldsSpy.calledWith(argument)).to.be.true;
|
||||
@@ -55,7 +58,7 @@ describe('FileFieldsInterceptor', () => {
|
||||
array: () => callback,
|
||||
};
|
||||
expect(
|
||||
target.intercept(new ExecutionContextHost([]), stream$),
|
||||
target.intercept(new ExecutionContextHost([]), handler),
|
||||
).to.eventually.throw();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CallHandler } from '@nestjs/common';
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
|
||||
import { expect } from 'chai';
|
||||
import { of } from 'rxjs';
|
||||
@@ -10,9 +11,11 @@ describe('FileInterceptor', () => {
|
||||
expect(targetClass.prototype.intercept).to.not.be.undefined;
|
||||
});
|
||||
describe('intercept', () => {
|
||||
let stream$;
|
||||
let handler: CallHandler;
|
||||
beforeEach(() => {
|
||||
stream$ = of('test');
|
||||
handler = {
|
||||
handle: () => of('test'),
|
||||
};
|
||||
});
|
||||
it('should call single() with expected params', async () => {
|
||||
const fieldName = 'file';
|
||||
@@ -22,7 +25,7 @@ describe('FileInterceptor', () => {
|
||||
.stub((target as any).multer, 'single')
|
||||
.returns(callback);
|
||||
|
||||
await target.intercept(new ExecutionContextHost([]), stream$);
|
||||
await target.intercept(new ExecutionContextHost([]), handler);
|
||||
|
||||
expect(singleSpy.called).to.be.true;
|
||||
expect(singleSpy.calledWith(fieldName)).to.be.true;
|
||||
@@ -37,7 +40,7 @@ describe('FileInterceptor', () => {
|
||||
single: () => callback,
|
||||
};
|
||||
expect(
|
||||
target.intercept(new ExecutionContextHost([]), stream$),
|
||||
target.intercept(new ExecutionContextHost([]), handler),
|
||||
).to.eventually.throw();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CallHandler } from '@nestjs/common';
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
|
||||
import { expect } from 'chai';
|
||||
import { of } from 'rxjs';
|
||||
@@ -10,9 +11,11 @@ describe('FilesInterceptor', () => {
|
||||
expect(targetClass.prototype.intercept).to.not.be.undefined;
|
||||
});
|
||||
describe('intercept', () => {
|
||||
let stream$;
|
||||
let handler: CallHandler;
|
||||
beforeEach(() => {
|
||||
stream$ = of('test');
|
||||
handler = {
|
||||
handle: () => of('test'),
|
||||
};
|
||||
});
|
||||
it('should call array() with expected params', async () => {
|
||||
const fieldName = 'file';
|
||||
@@ -24,7 +27,7 @@ describe('FilesInterceptor', () => {
|
||||
.stub((target as any).multer, 'array')
|
||||
.returns(callback);
|
||||
|
||||
await target.intercept(new ExecutionContextHost([]), stream$);
|
||||
await target.intercept(new ExecutionContextHost([]), handler);
|
||||
|
||||
expect(arraySpy.called).to.be.true;
|
||||
expect(arraySpy.calledWith(fieldName, maxCount)).to.be.true;
|
||||
@@ -39,7 +42,7 @@ describe('FilesInterceptor', () => {
|
||||
array: () => callback,
|
||||
};
|
||||
expect(
|
||||
target.intercept(new ExecutionContextHost([]), stream$),
|
||||
target.intercept(new ExecutionContextHost([]), handler),
|
||||
).to.eventually.throw();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/testing",
|
||||
"version": "5.4.1",
|
||||
"version": "5.5.0",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@testing)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": false,
|
||||
"removeComments": false,
|
||||
|
||||
@@ -1,18 +1,41 @@
|
||||
import { isFunction } from '@nestjs/common/utils/shared.utils';
|
||||
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
|
||||
import { empty } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { WsExceptionsHandler } from '../exceptions/ws-exceptions-handler';
|
||||
|
||||
export class WsProxy {
|
||||
public create(
|
||||
targetCallback: (...args: any[]) => Promise<void>,
|
||||
targetCallback: (...args: any[]) => Promise<any>,
|
||||
exceptionsHandler: WsExceptionsHandler,
|
||||
): (...args: any[]) => Promise<void> {
|
||||
): (...args: any[]) => Promise<any> {
|
||||
return async (...args: any[]) => {
|
||||
try {
|
||||
return await targetCallback(...args);
|
||||
} catch (e) {
|
||||
const host = new ExecutionContextHost(args);
|
||||
exceptionsHandler.handle(e, host);
|
||||
const result = await targetCallback(...args);
|
||||
return !this.isObservable(result)
|
||||
? result
|
||||
: result.pipe(
|
||||
catchError(error => {
|
||||
this.handleError(exceptionsHandler, args, error);
|
||||
return empty();
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
this.handleError(exceptionsHandler, args, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleError<T>(
|
||||
exceptionsHandler: WsExceptionsHandler,
|
||||
args: any[],
|
||||
error: T,
|
||||
) {
|
||||
const host = new ExecutionContextHost(args);
|
||||
exceptionsHandler.handle(error, host);
|
||||
}
|
||||
|
||||
isObservable(result: any): boolean {
|
||||
return result && isFunction(result.subscribe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ export class ObservableSocket {
|
||||
const init = new ReplaySubject<T>();
|
||||
init.next(server);
|
||||
|
||||
const connection = new Subject();
|
||||
const disconnect = new Subject();
|
||||
return {
|
||||
init,
|
||||
connection: new Subject(),
|
||||
disconnect: new Subject(),
|
||||
connection,
|
||||
disconnect,
|
||||
server,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nestjs/websockets",
|
||||
"version": "5.4.1",
|
||||
"version": "5.5.0",
|
||||
"description": "Nest - modern, fast, powerful node.js web framework (@websockets)",
|
||||
"author": "Kamil Mysliwiec",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { of, throwError } from 'rxjs';
|
||||
import * as sinon from 'sinon';
|
||||
import { WsProxy } from '../../context/ws-proxy';
|
||||
import { WsExceptionsHandler } from '../../exceptions/ws-exceptions-handler';
|
||||
import { WsException } from '../../errors/ws-exception';
|
||||
import { WsExceptionsHandler } from '../../exceptions/ws-exceptions-handler';
|
||||
|
||||
describe('WsProxy', () => {
|
||||
let routerProxy: WsProxy;
|
||||
@@ -30,5 +31,26 @@ describe('WsProxy', () => {
|
||||
await proxy(null, null);
|
||||
expectation.verify();
|
||||
});
|
||||
|
||||
it('should attach "catchError" operator when observable was returned', async () => {
|
||||
const expectation = handlerMock.expects('handle').once();
|
||||
const proxy = routerProxy.create(async (client, data) => {
|
||||
return throwError(new WsException('test'));
|
||||
}, handler);
|
||||
(await proxy(null, null)).subscribe(null, () => expectation.verify());
|
||||
});
|
||||
});
|
||||
|
||||
describe('isObservable', () => {
|
||||
describe('when observable', () => {
|
||||
it('should return true', () => {
|
||||
expect(routerProxy.isObservable(of('test'))).to.be.true;
|
||||
});
|
||||
});
|
||||
describe('when not observable', () => {
|
||||
it('should return false', () => {
|
||||
expect(routerProxy.isObservable({})).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { HttpException } from '@nestjs/common';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ErrorsInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
return call$.pipe(
|
||||
catchError(err =>
|
||||
throwError(new HttpException('Message', HttpStatus.BAD_GATEWAY)),
|
||||
),
|
||||
);
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next
|
||||
.handle()
|
||||
.pipe(
|
||||
catchError(err =>
|
||||
throwError(new HttpException('New message', HttpStatus.BAD_GATEWAY)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class LoggingInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
console.log('Before...');
|
||||
|
||||
const now = Date.now();
|
||||
return call$.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
|
||||
return next
|
||||
.handle()
|
||||
.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { timeout } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class TimeoutInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
return call$.pipe(timeout(5000));
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(timeout(5000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@@ -11,8 +16,8 @@ export class TransformInterceptor<T>
|
||||
implements NestInterceptor<T, Response<T>> {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<T>,
|
||||
next: CallHandler<T>,
|
||||
): Observable<Response<T>> {
|
||||
return call$.pipe(map(data => ({ data })));
|
||||
return next.handle().pipe(map(data => ({ data })));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class LoggingInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
console.log('Before...');
|
||||
|
||||
const now = Date.now();
|
||||
return call$.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
|
||||
return next
|
||||
.handle()
|
||||
.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { HttpException } from '@nestjs/common';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ExceptionInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
return call$.pipe(
|
||||
catchError(err =>
|
||||
throwError(
|
||||
new HttpException(
|
||||
'Exception interceptor message',
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next
|
||||
.handle()
|
||||
.pipe(
|
||||
catchError(err =>
|
||||
throwError(
|
||||
new HttpException(
|
||||
'Exception interceptor message',
|
||||
HttpStatus.BAD_GATEWAY,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class LoggingInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
console.log('Before...');
|
||||
|
||||
const now = Date.now();
|
||||
return call$.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
|
||||
return next
|
||||
.handle()
|
||||
.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class TransformInterceptor implements NestInterceptor {
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
call$: Observable<any>,
|
||||
): Observable<any> {
|
||||
return call$.pipe(map(data => ({ data })));
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(map(data => ({ data })));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig.json",
|
||||
"include": [
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"include": ["**/*.spec.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user