samples(@nestjs) update sample applications

This commit is contained in:
Kamil Myśliwiec
2018-03-25 21:38:52 +02:00
parent e50ddf9cb9
commit 91b4974d2c
177 changed files with 808 additions and 1370 deletions

View File

@@ -18,7 +18,6 @@ describe('NATS transport', () => {
app = module.createNestApplication(server); app = module.createNestApplication(server);
app.connectMicroservice({ app.connectMicroservice({
transport: Transport.NATS, transport: Transport.NATS,
url: 'nats://localhost:4222'
}); });
app.connectMicroservice({ app.connectMicroservice({
transport: Transport.NATS, transport: Transport.NATS,

View File

@@ -7,7 +7,7 @@ service Math {
} }
message SumResult { message SumResult {
required int32 result = 1; int32 result = 1;
} }
message RequestSum { message RequestSum {

144
package-lock.json generated
View File

@@ -37,78 +37,6 @@
"through2": "2.0.3" "through2": "2.0.3"
} }
}, },
"@nestjs/common": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-4.4.1.tgz",
"integrity": "sha1-Chu/WaCIbkqzM/i7W0v6zrECCYY=",
"requires": {
"class-transformer": "0.1.8",
"class-validator": "0.7.3",
"cli-color": "1.1.0"
},
"dependencies": {
"class-validator": {
"version": "0.7.3",
"resolved": "http://192.168.228.42:5000/class-validator/-/class-validator-0.7.3.tgz",
"integrity": "sha512-aRRlS1WlQ+52aHlmDCDX5dLwtpbg9is7i9yYhzQosTAVs86nX0Um8hb7ChTwMn7jfpyxxjAZpBrlrAc2tqNpYA==",
"requires": {
"validator": "7.2.0"
}
},
"cli-color": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.1.0.tgz",
"integrity": "sha1-3hiM3Ekp2DtnrqBBEPvtQP2/Z3U=",
"requires": {
"ansi-regex": "2.1.1",
"d": "0.1.1",
"es5-ext": "0.10.37",
"es6-iterator": "2.0.3",
"memoizee": "0.3.10",
"timers-ext": "0.1.2"
}
}
}
},
"@nestjs/core": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-4.4.1.tgz",
"integrity": "sha1-5VHSv/dfwt4C2Ff8wTIn6HNqYVA=",
"requires": {
"body-parser": "1.17.2",
"express": "4.16.2",
"iterare": "0.0.8",
"optional": "0.1.4"
}
},
"@nestjs/microservices": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-4.4.1.tgz",
"integrity": "sha1-eyjwfS6sGZZmu2tiMqErlG2YpEQ=",
"requires": {
"iterare": "0.0.8",
"json-socket": "0.2.1",
"optional": "0.1.4",
"redis": "2.8.0"
}
},
"@nestjs/testing": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-4.4.1.tgz",
"integrity": "sha1-mwhdjTrZJYzwV7wbGRv/4EhPQps=",
"requires": {
"optional": "0.1.4"
}
},
"@nestjs/websockets": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-4.4.1.tgz",
"integrity": "sha1-uvxDQQijIGY3Vqy3mgZ/lgIGnMk=",
"requires": {
"iterare": "0.0.8",
"socket.io": "2.0.4"
}
},
"@protobufjs/aspromise": { "@protobufjs/aspromise": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -1887,6 +1815,7 @@
"version": "1.17.2", "version": "1.17.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz",
"integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=", "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=",
"dev": true,
"requires": { "requires": {
"bytes": "2.4.0", "bytes": "2.4.0",
"content-type": "1.0.4", "content-type": "1.0.4",
@@ -2068,7 +1997,8 @@
"bytes": { "bytes": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz",
"integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=",
"dev": true
}, },
"cache-base": { "cache-base": {
"version": "1.0.1", "version": "1.0.1",
@@ -3427,14 +3357,6 @@
"array-find-index": "1.0.2" "array-find-index": "1.0.2"
} }
}, },
"d": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz",
"integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=",
"requires": {
"es5-ext": "0.10.37"
}
},
"dargs": { "dargs": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz",
@@ -4052,38 +3974,6 @@
} }
} }
}, },
"es6-weak-map": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz",
"integrity": "sha1-cGzvnpmqI2undmwjnIueKG6n0ig=",
"requires": {
"d": "0.1.1",
"es5-ext": "0.10.37",
"es6-iterator": "0.1.3",
"es6-symbol": "2.0.1"
},
"dependencies": {
"es6-iterator": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz",
"integrity": "sha1-1vWLjE/EE8JJtLqhl2j45NfIlE4=",
"requires": {
"d": "0.1.1",
"es5-ext": "0.10.37",
"es6-symbol": "2.0.1"
}
},
"es6-symbol": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz",
"integrity": "sha1-dhtcZ8/U8dGK+yNPaR1nhoLLO/M=",
"requires": {
"d": "0.1.1",
"es5-ext": "0.10.37"
}
}
}
},
"escape-html": { "escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -9527,20 +9417,6 @@
"mimic-fn": "1.1.0" "mimic-fn": "1.1.0"
} }
}, },
"memoizee": {
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.3.10.tgz",
"integrity": "sha1-TsoNiu057J0Bf0xcLy9kMvQuXI8=",
"requires": {
"d": "0.1.1",
"es5-ext": "0.10.37",
"es6-weak-map": "0.1.4",
"event-emitter": "0.3.5",
"lru-queue": "0.1.0",
"next-tick": "0.2.2",
"timers-ext": "0.1.2"
}
},
"memory-fs": { "memory-fs": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -9978,11 +9854,6 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
}, },
"next-tick": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz",
"integrity": "sha1-ddpKkn7liH45BliABltzNkE7MQ0="
},
"node-dir": { "node-dir": {
"version": "0.1.8", "version": "0.1.8",
"resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.8.tgz", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.8.tgz",
@@ -12497,7 +12368,8 @@
"qs": { "qs": {
"version": "6.4.0", "version": "6.4.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
"integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
"dev": true
}, },
"quick-format-unescaped": { "quick-format-unescaped": {
"version": "1.1.2", "version": "1.1.2",
@@ -12541,6 +12413,7 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz",
"integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=", "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=",
"dev": true,
"requires": { "requires": {
"bytes": "2.4.0", "bytes": "2.4.0",
"iconv-lite": "0.4.15", "iconv-lite": "0.4.15",
@@ -14777,11 +14650,6 @@
"spdx-expression-parse": "1.0.4" "spdx-expression-parse": "1.0.4"
} }
}, },
"validator": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-7.2.0.tgz",
"integrity": "sha1-pj3Lq6UdQ1C/jfIJiODVpU1xF5E="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -24,11 +24,11 @@
"author": "Kamil Mysliwiec", "author": "Kamil Mysliwiec",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@nestjs/common": "^4.0.0", "@nestjs/common": "^5.0.0",
"@nestjs/core": "^4.0.0", "@nestjs/core": "^5.0.0",
"@nestjs/microservices": "^4.0.0", "@nestjs/microservices": "^5.0.0",
"@nestjs/testing": "^4.0.0", "@nestjs/testing": "^5.0.0",
"@nestjs/websockets": "^4.0.0", "@nestjs/websockets": "^5.0.0",
"axios": "^0.17.1", "axios": "^0.17.1",
"class-transformer": "^0.1.8", "class-transformer": "^0.1.8",
"class-validator": "^0.8.1", "class-validator": "^0.8.1",

View File

@@ -53,6 +53,6 @@ export function mixin(mixinClass) {
Object.defineProperty(mixinClass, 'name', { Object.defineProperty(mixinClass, 'name', {
value: JSON.stringify(this.offset), value: JSON.stringify(this.offset),
}); });
Component()(mixinClass); Injectable()(mixinClass);
return mixinClass; return mixinClass;
} }

View File

@@ -3,10 +3,14 @@ import { EXCEPTION_FILTERS_METADATA } from '../../constants';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { ExceptionFilter } from '../../index'; import { ExceptionFilter } from '../../index';
import { extendArrayMetadata } from '../../utils/extend-metadata.util'; import { extendArrayMetadata } from '../../utils/extend-metadata.util';
import { isFunction } from '../../utils/shared.utils';
import { validateEach } from '../../utils/validate-each.util';
const defineFiltersMetadata = (...filters: ExceptionFilter[]) => { const defineFiltersMetadata = (...filters: ExceptionFilter[]) => {
return (target: object, key?, descriptor?) => { return (target: any, key?, descriptor?) => {
const isFilterValid = (filter) => isFunction(filter.catch);
if (descriptor) { if (descriptor) {
validateEach(target.constructor, filters, isFilterValid, '@UseFilters', 'filter');
extendArrayMetadata( extendArrayMetadata(
EXCEPTION_FILTERS_METADATA, EXCEPTION_FILTERS_METADATA,
filters, filters,
@@ -14,6 +18,7 @@ const defineFiltersMetadata = (...filters: ExceptionFilter[]) => {
); );
return descriptor; return descriptor;
} }
validateEach(target, filters, isFilterValid, '@UseFilters', 'filter');
extendArrayMetadata(EXCEPTION_FILTERS_METADATA, filters, target); extendArrayMetadata(EXCEPTION_FILTERS_METADATA, filters, target);
return target; return target;
}; };

View File

@@ -1,5 +1,7 @@
import { GUARDS_METADATA } from '../../constants'; import { GUARDS_METADATA } from '../../constants';
import { extendArrayMetadata } from '../../utils/extend-metadata.util'; import { extendArrayMetadata } from '../../utils/extend-metadata.util';
import { validateEach } from '../../utils/validate-each.util';
import { isFunction } from '../../utils/shared.utils';
/** /**
* Binds guards to the particular context. * Binds guards to the particular context.
@@ -12,11 +14,13 @@ import { extendArrayMetadata } from '../../utils/extend-metadata.util';
* @param {} ...guards (types) * @param {} ...guards (types)
*/ */
export function UseGuards(...guards: any[]) { export function UseGuards(...guards: any[]) {
return (target: object, key?, descriptor?) => { return (target: any, key?, descriptor?) => {
if (descriptor) { if (descriptor) {
validateEach(target.constructor, guards, isFunction, '@UseGuards', 'guard');
extendArrayMetadata(GUARDS_METADATA, guards, descriptor.value); extendArrayMetadata(GUARDS_METADATA, guards, descriptor.value);
return descriptor; return descriptor;
} }
validateEach(target, guards, isFunction, '@UseGuards', 'guard');
extendArrayMetadata(GUARDS_METADATA, guards, target); extendArrayMetadata(GUARDS_METADATA, guards, target);
return target; return target;
}; };

View File

@@ -1,5 +1,7 @@
import { INTERCEPTORS_METADATA } from '../../constants'; import { INTERCEPTORS_METADATA } from '../../constants';
import { extendArrayMetadata } from '../../utils/extend-metadata.util'; import { extendArrayMetadata } from '../../utils/extend-metadata.util';
import { isFunction } from '../../utils/shared.utils';
import { validateEach } from '../../utils/validate-each.util';
/** /**
* Binds interceptors to the particular context. * Binds interceptors to the particular context.
@@ -12,8 +14,15 @@ import { extendArrayMetadata } from '../../utils/extend-metadata.util';
* @param {} ...interceptors (types) * @param {} ...interceptors (types)
*/ */
export function UseInterceptors(...interceptors: any[]) { export function UseInterceptors(...interceptors: any[]) {
return (target: object, key?, descriptor?) => { return (target: any, key?, descriptor?) => {
if (descriptor) { if (descriptor) {
validateEach(
target.constructor,
interceptors,
isFunction,
'@UseInterceptors',
'interceptor',
);
extendArrayMetadata( extendArrayMetadata(
INTERCEPTORS_METADATA, INTERCEPTORS_METADATA,
interceptors, interceptors,
@@ -21,6 +30,13 @@ export function UseInterceptors(...interceptors: any[]) {
); );
return descriptor; return descriptor;
} }
validateEach(
target,
interceptors,
isFunction,
'@UseInterceptors',
'interceptor',
);
extendArrayMetadata(INTERCEPTORS_METADATA, interceptors, target); extendArrayMetadata(INTERCEPTORS_METADATA, interceptors, target);
return target; return target;
}; };

View File

@@ -1,6 +1,8 @@
import { PipeTransform } from '../../interfaces/index'; import { PipeTransform } from '../../interfaces/index';
import { PIPES_METADATA } from '../../constants'; import { PIPES_METADATA } from '../../constants';
import { extendArrayMetadata } from '../../utils/extend-metadata.util'; import { extendArrayMetadata } from '../../utils/extend-metadata.util';
import { validateEach } from '../../utils/validate-each.util';
import { isFunction } from '../../utils/shared.utils';
/** /**
* Binds pipes to the particular context. * Binds pipes to the particular context.
@@ -13,11 +15,14 @@ import { extendArrayMetadata } from '../../utils/extend-metadata.util';
* @param {PipeTransform[]} ...pipes (instances) * @param {PipeTransform[]} ...pipes (instances)
*/ */
export function UsePipes(...pipes: PipeTransform<any>[]) { export function UsePipes(...pipes: PipeTransform<any>[]) {
return (target: object, key?, descriptor?) => { return (target: any, key?, descriptor?) => {
const isPipeValid = (pipe) => isFunction(pipe.transform);
if (descriptor) { if (descriptor) {
validateEach(target.constructor, pipes, isPipeValid, '@UsePipes', 'pipe');
extendArrayMetadata(PIPES_METADATA, pipes, descriptor.value); extendArrayMetadata(PIPES_METADATA, pipes, descriptor.value);
return descriptor; return descriptor;
} }
validateEach(target, pipes, isPipeValid, '@UsePipes', 'pipe');
extendArrayMetadata(PIPES_METADATA, pipes, target); extendArrayMetadata(PIPES_METADATA, pipes, target);
return target; return target;
}; };

View File

@@ -3,46 +3,64 @@ import { CustomTransportStrategy } from './custom-transport-strategy.interface';
import { IClientOptions } from 'mqtt'; import { IClientOptions } from 'mqtt';
import { ServerCredentials } from 'grpc'; import { ServerCredentials } from 'grpc';
export interface MicroserviceOptions { export type MicroserviceOptions =
transport?: Transport; | GrpcOptions
| TcpOptions
| RedisOptions
| NatsOptions
| MqttOptions
| CustomStrategy;
export interface CustomStrategy {
strategy?: CustomTransportStrategy; strategy?: CustomTransportStrategy;
options?: options?: {};
| TcpOptions
| RedisOptions
| NatsOptions
| MqttOptions;
} }
export interface GrpcOptions { export interface GrpcOptions {
url?: string; transport?: Transport.GRPC;
credentials?: ServerCredentials; options: {
protoPath: string; url?: string;
package: string; credentials?: ServerCredentials;
protoPath: string;
package: string;
};
} }
export interface TcpOptions { export interface TcpOptions {
host?: string; transport?: Transport.TCP;
port?: number; options?: {
retryAttempts?: number; host?: string;
retryDelay?: number; port?: number;
retryAttempts?: number;
retryDelay?: number;
};
} }
export interface RedisOptions { export interface RedisOptions {
url?: string; transport?: Transport.REDIS;
retryAttempts?: number; options?: {
retryDelay?: number; url?: string;
retryAttempts?: number;
retryDelay?: number;
};
} }
export interface MqttOptions extends IClientOptions { export interface MqttOptions {
url?: string; transport?: Transport.MQTT;
options?: IClientOptions & {
url?: string;
};
} }
export interface NatsOptions { export interface NatsOptions {
url?: string; transport?: Transport.NATS;
name?: string; options?: {
pass?: string; url?: string;
maxReconnectAttempts?: number; name?: string;
reconnectTimeWait?: number; pass?: string;
servers?: string[]; maxReconnectAttempts?: number;
tls?: any; reconnectTimeWait?: number;
} servers?: string[];
tls?: any;
};
}

View File

@@ -3,5 +3,4 @@ import { LoggerService } from '../../services/logger.service';
import { NestApplicationContextOptions } from '../nest-application-context-options.interface'; import { NestApplicationContextOptions } from '../nest-application-context-options.interface';
export interface NestMicroserviceOptions export interface NestMicroserviceOptions
extends MicroserviceOptions, extends NestApplicationContextOptions {}
NestApplicationContextOptions {}

View File

@@ -1,5 +1,6 @@
import { MiddlewaresConsumer } from './middlewares-consumer.interface'; import { MiddlewaresConsumer } from './middlewares-consumer.interface';
import { RequestMappingMetadata } from '../request-mapping-metadata.interface'; import { RequestMappingMetadata } from '../request-mapping-metadata.interface';
import { Type } from '../type.interface';
export interface MiddlewareConfigProxy { export interface MiddlewareConfigProxy {
/** /**
@@ -8,20 +9,14 @@ export interface MiddlewareConfigProxy {
* @param {} ...data * @param {} ...data
* @returns MiddlewareConfigProxy * @returns MiddlewareConfigProxy
*/ */
with(...data): MiddlewareConfigProxy; with(...data: any[]): MiddlewareConfigProxy;
/** /**
* Attaches passed routes / controllers to the processed middleware(s). * Attaches passed either routes (strings) or controllers to the processed middleware(s).
* Single route can be defined as a literal object:
* ```
* path: string;
* method: RequestMethod;
* ```
*
* When you pass Controller class, Nest will attach middleware to every HTTP route handler inside this controller. * When you pass Controller class, Nest will attach middleware to every HTTP route handler inside this controller.
* *
* @param {} ...routes * @param {} ...routes
* @returns MiddlewaresConsumer * @returns MiddlewaresConsumer
*/ */
forRoutes(...routes): MiddlewaresConsumer; forRoutes(...routes: (string | Type<any>)[]): MiddlewaresConsumer;
} }

View File

@@ -2,8 +2,8 @@ import { MiddlewareConfigProxy } from './middleware-config-proxy.interface';
export interface MiddlewaresConsumer { export interface MiddlewaresConsumer {
/** /**
* Takes single middleware class or array of classes, * Takes single middleware class or array of classes
* which subsequently can be attached to the passed routes / controllers. * that subsequently could be attached to the passed either routes or controllers.
* *
* @param {any|any[]} middlewares * @param {any|any[]} middlewares
* @returns MiddlewareConfigProxy * @returns MiddlewareConfigProxy

View File

@@ -19,5 +19,5 @@ export interface ValueProvider {
export interface FactoryProvider { export interface FactoryProvider {
provide: any; provide: any;
useFactory: (...args: any[]) => any; useFactory: (...args: any[]) => any;
inject: Array<Type<any> | string | any>; inject?: Array<Type<any> | string | any>;
} }

View File

@@ -4,6 +4,7 @@ import { CanActivate } from './features/can-activate.interface';
import { NestInterceptor } from './features/nest-interceptor.interface'; import { NestInterceptor } from './features/nest-interceptor.interface';
import { INestApplicationContext } from './nest-application-context.interface'; import { INestApplicationContext } from './nest-application-context.interface';
import { CorsOptions } from './external/cors-options.interface'; import { CorsOptions } from './external/cors-options.interface';
import { MicroserviceOptions } from './microservices/microservice-configuration.interface';
export interface INestApplication extends INestApplicationContext { export interface INestApplication extends INestApplicationContext {
/** /**
@@ -70,12 +71,12 @@ export interface INestApplication extends INestApplicationContext {
useWebSocketAdapter(adapter: WebSocketAdapter): this; useWebSocketAdapter(adapter: WebSocketAdapter): this;
/** /**
* Connects microservice to the NestApplication instance. It transforms application to the hybrid instance. * Connects microservice to the NestApplication instance. Transforms application to the hybrid instance.
* *
* @param {MicroserviceConfiguration} config Microservice configuration objet * @param {MicroserviceOptions} options Microservice options object
* @returns INestMicroservice * @returns INestMicroservice
*/ */
connectMicroservice(config): INestMicroservice; connectMicroservice(options: MicroserviceOptions): INestMicroservice;
/** /**
* Returns array of the connected microservices to the NestApplication. * Returns array of the connected microservices to the NestApplication.

View File

@@ -40,7 +40,9 @@ export interface INestExpressApplication {
* *
* @returns this * @returns this
*/ */
useStaticAssets(path: string, options: ServeStaticOptions): this; useStaticAssets(options: any): this;
useStaticAssets(path: string, options?: ServeStaticOptions);
useStaticAssets(pathOrOptions: any, options?: ServeStaticOptions): this;
/** /**
* Sets a base directory for templates (views). * Sets a base directory for templates (views).

View File

@@ -2,9 +2,14 @@ import 'reflect-metadata';
import { expect } from 'chai'; import { expect } from 'chai';
import { EXCEPTION_FILTERS_METADATA } from '../../constants'; import { EXCEPTION_FILTERS_METADATA } from '../../constants';
import { UseFilters } from '../../decorators/core/exception-filters.decorator'; import { UseFilters } from '../../decorators/core/exception-filters.decorator';
import { InvalidDecoratorItemException } from '../../utils/validate-each.util';
class Filter {
catch() {}
}
describe('@UseFilters', () => { describe('@UseFilters', () => {
const filters = ['exception', 'exception2']; const filters = [new Filter(), new Filter()];
@UseFilters(...(filters as any)) @UseFilters(...(filters as any))
class Test {} class Test {}
@@ -26,4 +31,13 @@ describe('@UseFilters', () => {
); );
expect(metadata).to.be.eql(filters); expect(metadata).to.be.eql(filters);
}); });
it('when object is invalid should throw exception', () => {
try {
UseFilters('test' as any)({});
}
catch (e) {
expect(e).to.be.instanceof(InvalidDecoratorItemException);
}
});
}); });

View File

@@ -2,9 +2,12 @@ import 'reflect-metadata';
import { expect } from 'chai'; import { expect } from 'chai';
import { UseGuards } from '../../decorators/core/use-guards.decorator'; import { UseGuards } from '../../decorators/core/use-guards.decorator';
import { GUARDS_METADATA } from '../../constants'; import { GUARDS_METADATA } from '../../constants';
import { InvalidDecoratorItemException } from '../../utils/validate-each.util';
class Guard {}
describe('@UseGuards', () => { describe('@UseGuards', () => {
const guards = ['guard1', 'guard2']; const guards = [Guard, Guard];
@UseGuards(...(guards as any)) @UseGuards(...(guards as any))
class Test {} class Test {}
@@ -34,4 +37,13 @@ describe('@UseGuards', () => {
const metadata = Reflect.getMetadata(GUARDS_METADATA, Test2.test); const metadata = Reflect.getMetadata(GUARDS_METADATA, Test2.test);
expect(metadata).to.be.eql(guards.concat(guards)); expect(metadata).to.be.eql(guards.concat(guards));
}); });
it('when object is invalid should throw exception', () => {
try {
UseGuards('test')({});
}
catch (e) {
expect(e).to.be.instanceof(InvalidDecoratorItemException);
}
});
}); });

View File

@@ -2,9 +2,12 @@ import 'reflect-metadata';
import { expect } from 'chai'; import { expect } from 'chai';
import { UseInterceptors } from '../../decorators/core/use-interceptors.decorator'; import { UseInterceptors } from '../../decorators/core/use-interceptors.decorator';
import { INTERCEPTORS_METADATA } from '../../constants'; import { INTERCEPTORS_METADATA } from '../../constants';
import { InvalidDecoratorItemException } from '../../utils/validate-each.util';
class Interceptor {}
describe('@UseInterceptors', () => { describe('@UseInterceptors', () => {
const interceptors = ['interceptor1', 'interceptor2']; const interceptors = [Interceptor, Interceptor];
@UseInterceptors(...(interceptors as any)) @UseInterceptors(...(interceptors as any))
class Test {} class Test {}
@@ -26,4 +29,13 @@ describe('@UseInterceptors', () => {
); );
expect(metadata).to.be.eql(interceptors); expect(metadata).to.be.eql(interceptors);
}); });
it('when object is invalid should throw exception', () => {
try {
UseInterceptors('test')({});
}
catch (e) {
expect(e).to.be.instanceof(InvalidDecoratorItemException);
}
});
}); });

View File

@@ -2,9 +2,14 @@ import 'reflect-metadata';
import { expect } from 'chai'; import { expect } from 'chai';
import { UsePipes } from '../../decorators/core/use-pipes.decorator'; import { UsePipes } from '../../decorators/core/use-pipes.decorator';
import { PIPES_METADATA } from '../../constants'; import { PIPES_METADATA } from '../../constants';
import { InvalidDecoratorItemException } from '../../utils/validate-each.util';
class Pipe {
transform() {}
}
describe('@UsePipes', () => { describe('@UsePipes', () => {
const pipes = ['pipe1', 'pipe2']; const pipes = [new Pipe(), new Pipe()];
@UsePipes(...(pipes as any)) @UsePipes(...(pipes as any))
class Test {} class Test {}
@@ -23,4 +28,13 @@ describe('@UsePipes', () => {
const metadata = Reflect.getMetadata(PIPES_METADATA, TestWithMethod.test); const metadata = Reflect.getMetadata(PIPES_METADATA, TestWithMethod.test);
expect(metadata).to.be.eql(pipes); expect(metadata).to.be.eql(pipes);
}); });
it('when object is invalid should throw exception', () => {
try {
UsePipes('test' as any)({});
}
catch (e) {
expect(e).to.be.instanceof(InvalidDecoratorItemException);
}
});
}); });

View File

@@ -0,0 +1,23 @@
import { expect } from 'chai';
import { isFunction } from '../../utils/shared.utils';
import { validateEach, InvalidDecoratorItemException } from '../../utils/validate-each.util';
describe('validateEach', () => {
describe('when any item will not pass predicate', () => {
it('should throw exception', () => {
try {
validateEach({} as any, ['test'], isFunction, '', '');
}
catch(e) {
expect(e).to.be.instanceof(InvalidDecoratorItemException);
}
})
});
describe('when all items passed predicate', () => {
it('should return true', () => {
expect(
validateEach({} as any, [() => null], isFunction, '', '')
).to.be.true;
});
});
});

View File

@@ -0,0 +1,24 @@
import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.exception';
export class InvalidDecoratorItemException extends RuntimeException {
constructor(decorator: string, item: string, context: string) {
super(`Invalid ${item} passed to ${decorator}() decorator (${context}).`);
}
}
export function validateEach(
context: { name: string },
arr: any[],
predicate: Function,
decorator: string,
item: string,
): boolean {
if (!context || !context.name) {
return true;
}
const errors = arr.filter(item => !predicate(item));
if (errors.length > 0) {
throw new InvalidDecoratorItemException(decorator, item, context.name);
}
return true;
}

View File

@@ -10,12 +10,8 @@ import { ServeStaticOptions } from '@nestjs/common/interfaces/external/serve-sta
export class ExpressAdapter implements HttpServer { export class ExpressAdapter implements HttpServer {
constructor(private readonly instance) {} constructor(private readonly instance) {}
use(handler: RequestHandler | ErrorHandler); use(...args: any[]) {
use(path: any, handler: RequestHandler | ErrorHandler); return this.instance.use(...args);
use(pathOrHandler: any, handler?: RequestHandler | ErrorHandler | undefined) {
return handler
? this.instance.use(pathOrHandler, handler)
: this.instance.use(pathOrHandler);
} }
get(handler: RequestHandler); get(handler: RequestHandler);

View File

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

View File

@@ -24,7 +24,7 @@ export const UnknownExportMessage = (module: string) =>
`Nest cannot export component / module that is not a part of the currently proccessed module (${module}). Please verify whether each exported unit is available in this particular context.`; `Nest cannot export component / module that is not a part of the currently proccessed module (${module}). Please verify whether each exported unit is available in this particular context.`;
export const MissingRequiredDependency = (name: string, reason: string) => export const MissingRequiredDependency = (name: string, reason: string) =>
`The ${name} package is missing. Please, make sure to install it (npm install ${name}) to take advantage of ${reason}.` `The "${name}" package is missing. Please, make sure to install this library (npm install ${name}) to take advantage of ${reason} class.`
export const INVALID_MIDDLEWARE_CONFIGURATION = `Invalid middleware configuration passed inside the module 'configure()' method.`; export const INVALID_MIDDLEWARE_CONFIGURATION = `Invalid middleware configuration passed inside the module 'configure()' method.`;
export const UNKNOWN_REQUEST_MAPPING = `Request mapping properties not defined in the @RequestMapping() annotation!`; export const UNKNOWN_REQUEST_MAPPING = `Request mapping properties not defined in the @RequestMapping() annotation!`;

View File

@@ -2,10 +2,15 @@ import 'reflect-metadata';
import { RouterExplorer } from '../router/router-explorer'; import { RouterExplorer } from '../router/router-explorer';
import { UnknownRequestMappingException } from '../errors/exceptions/unknown-request-mapping.exception'; import { UnknownRequestMappingException } from '../errors/exceptions/unknown-request-mapping.exception';
import { RequestMethod } from '@nestjs/common/enums/request-method.enum'; import { RequestMethod } from '@nestjs/common/enums/request-method.enum';
import { isUndefined, validatePath, isString } from '@nestjs/common/utils/shared.utils'; import {
isUndefined,
validatePath,
isString,
} from '@nestjs/common/utils/shared.utils';
import { PATH_METADATA } from '@nestjs/common/constants'; import { PATH_METADATA } from '@nestjs/common/constants';
import { MetadataScanner } from '../metadata-scanner'; import { MetadataScanner } from '../metadata-scanner';
import { NestContainer } from '../injector/container'; import { NestContainer } from '../injector/container';
import { Type } from '@nestjs/common/interfaces';
export class RoutesMapper { export class RoutesMapper {
private readonly routerExplorer: RouterExplorer; private readonly routerExplorer: RouterExplorer;
@@ -14,18 +19,25 @@ export class RoutesMapper {
this.routerExplorer = new RouterExplorer(new MetadataScanner(), container); this.routerExplorer = new RouterExplorer(new MetadataScanner(), container);
} }
public mapRouteToRouteProps(routeMetatype): string[] { public mapRouteToRouteProps(route: Type<any> | any | string): string[] {
const routePath: string = Reflect.getMetadata(PATH_METADATA, routeMetatype); if (isString(route)) {
return [route];
}
const routePath: string = Reflect.getMetadata(PATH_METADATA, route);
if (isUndefined(routePath)) { if (isUndefined(routePath)) {
return [this.mapObjectToPath(routeMetatype)]; return [this.mapObjectToPath(route)];
} }
const paths = this.routerExplorer.scanForPaths( const paths = this.routerExplorer.scanForPaths(
Object.create(routeMetatype), Object.create(route),
routeMetatype.prototype, route.prototype,
);
const uniquePathsSet = new Set(
paths.map(
route =>
this.validateGlobalPath(routePath) +
this.validateRoutePath(route.path),
),
); );
const uniquePathsSet = new Set(paths.map(route => (
this.validateGlobalPath(routePath) + this.validateRoutePath(route.path)
)));
return [...uniquePathsSet.values()]; return [...uniquePathsSet.values()];
} }

View File

@@ -3,7 +3,6 @@ import * as http from 'http';
import * as https from 'https'; import * as https from 'https';
import * as optional from 'optional'; import * as optional from 'optional';
import * as bodyParser from 'body-parser'; import * as bodyParser from 'body-parser';
import * as formbody from 'fastify-formbody';
import iterate from 'iterare'; import iterate from 'iterare';
import { import {
CanActivate, CanActivate,
@@ -46,6 +45,7 @@ import { FastifyAdapter } from './adapters/fastify-adapter';
import { INestExpressApplication } from '@nestjs/common/interfaces/nest-express-application.interface'; import { INestExpressApplication } from '@nestjs/common/interfaces/nest-express-application.interface';
import { INestFastifyApplication } from '@nestjs/common/interfaces/nest-fastify-application.interface'; import { INestFastifyApplication } from '@nestjs/common/interfaces/nest-fastify-application.interface';
import { ServeStaticOptions } from '@nestjs/common/interfaces/external/serve-static-options.interface'; import { ServeStaticOptions } from '@nestjs/common/interfaces/external/serve-static-options.interface';
import { MissingRequiredDependencyException } from './errors/exceptions/missing-dependency.exception';
const { SocketModule } = const { SocketModule } =
optional('@nestjs/websockets/socket-module') || ({} as any); optional('@nestjs/websockets/socket-module') || ({} as any);
@@ -159,7 +159,9 @@ export class NestApplication extends NestApplicationContext
public registerParserMiddlewares() { public registerParserMiddlewares() {
if (this.httpAdapter instanceof FastifyAdapter) { if (this.httpAdapter instanceof FastifyAdapter) {
return this.httpAdapter.register(formbody); return this.httpAdapter.register(
this.loadPackage('fastify-formbody', 'FastifyAdapter'),
);
} }
if (!this.isExpress()) { if (!this.isExpress()) {
return void 0; return void 0;
@@ -341,25 +343,33 @@ export class NestApplication extends NestApplicationContext
return this; return this;
} }
useStaticAssets(options: any): this; public useStaticAssets(options: any): this;
useStaticAssets(path: string, options?: ServeStaticOptions); public useStaticAssets(path: string, options?: ServeStaticOptions);
useStaticAssets(pathOrOptions: any, options?: ServeStaticOptions): this { public useStaticAssets(pathOrOptions: any, options?: ServeStaticOptions): this {
this.httpAdapter.useStaticAssets && this.httpAdapter.useStaticAssets &&
this.httpAdapter.useStaticAssets(pathOrOptions, options); this.httpAdapter.useStaticAssets(pathOrOptions, options);
return this; return this;
} }
setBaseViewsDir(path: string): this { public setBaseViewsDir(path: string): this {
this.httpAdapter.setBaseViewsDir && this.httpAdapter.setBaseViewsDir(path); this.httpAdapter.setBaseViewsDir && this.httpAdapter.setBaseViewsDir(path);
return this; return this;
} }
setViewEngine(engineOrOptions: any): this { public setViewEngine(engineOrOptions: any): this {
this.httpAdapter.setViewEngine && this.httpAdapter.setViewEngine &&
this.httpAdapter.setViewEngine(engineOrOptions); this.httpAdapter.setViewEngine(engineOrOptions);
return this; return this;
} }
private loadPackage(name: string, ctx: string) {
try {
return require(name);
} catch (e) {
throw new MissingRequiredDependencyException(name, ctx);
}
}
private async registerMiddlewares(instance) { private async registerMiddlewares(instance) {
await this.middlewaresModule.registerMiddlewares( await this.middlewaresModule.registerMiddlewares(
this.middlewaresContainer, this.middlewaresContainer,

View File

@@ -26,6 +26,7 @@ import { ExpressAdapter } from './adapters/express-adapter';
import { INestExpressApplication } from '@nestjs/common/interfaces/nest-express-application.interface'; import { INestExpressApplication } from '@nestjs/common/interfaces/nest-express-application.interface';
import { FastifyAdapter } from './adapters/fastify-adapter'; import { FastifyAdapter } from './adapters/fastify-adapter';
import { INestFastifyApplication } from '@nestjs/common/interfaces/nest-fastify-application.interface'; import { INestFastifyApplication } from '@nestjs/common/interfaces/nest-fastify-application.interface';
import { MicroserviceOptions } from '@nestjs/common/interfaces/microservices/microservice-configuration.interface';
const { NestMicroservice } = const { NestMicroservice } =
optional('@nestjs/microservices/nest-microservice') || ({} as any); optional('@nestjs/microservices/nest-microservice') || ({} as any);
@@ -80,12 +81,12 @@ export class NestFactoryStatic {
* Creates an instance of the NestMicroservice (returns Promise) * Creates an instance of the NestMicroservice (returns Promise)
* *
* @param {} module Entry (root) application module class * @param {} module Entry (root) application module class
* @param {NestMicroserviceOptions} options Optional microservice configuration * @param {NestMicroserviceOptions & MicroserviceOptions} options Optional microservice configuration
* @returns an `Promise` of the INestMicroservice instance * @returns an `Promise` of the INestMicroservice instance
*/ */
public async createMicroservice( public async createMicroservice(
module, module,
options?: NestMicroserviceOptions, options?: NestMicroserviceOptions & MicroserviceOptions,
): Promise<INestMicroservice> { ): Promise<INestMicroservice> {
if (!NestMicroservice) { if (!NestMicroservice) {
throw new MicroservicesPackageNotFoundException(); throw new MicroservicesPackageNotFoundException();

View File

@@ -92,6 +92,10 @@ export class DependenciesScanner {
token, token,
metadata.COMPONENTS as 'components', metadata.COMPONENTS as 'components',
), ),
...this.container.getDynamicMetadataByToken(
token,
metadata.PROVIDERS as 'providers',
),
]; ];
components.map(component => { components.map(component => {
this.storeComponent(component, token); this.storeComponent(component, token);

View File

@@ -41,7 +41,6 @@ describe('RoutesMapper', () => {
middlewares: 'Test', middlewares: 'Test',
forRoutes: [{ method: RequestMethod.GET }], forRoutes: [{ method: RequestMethod.GET }],
}; };
expect( expect(
mapper.mapRouteToRouteProps.bind(mapper, config.forRoutes[0]), mapper.mapRouteToRouteProps.bind(mapper, config.forRoutes[0]),
).throws(UnknownRequestMappingException); ).throws(UnknownRequestMappingException);

View File

@@ -12,6 +12,8 @@ import { GUARDS_METADATA } from '../../common/constants';
import { ApplicationConfig } from '../application-config'; import { ApplicationConfig } from '../application-config';
import { APP_INTERCEPTOR, APP_GUARD, APP_PIPE, APP_FILTER } from '../constants'; import { APP_INTERCEPTOR, APP_GUARD, APP_PIPE, APP_FILTER } from '../constants';
class Guard {}
describe('DependenciesScanner', () => { describe('DependenciesScanner', () => {
@Component() @Component()
class TestComponent {} class TestComponent {}
@@ -123,7 +125,7 @@ describe('DependenciesScanner', () => {
}); });
class CompMethod { class CompMethod {
@UseGuards('test') @UseGuards(Guard)
public method() {} public method() {}
} }
describe('reflectKeyMetadata', () => { describe('reflectKeyMetadata', () => {
@@ -137,7 +139,7 @@ describe('DependenciesScanner', () => {
GUARDS_METADATA, GUARDS_METADATA,
'method', 'method',
); );
expect(result).to.be.eql(['test']); expect(result).to.be.eql([Guard]);
}); });
}); });

View File

@@ -1,4 +1,4 @@
import * as grpc from 'grpc'; import { GrpcObject } from 'grpc';
import { ClientProxy } from './client-proxy'; import { ClientProxy } from './client-proxy';
import { Logger } from '@nestjs/common/services/logger.service'; import { Logger } from '@nestjs/common/services/logger.service';
import { ClientOptions } from '../interfaces/client-metadata.interface'; import { ClientOptions } from '../interfaces/client-metadata.interface';
@@ -12,10 +12,6 @@ import { InvalidProtoDefinitionException } from '../exceptions/invalid-proto-def
let grpcPackage: any = {}; let grpcPackage: any = {};
export interface GrpcService {
[name: string]: (...args: any[]) => Observable<any>;
}
export class ClientGrpcProxy extends ClientProxy implements ClientGrpc { export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
private readonly logger = new Logger(ClientProxy.name); private readonly logger = new Logger(ClientProxy.name);
private readonly url: string; private readonly url: string;
@@ -30,7 +26,7 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
this.grpcClient = this.createClient(); this.grpcClient = this.createClient();
} }
public getService<T extends GrpcService = any>(name: string): T { public getService<T = any>(name: string): T {
var options, credentials; var options, credentials;
if (!this.grpcClient[name]) { if (!this.grpcClient[name]) {
throw new InvalidGrpcServiceException(); throw new InvalidGrpcServiceException();
@@ -101,7 +97,7 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc {
return grpcPackage; return grpcPackage;
} }
public loadProto(): grpc.GrpcObject { public loadProto(): GrpcObject {
try { try {
const context = grpcPackage.load( const context = grpcPackage.load(
this.getOptionsProp<GrpcOptions>(this.options, 'protoPath'), this.getOptionsProp<GrpcOptions>(this.options, 'protoPath'),

View File

@@ -1,4 +1,4 @@
import * as mqtt from 'mqtt'; import { MqttClient } from 'mqtt';
import { ClientProxy } from './client-proxy'; import { ClientProxy } from './client-proxy';
import { Logger } from '@nestjs/common/services/logger.service'; import { Logger } from '@nestjs/common/services/logger.service';
import { ClientOptions } from '../interfaces/client-metadata.interface'; import { ClientOptions } from '../interfaces/client-metadata.interface';
@@ -17,7 +17,7 @@ let mqttPackage: any = {};
export class ClientMqtt extends ClientProxy { export class ClientMqtt extends ClientProxy {
private readonly logger = new Logger(ClientProxy.name); private readonly logger = new Logger(ClientProxy.name);
private readonly url: string; private readonly url: string;
private mqttClient: mqtt.MqttClient; private mqttClient: MqttClient;
constructor(private readonly options: ClientOptions) { constructor(private readonly options: ClientOptions) {
super(); super();
@@ -86,11 +86,11 @@ export class ClientMqtt extends ClientProxy {
this.handleError(this.mqttClient, callback); this.handleError(this.mqttClient, callback);
} }
public createClient(): mqtt.MqttClient { public createClient(): MqttClient {
return mqttPackage.connect(this.url, this.options.options as MqttOptions); return mqttPackage.connect(this.url, this.options.options as MqttOptions);
} }
public handleError(client: mqtt.MqttClient, callback: (...args) => any) { public handleError(client: MqttClient, callback: (...args) => any) {
const errorCallback = err => { const errorCallback = err => {
if (err.code === 'ECONNREFUSED') { if (err.code === 'ECONNREFUSED') {
callback(err, null); callback(err, null);

View File

@@ -1,4 +1,4 @@
import * as nats from 'nats'; import { Client } from 'nats';
import { ClientProxy } from './client-proxy'; import { ClientProxy } from './client-proxy';
import { Logger } from '@nestjs/common/services/logger.service'; import { Logger } from '@nestjs/common/services/logger.service';
import { ClientOptions } from '../interfaces/client-metadata.interface'; import { ClientOptions } from '../interfaces/client-metadata.interface';
@@ -15,7 +15,7 @@ let natsPackage: any = {};
export class ClientNats extends ClientProxy { export class ClientNats extends ClientProxy {
private readonly logger = new Logger(ClientProxy.name); private readonly logger = new Logger(ClientProxy.name);
private readonly url: string; private readonly url: string;
private natsClient: nats.Client; private natsClient: Client;
constructor(private readonly options: ClientOptions) { constructor(private readonly options: ClientOptions) {
super(); super();
@@ -80,7 +80,7 @@ export class ClientNats extends ClientProxy {
this.handleError(this.natsClient, callback); this.handleError(this.natsClient, callback);
} }
public createClient(): Promise<nats.Client> { public createClient(): Promise<Client> {
const options = this.options.options || ({} as NatsOptions); const options = this.options.options || ({} as NatsOptions);
const client = natsPackage.connect({ const client = natsPackage.connect({
...(options as any), ...(options as any),
@@ -90,7 +90,7 @@ export class ClientNats extends ClientProxy {
return new Promise(resolve => client.on(CONNECT_EVENT, resolve)); return new Promise(resolve => client.on(CONNECT_EVENT, resolve));
} }
public handleError(client: nats.Client, callback: (...args) => any) { public handleError(client: Client, callback: (...args) => any) {
const errorCallback = err => { const errorCallback = err => {
if (err.code === 'ECONNREFUSED') { if (err.code === 'ECONNREFUSED') {
callback(err, null); callback(err, null);

View File

@@ -7,6 +7,7 @@ import { ReadPacket, PacketId, WritePacket, ClientOptions } from './../interface
import { MissingRequiredDependencyException } from '@nestjs/core/errors/exceptions/missing-dependency.exception'; import { MissingRequiredDependencyException } from '@nestjs/core/errors/exceptions/missing-dependency.exception';
export abstract class ClientProxy { export abstract class ClientProxy {
public abstract close(): any;
protected abstract publish( protected abstract publish(
packet: ReadPacket, packet: ReadPacket,
callback: (packet: WritePacket) => void, callback: (packet: WritePacket) => void,
@@ -50,9 +51,9 @@ export abstract class ClientProxy {
return Object.assign(packet, { id }); return Object.assign(packet, { id });
} }
protected getOptionsProp<T>( protected getOptionsProp<T extends { options? }>(
obj: ClientOptions, obj: ClientOptions,
prop: keyof T, prop: keyof T['options'],
defaultValue = undefined, defaultValue = undefined,
) { ) {
return obj && obj.options ? obj.options[prop as any] : defaultValue; return obj && obj.options ? obj.options[prop as any] : defaultValue;

View File

@@ -1,4 +1,4 @@
import * as redis from 'redis'; import { ClientOpts, RetryStrategyOptions, RedisClient } from 'redis';
import { ClientProxy } from './client-proxy'; import { ClientProxy } from './client-proxy';
import { Logger } from '@nestjs/common/services/logger.service'; import { Logger } from '@nestjs/common/services/logger.service';
import { ClientOptions } from '../interfaces/client-metadata.interface'; import { ClientOptions } from '../interfaces/client-metadata.interface';
@@ -21,8 +21,8 @@ let redisPackage: any = {};
export class ClientRedis extends ClientProxy { export class ClientRedis extends ClientProxy {
private readonly logger = new Logger(ClientProxy.name); private readonly logger = new Logger(ClientProxy.name);
private readonly url: string; private readonly url: string;
private pubClient: redis.RedisClient; private pubClient: RedisClient;
private subClient: redis.RedisClient; private subClient: RedisClient;
private isExplicitlyTerminated = false; private isExplicitlyTerminated = false;
constructor(private readonly options: ClientOptions) { constructor(private readonly options: ClientOptions) {
@@ -107,14 +107,14 @@ export class ClientRedis extends ClientProxy {
this.handleError(this.subClient, callback); this.handleError(this.subClient, callback);
} }
public createClient(): redis.RedisClient { public createClient(): RedisClient {
return redisPackage.createClient({ return redisPackage.createClient({
...this.getClientOptions(), ...this.getClientOptions(),
url: this.url, url: this.url,
}); });
} }
public handleError(client: redis.RedisClient, callback: (...args) => any) { public handleError(client: RedisClient, callback: (...args) => any) {
const errorCallback = err => { const errorCallback = err => {
if (err.code === 'ECONNREFUSED') { if (err.code === 'ECONNREFUSED') {
callback(err, null); callback(err, null);
@@ -129,7 +129,7 @@ export class ClientRedis extends ClientProxy {
}); });
} }
public getClientOptions(): Partial<redis.ClientOpts> { public getClientOptions(): Partial<ClientOpts> {
const retry_strategy = options => this.createRetryStrategy(options); const retry_strategy = options => this.createRetryStrategy(options);
return { return {
retry_strategy, retry_strategy,
@@ -137,7 +137,7 @@ export class ClientRedis extends ClientProxy {
} }
public createRetryStrategy( public createRetryStrategy(
options: redis.RetryStrategyOptions, options: RetryStrategyOptions,
): undefined | number { ): undefined | number {
if ( if (
this.isExplicitlyTerminated || this.isExplicitlyTerminated ||

View File

@@ -1,3 +1,2 @@
export * from './client-proxy'; export * from './client-proxy';
export * from './client-proxy-factory'; export * from './client-proxy-factory';
export { GrpcService } from './client-grpc';

View File

@@ -1,5 +1,3 @@
import { GrpcService } from '../client/client-grpc';
export interface ClientGrpc { export interface ClientGrpc {
getService<T extends GrpcService = any>(name: string): T; getService<T = any>(name: string): T;
} }

View File

@@ -7,17 +7,17 @@ import {
GrpcOptions, GrpcOptions,
} from './microservice-configuration.interface'; } from './microservice-configuration.interface';
export interface ClientOptions { export type ClientOptions =
transport?: Transport; | RedisOptions
options?: | NatsOptions
| TcpClientOptions | MqttOptions
| RedisOptions | GrpcOptions
| NatsOptions | TcpClientOptions;
| MqttOptions
| GrpcOptions;
}
export interface TcpClientOptions { export interface TcpClientOptions {
host?: string; transport: Transport.TCP;
port?: number; options?: {
host?: string;
port?: number;
};
} }

View File

@@ -4,46 +4,64 @@ import { IClientOptions } from 'mqtt';
import { ServerCredentials } from 'grpc'; import { ServerCredentials } from 'grpc';
import { Server } from './../server/server'; import { Server } from './../server/server';
export interface MicroserviceOptions { export type MicroserviceOptions =
transport?: Transport; | GrpcOptions
strategy?: Server & CustomTransportStrategy; | TcpOptions
options?: | RedisOptions
| TcpOptions | NatsOptions
| RedisOptions | MqttOptions
| NatsOptions | CustomStrategy;
| MqttOptions;
export interface CustomStrategy {
strategy: Server & CustomTransportStrategy;
options?: {};
} }
export interface GrpcOptions { export interface GrpcOptions {
url?: string; transport?: Transport.GRPC;
credentials?: ServerCredentials; options: {
protoPath: string; url?: string;
package: string; credentials?: ServerCredentials;
protoPath: string;
package: string;
};
} }
export interface TcpOptions { export interface TcpOptions {
host?: string; transport?: Transport.TCP;
port?: number; options?: {
retryAttempts?: number; host?: string;
retryDelay?: number; port?: number;
retryAttempts?: number;
retryDelay?: number;
};
} }
export interface RedisOptions { export interface RedisOptions {
url?: string; transport?: Transport.REDIS;
retryAttempts?: number; options?: {
retryDelay?: number; url?: string;
retryAttempts?: number;
retryDelay?: number;
};
} }
export interface MqttOptions extends IClientOptions { export interface MqttOptions {
url?: string; transport?: Transport.MQTT;
options?: IClientOptions & {
url?: string;
};
} }
export interface NatsOptions { export interface NatsOptions {
url?: string; transport?: Transport.NATS;
name?: string; options?: {
pass?: string; url?: string;
maxReconnectAttempts?: number; name?: string;
reconnectTimeWait?: number; pass?: string;
servers?: string[]; maxReconnectAttempts?: number;
tls?: any; reconnectTimeWait?: number;
} servers?: string[];
tls?: any;
};
}

View File

@@ -59,14 +59,20 @@ export class NestMicroservice extends NestApplicationContext
} }
public createServer(config: MicroserviceOptions) { public createServer(config: MicroserviceOptions) {
this.microserviceConfig = { try {
transport: Transport.TCP, this.microserviceConfig = {
...config, transport: Transport.TCP,
}; ...config,
const { strategy } = config; } as any;
this.server = strategy const { strategy } = config as any;
? strategy this.server = strategy
: ServerFactory.create(this.microserviceConfig); ? strategy
: ServerFactory.create(this.microserviceConfig);
}
catch (e) {
this.logger.error(e);
throw e;
}
} }
public registerModules() { public registerModules() {

View File

@@ -15,7 +15,7 @@ export class ServerFactory {
public static create( public static create(
options: MicroserviceOptions, options: MicroserviceOptions,
): Server & CustomTransportStrategy { ): Server & CustomTransportStrategy {
const { transport } = options; const { transport } = options as any;
switch (transport) { switch (transport) {
case Transport.REDIS: case Transport.REDIS:
return new ServerRedis(options); return new ServerRedis(options);

View File

@@ -1,4 +1,4 @@
import * as grpc from 'grpc'; import { GrpcObject } from 'grpc';
import { Server } from './server'; import { Server } from './server';
import { import {
MicroserviceOptions, MicroserviceOptions,
@@ -8,6 +8,7 @@ import { CustomTransportStrategy } from './../interfaces';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { GRPC_DEFAULT_URL } from './../constants'; import { GRPC_DEFAULT_URL } from './../constants';
import { InvalidGrpcPackageException } from '../exceptions/invalid-grpc-package.exception'; import { InvalidGrpcPackageException } from '../exceptions/invalid-grpc-package.exception';
import { InvalidProtoDefinitionException } from '../exceptions/invalid-proto-definition.exception';
let grpcPackage: any = {}; let grpcPackage: any = {};
@@ -35,9 +36,7 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
} }
public async bindEvents() { public async bindEvents() {
const grpcContext = grpcPackage.load( const grpcContext = this.loadProto();
this.getOptionsProp<GrpcOptions>(this.options, 'protoPath'),
);
const packageName = this.getOptionsProp<GrpcOptions>( const packageName = this.getOptionsProp<GrpcOptions>(
this.options, this.options,
'package', 'package',
@@ -146,4 +145,16 @@ export class ServerGrpc extends Server implements CustomTransportStrategy {
} }
return pkg; return pkg;
} }
public loadProto(): GrpcObject {
try {
const context = grpcPackage.load(
this.getOptionsProp<GrpcOptions>(this.options, 'protoPath'),
);
return context;
}
catch (e) {
throw new InvalidProtoDefinitionException();
}
}
} }

View File

@@ -1,4 +1,4 @@
import * as mqtt from 'mqtt'; import { MqttClient } from 'mqtt';
import { Server } from './server'; import { Server } from './server';
import { NO_PATTERN_MESSAGE } from '../constants'; import { NO_PATTERN_MESSAGE } from '../constants';
import { import {
@@ -22,7 +22,7 @@ let mqttPackage: any = {};
export class ServerMqtt extends Server implements CustomTransportStrategy { export class ServerMqtt extends Server implements CustomTransportStrategy {
private readonly url: string; private readonly url: string;
private mqttClient: mqtt.MqttClient; private mqttClient:MqttClient;
constructor(private readonly options: MicroserviceOptions) { constructor(private readonly options: MicroserviceOptions) {
super(); super();
@@ -44,7 +44,7 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
this.mqttClient.on(CONNECT_EVENT, callback); this.mqttClient.on(CONNECT_EVENT, callback);
} }
public bindEvents(mqttClient: mqtt.MqttClient) { public bindEvents(mqttClient: MqttClient) {
mqttClient.on(MESSAGE_EVENT, this.getMessageHandler(mqttClient).bind(this)); mqttClient.on(MESSAGE_EVENT, this.getMessageHandler(mqttClient).bind(this));
const registeredPatterns = Object.keys(this.messageHandlers); const registeredPatterns = Object.keys(this.messageHandlers);
registeredPatterns.forEach(pattern => registeredPatterns.forEach(pattern =>
@@ -56,11 +56,11 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
this.mqttClient && this.mqttClient.end(); this.mqttClient && this.mqttClient.end();
} }
public createMqttClient(): mqtt.MqttClient { public createMqttClient(): MqttClient {
return mqttPackage.connect(this.url, this.options.options as MqttOptions); return mqttPackage.connect(this.url, this.options.options as MqttOptions);
} }
public getMessageHandler(pub: mqtt.MqttClient): any { public getMessageHandler(pub: MqttClient): any {
return async (channel, buffer) => return async (channel, buffer) =>
await this.handleMessage(channel, buffer, pub); await this.handleMessage(channel, buffer, pub);
} }
@@ -68,7 +68,7 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
public async handleMessage( public async handleMessage(
channel: string, channel: string,
buffer: Buffer, buffer: Buffer,
pub: mqtt.MqttClient, pub: MqttClient,
): Promise<any> { ): Promise<any> {
const packet = this.deserialize(buffer.toString()); const packet = this.deserialize(buffer.toString());
const pattern = channel.replace(/_ack$/, ''); const pattern = channel.replace(/_ack$/, '');
@@ -85,7 +85,7 @@ export class ServerMqtt extends Server implements CustomTransportStrategy {
response$ && this.send(response$, publish); response$ && this.send(response$, publish);
} }
public getPublisher(client: mqtt.MqttClient, pattern: any, id: string): any { public getPublisher(client: MqttClient, pattern: any, id: string): any {
return response => return response =>
client.publish( client.publish(
this.getResQueueName(pattern), this.getResQueueName(pattern),

View File

@@ -1,4 +1,4 @@
import * as nats from 'nats'; import { Client } from 'nats';
import { Server } from './server'; import { Server } from './server';
import { NO_PATTERN_MESSAGE } from '../constants'; import { NO_PATTERN_MESSAGE } from '../constants';
import { import {
@@ -17,7 +17,7 @@ let natsPackage: any = {};
export class ServerNats extends Server implements CustomTransportStrategy { export class ServerNats extends Server implements CustomTransportStrategy {
private readonly url: string; private readonly url: string;
private natsClient: nats.Client; private natsClient: Client;
constructor(private readonly options: MicroserviceOptions) { constructor(private readonly options: MicroserviceOptions) {
super(); super();
@@ -38,7 +38,7 @@ export class ServerNats extends Server implements CustomTransportStrategy {
this.natsClient.on(CONNECT_EVENT, callback); this.natsClient.on(CONNECT_EVENT, callback);
} }
public bindEvents(client: nats.Client) { public bindEvents(client: Client) {
const registeredPatterns = Object.keys(this.messageHandlers); const registeredPatterns = Object.keys(this.messageHandlers);
registeredPatterns.forEach(pattern => { registeredPatterns.forEach(pattern => {
const channel = this.getAckQueueName(pattern); const channel = this.getAckQueueName(pattern);
@@ -54,7 +54,7 @@ export class ServerNats extends Server implements CustomTransportStrategy {
this.natsClient = null; this.natsClient = null;
} }
public createNatsClient(): nats.Client { public createNatsClient(): Client {
const options = this.options.options || ({} as NatsOptions); const options = this.options.options || ({} as NatsOptions);
return natsPackage.connect({ return natsPackage.connect({
...(options as any), ...(options as any),
@@ -63,14 +63,14 @@ export class ServerNats extends Server implements CustomTransportStrategy {
}); });
} }
public getMessageHandler(channel: string, client: nats.Client) { public getMessageHandler(channel: string, client: Client) {
return async buffer => await this.handleMessage(channel, buffer, client); return async buffer => await this.handleMessage(channel, buffer, client);
} }
public async handleMessage( public async handleMessage(
channel: string, channel: string,
message: ReadPacket & PacketId, message: ReadPacket & PacketId,
client: nats.Client, client: Client,
) { ) {
const pattern = channel.replace(/_ack$/, ''); const pattern = channel.replace(/_ack$/, '');
const publish = this.getPublisher(client, pattern, message.id); const publish = this.getPublisher(client, pattern, message.id);
@@ -86,7 +86,7 @@ export class ServerNats extends Server implements CustomTransportStrategy {
response$ && this.send(response$, publish); response$ && this.send(response$, publish);
} }
public getPublisher(publisher: nats.Client, pattern: any, id: string) { public getPublisher(publisher: Client, pattern: any, id: string) {
return response => return response =>
publisher.publish(this.getResQueueName(pattern), Object.assign(response, { publisher.publish(this.getResQueueName(pattern), Object.assign(response, {
id, id,

View File

@@ -1,4 +1,4 @@
import * as redis from 'redis'; import { RedisClient, ClientOpts, RetryStrategyOptions } from 'redis';
import { Server } from './server'; import { Server } from './server';
import { NO_PATTERN_MESSAGE } from '../constants'; import { NO_PATTERN_MESSAGE } from '../constants';
import { import {
@@ -22,8 +22,8 @@ let redisPackage: any = {};
export class ServerRedis extends Server implements CustomTransportStrategy { export class ServerRedis extends Server implements CustomTransportStrategy {
private readonly url: string; private readonly url: string;
private subClient: redis.RedisClient; private subClient: RedisClient;
private pubClient: redis.RedisClient; private pubClient: RedisClient;
private isExplicitlyTerminated = false; private isExplicitlyTerminated = false;
constructor(private readonly options: MicroserviceOptions) { constructor(private readonly options: MicroserviceOptions) {
@@ -50,12 +50,12 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
} }
public bindEvents( public bindEvents(
subClient: redis.RedisClient, subClient: RedisClient,
pubClient: redis.RedisClient, pubClient: RedisClient,
) { ) {
subClient.on(MESSAGE_EVENT, this.getMessageHandler(pubClient).bind(this)); subClient.on(MESSAGE_EVENT, this.getMessageHandler(pubClient).bind(this));
const registeredPatterns = Object.keys(this.messageHandlers); const subscribePatterns = Object.keys(this.messageHandlers);
registeredPatterns.forEach(pattern => subscribePatterns.forEach(pattern =>
subClient.subscribe(this.getAckQueueName(pattern)), subClient.subscribe(this.getAckQueueName(pattern)),
); );
} }
@@ -66,14 +66,14 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
this.subClient && this.subClient.quit(); this.subClient && this.subClient.quit();
} }
public createRedisClient(): redis.RedisClient { public createRedisClient(): RedisClient {
return redisPackage.createClient({ return redisPackage.createClient({
...this.getClientOptions(), ...this.getClientOptions(),
url: this.url, url: this.url,
}); });
} }
public getMessageHandler(pub: redis.RedisClient) { public getMessageHandler(pub: RedisClient) {
return async (channel, buffer) => return async (channel, buffer) =>
await this.handleMessage(channel, buffer, pub); await this.handleMessage(channel, buffer, pub);
} }
@@ -81,7 +81,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
public async handleMessage( public async handleMessage(
channel, channel,
buffer: string | any, buffer: string | any,
pub: redis.RedisClient, pub: RedisClient,
) { ) {
const packet = this.deserialize(buffer); const packet = this.deserialize(buffer);
const pattern = channel.replace(/_ack$/, ''); const pattern = channel.replace(/_ack$/, '');
@@ -98,7 +98,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
response$ && this.send(response$, publish); response$ && this.send(response$, publish);
} }
public getPublisher(pub: redis.RedisClient, pattern: any, id: string) { public getPublisher(pub: RedisClient, pattern: any, id: string) {
return response => return response =>
pub.publish( pub.publish(
this.getResQueueName(pattern), this.getResQueueName(pattern),
@@ -126,7 +126,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
stream.on(ERROR_EVENT, err => this.logger.error(err)); stream.on(ERROR_EVENT, err => this.logger.error(err));
} }
public getClientOptions(): Partial<redis.ClientOpts> { public getClientOptions(): Partial<ClientOpts> {
const retry_strategy = options => this.createRetryStrategy(options); const retry_strategy = options => this.createRetryStrategy(options);
return { return {
retry_strategy, retry_strategy,
@@ -134,7 +134,7 @@ export class ServerRedis extends Server implements CustomTransportStrategy {
} }
public createRetryStrategy( public createRetryStrategy(
options: redis.RetryStrategyOptions, options: RetryStrategyOptions,
): undefined | number { ): undefined | number {
if ( if (
this.isExplicitlyTerminated || this.isExplicitlyTerminated ||

View File

@@ -44,7 +44,7 @@ export abstract class Server {
.subscribe(response => respond({ err: null, response })); .subscribe(response => respond({ err: null, response }));
} }
public transformToObservable(resultOrDeffered) { public transformToObservable<T = any>(resultOrDeffered): Observable<T> {
if (resultOrDeffered instanceof Promise) { if (resultOrDeffered instanceof Promise) {
return fromPromise(resultOrDeffered); return fromPromise(resultOrDeffered);
} else if (!(resultOrDeffered && isFunction(resultOrDeffered.subscribe))) { } else if (!(resultOrDeffered && isFunction(resultOrDeffered.subscribe))) {
@@ -53,9 +53,9 @@ export abstract class Server {
return resultOrDeffered; return resultOrDeffered;
} }
public getOptionsProp<T>( public getOptionsProp<T extends { options? }>(
obj: MicroserviceOptions, obj: MicroserviceOptions,
prop: keyof T, prop: keyof T['options'],
defaultValue = undefined, defaultValue = undefined,
) { ) {
return obj && obj.options ? obj.options[prop as any] : defaultValue; return obj && obj.options ? obj.options[prop as any] : defaultValue;

View File

@@ -6,6 +6,7 @@ import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
class TestClientProxy extends ClientProxy { class TestClientProxy extends ClientProxy {
public publish(pattern, callback) {} public publish(pattern, callback) {}
public close() {}
} }
describe('ClientProxy', () => { describe('ClientProxy', () => {

View File

@@ -13,8 +13,8 @@ describe('ListenerMetadataExplorer', () => {
const clientSecMetadata = { transport: Transport.REDIS }; const clientSecMetadata = { transport: Transport.REDIS };
class Test { class Test {
@Client(clientMetadata) public client; @Client(clientMetadata as any) public client;
@Client(clientSecMetadata) public redisClient; @Client(clientSecMetadata as any) public redisClient;
get testGet() { get testGet() {
return 0; return 0;

View File

@@ -43,8 +43,10 @@ describe('ServerFactory', () => {
it(`should return grpc server`, () => { it(`should return grpc server`, () => {
expect( expect(
ServerFactory.create({ transport: Transport.GRPC }) instanceof ServerFactory.create({
ServerGrpc, transport: Transport.GRPC,
options: { protoPath: '', package: '' },
}) instanceof ServerGrpc,
).to.be.true; ).to.be.true;
}); });
}); });

View File

@@ -3,7 +3,6 @@ import {
InstanceWrapper, InstanceWrapper,
} from '@nestjs/core/injector/container'; } from '@nestjs/core/injector/container';
import { ModuleMetadata } from '@nestjs/common/interfaces/modules/module-metadata.interface'; import { ModuleMetadata } from '@nestjs/common/interfaces/modules/module-metadata.interface';
import { Module } from '@nestjs/common/utils/decorators/module.decorator';
import { DependenciesScanner } from '@nestjs/core/scanner'; import { DependenciesScanner } from '@nestjs/core/scanner';
import { InstanceLoader } from '@nestjs/core/injector/instance-loader'; import { InstanceLoader } from '@nestjs/core/injector/instance-loader';
import { Logger } from '@nestjs/common/services/logger.service'; import { Logger } from '@nestjs/common/services/logger.service';

View File

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

View File

@@ -1,16 +1,30 @@
import * as WebSocket from 'ws';
import { Server } from 'http'; import { Server } from 'http';
import { MessageMappingProperties } from '../gateway-metadata-explorer'; import { MessageMappingProperties } from '../gateway-metadata-explorer';
import { CONNECTION_EVENT, DISCONNECT_EVENT, CLOSE_EVENT } from '../constants'; import {
import { WebSocketAdapter } from '@nestjs/common'; CONNECTION_EVENT,
DISCONNECT_EVENT,
CLOSE_EVENT,
ERROR_EVENT,
} from '../constants';
import { WebSocketAdapter, Logger } from '@nestjs/common';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { mergeMap, filter, tap } from 'rxjs/operators'; import { mergeMap, filter, tap } from 'rxjs/operators';
import { fromEvent } from 'rxjs/observable/fromEvent'; import { fromEvent } from 'rxjs/observable/fromEvent';
import { isFunction } from '@nestjs/common/utils/shared.utils'; import { isFunction } from '@nestjs/common/utils/shared.utils';
import 'rxjs/add/observable/empty'; import { empty } from 'rxjs/observable/empty';
import { MissingRequiredDependencyException } from '@nestjs/core/errors/exceptions/missing-dependency.exception';
let wsPackage: any = {};
export class WsAdapter implements WebSocketAdapter { export class WsAdapter implements WebSocketAdapter {
constructor(private readonly httpServer: Server | null = null) {} private readonly logger = new Logger(WsAdapter.name);
constructor(private readonly httpServer: Server | null = null) {
try {
wsPackage = require('ws');
} catch (e) {
throw new MissingRequiredDependencyException('ws', 'WsAdapter');
}
}
public create( public create(
port: number, port: number,
@@ -18,17 +32,21 @@ export class WsAdapter implements WebSocketAdapter {
): any { ): any {
const { server, ...wsOptions } = options; const { server, ...wsOptions } = options;
if (port === 0 && this.httpServer) { if (port === 0 && this.httpServer) {
return new WebSocket.Server({ return this.bindErrorHandler(
server: this.httpServer, new wsPackage.Server({
...wsOptions, server: this.httpServer,
}); ...wsOptions,
}),
);
} }
return server return server
? server ? server
: new WebSocket.Server({ : this.bindErrorHandler(
port, new wsPackage.Server({
...wsOptions, port,
}); ...wsOptions,
}),
);
} }
public bindClientConnect(server, callback: (...args) => void) { public bindClientConnect(server, callback: (...args) => void) {
@@ -62,7 +80,7 @@ export class WsAdapter implements WebSocketAdapter {
handler => handler.message === message.event, handler => handler.message === message.event,
); );
if (!messageHandler) { if (!messageHandler) {
return Observable.empty(); return empty();
} }
const { callback } = messageHandler; const { callback } = messageHandler;
return process(callback(message.data)); return process(callback(message.data));
@@ -71,4 +89,9 @@ export class WsAdapter implements WebSocketAdapter {
public close(server) { public close(server) {
isFunction(server.close) && server.close(); isFunction(server.close) && server.close();
} }
public bindErrorHandler(server) {
server.on(ERROR_EVENT, err => this.logger.error(err));
return server;
}
} }

View File

@@ -9,4 +9,5 @@ export const GATEWAY_OPTIONS = '__gatewayOptions';
export const CONNECTION_EVENT = 'connection'; export const CONNECTION_EVENT = 'connection';
export const DISCONNECT_EVENT = 'disconnect'; export const DISCONNECT_EVENT = 'disconnect';
export const CLOSE_EVENT = 'close'; export const CLOSE_EVENT = 'close';
export const ERROR_EVENT = 'error';

View File

@@ -19,7 +19,6 @@
"@nestjs/microservices": "^5.0.0", "@nestjs/microservices": "^5.0.0",
"@nestjs/testing": "^5.0.0", "@nestjs/testing": "^5.0.0",
"@nestjs/websockets": "^5.0.0", "@nestjs/websockets": "^5.0.0",
"amqplib": "^0.5.2",
"class-transformer": "^0.1.7", "class-transformer": "^0.1.7",
"class-validator": "^0.8.1", "class-validator": "^0.8.1",
"fastify": "^1.1.0", "fastify": "^1.1.0",

View File

@@ -1,6 +1,5 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module'; import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';
@Module({ @Module({
imports: [CatsModule], imports: [CatsModule],

View File

@@ -1,6 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface'; import { Cat } from './interfaces/cat.interface';
import { CatsModule } from './cats.module';
@Injectable() @Injectable()
export class CatsService { export class CatsService {

View File

@@ -1,8 +0,0 @@
import { HttpException } from '@nestjs/common';
import { HttpStatus } from '@nestjs/common';
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}

View File

@@ -1,16 +1,15 @@
import { ExceptionFilter, Catch } from '@nestjs/common'; import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { HttpException } from '@nestjs/common'; import { HttpException } from '@nestjs/common';
@Catch(HttpException) @Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter { export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, response) { catch(exception: HttpException, host: ArgumentsHost) {
const status = exception.getStatus(); const status = exception.getStatus();
const response = host.switchToHttp().getResponse();
response response.status(status).json({
.status(status) statusCode: status,
.json({ message: `This is a message from the exception filter`,
statusCode: status, });
message: `This is a message from the exception filter`,
});
} }
} }

View File

@@ -5,13 +5,13 @@ import { Reflector } from '@nestjs/core';
export class RolesGuard implements CanActivate { export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {} constructor(private readonly reflector: Reflector) {}
canActivate(req, context: ExecutionContext): boolean { canActivate(context: ExecutionContext): boolean {
const { parent, handler } = context; const roles = this.reflector.get<string[]>('roles', context.getHandler());
const roles = this.reflector.get<string[]>('roles', handler);
if (!roles) { if (!roles) {
return true; return true;
} }
const user = req.user; const request = context.switchToHttp().getRequest();
const user = request.user;
const hasRole = () => const hasRole = () =>
!!user.roles.find(role => !!roles.find(item => item === role)); !!user.roles.find(role => !!roles.find(item => item === role));
return user && user.roles && hasRole(); return user && user.roles && hasRole();

View File

@@ -1,19 +0,0 @@
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
@Injectable()
export abstract class CacheInterceptor implements NestInterceptor {
protected abstract readonly isCached: () => boolean;
intercept(
dataOrRequest,
context: ExecutionContext,
stream$: Observable<any>,
): Observable<any> {
if (this.isCached()) {
return of([]);
}
return stream$;
}
}

View File

@@ -12,7 +12,6 @@ import { _throw } from 'rxjs/observable/throw';
@Injectable() @Injectable()
export class ExceptionInterceptor implements NestInterceptor { export class ExceptionInterceptor implements NestInterceptor {
intercept( intercept(
dataOrRequest,
context: ExecutionContext, context: ExecutionContext,
stream$: Observable<any>, stream$: Observable<any>,
): Observable<any> { ): Observable<any> {

View File

@@ -5,7 +5,6 @@ import { tap } from 'rxjs/operators';
@Injectable() @Injectable()
export class LoggingInterceptor implements NestInterceptor { export class LoggingInterceptor implements NestInterceptor {
intercept( intercept(
dataOrRequest,
context: ExecutionContext, context: ExecutionContext,
stream$: Observable<any>, stream$: Observable<any>,
): Observable<any> { ): Observable<any> {

View File

@@ -1,10 +0,0 @@
import { mixin } from '@nestjs/common';
import { CacheInterceptor } from './cache.interceptor';
export function mixinCacheInterceptor(isCached: () => boolean) {
return mixin(
class extends CacheInterceptor {
protected readonly isCached = isCached;
},
);
}

View File

@@ -5,7 +5,6 @@ import { map } from 'rxjs/operators';
@Injectable() @Injectable()
export class TransformInterceptor implements NestInterceptor { export class TransformInterceptor implements NestInterceptor {
intercept( intercept(
dataOrRequest,
context: ExecutionContext, context: ExecutionContext,
stream$: Observable<any>, stream$: Observable<any>,
): Observable<any> { ): Observable<any> {

View File

@@ -3,7 +3,6 @@ import {
PipeTransform, PipeTransform,
Pipe, Pipe,
ArgumentMetadata, ArgumentMetadata,
HttpStatus,
} from '@nestjs/common'; } from '@nestjs/common';
@Pipe() @Pipe()

View File

@@ -1,6 +1,6 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { ApplicationModule } from './app.module'; import { ApplicationModule } from './app.module';
import { ValidationPipe } from './common/pipes/validation.pipe';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(ApplicationModule); const app = await NestFactory.create(ApplicationModule);

View File

@@ -1,22 +1,25 @@
<html> <html>
<head>
<script src="socket.io.js"></script> <head>
<script> <script src="socket.io.js"></script>
const socket = io('http://localhost:3000'); <script>
socket.on('connect', function() { const socket = io('http://localhost:3000');
console.log('Connected'); socket.on('connect', function () {
socket.emit('events', { test: 'test' }); console.log('Connected');
}); socket.emit('events', { test: 'test' });
socket.on('events', function(data) { });
console.log('event', data); socket.on('events', function (data) {
}); console.log('event', data);
socket.on('exception', function(data) { });
console.log('event', data); socket.on('exception', function (data) {
}); console.log('event', data);
socket.on('disconnect', function() { });
console.log('Disconnected'); socket.on('disconnect', function () {
}); console.log('Disconnected');
</script> });
</head> </script>
<body></body> </head>
<body></body>
</html> </html>

View File

@@ -1,18 +0,0 @@
<html>
<head>
<script>
const socket = new WebSocket('ws://localhost:81');
socket.onopen = function() {
console.log('Connected');
socket.send(JSON.stringify({
type: 'events',
message: 'test',
}));
socket.onmessage = function(data) {
console.log(data);
}
};
</script>
</head>
<body></body>
</html>

View File

@@ -1,45 +0,0 @@
import * as WebSocket from 'ws';
import { WebSocketAdapter } from '@nestjs/common';
import { MessageMappingProperties } from '@nestjs/websockets';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/observable/empty';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/filter';
export class WsAdapter implements WebSocketAdapter {
public create(port: number) {
return new WebSocket.Server({ port });
}
public bindClientConnect(server, callback: (...args: any[]) => void) {
server.on('connection', callback);
}
public bindMessageHandlers(
client: WebSocket,
handlers: MessageMappingProperties[],
process: (data: any) => Observable<any>,
) {
Observable.fromEvent(client, 'message')
.switchMap(buffer => this.bindMessageHandler(buffer, handlers, process))
.filter(result => !!result)
.subscribe(response => client.send(JSON.stringify(response)));
}
public bindMessageHandler(
buffer,
handlers: MessageMappingProperties[],
process: (data: any) => Observable<any>,
): Observable<any> {
const data = JSON.parse(buffer.data);
const messageHandler = handlers.find(
handler => handler.message === data.type,
);
if (!messageHandler) {
return Observable.empty();
}
const { callback } = messageHandler;
return process(callback(data));
}
}

View File

@@ -1,3 +0,0 @@
import { ReflectMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles);

View File

@@ -1,12 +0,0 @@
import { Catch, WsExceptionFilter } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
@Catch(WsException)
export class ExceptionFilter implements WsExceptionFilter {
catch(exception: WsException, client) {
client.emit('exception', {
status: 'error',
message: `It's a message from the exception filter`,
});
}
}

View File

@@ -1,21 +0,0 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(data, context: ExecutionContext): boolean {
const { parent, handler } = context;
const roles = this.reflector.get<string[]>('roles', handler);
if (!roles) {
return true;
}
const user = data.user;
const hasRole = () =>
!!user.roles.find(role => !!roles.find(item => item === role));
return user && user.roles && hasRole();
}
}

View File

@@ -1,18 +0,0 @@
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(
dataOrRequest,
context: ExecutionContext,
stream$: Observable<any>,
): Observable<any> {
const isCached = true;
if (isCached) {
return Observable.of([]);
}
return stream$;
}
}

View File

@@ -1,17 +0,0 @@
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(
dataOrRequest,
context: ExecutionContext,
stream$: Observable<any>,
): Observable<any> {
console.log('Before...');
const now = Date.now();
return stream$.do(() => console.log(`After... ${Date.now() - now}ms`));
}
}

View File

@@ -1,14 +0,0 @@
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(
dataOrRequest,
context: ExecutionContext,
stream$: Observable<any>,
): Observable<any> {
return stream$.map(data => ({ data }));
}
}

View File

@@ -1,30 +0,0 @@
import {
PipeTransform,
Pipe,
ArgumentMetadata,
HttpStatus,
} from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { WsException } from '@nestjs/websockets';
@Pipe()
export class ValidationPipe implements PipeTransform<any> {
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new WsException('Validation failed');
}
return value;
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
}
}

View File

@@ -1,44 +0,0 @@
import * as amqp from 'amqplib';
import { ClientProxy } from '@nestjs/microservices';
export class RabbitMQClient extends ClientProxy {
constructor(private readonly host: string, private readonly queue: string) {
super();
}
protected async sendSingleMessage(
messageObj,
callback: (err, result, disposed?: boolean) => void,
) {
const server = await amqp.connect(this.host);
const channel = await server.createChannel();
const { sub, pub } = this.getQueues();
channel.assertQueue(sub, { durable: false });
channel.assertQueue(pub, { durable: false });
channel.consume(
pub,
message => this.handleMessage(message, server, callback),
{ noAck: true },
);
channel.sendToQueue(sub, Buffer.from(JSON.stringify(messageObj)));
}
private handleMessage(
message,
server,
callback: (err, result, disposed?: boolean) => void,
) {
const { content } = message;
const { err, response, disposed } = JSON.parse(content.toString());
if (disposed) {
server.close();
}
callback(err, response, disposed);
}
private getQueues() {
return { pub: `${this.queue}_pub`, sub: `${this.queue}_sub` };
}
}

View File

@@ -1,51 +0,0 @@
import * as amqp from 'amqplib';
import { Server, CustomTransportStrategy } from '@nestjs/microservices';
import { Observable } from 'rxjs/Observable';
export class RabbitMQServer extends Server implements CustomTransportStrategy {
private server: amqp.Connection;
private channel: amqp.Channel;
constructor(private readonly host: string, private readonly queue: string) {
super();
}
public async listen(callback: () => void) {
await this.init();
this.channel.consume(`${this.queue}_sub`, this.handleMessage.bind(this), {
noAck: true,
});
}
public close() {
this.channel && this.channel.close();
this.server && this.server.close();
}
private async handleMessage(message) {
const { content } = message;
const messageObj = JSON.parse(content.toString());
const pattern = JSON.stringify(messageObj.pattern);
const handler = this.getHandlerByPattern(pattern);
if (!handler) {
return;
}
const response$ = this.transformToObservable(
await handler(messageObj.data),
) as Observable<any>;
response$ && this.send(response$, data => this.sendMessage(data));
}
private sendMessage(message) {
const buffer = Buffer.from(JSON.stringify(message));
this.channel.sendToQueue(`${this.queue}_pub`, buffer);
}
private async init() {
this.server = await amqp.connect(this.host);
this.channel = await this.server.createChannel();
this.channel.assertQueue(`${this.queue}_sub`, { durable: false });
this.channel.assertQueue(`${this.queue}_pub`, { durable: false });
}
}

View File

@@ -1,3 +0,0 @@
import { ReflectMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles);

View File

@@ -1,20 +0,0 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(data, context: ExecutionContext): boolean {
const { parent, handler } = context;
const roles = this.reflector.get<string[]>('roles', handler);
if (!roles) {
return true;
}
const user = data.user;
const hasRole = () =>
!!user.roles.find(role => !!roles.find(item => item === role));
return user && user.roles && hasRole();
}
}

View File

@@ -1,18 +0,0 @@
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(
dataOrRequest,
context: ExecutionContext,
stream$: Observable<any>,
): Observable<any> {
const isCached = true;
if (isCached) {
return of([]);
}
return stream$;
}
}

View File

@@ -5,7 +5,6 @@ import { tap } from 'rxjs/operators';
@Injectable() @Injectable()
export class LoggingInterceptor implements NestInterceptor { export class LoggingInterceptor implements NestInterceptor {
intercept( intercept(
dataOrRequest,
context: ExecutionContext, context: ExecutionContext,
stream$: Observable<any>, stream$: Observable<any>,
): Observable<any> { ): Observable<any> {

View File

@@ -1,14 +0,0 @@
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(
dataOrRequest,
context: ExecutionContext,
stream$: Observable<any>,
): Observable<any> {
return stream$.pipe(map(data => ({ data })));
}
}

View File

@@ -1,25 +0,0 @@
import { PipeTransform, Pipe, ArgumentMetadata } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { RpcException } from '@nestjs/microservices';
@Pipe()
export class ValidationPipe implements PipeTransform<any> {
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new RpcException('Validation failed');
}
return value;
}
private toValidate(metatype): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find(type => metatype === type);
}
}

View File

@@ -3,19 +3,22 @@ import { Transport } from '@nestjs/microservices';
import { ApplicationModule } from './app.module'; import { ApplicationModule } from './app.module';
async function bootstrap() { async function bootstrap() {
/*const app = await NestFactory.createMicroservice(ApplicationModule, { /**
retryAttempts: 5, * Hybrid application (HTTP + TCP)
retryDelay: 3000, * Switch to basic microservice with NestFactory.createMicroservice():
transport: Transport.REDIS, *
} as any); * const app = await NestFactory.createMicroservice(ApplicationModule, {
await app.listenAsync();*/ * transport: Transport.TCP,
/** Hybrid application (HTTP + (n)Microservices)*/ * options: { retryAttempts: 5, retryDelay: 3000 },
* });
* await app.listenAsync();
*
*/
const app = await NestFactory.create(ApplicationModule); const app = await NestFactory.create(ApplicationModule);
app.connectMicroservice({ app.connectMicroservice({
retryAttempts: 5, transport: Transport.TCP,
retryDelay: 3000, options: { retryAttempts: 5, retryDelay: 3000 },
transport: Transport.TCP, });
} as any);
await app.startAllMicroservicesAsync(); await app.startAllMicroservicesAsync();
await app.listen(3001); await app.listen(3001);

View File

@@ -1,4 +1,4 @@
import { Controller, Get, UseInterceptors } from '@nestjs/common'; import { Controller, Get } from '@nestjs/common';
import { import {
ClientProxy, ClientProxy,
Client, Client,
@@ -6,10 +6,8 @@ import {
MessagePattern, MessagePattern,
} from '@nestjs/microservices'; } from '@nestjs/microservices';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { LoggingInterceptor } from '../common/interceptors/logging.interceptor';
@Controller() @Controller()
@UseInterceptors(LoggingInterceptor)
export class MathController { export class MathController {
@Client({ transport: Transport.TCP }) @Client({ transport: Transport.TCP })
client: ClientProxy; client: ClientProxy;

View File

@@ -4,7 +4,7 @@
"description": "Nest TypeScript starter repository", "description": "Nest TypeScript starter repository",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"start": "ts-node src/main", "start": "ts-node src/main.ts",
"prestart:prod": "tsc", "prestart:prod": "tsc",
"start:prod": "node dist/main.js" "start:prod": "node dist/main.js"
}, },
@@ -14,11 +14,9 @@
"@nestjs/microservices": "^5.0.0", "@nestjs/microservices": "^5.0.0",
"@nestjs/testing": "^5.0.0", "@nestjs/testing": "^5.0.0",
"@nestjs/websockets": "^5.0.0", "@nestjs/websockets": "^5.0.0",
"@types/passport-jwt": "^2.0.24", "class-transformer": "^0.1.7",
"jsonwebtoken": "^8.0.1", "class-validator": "^0.7.2",
"passport": "^0.4.0", "grpc": "^1.10.0",
"passport-jwt": "^3.0.0",
"redis": "^2.7.1",
"reflect-metadata": "^0.1.10", "reflect-metadata": "^0.1.10",
"rxjs": "^5.5.6", "rxjs": "^5.5.6",
"typescript": "^2.4.2" "typescript": "^2.4.2"

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module'; import { HeroModule } from './hero/hero.module';
@Module({ @Module({
imports: [AuthModule], imports: [HeroModule],
}) })
export class ApplicationModule {} export class ApplicationModule {}

View File

@@ -0,0 +1,39 @@
import { Controller, Get } from '@nestjs/common';
import {
ClientProxy,
Client,
Transport,
MessagePattern,
GrpcRoute,
ClientGrpc,
} from '@nestjs/microservices';
import { Observable } from 'rxjs/Observable';
import { join } from 'path';
interface HeroService {
FindOne(data: { id: number }): Observable<any>;
}
@Controller()
export class HeroController {
@Client({
transport: Transport.GRPC,
options: {
package: 'hero',
protoPath: join(__dirname, './hero.proto'),
},
})
client: ClientGrpc;
@Get()
call(): Observable<any> {
const heroService = this.client.getService<HeroService>('HeroService');
return heroService.FindOne({ id: 1 });
}
@GrpcRoute('HeroService', 'FindOne')
findOne(data: { id: number }): any {
const items = [{ id: 1, name: 'John' }, { id: 2, name: 'Doe' }];
return items.find(({ id }) => id === data.id);
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { HeroController } from './hero.controller';
@Module({
controllers: [HeroController],
})
export class HeroModule {}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package hero;
service HeroService {
rpc FindOne (HeroById) returns (Hero) {}
}
message HeroById {
int32 id = 1;
}
message Hero {
int32 id = 1;
string name = 2;
}

View File

@@ -0,0 +1,32 @@
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { ApplicationModule } from './app.module';
import { join } from 'path';
async function bootstrap() {
/**
* Hybrid application (HTTP + GRPC)
* Switch to basic microservice with NestFactory.createMicroservice():
*
* const app = await NestFactory.createMicroservice(ApplicationModule, {
* transport: Transport.GRPC,
* options: {
* package: 'hero',
* protoPath: join(__dirname, './hero/hero.proto'),
* }
* });
* await app.listenAsync();
*
*/
const app = await NestFactory.create(ApplicationModule);
app.connectMicroservice({
transport: Transport.GRPC,
options: {
package: 'hero',
protoPath: join(__dirname, './hero/hero.proto'),
}
});
await app.startAllMicroservicesAsync();
await app.listen(3001);
}
bootstrap();

View File

@@ -1,8 +0,0 @@
import { Module } from '@nestjs/common';
import { CoreModule } from './core/core.module';
import { FeatureModule } from './feature/feature.module';
@Module({
imports: [FeatureModule],
})
export class ApplicationModule {}

View File

@@ -1,8 +0,0 @@
import { Module, SingleScope } from '@nestjs/common';
import { CommonService } from './common.service';
@Module({
providers: [CommonService],
exports: [CommonService],
})
export class CommonModule {}

View File

@@ -1,9 +0,0 @@
import { Injectable } from '@nestjs/common';
import { CoreService } from '../core/core.service';
@Injectable()
export class CommonService {
constructor(private readonly coreService: CoreService) {
console.log('CommonService', coreService);
}
}

View File

@@ -1,10 +0,0 @@
import { Injectable } from '@nestjs/common';
import { CommonService } from '../common/common.service';
import { CoreService } from './core.service';
@Injectable()
export class ContextService {
constructor(private readonly commonService: CoreService) {
console.log('ContextService', commonService);
}
}

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