From 6071ea80c8d389e6bced01d2e76ae50fd9b5ab21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Thu, 13 Dec 2018 11:15:51 +0100 Subject: [PATCH 1/9] feature(common) enhance internal logger --- .../common/enums/nest-environment.enum.ts | 4 - packages/common/services/logger.service.ts | 107 ++++++++++-------- .../exceptions/exceptions-handler.spec.ts | 4 - .../test/injector/instance-loader.spec.ts | 4 - .../core/test/middleware/resolver.spec.ts | 4 - 5 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 packages/common/enums/nest-environment.enum.ts diff --git a/packages/common/enums/nest-environment.enum.ts b/packages/common/enums/nest-environment.enum.ts deleted file mode 100644 index 24a3827f929..00000000000 --- a/packages/common/enums/nest-environment.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum NestEnvironment { - RUN, - TEST, -} diff --git a/packages/common/services/logger.service.ts b/packages/common/services/logger.service.ts index d1eb5ed0a91..579091814fe 100644 --- a/packages/common/services/logger.service.ts +++ b/packages/common/services/logger.service.ts @@ -1,62 +1,50 @@ import * as clc from 'cli-color'; import { Injectable, Optional } from '../decorators'; -import { NestEnvironment } from '../enums/nest-environment.enum'; import { isObject } from '../utils/shared.utils'; declare const process: any; +const yellow = clc.xterm(3); export interface LoggerService { - log(message: any, context?: string): any; - error(message: any, trace?: string, context?: string): any; - warn(message: any, context?: string): any; + log(message: any, context?: string); + error(message: any, trace?: string, context?: string); + warn(message: any, context?: string); } @Injectable() export class Logger implements LoggerService { - private static prevTimestamp?: number; - private static contextEnvironment = NestEnvironment.RUN; - private static logger?: typeof Logger | LoggerService = Logger; - private static readonly yellow = clc.xterm(3); + private static lastTimestamp?: number; + private static instance?: typeof Logger | LoggerService = Logger; constructor( @Optional() private readonly context?: string, - @Optional() private readonly isTimeDiffEnabled = false, + @Optional() private readonly isTimestampEnabled = false, ) {} - log(message: any, context?: string) { - const { logger } = Logger; - if (logger === this) { - Logger.log(message, context || this.context, this.isTimeDiffEnabled); - return; - } - logger && logger.log.call(logger, message, context || this.context); + error(message: any, trace = '', context?: string) { + const instance = this.getInstance(); + instance && + instance.error.call(instance, message, trace, context || this.context); } - error(message: any, trace = '', context?: string) { - const { logger } = Logger; - if (logger === this) { - Logger.error(message, trace, context || this.context); - return; - } - logger && - logger.error.call(logger, message, trace, context || this.context); + log(message: any, context?: string) { + this.callFunction('log', message, context); } warn(message: any, context?: string) { - const { logger } = Logger; - if (logger === this) { - Logger.warn(message, context || this.context, this.isTimeDiffEnabled); - return; - } - logger && logger.warn.call(logger, message, context || this.context); + this.callFunction('warn', message, context); } - static overrideLogger(logger: LoggerService | boolean) { - this.logger = logger ? (logger as LoggerService) : undefined; + debug(message: any, context?: string) { + this.callFunction('debug', message, context); } - static setMode(mode: NestEnvironment) { - this.contextEnvironment = mode; + verbose(message: any, context?: string) { + this.callFunction('verbose', message, context); + } + + static overrideLogger(logger: LoggerService | boolean) { + this.instance = isObject(logger) ? (logger as LoggerService) : undefined; } static log(message: any, context = '', isTimeDiffEnabled = true) { @@ -77,38 +65,65 @@ export class Logger implements LoggerService { this.printMessage(message, clc.yellow, context, isTimeDiffEnabled); } + static debug(message: any, context = '', isTimeDiffEnabled = true) { + this.printMessage(message, clc.magentaBright, context, isTimeDiffEnabled); + } + + static verbose(message: any, context = '', isTimeDiffEnabled = true) { + this.printMessage(message, clc.cyanBright, context, isTimeDiffEnabled); + } + + private callFunction( + name: 'log' | 'warn' | 'debug' | 'verbose', + message: any, + context?: string, + ) { + const instance = this.getInstance(); + const func = instance && (instance as typeof Logger)[name]; + func && + func.call( + instance, + message, + context || this.context, + this.isTimestampEnabled, + ); + } + + private getInstance(): typeof Logger | LoggerService { + const { instance } = Logger; + return instance === this ? Logger : instance; + } + private static printMessage( message: any, color: (message: string) => string, context: string = '', isTimeDiffEnabled?: boolean, ) { - if (Logger.contextEnvironment === NestEnvironment.TEST) { - return; - } - const output = isObject(message) ? JSON.stringify(message, null, 2) : message; + const output = isObject(message) + ? `${color('Object:')}\n${JSON.stringify(message, null, 2)}\n` + : color(message); + process.stdout.write(color(`[Nest] ${process.pid} - `)); process.stdout.write(`${new Date(Date.now()).toLocaleString()} `); - context && process.stdout.write(this.yellow(`[${context}] `)); - process.stdout.write(color(output)); + context && process.stdout.write(yellow(`[${context}] `)); + process.stdout.write(output); this.printTimestamp(isTimeDiffEnabled); process.stdout.write(`\n`); } private static printTimestamp(isTimeDiffEnabled?: boolean) { - const includeTimestamp = Logger.prevTimestamp && isTimeDiffEnabled; + const includeTimestamp = Logger.lastTimestamp && isTimeDiffEnabled; if (includeTimestamp) { - process.stdout.write( - this.yellow(` +${Date.now() - Logger.prevTimestamp}ms`), - ); + process.stdout.write(yellow(` +${Date.now() - Logger.lastTimestamp}ms`)); } - Logger.prevTimestamp = Date.now(); + Logger.lastTimestamp = Date.now(); } private static printStackTrace(trace: string) { - if (this.contextEnvironment === NestEnvironment.TEST || !trace) { + if (!trace) { return; } process.stdout.write(trace); diff --git a/packages/core/test/exceptions/exceptions-handler.spec.ts b/packages/core/test/exceptions/exceptions-handler.spec.ts index 23903ab3f9e..19c648731df 100644 --- a/packages/core/test/exceptions/exceptions-handler.spec.ts +++ b/packages/core/test/exceptions/exceptions-handler.spec.ts @@ -2,8 +2,6 @@ import { HttpException } from '@nestjs/common'; import { isNil, isObject } from '@nestjs/common/utils/shared.utils'; import { expect } from 'chai'; import * as sinon from 'sinon'; -import { NestEnvironment } from '../../../common/enums/nest-environment.enum'; -import { Logger } from '../../../common/services/logger.service'; import { AbstractHttpAdapter } from '../../adapters'; import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception'; import { ExceptionsHandler } from '../../exceptions/exceptions-handler'; @@ -17,8 +15,6 @@ describe('ExceptionsHandler', () => { let jsonStub: sinon.SinonStub; let response; - before(() => Logger.setMode(NestEnvironment.TEST)); - beforeEach(() => { adapter = new NoopHttpAdapter({}); handler = new ExceptionsHandler(adapter); diff --git a/packages/core/test/injector/instance-loader.spec.ts b/packages/core/test/injector/instance-loader.spec.ts index c46cbfa5170..3f8b09015cf 100644 --- a/packages/core/test/injector/instance-loader.spec.ts +++ b/packages/core/test/injector/instance-loader.spec.ts @@ -2,8 +2,6 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import { Injectable } from '../../../common'; import { Controller } from '../../../common/decorators/core/controller.decorator'; -import { NestEnvironment } from '../../../common/enums/nest-environment.enum'; -import { Logger } from '../../../common/services/logger.service'; import { NestContainer } from '../../injector/container'; import { Injector } from '../../injector/injector'; import { InstanceLoader } from '../../injector/instance-loader'; @@ -19,8 +17,6 @@ describe('InstanceLoader', () => { @Injectable() class TestProvider {} - before(() => Logger.setMode(NestEnvironment.TEST)); - beforeEach(() => { container = new NestContainer(); loader = new InstanceLoader(container); diff --git a/packages/core/test/middleware/resolver.spec.ts b/packages/core/test/middleware/resolver.spec.ts index 0ee5c1b73ae..82893560986 100644 --- a/packages/core/test/middleware/resolver.spec.ts +++ b/packages/core/test/middleware/resolver.spec.ts @@ -1,9 +1,7 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import { Injectable } from '../../../common'; -import { NestEnvironment } from '../../../common/enums/nest-environment.enum'; import { NestMiddleware } from '../../../common/interfaces/middleware/nest-middleware.interface'; -import { Logger } from '../../../common/services/logger.service'; import { MiddlewareContainer } from '../../middleware/container'; import { MiddlewareResolver } from '../../middleware/resolver'; @@ -19,8 +17,6 @@ describe('MiddlewareResolver', () => { let container: MiddlewareContainer; let mockContainer: sinon.SinonMock; - before(() => Logger.setMode(NestEnvironment.TEST)); - beforeEach(() => { container = new MiddlewareContainer(); resolver = new MiddlewareResolver(container); From 17e0ae6c6352c908a64718804002181249d0836e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Sat, 15 Dec 2018 21:22:36 +0100 Subject: [PATCH 2/9] feature() wip: DI scopes --- benchmarks/nest/app.controller.js | 65 +-- gulpfile.js | 9 +- package-lock.json | 34 +- package.json | 6 +- packages/common/constants.ts | 1 + .../decorators/core/controller.decorator.ts | 23 +- .../decorators/core/injectable.decorator.ts | 10 +- packages/common/index.ts | 1 + packages/common/interfaces/index.ts | 1 + .../interfaces/modules/provider.interface.ts | 5 +- .../interfaces/scope-options.interface.ts | 9 + .../base-exception-filter-context.ts | 26 +- .../core/guards/guards-context-creator.ts | 24 +- packages/core/helpers/context-creator.ts | 13 +- packages/core/hooks/async-context.ts | 63 +++ packages/core/hooks/async-hooks-helper.ts | 25 ++ packages/core/hooks/async-hooks-storage.ts | 31 ++ packages/core/hooks/index.ts | 1 + packages/core/index.ts | 1 + packages/core/injector/constants.ts | 6 + packages/core/injector/container-scanner.ts | 5 +- packages/core/injector/container.ts | 26 +- packages/core/injector/injector.ts | 370 +++++++++++++----- packages/core/injector/instance-loader.ts | 45 +-- packages/core/injector/instance-wrapper.ts | 138 +++++++ packages/core/injector/module-ref.ts | 8 +- .../core/injector/module-token-factory.ts | 2 +- packages/core/injector/module.ts | 309 ++++++++++----- .../interceptors-context-creator.ts | 34 +- packages/core/middleware/container.ts | 37 +- packages/core/middleware/middleware-module.ts | 8 +- packages/core/middleware/resolver.ts | 17 +- packages/core/pipes/pipes-context-creator.ts | 26 +- .../interfaces/exceptions-filter.interface.ts | 2 + .../core/router/router-exception-filters.ts | 3 + .../core/router/router-execution-context.ts | 17 +- packages/core/router/router-explorer.ts | 44 ++- packages/core/router/routes-resolver.ts | 3 +- packages/core/scanner.ts | 16 +- packages/core/test/injector/container.spec.ts | 4 +- packages/core/test/injector/injector.spec.ts | 84 ++-- .../test/injector/instance-loader.spec.ts | 29 +- packages/core/test/injector/module.spec.ts | 9 +- .../core/test/middleware/resolver.spec.ts | 11 +- packages/microservices/client/client-redis.ts | 2 +- .../microservices/microservices-module.ts | 6 +- packages/websockets/socket-module.ts | 6 +- .../01-cats-app/src/cats/cats.controller.ts | 23 +- sample/01-cats-app/src/cats/cats.module.ts | 33 +- sample/01-cats-app/src/cats/cats.service.ts | 22 +- .../src/common/guards/roles.guard.ts | 11 +- 51 files changed, 1208 insertions(+), 496 deletions(-) create mode 100644 packages/common/interfaces/scope-options.interface.ts create mode 100644 packages/core/hooks/async-context.ts create mode 100644 packages/core/hooks/async-hooks-helper.ts create mode 100644 packages/core/hooks/async-hooks-storage.ts create mode 100644 packages/core/hooks/index.ts create mode 100644 packages/core/injector/constants.ts create mode 100644 packages/core/injector/instance-wrapper.ts diff --git a/benchmarks/nest/app.controller.js b/benchmarks/nest/app.controller.js index 568febb9519..f86c0fed833 100644 --- a/benchmarks/nest/app.controller.js +++ b/benchmarks/nest/app.controller.js @@ -1,28 +1,47 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; +'use strict'; +var __decorate = + (this && this.__decorate) || + function(decorators, target, key, desc) { + var c = arguments.length, + r = + c < 3 + ? target + : desc === null + ? (desc = Object.getOwnPropertyDescriptor(target, key)) + : desc, + d; + if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') + r = Reflect.decorate(decorators, target, key, desc); + else + for (var i = decorators.length - 1; i >= 0; i--) + if ((d = decorators[i])) + r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const common_1 = require("@nestjs/common"); + }; +var __metadata = + (this && this.__metadata) || + function(k, v) { + if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function') + return Reflect.metadata(k, v); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const common_1 = require('@nestjs/common'); let AppController = class AppController { - root() { - return 'Hello world!'; - } + root() { + return 'Hello world!'; + } }; -__decorate([ +__decorate( + [ common_1.Get(), - __metadata("design:type", Function), - __metadata("design:paramtypes", []), - __metadata("design:returntype", String) -], AppController.prototype, "root", null); -AppController = __decorate([ - common_1.Controller() -], AppController); + __metadata('design:type', Function), + __metadata('design:paramtypes', []), + __metadata('design:returntype', String), + ], + AppController.prototype, + 'root', + null, +); +AppController = __decorate([common_1.Controller()], AppController); exports.AppController = AppController; -//# sourceMappingURL=app.controller.js.map \ No newline at end of file +//# sourceMappingURL=app.controller.js.map diff --git a/gulpfile.js b/gulpfile.js index 3d599a61ad9..d0225492938 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -39,9 +39,12 @@ gulp.task('copy-misc', function() { gulp.task('clean:output', function() { return gulp - .src([`${source}/**/*.js`, `${source}/**/*.d.ts`], { - read: false, - }) + .src( + [`${source}/**/*.js`, `${source}/**/*.d.ts`, `${source}/**/*.js.map`], + { + read: false, + }, + ) .pipe(clean()); }); diff --git a/package-lock.json b/package-lock.json index ec957bc89e8..2e29a75c295 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,20 +111,32 @@ } }, "@nestjs/common": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-5.1.0.tgz", - "integrity": "sha512-JAZFqdU+f4DRE4yOvpfWDtwgmCavyfE2Vu7mSwYsklU9TlBBE9XBygN2J38aQC83dmCJ1H889shd+hBIiVyEXA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-5.5.0.tgz", + "integrity": "sha512-Ifh1D4ypsJYs/3YBIocU+X5yuAZSVKuCsz8kaKB4ZUO5WwJjh4/x6hlr4A+9XUMe8fPLtYXVohJoRUU5HbwyIA==", "requires": { - "axios": "0.17.1", + "axios": "0.18.0", "cli-color": "1.2.0", "deprecate": "1.0.0", - "multer": "1.3.0" + "multer": "1.3.0", + "uuid": "3.3.2" + }, + "dependencies": { + "axios": { + "version": "0.18.0", + "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "requires": { + "follow-redirects": "^1.3.0", + "is-buffer": "^1.1.5" + } + } } }, "@nestjs/core": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-5.4.1.tgz", - "integrity": "sha512-PAlSEsycXPIdVm1GOeghs0co8cUmMX65FPjzBLtRJKKcPS8YM7Xo3WKd2f2oGMACYNXbulLk8rWN0xTO2HTgww==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-5.5.0.tgz", + "integrity": "sha512-XPUjSJyex6KMdTUKK1oeD7ea9mNLcwlSEbcKV7OWaNHIVq/XJaFpbzjbmd+/U/ZZaO1IWhpisfLW9gr/O8eb4w==", "requires": { "@nuxtjs/opencollective": "0.1.0", "body-parser": "1.18.3", @@ -586,9 +598,9 @@ "dev": true }, "@types/node": { - "version": "7.0.48", - "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.48.tgz", - "integrity": "sha512-LLlXafM3BD52MH056tHxTXO8JFCnpJJQkdzIU3+m8ew+CXJY/5zIXgDNb4TK/QFvlI8QexLS5tL+sE0Qhegr1w==" + "version": "10.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.14.tgz", + "integrity": "sha512-0rVcFRhM93kRGAU88ASCjX9Y3FWDCh+33G5Z5evpKOea4xcpLqDGwmo64+DjgaSezTN5j9KdnUzvxhOw7fNciQ==" }, "@types/pino": { "version": "4.7.1", diff --git a/package.json b/package.json index c333799014b..59c98656024 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "license": "MIT", "dependencies": { "@grpc/proto-loader": "^0.3.0", - "@nestjs/common": "5.1.0", - "@nestjs/core": "^5.3.10", + "@nestjs/common": "^5.5.0", + "@nestjs/core": "^5.5.0", "@nestjs/microservices": "5.1.0", "@nestjs/testing": "5.1.0", "@nestjs/websockets": "5.1.0", @@ -82,7 +82,7 @@ "@types/express": "^4.0.39", "@types/kafka-node": "^2.0.6", "@types/mocha": "^2.2.38", - "@types/node": "^7.0.5", + "@types/node": "^10.12.14", "@types/redis": "^0.12.36", "@types/reflect-metadata": "0.0.5", "@types/sinon": "^1.16.36", diff --git a/packages/common/constants.ts b/packages/common/constants.ts index 31ce8259209..5c75b53e158 100644 --- a/packages/common/constants.ts +++ b/packages/common/constants.ts @@ -13,6 +13,7 @@ export const SELF_DECLARED_DEPS_METADATA = 'self:paramtypes'; export const OPTIONAL_DEPS_METADATA = 'optional:paramtypes'; export const PROPERTY_DEPS_METADATA = 'self:properties_metadata'; export const OPTIONAL_PROPERTY_DEPS_METADATA = 'optional:properties_metadata'; +export const SCOPE_OPTIONS_METADATA = 'scope:options'; export const METHOD_METADATA = 'method'; export const ROUTE_ARGS_METADATA = '__routeArguments__'; diff --git a/packages/common/decorators/core/controller.decorator.ts b/packages/common/decorators/core/controller.decorator.ts index f83705d5b79..82b24151a6b 100644 --- a/packages/common/decorators/core/controller.decorator.ts +++ b/packages/common/decorators/core/controller.decorator.ts @@ -1,13 +1,30 @@ -import { PATH_METADATA } from '../../constants'; -import { isUndefined } from '../../utils/shared.utils'; +import { PATH_METADATA, SCOPE_OPTIONS_METADATA } from '../../constants'; +import { isObject, isUndefined } from '../../utils/shared.utils'; +import { ScopeOptions } from './../../interfaces/scope-options.interface'; + +export interface ControllerOptions extends ScopeOptions {} /** * Defines the controller. Controller can inject dependencies through constructor. * Those dependencies have to belong to the same module. */ -export function Controller(prefix?: string): ClassDecorator { +export function Controller(prefix?: string): ClassDecorator; +export function Controller(options?: ControllerOptions): ClassDecorator; +export function Controller( + prefix?: string, + options?: ControllerOptions, +): ClassDecorator; +export function Controller( + prefixOrOptions?: string | ControllerOptions, + options?: ControllerOptions, +): ClassDecorator { + const [prefix, controllerOptions] = isObject(prefixOrOptions) + ? [undefined, prefixOrOptions] + : [prefixOrOptions, options]; + const path = isUndefined(prefix) ? '/' : prefix; return (target: object) => { Reflect.defineMetadata(PATH_METADATA, path, target); + Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, controllerOptions, target); }; } diff --git a/packages/common/decorators/core/injectable.decorator.ts b/packages/common/decorators/core/injectable.decorator.ts index 0a4c73a4cda..1838713c969 100644 --- a/packages/common/decorators/core/injectable.decorator.ts +++ b/packages/common/decorators/core/injectable.decorator.ts @@ -1,12 +1,18 @@ import * as uuid from 'uuid/v4'; +import { ScopeOptions } from '../../interfaces/scope-options.interface'; +import { SCOPE_OPTIONS_METADATA } from './../../constants'; import { Type } from './../../interfaces/type.interface'; +export interface InjectableOptions extends ScopeOptions {} + /** * Defines the injectable class. This class can inject dependencies through constructor. * Those dependencies have to belong to the same module. */ -export function Injectable(): ClassDecorator { - return (target: object) => {}; +export function Injectable(options?: InjectableOptions): ClassDecorator { + return (target: object) => { + Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, options, target); + }; } export function mixin(mixinClass: Type) { diff --git a/packages/common/index.ts b/packages/common/index.ts index 73d9eabc9fc..794e642a517 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -39,6 +39,7 @@ export { PipeTransform, Provider, RpcExceptionFilter, + Scope, Type, ValidationError, WebSocketAdapter, diff --git a/packages/common/interfaces/index.ts b/packages/common/interfaces/index.ts index 53a12e139d2..ae259bff185 100644 --- a/packages/common/interfaces/index.ts +++ b/packages/common/interfaces/index.ts @@ -29,5 +29,6 @@ export * from './nest-fastify-application.interface'; export * from './nest-microservice.interface'; export * from './on-application-bootstrap.interface'; export * from './request-mapping-metadata.interface'; +export * from './scope-options.interface'; export * from './type.interface'; export * from './websockets/web-socket-adapter.interface'; diff --git a/packages/common/interfaces/modules/provider.interface.ts b/packages/common/interfaces/modules/provider.interface.ts index 1b729a14e11..24fdf2a9d35 100644 --- a/packages/common/interfaces/modules/provider.interface.ts +++ b/packages/common/interfaces/modules/provider.interface.ts @@ -1,3 +1,4 @@ +import { Scope } from '../scope-options.interface'; import { Type } from '../type.interface'; export type Provider = @@ -9,6 +10,7 @@ export type Provider = export interface ClassProvider { provide: any; useClass: Type; + scope?: Scope; } export interface ValueProvider { @@ -20,4 +22,5 @@ export interface FactoryProvider { provide: any; useFactory: (...args: any[]) => any; inject?: Array | string | any>; -} \ No newline at end of file + scope?: Scope; +} diff --git a/packages/common/interfaces/scope-options.interface.ts b/packages/common/interfaces/scope-options.interface.ts new file mode 100644 index 00000000000..40274f6f23f --- /dev/null +++ b/packages/common/interfaces/scope-options.interface.ts @@ -0,0 +1,9 @@ +export enum Scope { + DEFAULT, + REQUEST, + LAZY, +} + +export interface ScopeOptions { + scope?: Scope; +} diff --git a/packages/core/exceptions/base-exception-filter-context.ts b/packages/core/exceptions/base-exception-filter-context.ts index adbc76b572c..39b6cf9f027 100644 --- a/packages/core/exceptions/base-exception-filter-context.ts +++ b/packages/core/exceptions/base-exception-filter-context.ts @@ -1,14 +1,12 @@ import { FILTER_CATCH_EXCEPTIONS } from '@nestjs/common/constants'; import { Type } from '@nestjs/common/interfaces'; import { ExceptionFilter } from '@nestjs/common/interfaces/exceptions/exception-filter.interface'; -import { - isEmpty, - isFunction, - isUndefined, -} from '@nestjs/common/utils/shared.utils'; +import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils'; import iterate from 'iterare'; import { ContextCreator } from '../helpers/context-creator'; +import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; +import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; export class BaseExceptionFilterContext extends ContextCreator { protected moduleContext: string; @@ -19,6 +17,7 @@ export class BaseExceptionFilterContext extends ContextCreator { public createConcreteContext( metadata: T, + contextId = STATIC_CONTEXT, ): R { if (isEmpty(metadata)) { return [] as R; @@ -27,7 +26,7 @@ export class BaseExceptionFilterContext extends ContextCreator { .filter( instance => instance && (isFunction(instance.catch) || instance.name), ) - .map(filter => this.getFilterInstance(filter)) + .map(filter => this.getFilterInstance(filter, contextId)) .map(instance => ({ func: instance.catch.bind(instance), exceptionMetatypes: this.reflectCatchExceptions(instance), @@ -35,20 +34,25 @@ export class BaseExceptionFilterContext extends ContextCreator { .toArray() as R; } - public getFilterInstance(filter: Function | ExceptionFilter) { + public getFilterInstance( + filter: Function | ExceptionFilter, + contextId: ContextId, + ) { const isObject = (filter as ExceptionFilter).catch; if (isObject) { return filter; } const instanceWrapper = this.getInstanceByMetatype(filter); - return instanceWrapper && instanceWrapper.instance - ? instanceWrapper.instance - : null; + if (!instanceWrapper) { + return null; + } + const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + return instanceHost && instanceHost.instance; } public getInstanceByMetatype>( filter: T, - ): { instance: any } | undefined { + ): InstanceWrapper | undefined { if (!this.moduleContext) { return undefined; } diff --git a/packages/core/guards/guards-context-creator.ts b/packages/core/guards/guards-context-creator.ts index e3dabdaeca2..e81e521cafb 100644 --- a/packages/core/guards/guards-context-creator.ts +++ b/packages/core/guards/guards-context-creator.ts @@ -2,14 +2,12 @@ import { CanActivate } from '@nestjs/common'; import { GUARDS_METADATA } from '@nestjs/common/constants'; import { Controller } from '@nestjs/common/interfaces'; import { ConfigurationProvider } from '@nestjs/common/interfaces/configuration-provider.interface'; -import { - isEmpty, - isFunction, - isUndefined, -} from '@nestjs/common/utils/shared.utils'; +import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils'; import iterate from 'iterare'; import { ContextCreator } from '../helpers/context-creator'; +import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; +import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; export class GuardsContextCreator extends ContextCreator { private moduleContext: string; @@ -25,6 +23,7 @@ export class GuardsContextCreator extends ContextCreator { instance: Controller, callback: (...args: any[]) => any, module: string, + contextId = STATIC_CONTEXT, ): CanActivate[] { this.moduleContext = module; return this.createContext(instance, callback, GUARDS_METADATA); @@ -32,31 +31,34 @@ export class GuardsContextCreator extends ContextCreator { public createConcreteContext( metadata: T, + contextId = STATIC_CONTEXT, ): R { if (isEmpty(metadata)) { return [] as R; } return iterate(metadata) .filter((guard: any) => guard && (guard.name || guard.canActivate)) - .map(guard => this.getGuardInstance(guard)) + .map(guard => this.getGuardInstance(guard, contextId)) .filter((guard: CanActivate) => guard && isFunction(guard.canActivate)) .toArray() as R; } - public getGuardInstance(guard: Function | CanActivate) { + public getGuardInstance(guard: Function | CanActivate, contextId: ContextId) { const isObject = (guard as CanActivate).canActivate; if (isObject) { return guard; } const instanceWrapper = this.getInstanceByMetatype(guard); - return instanceWrapper && instanceWrapper.instance - ? instanceWrapper.instance - : null; + if (!instanceWrapper) { + return null; + } + const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + return instanceHost && instanceHost.instance; } public getInstanceByMetatype>( guard: T, - ): { instance: any } | undefined { + ): InstanceWrapper | undefined { if (!this.moduleContext) { return undefined; } diff --git a/packages/core/helpers/context-creator.ts b/packages/core/helpers/context-creator.ts index 9fda8c0153b..f0f6017ebe1 100644 --- a/packages/core/helpers/context-creator.ts +++ b/packages/core/helpers/context-creator.ts @@ -1,8 +1,11 @@ import { Controller } from '@nestjs/common/interfaces'; +import { STATIC_CONTEXT } from '../injector/constants'; +import { ContextId } from '../injector/instance-wrapper'; export abstract class ContextCreator { public abstract createConcreteContext( metadata: T, + contextId?: ContextId, ): R; public getGlobalMetadata?(): T; @@ -10,15 +13,19 @@ export abstract class ContextCreator { instance: Controller, callback: (...args: any[]) => any, metadataKey: string, + contextId = STATIC_CONTEXT, ): R { const globalMetadata = this.getGlobalMetadata && this.getGlobalMetadata(); const classMetadata = this.reflectClassMetadata(instance, metadataKey); const methodMetadata = this.reflectMethodMetadata(callback, metadataKey); return [ - ...this.createConcreteContext(globalMetadata || ([] as T)), - ...this.createConcreteContext(classMetadata), - ...this.createConcreteContext(methodMetadata), + ...this.createConcreteContext( + globalMetadata || ([] as T), + contextId, + ), + ...this.createConcreteContext(classMetadata, contextId), + ...this.createConcreteContext(methodMetadata, contextId), ] as R; } diff --git a/packages/core/hooks/async-context.ts b/packages/core/hooks/async-context.ts new file mode 100644 index 00000000000..48306bd1e42 --- /dev/null +++ b/packages/core/hooks/async-context.ts @@ -0,0 +1,63 @@ +import { OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import * as asyncHooks from 'async_hooks'; +import { AsyncHooksHelper } from './async-hooks-helper'; +import { AsyncHooksStorage } from './async-hooks-storage'; + +export class AsyncContext implements OnModuleInit, OnModuleDestroy { + private static _instance: AsyncContext; + + private constructor( + private readonly internalStorage: Map, + private readonly asyncHookRef: asyncHooks.AsyncHook, + ) {} + + static get instance() { + if (!this._instance) { + this.initialize(); + } + return this._instance; + } + + onModuleInit() { + this.asyncHookRef.enable(); + } + + onModuleDestroy() { + this.asyncHookRef.disable(); + } + + set(key: TKey, value: TValue) { + const store = this.getAsyncStorage(); + store.set(key, value); + } + + get(key: TKey): TReturnValue { + const store = this.getAsyncStorage(); + return store.get(key) as TReturnValue; + } + + run(fn: Function) { + const eid = asyncHooks.executionAsyncId(); + this.internalStorage.set(eid, new Map()); + fn(); + } + + private getAsyncStorage(): Map { + const eid = asyncHooks.executionAsyncId(); + const state = this.internalStorage.get(eid); + if (!state) { + throw new Error( + `Async ID (${eid}) is not registered within internal cache.`, + ); + } + return state; + } + + private static initialize() { + const asyncHooksStorage = new AsyncHooksStorage(); + const asyncHook = AsyncHooksHelper.createHooks(asyncHooksStorage); + const storage = asyncHooksStorage.getInternalStorage(); + + this._instance = new AsyncContext(storage, asyncHook); + } +} diff --git a/packages/core/hooks/async-hooks-helper.ts b/packages/core/hooks/async-hooks-helper.ts new file mode 100644 index 00000000000..be71511f32e --- /dev/null +++ b/packages/core/hooks/async-hooks-helper.ts @@ -0,0 +1,25 @@ +import * as asyncHooks from 'async_hooks'; +import { AsyncHooksStorage } from './async-hooks-storage'; + +export class AsyncHooksHelper { + static createHooks(storage: AsyncHooksStorage): asyncHooks.AsyncHook { + function init( + asyncId: number, + type: string, + triggerId: number, + resource: Object, + ) { + if (storage.has(triggerId)) { + storage.inherit(asyncId, triggerId); + } + } + function destroy(asyncId) { + storage.delete(asyncId); + } + + return asyncHooks.createHook({ + init, + destroy, + } as asyncHooks.HookCallbacks); + } +} diff --git a/packages/core/hooks/async-hooks-storage.ts b/packages/core/hooks/async-hooks-storage.ts new file mode 100644 index 00000000000..0fbda6815d8 --- /dev/null +++ b/packages/core/hooks/async-hooks-storage.ts @@ -0,0 +1,31 @@ +export class AsyncHooksStorage { + constructor(private readonly asyncStorage = new Map()) { + this.initialize(); + } + + get(triggerId: number): T { + return this.asyncStorage.get(triggerId) as T; + } + + has(triggerId: number): boolean { + return this.asyncStorage.has(triggerId); + } + + inherit(asyncId: number, triggerId: number) { + const value = this.asyncStorage.get(triggerId); + this.asyncStorage.set(asyncId, value); + } + + delete(asyncId: number) { + this.asyncStorage.delete(asyncId); + } + + getInternalStorage(): Map { + return this.asyncStorage; + } + + private initialize() { + const initialAsyncId = 1; + this.asyncStorage.set(initialAsyncId, new Map()); + } +} diff --git a/packages/core/hooks/index.ts b/packages/core/hooks/index.ts new file mode 100644 index 00000000000..0bbd8adf86e --- /dev/null +++ b/packages/core/hooks/index.ts @@ -0,0 +1 @@ +export * from './async-context'; diff --git a/packages/core/index.ts b/packages/core/index.ts index 351e18097b5..67d69b1be22 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -10,6 +10,7 @@ export * from './adapters'; export { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from './constants'; export { BaseExceptionFilter } from './exceptions/base-exception-filter'; export { ApplicationReferenceHost } from './helpers/application-ref-host'; +export * from './hooks'; export { ModuleRef } from './injector/module-ref'; export { HTTP_SERVER_REF } from './injector/tokens'; export { MiddlewareBuilder } from './middleware/builder'; diff --git a/packages/core/injector/constants.ts b/packages/core/injector/constants.ts new file mode 100644 index 00000000000..9bbd217cf10 --- /dev/null +++ b/packages/core/injector/constants.ts @@ -0,0 +1,6 @@ +import { ContextId } from './instance-wrapper'; + +const STATIC_CONTEXT_ID = 1; +export const STATIC_CONTEXT: ContextId = Object.freeze({ + id: STATIC_CONTEXT_ID, +}); diff --git a/packages/core/injector/container-scanner.ts b/packages/core/injector/container-scanner.ts index 26c4abbbab1..f8c51679691 100644 --- a/packages/core/injector/container-scanner.ts +++ b/packages/core/injector/container-scanner.ts @@ -1,7 +1,8 @@ import { Type } from '@nestjs/common'; import { isFunction } from '@nestjs/common/utils/shared.utils'; import { UnknownElementException } from '../errors/exceptions/unknown-element.exception'; -import { InstanceWrapper, NestContainer } from './container'; +import { NestContainer } from './container'; +import { InstanceWrapper } from './instance-wrapper'; import { Module } from './module'; export class ContainerScanner { @@ -35,7 +36,7 @@ export class ContainerScanner { if (!instanceWrapper) { throw new UnknownElementException(); } - return (instanceWrapper as InstanceWrapper).instance; + return (instanceWrapper as InstanceWrapper).instance; } private initFlatContainer(): void { diff --git a/packages/core/injector/container.ts b/packages/core/injector/container.ts index b242310224a..442d8a5d7ea 100644 --- a/packages/core/injector/container.ts +++ b/packages/core/injector/container.ts @@ -101,7 +101,7 @@ export class NestContainer { return this.modules; } - public async addRelatedModule( + public async addImport( relatedModule: Type | DynamicModule, token: string, ) { @@ -165,20 +165,17 @@ export class NestContainer { } public bindGlobalScope() { - this.modules.forEach(module => this.bindGlobalsToRelatedModules(module)); + this.modules.forEach(module => this.bindGlobalsToImports(module)); } - public bindGlobalsToRelatedModules(module: Module) { + public bindGlobalsToImports(module: Module) { this.globalModules.forEach(globalModule => this.bindGlobalModuleToModule(module, globalModule), ); } - public bindGlobalModuleToModule(module: Module, globalModule: Module) { - if (module === globalModule) { - return; - } - module.addRelatedModule(globalModule); + public bindGlobalModuleToModule(target: Module, globalModule: Module) { + target !== globalModule && target.addRelatedModule(globalModule); } public getDynamicMetadataByToken( @@ -214,16 +211,3 @@ export class NestContainer { return this.modulesContainer; } } - -export interface InstanceWrapper { - name: any; - metatype: Type; - instance: T; - isResolved: boolean; - isPending?: boolean; - done$?: Promise; - inject?: Type[]; - isNotMetatype?: boolean; - forwardRef?: boolean; - async?: boolean; -} diff --git a/packages/core/injector/injector.ts b/packages/core/injector/injector.ts index f0f195b45c0..790cd0d8855 100644 --- a/packages/core/injector/injector.ts +++ b/packages/core/injector/injector.ts @@ -1,3 +1,4 @@ +import { Scope } from '@nestjs/common'; import { OPTIONAL_DEPS_METADATA, OPTIONAL_PROPERTY_DEPS_METADATA, @@ -17,14 +18,20 @@ import { import { RuntimeException } from '../errors/exceptions/runtime.exception'; import { UndefinedDependencyException } from '../errors/exceptions/undefined-dependency.exception'; import { UnknownDependenciesException } from '../errors/exceptions/unknown-dependencies.exception'; -import { MiddlewareWrapper } from '../middleware/container'; -import { InstanceWrapper } from './container'; +import { AsyncContext } from './../hooks'; +import { STATIC_CONTEXT } from './constants'; +import { + ContextId, + InstancePerContext, + InstanceWrapper, +} from './instance-wrapper'; import { Module } from './module'; +import { ModulesContainer } from './modules-container'; /** * The type of an injectable dependency */ -export type InjectorDependency = Type | Function | string; +export type InjectorDependency = Type | Function | string | symbol; /** * The property-based dependency @@ -61,73 +68,100 @@ export interface InjectorDependencyContext { } export class Injector { - public async loadInstanceOfMiddleware( - wrapper: MiddlewareWrapper, - collection: Map, + public async loadMiddleware( + wrapper: InstanceWrapper, + collection: Map, module: Module, + contextId = STATIC_CONTEXT, ) { const { metatype } = wrapper; - const currentMetatype = collection.get(metatype.name); - if (currentMetatype.instance !== null) { + const targetMetatype = collection.get(metatype.name); + if (targetMetatype.instance !== null) { return; } + const loadInstance = (instances: any[]) => { + const instanceWrapper = new InstanceWrapper({ + instance: new metatype(...instances), + metatype, + host: module, + }); + collection.set(metatype.name, instanceWrapper); + }; await this.resolveConstructorParams( - wrapper as any, + wrapper, module, null, - instances => { - collection.set(metatype.name, { - instance: new metatype(...instances), - metatype, - }); - }, + loadInstance, + contextId, ); } - public async loadInstanceOfController( + public async loadController( wrapper: InstanceWrapper, module: Module, + contextId = STATIC_CONTEXT, ) { const controllers = module.controllers; - await this.loadInstance(wrapper, controllers, module); + await this.loadInstance( + wrapper, + controllers, + module, + contextId, + ); } - public async loadInstanceOfInjectable( + public async loadInjectable( wrapper: InstanceWrapper, module: Module, + contextId = STATIC_CONTEXT, ) { const injectables = module.injectables; - await this.loadInstance(wrapper, injectables, module); + await this.loadInstance( + wrapper, + injectables, + module, + contextId, + ); + } + + public async loadProvider( + wrapper: InstanceWrapper, + module: Module, + contextId = STATIC_CONTEXT, + ) { + const providers = module.providers; + await this.loadInstance(wrapper, providers, module, contextId); } - public loadPrototypeOfInstance( + public loadPrototype( { metatype, name }: InstanceWrapper, collection: Map>, - ): void { + contextId = STATIC_CONTEXT, + ) { if (!collection) { return null; } const target = collection.get(name); - if (target.isResolved || !isNil(target.inject) || !metatype.prototype) { + const instanceHost = target.getInstanceByContextId(contextId); + if ( + instanceHost.isResolved || + !isNil(target.inject) || + !metatype.prototype + ) { return null; } - collection.set(name, { - ...collection.get(name), - instance: Object.create(metatype.prototype), - }); - } - - public async loadInstanceOfComponent( - wrapper: InstanceWrapper, - module: Module, - ) { - const providers = module.providers; - await this.loadInstance(wrapper, providers, module); + collection.set( + name, + new InstanceWrapper({ + ...target, + instance: Object.create(metatype.prototype), + }), + ); } - public applyDoneHook(wrapper: InstanceWrapper): () => void { + public applyDoneHook(wrapper: InstancePerContext): () => void { let done: () => void; - wrapper.done$ = new Promise((resolve, reject) => { + wrapper.donePromise = new Promise((resolve, reject) => { done = resolve; }); wrapper.isPending = true; @@ -136,33 +170,48 @@ export class Injector { public async loadInstance( wrapper: InstanceWrapper, - collection: Map>, + collection: Map, module: Module, + contextId = STATIC_CONTEXT, ) { - if (wrapper.isPending) { - return wrapper.done$; + const instanceHost = wrapper.getInstanceByContextId(contextId); + if (instanceHost.isPending) { + return instanceHost.donePromise; } - const done = this.applyDoneHook(wrapper); + const done = this.applyDoneHook(instanceHost); const { name, inject } = wrapper; const targetWrapper = collection.get(name); if (isUndefined(targetWrapper)) { throw new RuntimeException(); } - if (targetWrapper.isResolved) { + if (instanceHost.isResolved) { return; } const callback = async (instances: any[]) => { - const properties = await this.resolveProperties(wrapper, module, inject); + const properties = await this.resolveProperties( + wrapper, + module, + inject, + contextId, + ); const instance = await this.instantiateClass( instances, wrapper, targetWrapper, + module.id, + contextId, ); this.applyProperties(instance, properties); done(); }; - await this.resolveConstructorParams(wrapper, module, inject, callback); + await this.resolveConstructorParams( + wrapper, + module, + inject, + callback, + contextId, + ); } public async resolveConstructorParams( @@ -170,7 +219,21 @@ export class Injector { module: Module, inject: InjectorDependency[], callback: (args: any[]) => void, + contextId = STATIC_CONTEXT, ) { + const metadata = wrapper.getCtorMetadata(); + if (metadata) { + const dependenciesHosts = await Promise.all( + metadata.map(async item => + this.resolveComponentHost(item.host, item, contextId), + ), + ); + const deps = dependenciesHosts.map( + item => item.getInstanceByContextId(contextId).instance, + ); + return callback(deps); + } + const dependencies = isNil(inject) ? this.reflectConstructorParams(wrapper.metatype) : inject; @@ -179,28 +242,30 @@ export class Injector { : []; let isResolved = true; - const instances = await Promise.all( - dependencies.map(async (param, index) => { - try { - const paramWrapper = await this.resolveSingleParam( - wrapper, - param, - { index, dependencies }, - module, - ); - if (!paramWrapper.isResolved && !paramWrapper.forwardRef) { - isResolved = false; - } - return paramWrapper.instance; - } catch (err) { - const isOptional = optionalDependenciesIds.includes(index); - if (!isOptional) { - throw err; - } - return undefined; + + const findOneParam = async (param, index) => { + try { + const paramWrapper = await this.resolveSingleParam( + wrapper, + param, + { index, dependencies }, + module, + contextId, + ); + const instanceHost = paramWrapper.getInstanceByContextId(contextId); + if (!instanceHost.isResolved && !paramWrapper.forwardRef) { + isResolved = false; } - }), - ); + return instanceHost && instanceHost.instance; + } catch (err) { + const isOptional = optionalDependenciesIds.includes(index); + if (!isOptional) { + throw err; + } + return undefined; + } + }; + const instances = await Promise.all(dependencies.map(findOneParam)); isResolved && (await callback(instances)); } @@ -225,6 +290,7 @@ export class Injector { param: Type | string | symbol | any, dependencyContext: InjectorDependencyContext, module: Module, + contextId = STATIC_CONTEXT, ) { if (isUndefined(param)) { throw new UndefinedDependencyException( @@ -239,6 +305,7 @@ export class Injector { isFunction(token) ? (token as Type).name : token, dependencyContext, wrapper, + contextId, ); } @@ -258,32 +325,53 @@ export class Injector { name: any, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper, - ) { + contextId = STATIC_CONTEXT, + ): Promise { const providers = module.providers; const instanceWrapper = await this.lookupComponent( providers, module, { ...dependencyContext, name }, wrapper, + contextId, ); - if (!instanceWrapper.isResolved && !instanceWrapper.forwardRef) { - await this.loadInstanceOfComponent(instanceWrapper, module); + return this.resolveComponentHost(module, instanceWrapper, contextId); + } + + public async resolveComponentHost( + module: Module, + instanceWrapper: InstanceWrapper, + contextId = STATIC_CONTEXT, + ): Promise { + const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + if (!instanceHost.isResolved && !instanceWrapper.forwardRef) { + await this.loadProvider(instanceWrapper, module, contextId); } if (instanceWrapper.async) { - instanceWrapper.instance = await instanceWrapper.instance; + instanceWrapper.setInstanceByContextId( + contextId, + await instanceWrapper.getInstanceByContextId(contextId), + ); } return instanceWrapper; } public async lookupComponent( - providers: Map, + providers: Map, module: Module, dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper, - ) { + contextId = STATIC_CONTEXT, + ): Promise> { const { name } = dependencyContext; const scanInExports = () => - this.lookupComponentInExports(dependencyContext, module, wrapper); + this.lookupComponentInExports( + dependencyContext, + module, + wrapper, + contextId, + ); + return providers.has(name) ? providers.get(name) : scanInExports(); } @@ -291,10 +379,14 @@ export class Injector { dependencyContext: InjectorDependencyContext, module: Module, wrapper: InstanceWrapper, + contextId = STATIC_CONTEXT, ) { - const instanceWrapper = await this.lookupComponentInRelatedModules( + const instanceWrapper = await this.lookupComponentInImports( module, dependencyContext.name, + [], + wrapper, + contextId, ); if (isNil(instanceWrapper)) { throw new UnknownDependenciesException( @@ -306,15 +398,18 @@ export class Injector { return instanceWrapper; } - public async lookupComponentInRelatedModules( + public async lookupComponentInImports( module: Module, name: any, moduleRegistry: any[] = [], + wrapper: InstanceWrapper, + contextId = STATIC_CONTEXT, ): Promise { - let componentRef = null; + let instanceWrapperRef: InstanceWrapper = null; + + const imports: Set = module.imports || new Set(); + const children = [...imports.values()].filter(item => item); - const relatedModules: Set = module.relatedModules || new Set(); - const children = [...relatedModules.values()].filter(item => item); for (const relatedModule of children) { if (moduleRegistry.includes(relatedModule.id)) { continue; @@ -322,33 +417,52 @@ export class Injector { moduleRegistry.push(relatedModule.id); const { providers, exports } = relatedModule; if (!exports.has(name) || !providers.has(name)) { - const instanceRef = await this.lookupComponentInRelatedModules( + const instanceRef = await this.lookupComponentInImports( relatedModule, name, moduleRegistry, + wrapper, + contextId, ); if (instanceRef) { return instanceRef; } continue; } - componentRef = providers.get(name); - if (!componentRef.isResolved && !componentRef.forwardRef) { - await this.loadInstanceOfComponent(componentRef, relatedModule); + instanceWrapperRef = providers.get(name); + + const instanceHost = instanceWrapperRef.getInstanceByContextId(contextId); + if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) { + await this.loadProvider(instanceWrapperRef, relatedModule, contextId); break; } } - return componentRef; + return instanceWrapperRef; } public async resolveProperties( wrapper: InstanceWrapper, module: Module, inject?: InjectorDependency[], + contextId = STATIC_CONTEXT, ): Promise { if (!isNil(inject)) { return []; } + const metadata = wrapper.getPropertiesMetadata(); + if (metadata) { + const dependenciesHosts = await Promise.all( + metadata.map(async ({ wrapper: item, key }) => ({ + key, + host: await this.resolveComponentHost(item.host, item, contextId), + })), + ); + return dependenciesHosts.map(({ key, host }) => ({ + key, + name: key, + instance: host.getInstanceByContextId(contextId).instance, + })); + } const properties = this.reflectProperties(wrapper.metatype); const instances = await Promise.all( properties.map(async (item: PropertyDependency) => { @@ -362,8 +476,13 @@ export class Injector { item.name, dependencyContext, module, + contextId, ); - return (paramWrapper && paramWrapper.instance) || undefined; + if (!paramWrapper) { + return undefined; + } + const instanceHost = paramWrapper.getInstanceByContextId(contextId); + return instanceHost.instance; } catch (err) { if (!item.isOptional) { throw err; @@ -404,21 +523,90 @@ export class Injector { public async instantiateClass( instances: any[], - wrapper: InstanceWrapper, - targetMetatype: InstanceWrapper, + wrapper: InstanceWrapper, + targetMetatype: InstanceWrapper, + moduleId: string, + contextId = STATIC_CONTEXT, ): Promise { - const { metatype, inject } = wrapper; + const { metatype, inject, name, scope } = wrapper; + const instanceHost = targetMetatype.getInstanceByContextId(contextId); + const instanceKey = moduleId + name; + if (isNil(inject)) { - targetMetatype.instance = wrapper.forwardRef - ? Object.assign(targetMetatype.instance, new metatype(...instances)) + const targetInstance = wrapper.getInstanceByContextId(contextId); + targetInstance.instance = wrapper.forwardRef + ? Object.assign(targetInstance.instance, new metatype(...instances)) : new metatype(...instances); + + if (scope === Scope.LAZY) { + this.mergeAsyncProxy( + instances, + targetInstance, + instanceKey, + metatype, + (...args: any[]) => new metatype(...args), + ); + } } else { - const factoryResult = ((targetMetatype.metatype as any) as Function)( + const factoryReturnValue = ((targetMetatype.metatype as any) as Function)( ...instances, ); - targetMetatype.instance = await factoryResult; + instanceHost.instance = await factoryReturnValue; + if (scope === Scope.LAZY) { + this.mergeAsyncProxy( + instances, + instanceHost, + instanceKey, + metatype, + (...args: any[]) => + ((targetMetatype.metatype as any) as Function)(...args), + ); + } } - targetMetatype.isResolved = true; - return targetMetatype.instance; + instanceHost.isResolved = true; + return instanceHost.instance; + } + + mergeAsyncProxy( + instances: any[], + targetInstance: InstancePerContext, + instanceKey: string, + metatype: Type, + factory: (...intances: any[]) => any, + ) { + AsyncContext.instance.set(instanceKey, targetInstance.instance); + + const proxy = (target: unknown, property: string | number | symbol) => { + if (!(property in (target as object))) { + return; + } + const cachedInstance = AsyncContext.instance.get(instanceKey); + if (cachedInstance) { + return cachedInstance[property]; + } + const value = factory(...instances); + AsyncContext.instance.set(instanceKey, value); + return value[property]; + }; + targetInstance.instance = new Proxy(targetInstance.instance, { + get: proxy, + set: proxy, + }); + } + + async loadControllerPerContext( + instance: Controller, + modulesContainer: ModulesContainer, + moduleKey: string, + ctx: ContextId, + ): Promise { + const module = modulesContainer.get(moduleKey); + + const { controllers } = module; + const controller = controllers.get(instance.constructor.name); + await this.loadInstance(controller, controllers, module, ctx); + + const wrapper = controller.getInstanceByContextId(ctx); + return wrapper && (wrapper.instance as T); } } diff --git a/packages/core/injector/instance-loader.ts b/packages/core/injector/instance-loader.ts index 72d9e8349a5..436045cccfa 100644 --- a/packages/core/injector/instance-loader.ts +++ b/packages/core/injector/instance-loader.ts @@ -41,52 +41,49 @@ export class InstanceLoader { } private createPrototypesOfProviders(module: Module) { - module.providers.forEach(wrapper => { - this.injector.loadPrototypeOfInstance( - wrapper, - module.providers, - ); - }); + const { providers } = module; + providers.forEach(wrapper => + this.injector.loadPrototype(wrapper, providers), + ); } private async createInstancesOfProviders(module: Module) { + const { providers } = module; await Promise.all( - [...module.providers.values()].map(async wrapper => - this.injector.loadInstanceOfComponent(wrapper, module), + [...providers.values()].map(async wrapper => + this.injector.loadProvider(wrapper, module), ), ); } private createPrototypesOfControllers(module: Module) { - module.controllers.forEach(wrapper => { - this.injector.loadPrototypeOfInstance( - wrapper, - module.controllers, - ); - }); + const { controllers } = module; + controllers.forEach(wrapper => + this.injector.loadPrototype(wrapper, controllers), + ); } private async createInstancesOfControllers(module: Module) { + const { controllers } = module; await Promise.all( - [...module.controllers.values()].map(async wrapper => - this.injector.loadInstanceOfController(wrapper, module), + [...controllers.values()].map(async wrapper => + this.injector.loadController(wrapper, module), ), ); } private createPrototypesOfInjectables(module: Module) { - module.injectables.forEach(wrapper => { - this.injector.loadPrototypeOfInstance( - wrapper, - module.injectables, - ); - }); + const { injectables } = module; + injectables.forEach(wrapper => + this.injector.loadPrototype(wrapper, injectables), + ); } private async createInstancesOfInjectables(module: Module) { + const { injectables } = module; await Promise.all( - [...module.injectables.values()].map(async wrapper => - this.injector.loadInstanceOfInjectable(wrapper, module), + [...injectables.values()].map(async wrapper => + this.injector.loadInjectable(wrapper, module), ), ); } diff --git a/packages/core/injector/instance-wrapper.ts b/packages/core/injector/instance-wrapper.ts new file mode 100644 index 00000000000..2187e01e9bd --- /dev/null +++ b/packages/core/injector/instance-wrapper.ts @@ -0,0 +1,138 @@ +import { Scope, Type } from '@nestjs/common'; +import { STATIC_CONTEXT } from './constants'; +import { Module } from './module'; + +export const INSTANCE_METADATA_SYMBOL = Symbol.for('metadata:cache'); + +export interface ContextId { + readonly id: number; +} + +export interface InstancePerContext { + instance: T; + isResolved?: boolean; + isPending?: boolean; + donePromise?: Promise; +} +export interface PropertyMetadata { + key: string; + wrapper: InstanceWrapper; +} + +export interface InstanceMetadataStore { + dependencies?: InstanceWrapper[]; + properties?: PropertyMetadata[]; +} + +export class InstanceWrapper { + public readonly name: any; + public readonly metatype: Type; + public readonly inject?: (string | symbol | Function | Type)[]; + public readonly async?: boolean; + public readonly host?: Module; + public readonly scope?: Scope = Scope.DEFAULT; + public forwardRef?: boolean; + + private readonly values = new WeakMap>(); + private readonly [INSTANCE_METADATA_SYMBOL]: InstanceMetadataStore = {}; + + constructor( + metadata: Partial> & Partial> = {}, + ) { + this.initialize(metadata); + } + + set instance(value: T) { + this.values.set(STATIC_CONTEXT, { instance: value }); + } + + get instance(): T { + const instancePerContext = this.getInstanceByContextId(STATIC_CONTEXT); + return instancePerContext.instance; + } + + get isNotMetatype(): boolean { + return !!this.metatype; + } + + getInstanceByContextId(contextId: ContextId): InstancePerContext { + const instancePerContext = this.values.get(contextId); + return instancePerContext + ? instancePerContext + : this.cloneStaticInstance(contextId); + } + + setInstanceByContextId(contextId: ContextId, value: InstancePerContext) { + this.values.set(contextId, value); + } + + addCtorMetadata(index: number, wrapper: InstanceWrapper) { + if (!this[INSTANCE_METADATA_SYMBOL].dependencies) { + this[INSTANCE_METADATA_SYMBOL].dependencies = []; + } + this[INSTANCE_METADATA_SYMBOL].dependencies[index] = wrapper; + } + + getCtorMetadata(): InstanceWrapper[] { + return this[INSTANCE_METADATA_SYMBOL].dependencies; + } + + addPropertiesMetadata(key: string, wrapper: InstanceWrapper) { + if (!this[INSTANCE_METADATA_SYMBOL].properties) { + this[INSTANCE_METADATA_SYMBOL].properties = []; + } + this[INSTANCE_METADATA_SYMBOL].properties.push({ + key, + wrapper, + }); + } + + getPropertiesMetadata(): PropertyMetadata[] { + return this[INSTANCE_METADATA_SYMBOL].properties; + } + + isDependencyTreeStatic(): boolean { + if (this.scope === Scope.REQUEST) { + return false; + } + const { dependencies, properties } = this[INSTANCE_METADATA_SYMBOL]; + + const isItemStatic = (item: InstanceWrapper) => + item.isDependencyTreeStatic(); + const isTreeStatic = (tree: InstanceWrapper[]) => tree.every(isItemStatic); + + let isStatic = + (dependencies && isTreeStatic(dependencies)) || !dependencies; + isStatic = + isStatic && + ((properties && isTreeStatic(properties as any)) || !properties); + return isStatic; + } + + private initialize( + metadata: Partial> & Partial>, + ) { + const { instance, isResolved, ...wrapperPartial } = metadata; + Object.assign(this, wrapperPartial); + + this.setInstanceByContextId(STATIC_CONTEXT, { + instance, + isResolved, + }); + } + + private cloneStaticInstance(contextId: ContextId): InstancePerContext { + const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT); + if (this.isDependencyTreeStatic()) { + return staticInstance; + } + const instancePerContext: InstancePerContext = { + ...staticInstance, + instance: undefined, + isResolved: false, + isPending: false, + }; + this.setInstanceByContextId(contextId, instancePerContext); + return instancePerContext; + } +} diff --git a/packages/core/injector/module-ref.ts b/packages/core/injector/module-ref.ts index 23718cf9ea2..0d4cae4f26c 100644 --- a/packages/core/injector/module-ref.ts +++ b/packages/core/injector/module-ref.ts @@ -1,7 +1,8 @@ import { Type } from '@nestjs/common'; -import { InstanceWrapper, NestContainer } from './container'; +import { NestContainer } from './container'; import { ContainerScanner } from './container-scanner'; import { Injector } from './injector'; +import { InstanceWrapper } from './instance-wrapper'; import { Module } from './module'; export abstract class ModuleRef { @@ -29,12 +30,13 @@ export abstract class ModuleRef { type: Type, module: Module, ): Promise { - const wrapper: InstanceWrapper = { + const wrapper = new InstanceWrapper({ name: type.name, metatype: type, instance: undefined, isResolved: false, - }; + host: module, + }); return new Promise(async (resolve, reject) => { try { const callback = async (instances: any[]) => { diff --git a/packages/core/injector/module-token-factory.ts b/packages/core/injector/module-token-factory.ts index 8f2df4ffcd2..2a324b3410f 100644 --- a/packages/core/injector/module-token-factory.ts +++ b/packages/core/injector/module-token-factory.ts @@ -1,8 +1,8 @@ import { DynamicModule } from '@nestjs/common'; import { SHARED_MODULE_METADATA } from '@nestjs/common/constants'; import { Type } from '@nestjs/common/interfaces/type.interface'; -import * as hash from 'object-hash'; import stringify from 'fast-safe-stringify'; +import * as hash from 'object-hash'; export class ModuleTokenFactory { public create( diff --git a/packages/core/injector/module.ts b/packages/core/injector/module.ts index 4e2869c36fc..1ee36742324 100644 --- a/packages/core/injector/module.ts +++ b/packages/core/injector/module.ts @@ -1,3 +1,5 @@ +import { Scope } from '@nestjs/common'; +import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants'; import { Controller, DynamicModule, @@ -18,8 +20,10 @@ import { RuntimeException } from '../errors/exceptions/runtime.exception'; import { UnknownExportException } from '../errors/exceptions/unknown-export.exception'; import { ApplicationReferenceHost } from '../helpers/application-ref-host'; import { ExternalContextCreator } from '../helpers/external-context-creator'; +import { AsyncContext } from '../hooks/async-context'; import { Reflector } from '../services/reflector.service'; -import { InstanceWrapper, NestContainer } from './container'; +import { NestContainer } from './container'; +import { InstanceWrapper } from './instance-wrapper'; import { ModuleRef } from './module-ref'; import { ModulesContainer } from './modules-container'; import { HTTP_SERVER_REF } from './tokens'; @@ -28,11 +32,15 @@ export interface CustomProvider { provide: any; name: string; } -export type OpaqueToken = string | symbol | object | Type; -export type CustomClass = CustomProvider & { useClass: Type }; +export type OpaqueToken = string | symbol | Type; +export type CustomClass = CustomProvider & { + useClass: Type; + scope?: Scope; +}; export type CustomFactory = CustomProvider & { useFactory: (...args: any[]) => any; inject?: OpaqueToken[]; + scope?: Scope; }; export type CustomValue = CustomProvider & { useValue: any }; export type ProviderMetatype = @@ -43,7 +51,7 @@ export type ProviderMetatype = export class Module { private readonly _id: string; - private readonly _relatedModules = new Set(); + private readonly _imports = new Set(); private readonly _providers = new Map>(); private readonly _injectables = new Map>(); private readonly _controllers = new Map< @@ -69,14 +77,21 @@ export class Module { return this._scope; } - get relatedModules(): Set { - return this._relatedModules; - } - get providers(): Map> { return this._providers; } + get imports(): Set { + return this._imports; + } + + /** + * Left for backward-compatibility reasons + */ + get relatedModules(): Set { + return this._imports; + } + /** * Left for backward-compatibility reasons */ @@ -123,96 +138,145 @@ export class Module { this.addExternalContextCreator(container.getExternalContextCreator()); this.addModulesContainer(container.getModulesContainer()); this.addApplicationRefHost(container.getApplicationRefHost()); + + this._providers.set( + AsyncContext.name, + new InstanceWrapper({ + name: AsyncContext.name, + metatype: AsyncContext as any, + isResolved: true, + instance: AsyncContext.instance, + host: this, + }), + ); } public addModuleRef() { - const moduleRef = this.createModuleRefMetatype(); - this._providers.set(ModuleRef.name, { - name: ModuleRef.name, - metatype: ModuleRef as any, - isResolved: true, - instance: new moduleRef(), - }); + const moduleRef = this.createModuleReferenceType(); + this._providers.set( + ModuleRef.name, + new InstanceWrapper({ + name: ModuleRef.name, + metatype: ModuleRef as any, + isResolved: true, + instance: new moduleRef(), + host: this, + }), + ); } public addModuleAsProvider() { - this._providers.set(this._metatype.name, { - name: this._metatype.name, - metatype: this._metatype, - isResolved: false, - instance: null, - }); + this._providers.set( + this._metatype.name, + new InstanceWrapper({ + name: this._metatype.name, + metatype: this._metatype, + isResolved: false, + instance: null, + host: this, + }), + ); } public addReflector(reflector: Reflector) { - this._providers.set(Reflector.name, { - name: Reflector.name, - metatype: Reflector, - isResolved: true, - instance: reflector, - }); + this._providers.set( + Reflector.name, + new InstanceWrapper({ + name: Reflector.name, + metatype: Reflector, + isResolved: true, + instance: reflector, + host: this, + }), + ); } public addApplicationRef(applicationRef: any) { - this._providers.set(HTTP_SERVER_REF, { - name: HTTP_SERVER_REF, - metatype: {} as any, - isResolved: true, - instance: applicationRef || {}, - }); + this._providers.set( + HTTP_SERVER_REF, + new InstanceWrapper({ + name: HTTP_SERVER_REF, + metatype: {} as any, + isResolved: true, + instance: applicationRef || {}, + host: this, + }), + ); } public addExternalContextCreator( externalContextCreator: ExternalContextCreator, ) { - this._providers.set(ExternalContextCreator.name, { - name: ExternalContextCreator.name, - metatype: ExternalContextCreator, - isResolved: true, - instance: externalContextCreator, - }); + this._providers.set( + ExternalContextCreator.name, + new InstanceWrapper({ + name: ExternalContextCreator.name, + metatype: ExternalContextCreator, + isResolved: true, + instance: externalContextCreator, + host: this, + }), + ); } public addModulesContainer(modulesContainer: ModulesContainer) { - this._providers.set(ModulesContainer.name, { - name: ModulesContainer.name, - metatype: ModulesContainer, - isResolved: true, - instance: modulesContainer, - }); + this._providers.set( + ModulesContainer.name, + new InstanceWrapper({ + name: ModulesContainer.name, + metatype: ModulesContainer, + isResolved: true, + instance: modulesContainer, + host: this, + }), + ); } public addApplicationRefHost(applicationRefHost: ApplicationReferenceHost) { - this._providers.set(ApplicationReferenceHost.name, { - name: ApplicationReferenceHost.name, - metatype: ApplicationReferenceHost, - isResolved: true, - instance: applicationRefHost, - }); + this._providers.set( + ApplicationReferenceHost.name, + new InstanceWrapper({ + name: ApplicationReferenceHost.name, + metatype: ApplicationReferenceHost, + isResolved: true, + instance: applicationRefHost, + host: this, + }), + ); } public addInjectable(injectable: Type) { if (this.isCustomProvider(injectable)) { return this.addCustomProvider(injectable, this._injectables); } - this._injectables.set(injectable.name, { - name: injectable.name, - metatype: injectable, - instance: null, - isResolved: false, - }); + this._injectables.set( + injectable.name, + new InstanceWrapper({ + name: injectable.name, + metatype: injectable, + instance: null, + isResolved: false, + scope: this.getClassScope(injectable), + host: this, + }), + ); } public addProvider(provider: ProviderMetatype): string { if (this.isCustomProvider(provider)) { return this.addCustomProvider(provider, this._providers); } - this._providers.set((provider as Type).name, { - name: (provider as Type).name, - metatype: provider as Type, - instance: null, - isResolved: false, - }); + this._providers.set( + (provider as Type).name, + new InstanceWrapper({ + name: (provider as Type).name, + metatype: provider as Type, + instance: null, + isResolved: false, + scope: this.getClassScope(provider), + host: this, + }), + ); return (provider as Type).name; } @@ -228,17 +292,18 @@ export class Module { ): string { const { provide } = provider; const name = isFunction(provide) ? provide.name : provide; - const providerNamePair = { + + provider = { ...provider, name, }; - if (this.isCustomClass(providerNamePair)) - this.addCustomClass(providerNamePair, collection); - else if (this.isCustomValue(providerNamePair)) - this.addCustomValue(providerNamePair, collection); - else if (this.isCustomFactory(providerNamePair)) - this.addCustomFactory(providerNamePair, collection); - + if (this.isCustomClass(provider)) { + this.addCustomClass(provider, collection); + } else if (this.isCustomValue(provider)) { + this.addCustomValue(provider, collection); + } else if (this.isCustomFactory(provider)) { + this.addCustomFactory(provider, collection); + } return name; } @@ -258,41 +323,59 @@ export class Module { return exported && exported.module; } - public addCustomClass(provider: CustomClass, collection: Map) { - const { name, useClass } = provider; - collection.set(name, { + public addCustomClass( + provider: CustomClass, + collection: Map, + ) { + const { name, useClass, scope } = provider; + collection.set( name, - metatype: useClass, - instance: null, - isResolved: false, - }); - } - - public addCustomValue(provider: CustomValue, collection: Map) { + new InstanceWrapper({ + name, + metatype: useClass, + instance: null, + isResolved: false, + scope, + host: this, + }), + ); + } + + public addCustomValue( + provider: CustomValue, + collection: Map, + ) { const { name, useValue: value } = provider; - collection.set(name, { + collection.set( name, - metatype: null, - instance: value, - isResolved: true, - isNotMetatype: true, - async: value instanceof Promise, - }); + new InstanceWrapper({ + name, + metatype: null, + instance: value, + isResolved: true, + async: value instanceof Promise, + host: this, + }), + ); } public addCustomFactory( provider: CustomFactory, - collection: Map, + collection: Map, ) { - const { name, useFactory: factory, inject } = provider; - collection.set(name, { + const { name, useFactory: factory, inject, scope } = provider; + collection.set( name, - metatype: factory as any, - instance: null, - isResolved: false, - inject: inject || [], - isNotMetatype: true, - }); + new InstanceWrapper({ + name, + metatype: factory as any, + instance: null, + isResolved: false, + inject: inject || [], + scope, + host: this, + }), + ); } public addExportedProvider( @@ -326,14 +409,14 @@ export class Module { if (this._providers.has(token)) { return token; } - const importedArray = [...this._relatedModules.values()]; - const importedRefNames = importedArray + const importsArray = [...this._imports.values()]; + const importsNames = importsArray .filter(item => item) .map(({ metatype }) => metatype) .filter(metatype => metatype) .map(({ name }) => name); - if (!importedRefNames.includes(token as any)) { + if (!importsNames.includes(token as any)) { const { name } = this.metatype; throw new UnknownExportException(name); } @@ -341,16 +424,21 @@ export class Module { } public addController(controller: Type) { - this._controllers.set(controller.name, { - name: controller.name, - metatype: controller, - instance: null, - isResolved: false, - }); + this._controllers.set( + controller.name, + new InstanceWrapper({ + name: controller.name, + metatype: controller, + instance: null, + isResolved: false, + scope: this.getClassScope(controller), + host: this, + }), + ); } public addRelatedModule(module: any) { - this._relatedModules.add(module); + this._imports.add(module); } public replace(toReplace: string | symbol | Type, options: any) { @@ -363,7 +451,7 @@ export class Module { }); } - public createModuleRefMetatype(): any { + public createModuleReferenceType(): any { const self = this; return class extends ModuleRef { constructor() { @@ -391,4 +479,9 @@ export class Module { } }; } + + private getClassScope(provider: ProviderMetatype): Scope { + const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, provider); + return metadata && metadata.scope; + } } diff --git a/packages/core/interceptors/interceptors-context-creator.ts b/packages/core/interceptors/interceptors-context-creator.ts index 22c9d3275c9..1e093901fe2 100644 --- a/packages/core/interceptors/interceptors-context-creator.ts +++ b/packages/core/interceptors/interceptors-context-creator.ts @@ -1,14 +1,12 @@ import { INTERCEPTORS_METADATA } from '@nestjs/common/constants'; import { Controller, NestInterceptor } from '@nestjs/common/interfaces'; import { ConfigurationProvider } from '@nestjs/common/interfaces/configuration-provider.interface'; -import { - isEmpty, - isFunction, - isUndefined, -} from '@nestjs/common/utils/shared.utils'; +import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils'; import iterate from 'iterare'; import { ContextCreator } from '../helpers/context-creator'; +import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; +import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; export class InterceptorsContextCreator extends ContextCreator { private moduleContext: string; @@ -24,13 +22,20 @@ export class InterceptorsContextCreator extends ContextCreator { instance: Controller, callback: (...args: any[]) => any, module: string, + contextId = STATIC_CONTEXT, ): NestInterceptor[] { this.moduleContext = module; - return this.createContext(instance, callback, INTERCEPTORS_METADATA); + return this.createContext( + instance, + callback, + INTERCEPTORS_METADATA, + contextId, + ); } public createConcreteContext( metadata: T, + contextId = STATIC_CONTEXT, ): R { if (isEmpty(metadata)) { return [] as R; @@ -40,7 +45,7 @@ export class InterceptorsContextCreator extends ContextCreator { (interceptor: any) => interceptor && (interceptor.name || interceptor.intercept), ) - .map(interceptor => this.getInterceptorInstance(interceptor)) + .map(interceptor => this.getInterceptorInstance(interceptor, contextId)) .filter( (interceptor: NestInterceptor) => interceptor && isFunction(interceptor.intercept), @@ -48,20 +53,25 @@ export class InterceptorsContextCreator extends ContextCreator { .toArray() as R; } - public getInterceptorInstance(interceptor: Function | NestInterceptor) { + public getInterceptorInstance( + interceptor: Function | NestInterceptor, + contextId: ContextId, + ) { const isObject = (interceptor as NestInterceptor).intercept; if (isObject) { return interceptor; } const instanceWrapper = this.getInstanceByMetatype(interceptor); - return instanceWrapper && instanceWrapper.instance - ? instanceWrapper.instance - : null; + if (!instanceWrapper) { + return null; + } + const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + return instanceHost && instanceHost.instance; } public getInstanceByMetatype = any>( metatype: T, - ): { instance: any } | undefined { + ): InstanceWrapper | undefined { if (!this.moduleContext) { return undefined; } diff --git a/packages/core/middleware/container.ts b/packages/core/middleware/container.ts index 24fe43767d9..4d5b9aecd29 100644 --- a/packages/core/middleware/container.ts +++ b/packages/core/middleware/container.ts @@ -1,18 +1,14 @@ import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface'; -import { NestMiddleware } from '@nestjs/common/interfaces/middleware/nest-middleware.interface'; -import { Type } from '@nestjs/common/interfaces/type.interface'; +import { InstanceWrapper } from './../injector/instance-wrapper'; export class MiddlewareContainer { - private readonly middleware = new Map< - string, - Map - >(); + private readonly middleware = new Map>(); private readonly configurationSets = new Map< string, Set >(); - public getMiddleware(module: string): Map { + public getMiddleware(module: string): Map { return this.middleware.get(module) || new Map(); } @@ -22,24 +18,28 @@ export class MiddlewareContainer { public addConfig(configList: MiddlewareConfiguration[], module: string) { const middleware = this.getCurrentMiddleware(module); - const currentConfig = this.getCurrentConfig(module); + const targetConfig = this.getCurrentConfig(module); const configurations = configList || []; configurations.forEach(config => { - [].concat(config.middleware).map(metatype => { + const callback = metatype => { const token = metatype.name; - middleware.set(token, { - instance: null, - metatype, - }); - }); - currentConfig.add(config); + middleware.set( + token, + new InstanceWrapper({ + instance: null, + metatype, + }), + ); + }; + [].concat(config.middleware).map(callback); + targetConfig.add(config); }); } private getCurrentMiddleware(module: string) { if (!this.middleware.has(module)) { - this.middleware.set(module, new Map()); + this.middleware.set(module, new Map()); } return this.middleware.get(module); } @@ -51,8 +51,3 @@ export class MiddlewareContainer { return this.configurationSets.get(module); } } - -export interface MiddlewareWrapper { - instance: NestMiddleware; - metatype: Type; -} diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index 16c133801df..6457fdfb2c2 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -13,11 +13,12 @@ import { InvalidMiddlewareException } from '../errors/exceptions/invalid-middlew import { RuntimeException } from '../errors/exceptions/runtime.exception'; import { ExceptionsHandler } from '../exceptions/exceptions-handler'; import { NestContainer } from '../injector/container'; +import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; import { RouterExceptionFilters } from '../router/router-exception-filters'; import { RouterProxy } from '../router/router-proxy'; import { MiddlewareBuilder } from './builder'; -import { MiddlewareContainer, MiddlewareWrapper } from './container'; +import { MiddlewareContainer } from './container'; import { MiddlewareResolver } from './resolver'; import { RoutesMapper } from './routes-mapper'; @@ -140,7 +141,7 @@ export class MiddlewareModule { throw new RuntimeException(); } - const { instance } = middleware as MiddlewareWrapper; + const { instance } = middleware as InstanceWrapper; await this.bindHandler( instance, metatype, @@ -181,8 +182,7 @@ export class MiddlewareModule { middlewareInstance, path, ); - const resolve = instance.resolve(); - const middleware = await resolve; + const middleware = await instance.resolve(); bindWithProxy(middleware); } diff --git a/packages/core/middleware/resolver.ts b/packages/core/middleware/resolver.ts index 1e2f63046fd..0c8c7831f77 100644 --- a/packages/core/middleware/resolver.ts +++ b/packages/core/middleware/resolver.ts @@ -1,6 +1,7 @@ -import { MiddlewareContainer, MiddlewareWrapper } from './container'; import { Injector } from '../injector/injector'; +import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; +import { MiddlewareContainer } from './container'; export class MiddlewareResolver { private readonly instanceLoader = new Injector(); @@ -10,21 +11,17 @@ export class MiddlewareResolver { public async resolveInstances(module: Module, moduleName: string) { const middleware = this.middlewareContainer.getMiddleware(moduleName); await Promise.all( - [...middleware.values()].map( - async wrapper => this.resolveMiddlewareInstance(wrapper, middleware, module), + [...middleware.values()].map(async wrapper => + this.resolveMiddlewareInstance(wrapper, middleware, module), ), ); } private async resolveMiddlewareInstance( - wrapper: MiddlewareWrapper, - middleware: Map, + wrapper: InstanceWrapper, + middleware: Map, module: Module, ) { - await this.instanceLoader.loadInstanceOfMiddleware( - wrapper, - middleware, - module, - ); + await this.instanceLoader.loadMiddleware(wrapper, middleware, module); } } diff --git a/packages/core/pipes/pipes-context-creator.ts b/packages/core/pipes/pipes-context-creator.ts index 3ab3743347c..dba0cfdd41f 100644 --- a/packages/core/pipes/pipes-context-creator.ts +++ b/packages/core/pipes/pipes-context-creator.ts @@ -4,15 +4,13 @@ import { PipeTransform, Transform, } from '@nestjs/common/interfaces'; -import { - isEmpty, - isFunction, - isUndefined, -} from '@nestjs/common/utils/shared.utils'; +import { isEmpty, isFunction } from '@nestjs/common/utils/shared.utils'; import iterate from 'iterare'; import { ApplicationConfig } from '../application-config'; import { ContextCreator } from '../helpers/context-creator'; import { NestContainer } from '../injector/container'; +import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; +import { STATIC_CONTEXT } from './../injector/constants'; export class PipesContextCreator extends ContextCreator { private moduleContext: string; @@ -28,39 +26,43 @@ export class PipesContextCreator extends ContextCreator { instance: Controller, callback: (...args: any[]) => any, module: string, + contextId = STATIC_CONTEXT, ): Transform[] { this.moduleContext = module; - return this.createContext(instance, callback, PIPES_METADATA); + return this.createContext(instance, callback, PIPES_METADATA, contextId); } public createConcreteContext( metadata: T, + contextId = STATIC_CONTEXT, ): R { if (isEmpty(metadata)) { return [] as R; } return iterate(metadata) .filter((pipe: any) => pipe && (pipe.name || pipe.transform)) - .map(pipe => this.getPipeInstance(pipe)) + .map(pipe => this.getPipeInstance(pipe, contextId)) .filter(pipe => pipe && pipe.transform && isFunction(pipe.transform)) .map(pipe => pipe.transform.bind(pipe)) .toArray() as R; } - public getPipeInstance(pipe: Function | PipeTransform) { + public getPipeInstance(pipe: Function | PipeTransform, contextId: ContextId) { const isObject = (pipe as PipeTransform).transform; if (isObject) { return pipe; } const instanceWrapper = this.getInstanceByMetatype(pipe as Function); - return instanceWrapper && instanceWrapper.instance - ? instanceWrapper.instance - : null; + if (!instanceWrapper) { + return null; + } + const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + return instanceHost && instanceHost.instance; } public getInstanceByMetatype( metatype: T, - ): { instance: any } | undefined { + ): InstanceWrapper | undefined { if (!this.moduleContext) { return undefined; } diff --git a/packages/core/router/interfaces/exceptions-filter.interface.ts b/packages/core/router/interfaces/exceptions-filter.interface.ts index a21bca5871e..42e3f8805b4 100644 --- a/packages/core/router/interfaces/exceptions-filter.interface.ts +++ b/packages/core/router/interfaces/exceptions-filter.interface.ts @@ -1,10 +1,12 @@ import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface'; import { ExceptionsHandler } from '../../exceptions/exceptions-handler'; +import { ContextId } from '../../injector/instance-wrapper'; export interface ExceptionsFilter { create( instance: Controller, callback: Function, module: string, + contextId?: ContextId, ): ExceptionsHandler; } diff --git a/packages/core/router/router-exception-filters.ts b/packages/core/router/router-exception-filters.ts index 56d88582fd3..9cf09112053 100644 --- a/packages/core/router/router-exception-filters.ts +++ b/packages/core/router/router-exception-filters.ts @@ -5,6 +5,7 @@ import { isEmpty } from '@nestjs/common/utils/shared.utils'; import { ApplicationConfig } from '../application-config'; import { BaseExceptionFilterContext } from '../exceptions/base-exception-filter-context'; import { ExceptionsHandler } from '../exceptions/exceptions-handler'; +import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; import { RouterProxyCallback } from './router-proxy'; @@ -21,6 +22,7 @@ export class RouterExceptionFilters extends BaseExceptionFilterContext { instance: Controller, callback: RouterProxyCallback, module: string, + contextId = STATIC_CONTEXT, ): ExceptionsHandler { this.moduleContext = module; @@ -29,6 +31,7 @@ export class RouterExceptionFilters extends BaseExceptionFilterContext { instance, callback, EXCEPTION_FILTERS_METADATA, + contextId, ); if (isEmpty(filters)) { return exceptionHandler; diff --git a/packages/core/router/router-execution-context.ts b/packages/core/router/router-execution-context.ts index 47c9e011714..7f6d3ddc88d 100644 --- a/packages/core/router/router-execution-context.ts +++ b/packages/core/router/router-execution-context.ts @@ -24,6 +24,7 @@ import { FORBIDDEN_MESSAGE } from '../guards/constants'; import { GuardsConsumer } from '../guards/guards-consumer'; import { GuardsContextCreator } from '../guards/guards-context-creator'; import { ContextUtils } from '../helpers/context-utils'; +import { STATIC_CONTEXT } from '../injector/constants'; import { InterceptorsConsumer } from '../interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator'; import { PipesConsumer } from '../pipes/pipes-consumer'; @@ -69,6 +70,7 @@ export class RouterExecutionContext { methodName: string, module: string, requestMethod: RequestMethod, + contextId = STATIC_CONTEXT, ) { const metadata = this.contextUtils.reflectCallbackMetadata( @@ -78,16 +80,27 @@ export class RouterExecutionContext { ) || {}; const keys = Object.keys(metadata); const argsLength = this.contextUtils.getArgumentsLength(keys, metadata); - const pipes = this.pipesContextCreator.create(instance, callback, module); + const pipes = this.pipesContextCreator.create( + instance, + callback, + module, + contextId, + ); const paramtypes = this.contextUtils.reflectCallbackParamtypes( instance, methodName, ); - const guards = this.guardsContextCreator.create(instance, callback, module); + const guards = this.guardsContextCreator.create( + instance, + callback, + module, + contextId, + ); const interceptors = this.interceptorsContextCreator.create( instance, callback, module, + contextId, ); const httpCode = this.reflectHttpStatusCode(callback); const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module); diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index e639490e133..492e61d5d4d 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -11,7 +11,9 @@ import { GuardsConsumer } from '../guards/guards-consumer'; import { GuardsContextCreator } from '../guards/guards-context-creator'; import { ROUTE_MAPPED_MESSAGE } from '../helpers/messages'; import { RouterMethodFactory } from '../helpers/router-method-factory'; +import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; +import { Injector } from '../injector/injector'; import { InterceptorsConsumer } from '../interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator'; import { MetadataScanner } from '../metadata-scanner'; @@ -34,12 +36,15 @@ export class RouterExplorer { private readonly routerMethodFactory = new RouterMethodFactory(); private readonly logger = new Logger(RouterExplorer.name, true); + // TEMP + private readonly injector = new Injector(); + constructor( private readonly metadataScanner: MetadataScanner, - container: NestContainer, + private readonly container: NestContainer, private readonly routerProxy?: RouterProxy, private readonly exceptionsFilter?: ExceptionsFilter, - private readonly config?: ApplicationConfig, + config?: ApplicationConfig, ) { this.executionContextCreator = new RouterExecutionContext( new RouteParamsFactory(), @@ -155,6 +160,10 @@ export class RouterExplorer { .get(router, requestMethod) .bind(router); + const stripSlash = (str: string) => + str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str; + const fullPath = stripSlash(basePath) + path; + // TODO: const proxy = this.createCallbackProxy( instance, targetCallback, @@ -162,10 +171,32 @@ export class RouterExplorer { module, requestMethod, ); - const stripSlash = (str: string) => - str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str; - const fullPath = stripSlash(basePath) + path; routerMethod(stripSlash(fullPath) || '/', proxy); + /*routerMethod( + stripSlash(fullPath) || '/', + async ( + req: TRequest, + res: TResponse, + next: Function, + ) => { + const ctx = { id: 2 }; // asyncId + const contextInstance = await this.injector.loadControllerPerContext( + instance, + this.container.getModules(), + module, + ctx, + ); + const proxy = this.createCallbackProxy( + contextInstance, + contextInstance[methodName], + methodName, + module, + requestMethod, + ctx, + ); + proxy(req, res, next); + }, + );*/ } private createCallbackProxy( @@ -174,6 +205,7 @@ export class RouterExplorer { methodName: string, module: string, requestMethod: RequestMethod, + contextId = STATIC_CONTEXT, ) { const executionContext = this.executionContextCreator.create( instance, @@ -181,11 +213,13 @@ export class RouterExplorer { methodName, module, requestMethod, + contextId, ); const exceptionFilter = this.exceptionsFilter.create( instance, callback, module, + contextId, ); return this.routerProxy.createProxy(executionContext, exceptionFilter); } diff --git a/packages/core/router/routes-resolver.ts b/packages/core/router/routes-resolver.ts index 2a8c55fd76c..dd145c3cefb 100644 --- a/packages/core/router/routes-resolver.ts +++ b/packages/core/router/routes-resolver.ts @@ -5,7 +5,8 @@ import { Controller } from '@nestjs/common/interfaces/controllers/controller.int import { Logger } from '@nestjs/common/services/logger.service'; import { ApplicationConfig } from '../application-config'; import { CONTROLLER_MAPPING_MESSAGE } from '../helpers/messages'; -import { InstanceWrapper, NestContainer } from '../injector/container'; +import { NestContainer } from '../injector/container'; +import { InstanceWrapper } from '../injector/instance-wrapper'; import { MetadataScanner } from '../metadata-scanner'; import { Resolver } from './interfaces/resolver.interface'; import { RouterExceptionFilters } from './router-exception-filters'; diff --git a/packages/core/scanner.ts b/packages/core/scanner.ts index c3856bdc8e3..aa891163f57 100644 --- a/packages/core/scanner.ts +++ b/packages/core/scanner.ts @@ -91,14 +91,14 @@ export class DependenciesScanner { const modules = this.container.getModules(); for (const [token, { metatype }] of modules) { - await this.reflectRelatedModules(metatype, token, metatype.name); + await this.reflectImports(metatype, token, metatype.name); this.reflectProviders(metatype, token); this.reflectControllers(metatype, token); this.reflectExports(metatype, token); } } - public async reflectRelatedModules( + public async reflectImports( module: Type, token: string, context: string, @@ -111,7 +111,7 @@ export class DependenciesScanner { ), ]; for (const related of modules) { - await this.insertRelatedModule(related, token, context); + await this.insertImport(related, token, context); } } @@ -234,18 +234,14 @@ export class DependenciesScanner { return undefined; } - public async insertRelatedModule( - related: any, - token: string, - context: string, - ) { + public async insertImport(related: any, token: string, context: string) { if (isUndefined(related)) { throw new CircularDependencyException(context); } if (related && related.forwardRef) { - return this.container.addRelatedModule(related.forwardRef(), token); + return this.container.addImport(related.forwardRef(), token); } - await this.container.addRelatedModule(related, token); + await this.container.addImport(related, token); } public isCustomProvider( diff --git a/packages/core/test/injector/container.spec.ts b/packages/core/test/injector/container.spec.ts index 7a4aeb9d190..fdb51234c80 100644 --- a/packages/core/test/injector/container.spec.ts +++ b/packages/core/test/injector/container.spec.ts @@ -93,7 +93,7 @@ describe('NestContainer', () => { }); }); - describe('bindGlobalsToRelatedModules', () => { + describe('bindGlobalsToImports', () => { it('should call "bindGlobalModuleToModule" for every global module', () => { const global1 = { test: 1 }; const global2 = { test: 2 }; @@ -105,7 +105,7 @@ describe('NestContainer', () => { container, 'bindGlobalModuleToModule', ); - container.bindGlobalsToRelatedModules({ + container.bindGlobalsToImports({ addRelatedModule: sinon.spy(), } as any); expect(bindGlobalModuleToModuleSpy.calledTwice).to.be.true; diff --git a/packages/core/test/injector/injector.spec.ts b/packages/core/test/injector/injector.spec.ts index 0aa49dd985a..d6b85cfdf6d 100644 --- a/packages/core/test/injector/injector.spec.ts +++ b/packages/core/test/injector/injector.spec.ts @@ -145,12 +145,12 @@ describe('Injector', () => { metatype: Test, name: 'Test', }; - injector.loadPrototypeOfInstance(test, moduleDeps.providers); + injector.loadPrototype(test, moduleDeps.providers); expect(moduleDeps.providers.get('Test')).to.deep.equal(expectedResult); }); it('should return null when collection is nil', () => { - const result = injector.loadPrototypeOfInstance(test, null); + const result = injector.loadPrototype(test, null); expect(result).to.be.null; }); @@ -158,7 +158,7 @@ describe('Injector', () => { const collection = { get: () => ({ isResolved: true }), }; - const result = injector.loadPrototypeOfInstance(test, collection as any); + const result = injector.loadPrototype(test, collection as any); expect(result).to.be.null; }); @@ -166,7 +166,7 @@ describe('Injector', () => { const collection = { get: () => ({ inject: [] }), }; - const result = injector.loadPrototypeOfInstance(test, collection as any); + const result = injector.loadPrototype(test, collection as any); expect(result).to.be.null; }); }); @@ -200,7 +200,7 @@ describe('Injector', () => { set: (...args) => {}, }; - injector.loadInstanceOfMiddleware( + injector.loadMiddleware( { metatype: { name: '' } } as any, collection as any, null, @@ -216,7 +216,7 @@ describe('Injector', () => { set: (...args) => {}, }; - injector.loadInstanceOfMiddleware( + injector.loadMiddleware( { metatype: { name: '' } } as any, collection as any, null, @@ -237,7 +237,7 @@ describe('Injector', () => { const module = { controllers: [] }; const wrapper = { test: 'test' }; - await injector.loadInstanceOfController(wrapper as any, module as any); + await injector.loadController(wrapper as any, module as any); expect(loadInstance.calledWith(wrapper, module.controllers, module)).to.be .true; }); @@ -255,14 +255,14 @@ describe('Injector', () => { const module = { injectables: [] }; const wrapper = { test: 'test' }; - await injector.loadInstanceOfInjectable(wrapper as any, module as any); + await injector.loadInjectable(wrapper as any, module as any); expect(loadInstance.calledWith(wrapper, module.injectables, module)).to.be .true; }); }); describe('lookupComponent', () => { - let lookupComponentInRelatedModules: sinon.SinonStub; + let lookupComponentInImports: sinon.SinonStub; const metatype = { name: 'test', metatype: { name: 'test' } }; const wrapper: any = { name: 'Test', @@ -271,8 +271,8 @@ describe('Injector', () => { isResolved: false, }; beforeEach(() => { - lookupComponentInRelatedModules = sinon.stub(); - (injector as any).lookupComponentInRelatedModules = lookupComponentInRelatedModules; + lookupComponentInImports = sinon.stub(); + (injector as any).lookupComponentInImports = lookupComponentInImports; }); it('should return object from collection if exists', async () => { @@ -290,8 +290,8 @@ describe('Injector', () => { expect(result).to.be.equal(instance); }); - it('should call "lookupComponentInRelatedModules" when object is not in collection', async () => { - lookupComponentInRelatedModules.returns({}); + it('should call "lookupComponentInImports" when object is not in collection', async () => { + lookupComponentInImports.returns({}); const collection = { has: () => false, }; @@ -301,11 +301,11 @@ describe('Injector', () => { { name: metatype.name, index: 0, dependencies: [] }, wrapper, ); - expect(lookupComponentInRelatedModules.called).to.be.true; + expect(lookupComponentInImports.called).to.be.true; }); it('should throw "UnknownDependenciesException" when instanceWrapper is null and "exports" collection does not contain token', () => { - lookupComponentInRelatedModules.returns(null); + lookupComponentInImports.returns(null); const collection = { has: () => false, }; @@ -321,7 +321,7 @@ describe('Injector', () => { }); it('should not throw "UnknownDependenciesException" instanceWrapper is not null', () => { - lookupComponentInRelatedModules.returns({}); + lookupComponentInImports.returns({}); const collection = { has: () => false, }; @@ -337,20 +337,20 @@ describe('Injector', () => { }); }); - describe('lookupComponentInRelatedModules', () => { - let loadInstanceOfComponent: sinon.SinonSpy; + describe('lookupComponentInImports', () => { + let loadProvider: sinon.SinonSpy; const metatype = { name: 'test' }; const module = { relatedModules: new Map(), }; beforeEach(() => { - loadInstanceOfComponent = sinon.spy(); - (injector as any).loadInstanceOfComponent = loadInstanceOfComponent; + loadProvider = sinon.spy(); + (injector as any).loadProvider = loadProvider; }); it('should return null when there is no related modules', async () => { - const result = await injector.lookupComponentInRelatedModules( + const result = await injector.lookupComponentInImports( module as any, null, ); @@ -374,10 +374,7 @@ describe('Injector', () => { ] as any), }; expect( - injector.lookupComponentInRelatedModules( - module as any, - metatype as any, - ), + injector.lookupComponentInImports(module as any, metatype as any), ).to.be.eventually.eq(null); module = { @@ -396,14 +393,11 @@ describe('Injector', () => { ] as any), }; expect( - injector.lookupComponentInRelatedModules( - module as any, - metatype as any, - ), + injector.lookupComponentInImports(module as any, metatype as any), ).to.eventually.be.eq(null); }); - it('should call "loadInstanceOfComponent" when component is not resolved', async () => { + it('should call "loadProvider" when component is not resolved', async () => { const module = { relatedModules: new Map([ [ @@ -423,14 +417,11 @@ describe('Injector', () => { ], ] as any), }; - await injector.lookupComponentInRelatedModules( - module as any, - metatype as any, - ); - expect(loadInstanceOfComponent.called).to.be.true; + await injector.lookupComponentInImports(module as any, metatype as any); + expect(loadProvider.called).to.be.true; }); - it('should not call "loadInstanceOfComponent" when component is resolved', async () => { + it('should not call "loadProvider" when component is resolved', async () => { const module = { relatedModules: new Map([ [ @@ -450,11 +441,8 @@ describe('Injector', () => { ], ] as any), }; - await injector.lookupComponentInRelatedModules( - module as any, - metatype as any, - ); - expect(loadInstanceOfComponent.called).to.be.false; + await injector.lookupComponentInImports(module as any, metatype as any); + expect(loadProvider.called).to.be.false; }); }); @@ -506,9 +494,9 @@ describe('Injector', () => { }); describe('when instanceWrapper is not resolved and does not have forward ref', () => { - it('should call loadInstanceOfComponent', async () => { + it('should call loadProvider', async () => { const loadStub = sinon - .stub(injector, 'loadInstanceOfComponent') + .stub(injector, 'loadProvider') .callsFake(() => null); sinon.stub(injector, 'lookupComponent').returns({ isResolved: false }); @@ -520,9 +508,9 @@ describe('Injector', () => { ); expect(loadStub.called).to.be.true; }); - it('should not call loadInstanceOfComponent (isResolved)', async () => { + it('should not call loadProvider (isResolved)', async () => { const loadStub = sinon - .stub(injector, 'loadInstanceOfComponent') + .stub(injector, 'loadProvider') .callsFake(() => null); sinon.stub(injector, 'lookupComponent').returns({ isResolved: true }); @@ -534,9 +522,9 @@ describe('Injector', () => { ); expect(loadStub.called).to.be.false; }); - it('should not call loadInstanceOfComponent (forwardRef)', async () => { + it('should not call loadProvider (forwardRef)', async () => { const loadStub = sinon - .stub(injector, 'loadInstanceOfComponent') + .stub(injector, 'loadProvider') .callsFake(() => null); sinon .stub(injector, 'lookupComponent') @@ -555,7 +543,7 @@ describe('Injector', () => { describe('when instanceWraper has async property', () => { it('should await instance', async () => { const loadStub = sinon - .stub(injector, 'loadInstanceOfComponent') + .stub(injector, 'loadProvider') .callsFake(() => null); const instance = Promise.resolve(true); diff --git a/packages/core/test/injector/instance-loader.spec.ts b/packages/core/test/injector/instance-loader.spec.ts index c46cbfa5170..1456bda2631 100644 --- a/packages/core/test/injector/instance-loader.spec.ts +++ b/packages/core/test/injector/instance-loader.spec.ts @@ -27,7 +27,7 @@ describe('InstanceLoader', () => { mockContainer = sinon.mock(container); }); - it('should call "loadPrototypeOfInstance" for each provider and route in each module', async () => { + it('should call "loadPrototype" for each provider and route in each module', async () => { const injector = new Injector(); (loader as any).injector = injector; @@ -47,13 +47,10 @@ describe('InstanceLoader', () => { modules.set('Test', module); mockContainer.expects('getModules').returns(modules); - const loadProviderPrototypeStub = sinon.stub( - injector, - 'loadPrototypeOfInstance', - ); + const loadProviderPrototypeStub = sinon.stub(injector, 'loadPrototype'); - sinon.stub(injector, 'loadInstanceOfController'); - sinon.stub(injector, 'loadInstanceOfComponent'); + sinon.stub(injector, 'loadController'); + sinon.stub(injector, 'loadProvider'); await loader.createInstancesOfDependencies(); expect( @@ -64,7 +61,7 @@ describe('InstanceLoader', () => { ).to.be.true; }); - it('should call "loadInstanceOfComponent" for each provider in each module', async () => { + it('should call "loadProvider" for each provider in each module', async () => { const injector = new Injector(); (loader as any).injector = injector; @@ -86,8 +83,8 @@ describe('InstanceLoader', () => { modules.set('Test', module); mockContainer.expects('getModules').returns(modules); - const loadProviderStub = sinon.stub(injector, 'loadInstanceOfComponent'); - sinon.stub(injector, 'loadInstanceOfController'); + const loadProviderStub = sinon.stub(injector, 'loadProvider'); + sinon.stub(injector, 'loadController'); await loader.createInstancesOfDependencies(); expect( @@ -95,7 +92,7 @@ describe('InstanceLoader', () => { ).to.be.true; }); - it('should call "loadInstanceOfController" for each route in each module', async () => { + it('should call "loadController" for each route in each module', async () => { const injector = new Injector(); (loader as any).injector = injector; @@ -112,8 +109,8 @@ describe('InstanceLoader', () => { modules.set('Test', module); mockContainer.expects('getModules').returns(modules); - sinon.stub(injector, 'loadInstanceOfProvider'); - const loadRoutesStub = sinon.stub(injector, 'loadInstanceOfController'); + sinon.stub(injector, 'loadProvider'); + const loadRoutesStub = sinon.stub(injector, 'loadController'); await loader.createInstancesOfDependencies(); expect( @@ -121,7 +118,7 @@ describe('InstanceLoader', () => { ).to.be.true; }); - it('should call "loadInstanceOfInjectable" for each injectable in each module', async () => { + it('should call "loadInjectable" for each injectable in each module', async () => { const injector = new Injector(); (loader as any).injector = injector; @@ -142,8 +139,8 @@ describe('InstanceLoader', () => { modules.set('Test', module); mockContainer.expects('getModules').returns(modules); - const loadInjectableStub = sinon.stub(injector, 'loadInstanceOfInjectable'); - sinon.stub(injector, 'loadInstanceOfController'); + const loadInjectableStub = sinon.stub(injector, 'loadInjectable'); + sinon.stub(injector, 'loadController'); await loader.createInstancesOfDependencies(); expect( diff --git a/packages/core/test/injector/module.spec.ts b/packages/core/test/injector/module.spec.ts index 50ec63e3687..d8fcc6d7ef1 100644 --- a/packages/core/test/injector/module.spec.ts +++ b/packages/core/test/injector/module.spec.ts @@ -128,6 +128,7 @@ describe('Module', () => { const type = { name: 'TypeTest' }; const provider = { provide: type, useClass: type, name: 'test' }; let setSpy; + beforeEach(() => { const collection = new Map(); setSpy = sinon.spy(collection, 'set'); @@ -261,7 +262,7 @@ describe('Module', () => { describe('relatedModules', () => { it('should return relatedModules', () => { const test = ['test']; - (module as any)._relatedModules = test; + (module as any)._imports = test; expect(module.relatedModules).to.be.eql(test); }); }); @@ -290,13 +291,13 @@ describe('Module', () => { }); }); - describe('createModuleRefMetatype', () => { + describe('createModuleReferenceType', () => { let moduleRef; class SimpleClass {} beforeEach(() => { - const Class = module.createModuleRefMetatype(); + const Class = module.createModuleReferenceType(); moduleRef = new Class(); }); @@ -321,7 +322,7 @@ describe('Module', () => { describe('when unit exists in related modules collection', () => { it('should behave as identity', () => { const metatype = { name: token }; - (module as any)._relatedModules = new Set([ + (module as any)._imports = new Set([ new Module(metatype as any, [], new NestContainer()), ]); expect(module.validateExportedProvider(token)).to.be.eql(token); diff --git a/packages/core/test/middleware/resolver.spec.ts b/packages/core/test/middleware/resolver.spec.ts index 0ee5c1b73ae..fbc66e6274a 100644 --- a/packages/core/test/middleware/resolver.spec.ts +++ b/packages/core/test/middleware/resolver.spec.ts @@ -28,10 +28,10 @@ describe('MiddlewareResolver', () => { }); it('should resolve middleware instances from container', () => { - const loadInstanceOfMiddleware = sinon.stub( + const loadMiddleware = sinon.stub( // tslint:disable-next-line:no-string-literal resolver['instanceLoader'], - 'loadInstanceOfMiddleware', + 'loadMiddleware', ); const middleware = new Map(); const wrapper = { @@ -44,10 +44,9 @@ describe('MiddlewareResolver', () => { mockContainer.expects('getMiddleware').returns(middleware); resolver.resolveInstances(module, null); - expect(loadInstanceOfMiddleware.callCount).to.be.equal(middleware.size); - expect(loadInstanceOfMiddleware.calledWith(wrapper, middleware, module)).to - .be.true; + expect(loadMiddleware.callCount).to.be.equal(middleware.size); + expect(loadMiddleware.calledWith(wrapper, middleware, module)).to.be.true; - loadInstanceOfMiddleware.restore(); + loadMiddleware.restore(); }); }); diff --git a/packages/microservices/client/client-redis.ts b/packages/microservices/client/client-redis.ts index e8934cdeb18..e6cdc4b44a7 100644 --- a/packages/microservices/client/client-redis.ts +++ b/packages/microservices/client/client-redis.ts @@ -115,7 +115,7 @@ export class ClientRedis extends ClientProxy { return this.getOptionsProp(this.options, 'retryDelay') || 0; } - public createResponseCallback(): Function { + public createResponseCallback(): (channel: string, buffer: string) => void { return (channel: string, buffer: string) => { const { err, response, isDisposed, id } = JSON.parse( buffer, diff --git a/packages/microservices/microservices-module.ts b/packages/microservices/microservices-module.ts index 607887b9593..4fc1021635e 100644 --- a/packages/microservices/microservices-module.ts +++ b/packages/microservices/microservices-module.ts @@ -3,10 +3,8 @@ import { ApplicationConfig } from '@nestjs/core/application-config'; import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.exception'; import { GuardsConsumer } from '@nestjs/core/guards/guards-consumer'; import { GuardsContextCreator } from '@nestjs/core/guards/guards-context-creator'; -import { - InstanceWrapper, - NestContainer, -} from '@nestjs/core/injector/container'; +import { NestContainer } from '@nestjs/core/injector/container'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator'; import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer'; diff --git a/packages/websockets/socket-module.ts b/packages/websockets/socket-module.ts index 5bdeddc500a..04ff775b4ef 100644 --- a/packages/websockets/socket-module.ts +++ b/packages/websockets/socket-module.ts @@ -2,10 +2,8 @@ import { Injectable } from '@nestjs/common/interfaces/injectable.interface'; import { ApplicationConfig } from '@nestjs/core/application-config'; import { GuardsConsumer } from '@nestjs/core/guards/guards-consumer'; import { GuardsContextCreator } from '@nestjs/core/guards/guards-context-creator'; -import { - InstanceWrapper, - NestContainer, -} from '@nestjs/core/injector/container'; +import { NestContainer } from '@nestjs/core/injector/container'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator'; import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer'; diff --git a/sample/01-cats-app/src/cats/cats.controller.ts b/sample/01-cats-app/src/cats/cats.controller.ts index 62bffd1de34..52c75814482 100644 --- a/sample/01-cats-app/src/cats/cats.controller.ts +++ b/sample/01-cats-app/src/cats/cats.controller.ts @@ -5,24 +5,33 @@ import { Get, Param, Post, + Scope, UseGuards, UseInterceptors, } from '@nestjs/common'; +import { AsyncContext } from '@nestjs/core/hooks/async-context'; import { Roles } from '../common/decorators/roles.decorator'; import { RolesGuard } from '../common/guards/roles.guard'; import { LoggingInterceptor } from '../common/interceptors/logging.interceptor'; import { TransformInterceptor } from '../common/interceptors/transform.interceptor'; import { ParseIntPipe } from '../common/pipes/parse-int.pipe'; -import { CatsService } from './cats.service'; +import { CatsService, Rawr } from './cats.service'; import { CreateCatDto } from './dto/create-cat.dto'; import { Cat } from './interfaces/cat.interface'; - @Catch() -@Controller('cats') +@Controller('cats', { + scope: Scope.REQUEST, +}) @UseGuards(RolesGuard) @UseInterceptors(LoggingInterceptor, TransformInterceptor) export class CatsController { - constructor(private readonly catsService: CatsService) {} + constructor( + private readonly catsService: CatsService, + private readonly asyncContext: AsyncContext, + private readonly rawr: Rawr, + ) { + console.log('Cats controller has been created (request)'); + } @Post() @Roles('admin') @@ -32,6 +41,12 @@ export class CatsController { @Get() async findAll(): Promise { + const random = Math.random(); + console.log(random, this.rawr); + console.log( + ((this.asyncContext as any).internalStorage as Map).size, + ); + this.asyncContext.set('xd', random); return this.catsService.findAll(); } diff --git a/sample/01-cats-app/src/cats/cats.module.ts b/sample/01-cats-app/src/cats/cats.module.ts index f3291c7d11e..1d1192240f7 100644 --- a/sample/01-cats-app/src/cats/cats.module.ts +++ b/sample/01-cats-app/src/cats/cats.module.ts @@ -1,9 +1,34 @@ -import { Module } from '@nestjs/common'; +import { MiddlewareConsumer, Module, Scope } from '@nestjs/common'; +import { AsyncContext } from '@nestjs/core/hooks/async-context'; import { CatsController } from './cats.controller'; -import { CatsService } from './cats.service'; +import { CatsService, Rawr } from './cats.service'; +export class Boom { + boom() { + return 'bum'; + } +} @Module({ controllers: [CatsController], - providers: [CatsService], + providers: [ + CatsService, + Rawr, + { + provide: Boom, + useFactory: () => { + console.log('Boom has been created (lazy)'); + return new Boom(); + }, + scope: Scope.LAZY, + }, + ], }) -export class CatsModule {} +export class CatsModule { + constructor(private readonly asyncContext: AsyncContext) {} + + configure(consumer: MiddlewareConsumer) { + consumer + .apply((req, res, next) => this.asyncContext.run(next)) + .forRoutes('*'); + } +} diff --git a/sample/01-cats-app/src/cats/cats.service.ts b/sample/01-cats-app/src/cats/cats.service.ts index 2619cd7176d..18cee0d82fb 100644 --- a/sample/01-cats-app/src/cats/cats.service.ts +++ b/sample/01-cats-app/src/cats/cats.service.ts @@ -1,15 +1,35 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; +import { AsyncContext } from '@nestjs/core/hooks/async-context'; +import { Boom } from './cats.module'; import { Cat } from './interfaces/cat.interface'; @Injectable() +export class Rawr { + constructor() { + console.log('rawr created (transient)'); + } +} +@Injectable({ scope: Scope.REQUEST }) export class CatsService { private readonly cats: Cat[] = []; + constructor( + private readonly asyncContext: AsyncContext, + @Inject('Boom') private readonly instance: Boom, + private readonly rawr: Rawr, + ) { + console.log('CatsService has been created'); + } + create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { + console.log(this.rawr); + this.instance.boom(); + + console.log(this.asyncContext.get('xd')); return this.cats; } } diff --git a/sample/01-cats-app/src/common/guards/roles.guard.ts b/sample/01-cats-app/src/common/guards/roles.guard.ts index d45e546f731..778cacfb4ce 100644 --- a/sample/01-cats-app/src/common/guards/roles.guard.ts +++ b/sample/01-cats-app/src/common/guards/roles.guard.ts @@ -1,9 +1,11 @@ -import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -@Injectable() +@Injectable({ dynamic: true }) export class RolesGuard implements CanActivate { - constructor(private readonly reflector: Reflector) {} + constructor(private readonly reflector: Reflector) { + console.log('guard'); + } canActivate(context: ExecutionContext): boolean { const roles = this.reflector.get('roles', context.getHandler()); @@ -12,7 +14,8 @@ export class RolesGuard implements CanActivate { } const request = context.switchToHttp().getRequest(); const user = request.user; - const hasRole = () => user.roles.some((role) => !!roles.find((item) => item === role)); + const hasRole = () => + user.roles.some(role => !!roles.find(item => item === role)); return user && user.roles && hasRole(); } } From 5cd3448670596a35a0c44b654cd0ed5a5227e86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Sat, 15 Dec 2018 23:01:48 +0100 Subject: [PATCH 3/9] feature() fix request scoped --- benchmarks/nest/app.controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/benchmarks/nest/app.controller.js b/benchmarks/nest/app.controller.js index f86c0fed833..005c2dc85c8 100644 --- a/benchmarks/nest/app.controller.js +++ b/benchmarks/nest/app.controller.js @@ -42,6 +42,9 @@ __decorate( 'root', null, ); -AppController = __decorate([common_1.Controller()], AppController); +AppController = __decorate( + [common_1.Controller({ scope: common_1.Scope.REQUEST })], + AppController, +); exports.AppController = AppController; //# sourceMappingURL=app.controller.js.map From 5352bba8f75f39753babe28d5323505443ec3094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Sat, 15 Dec 2018 23:02:04 +0100 Subject: [PATCH 4/9] feature() fix request scoped --- .../core/helpers/handler-metadata-storage.ts | 36 +++++++ packages/core/hooks/async-hooks-module.ts | 13 +++ packages/core/hooks/index.ts | 1 + packages/core/injector/instance-wrapper.ts | 2 +- packages/core/injector/module.ts | 12 --- packages/core/middleware/middleware-module.ts | 14 +++ .../core/router/router-execution-context.ts | 94 +++++++++++++------ packages/core/router/router-explorer.ts | 69 ++++++++------ 8 files changed, 170 insertions(+), 71 deletions(-) create mode 100644 packages/core/helpers/handler-metadata-storage.ts create mode 100644 packages/core/hooks/async-hooks-module.ts diff --git a/packages/core/helpers/handler-metadata-storage.ts b/packages/core/helpers/handler-metadata-storage.ts new file mode 100644 index 00000000000..9c2bf4eb5a2 --- /dev/null +++ b/packages/core/helpers/handler-metadata-storage.ts @@ -0,0 +1,36 @@ +import { Controller } from '@nestjs/common/interfaces'; +import { ParamProperties } from './context-utils'; + +export const HANDLER_METADATA_SYMBOL = Symbol.for('handler_metadata:cache'); + +export interface HandlerMetadata { + argsLength: number; + paramsOptions: (ParamProperties & { metatype?: any })[]; + fnHandleResponse: ( + result: TResult, + res: TResponse, + ) => any; +} + +export class HandlerMetadataStorage { + private readonly [HANDLER_METADATA_SYMBOL] = new Map< + string, + HandlerMetadata + >(); + + set(controller: T, methodName: string, metadata: HandlerMetadata) { + const metadataKey = this.getMetadataKey(controller, methodName); + this[HANDLER_METADATA_SYMBOL].set(metadataKey, metadata); + } + + get(controller: T, methodName: string): HandlerMetadata | undefined { + const metadataKey = this.getMetadataKey(controller, methodName); + return this[HANDLER_METADATA_SYMBOL].get(metadataKey); + } + + private getMetadataKey(controller: Controller, methodName: string): string { + const ctor = controller.constructor; + const controllerKey = ctor && ctor.name; + return controllerKey + methodName; + } +} diff --git a/packages/core/hooks/async-hooks-module.ts b/packages/core/hooks/async-hooks-module.ts new file mode 100644 index 00000000000..c1e153ad6ea --- /dev/null +++ b/packages/core/hooks/async-hooks-module.ts @@ -0,0 +1,13 @@ +import { Global, Module } from '@nestjs/common'; +import { AsyncContext } from './async-context'; + +@Global() +@Module({ + providers: [ + { + provide: AsyncContext, + useValue: AsyncContext.instance, + }, + ], +}) +export class AsyncHooksModule {} diff --git a/packages/core/hooks/index.ts b/packages/core/hooks/index.ts index 0bbd8adf86e..cabc02887c7 100644 --- a/packages/core/hooks/index.ts +++ b/packages/core/hooks/index.ts @@ -1 +1,2 @@ export * from './async-context'; +export * from './async-hooks-module'; diff --git a/packages/core/injector/instance-wrapper.ts b/packages/core/injector/instance-wrapper.ts index 2187e01e9bd..6af7b815a78 100644 --- a/packages/core/injector/instance-wrapper.ts +++ b/packages/core/injector/instance-wrapper.ts @@ -2,7 +2,7 @@ import { Scope, Type } from '@nestjs/common'; import { STATIC_CONTEXT } from './constants'; import { Module } from './module'; -export const INSTANCE_METADATA_SYMBOL = Symbol.for('metadata:cache'); +export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache'); export interface ContextId { readonly id: number; diff --git a/packages/core/injector/module.ts b/packages/core/injector/module.ts index 1ee36742324..e327dd3895c 100644 --- a/packages/core/injector/module.ts +++ b/packages/core/injector/module.ts @@ -20,7 +20,6 @@ import { RuntimeException } from '../errors/exceptions/runtime.exception'; import { UnknownExportException } from '../errors/exceptions/unknown-export.exception'; import { ApplicationReferenceHost } from '../helpers/application-ref-host'; import { ExternalContextCreator } from '../helpers/external-context-creator'; -import { AsyncContext } from '../hooks/async-context'; import { Reflector } from '../services/reflector.service'; import { NestContainer } from './container'; import { InstanceWrapper } from './instance-wrapper'; @@ -138,17 +137,6 @@ export class Module { this.addExternalContextCreator(container.getExternalContextCreator()); this.addModulesContainer(container.getModulesContainer()); this.addApplicationRefHost(container.getApplicationRefHost()); - - this._providers.set( - AsyncContext.name, - new InstanceWrapper({ - name: AsyncContext.name, - metatype: AsyncContext as any, - isResolved: true, - instance: AsyncContext.instance, - host: this, - }), - ); } public addModuleRef() { diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index 6457fdfb2c2..9380984f828 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -1,5 +1,7 @@ import { HttpServer } from '@nestjs/common'; +import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants'; import { RequestMethod } from '@nestjs/common/enums/request-method.enum'; +import { Scope } from '@nestjs/common/interfaces'; import { MiddlewareConfiguration, RouteInfo, @@ -182,6 +184,13 @@ export class MiddlewareModule { middlewareInstance, path, ); + + const classScope = this.getClassScope(instance); + if (classScope === Scope.REQUEST) { + return bindWithProxy(async (...args: unknown[]) => + (await instance.resolve())(...args), + ); + } const middleware = await instance.resolve(); bindWithProxy(middleware); } @@ -201,4 +210,9 @@ export class MiddlewareModule { const basePath = validatePath(prefix); router(basePath + path, proxy); } + + private getClassScope(instance: NestMiddleware): Scope { + const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, instance); + return metadata && metadata.scope; + } } diff --git a/packages/core/router/router-execution-context.ts b/packages/core/router/router-execution-context.ts index 7f6d3ddc88d..ce00fdc5f52 100644 --- a/packages/core/router/router-execution-context.ts +++ b/packages/core/router/router-execution-context.ts @@ -24,6 +24,10 @@ import { FORBIDDEN_MESSAGE } from '../guards/constants'; import { GuardsConsumer } from '../guards/guards-consumer'; import { GuardsContextCreator } from '../guards/guards-context-creator'; import { ContextUtils } from '../helpers/context-utils'; +import { + HandlerMetadata, + HandlerMetadataStorage, +} from '../helpers/handler-metadata-storage'; import { STATIC_CONTEXT } from '../injector/constants'; import { InterceptorsConsumer } from '../interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator'; @@ -48,6 +52,7 @@ export interface ParamProperties { } export class RouterExecutionContext { + private readonly handlerMetadataStorage = new HandlerMetadataStorage(); private readonly contextUtils = new ContextUtils(); private readonly responseController: RouterResponseController; @@ -72,23 +77,18 @@ export class RouterExecutionContext { requestMethod: RequestMethod, contextId = STATIC_CONTEXT, ) { - const metadata = - this.contextUtils.reflectCallbackMetadata( - instance, - methodName, - ROUTE_ARGS_METADATA, - ) || {}; - const keys = Object.keys(metadata); - const argsLength = this.contextUtils.getArgumentsLength(keys, metadata); - const pipes = this.pipesContextCreator.create( + const { argsLength, paramsOptions, fnHandleResponse } = this.getMetadata( instance, callback, + methodName, module, - contextId, + requestMethod, ); - const paramtypes = this.contextUtils.reflectCallbackParamtypes( + const pipes = this.pipesContextCreator.create( instance, - methodName, + callback, + module, + contextId, ); const guards = this.guardsContextCreator.create( instance, @@ -102,27 +102,10 @@ export class RouterExecutionContext { module, contextId, ); - const httpCode = this.reflectHttpStatusCode(callback); - const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module); - const isResponseHandled = paramsMetadata.some( - ({ type }) => - type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT, - ); - const paramsOptions = this.contextUtils.mergeParamsMetatypes( - paramsMetadata, - paramtypes, - ); - const httpStatusCode = httpCode - ? httpCode - : this.responseController.getStatusByMethod(requestMethod); const fnCanActivate = this.createGuardsFn(guards, instance, callback); const fnApplyPipes = this.createPipesFn(pipes, paramsOptions); - const fnHandleResponse = this.createHandleResponseFn( - callback, - isResponseHandled, - httpStatusCode, - ); + const handler = ( args: any[], req: TRequest, @@ -152,6 +135,57 @@ export class RouterExecutionContext { }; } + public getMetadata( + instance: Controller, + callback: (...args: any[]) => any, + methodName: string, + module: string, + requestMethod: RequestMethod, + ): HandlerMetadata { + const cacheMetadata = this.handlerMetadataStorage.get(instance, methodName); + if (cacheMetadata) { + return cacheMetadata; + } + const metadata = + this.contextUtils.reflectCallbackMetadata( + instance, + methodName, + ROUTE_ARGS_METADATA, + ) || {}; + const keys = Object.keys(metadata); + const argsLength = this.contextUtils.getArgumentsLength(keys, metadata); + const paramtypes = this.contextUtils.reflectCallbackParamtypes( + instance, + methodName, + ); + const httpCode = this.reflectHttpStatusCode(callback); + const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module); + const isResponseHandled = paramsMetadata.some( + ({ type }) => + type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT, + ); + const paramsOptions = this.contextUtils.mergeParamsMetatypes( + paramsMetadata, + paramtypes, + ); + const httpStatusCode = httpCode + ? httpCode + : this.responseController.getStatusByMethod(requestMethod); + + const fnHandleResponse = this.createHandleResponseFn( + callback, + isResponseHandled, + httpStatusCode, + ); + const handlerMetadata: HandlerMetadata = { + argsLength, + paramsOptions, + fnHandleResponse, + }; + this.handlerMetadataStorage.set(instance, methodName, handlerMetadata); + return handlerMetadata; + } + public reflectHttpStatusCode(callback: (...args: any[]) => any): number { return Reflect.getMetadata(HTTP_CODE_METADATA, callback); } diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index 492e61d5d4d..a425362f0f4 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -1,5 +1,9 @@ -import { HttpServer } from '@nestjs/common'; -import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants'; +import { HttpServer, Scope } from '@nestjs/common'; +import { + METHOD_METADATA, + PATH_METADATA, + SCOPE_OPTIONS_METADATA, +} from '@nestjs/common/constants'; import { RequestMethod } from '@nestjs/common/enums/request-method.enum'; import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface'; import { Type } from '@nestjs/common/interfaces/type.interface'; @@ -163,7 +167,36 @@ export class RouterExplorer { const stripSlash = (str: string) => str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str; const fullPath = stripSlash(basePath) + path; - // TODO: + + const classScope = this.getClassScope(instance); + const isRequestScoped = classScope === Scope.REQUEST; + if (isRequestScoped) { + routerMethod( + stripSlash(fullPath) || '/', + async ( + req: TRequest, + res: TResponse, + next: Function, + ) => { + const contextId = { id: 1 }; // asyncId + const contextInstance = await this.injector.loadControllerPerContext( + instance, + this.container.getModules(), + module, + contextId, + ); + this.createCallbackProxy( + contextInstance, + contextInstance[methodName], + methodName, + module, + requestMethod, + contextId, + )(req, res, next); + }, + ); + return; + } const proxy = this.createCallbackProxy( instance, targetCallback, @@ -172,31 +205,6 @@ export class RouterExplorer { requestMethod, ); routerMethod(stripSlash(fullPath) || '/', proxy); - /*routerMethod( - stripSlash(fullPath) || '/', - async ( - req: TRequest, - res: TResponse, - next: Function, - ) => { - const ctx = { id: 2 }; // asyncId - const contextInstance = await this.injector.loadControllerPerContext( - instance, - this.container.getModules(), - module, - ctx, - ); - const proxy = this.createCallbackProxy( - contextInstance, - contextInstance[methodName], - methodName, - module, - requestMethod, - ctx, - ); - proxy(req, res, next); - }, - );*/ } private createCallbackProxy( @@ -223,4 +231,9 @@ export class RouterExplorer { ); return this.routerProxy.createProxy(executionContext, exceptionFilter); } + + private getClassScope(controller: Controller): Scope { + const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, controller); + return metadata && metadata.scope; + } } From cb062be24fdfb286d0a5a3fa8a08d2ba2dc69746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Mon, 17 Dec 2018 10:13:49 +0100 Subject: [PATCH 5/9] feature() wip DI scopes --- .../decorators/core/controller.decorator.ts | 23 +--- .../interfaces/scope-options.interface.ts | 3 +- .../base-exception-filter-context.ts | 4 +- .../core/guards/guards-context-creator.ts | 7 +- packages/core/hooks/async-context.ts | 2 +- packages/core/hooks/async-hooks-middleware.ts | 11 ++ packages/core/hooks/async-hooks-module.ts | 3 +- packages/core/hooks/index.ts | 1 + packages/core/injector/injector.ts | 26 ++-- packages/core/injector/instance-wrapper.ts | 21 +-- packages/core/injector/module.ts | 1 - .../interceptors-context-creator.ts | 4 +- packages/core/nest-application.ts | 7 +- packages/core/pipes/pipes-context-creator.ts | 7 +- packages/core/router/router-explorer.ts | 35 ++--- packages/core/router/routes-resolver.ts | 9 +- .../exceptions/base-exception-filter.spec.ts | 7 +- .../guards/guards-context-creator.spec.ts | 10 +- packages/core/test/injector/injector.spec.ts | 125 ++++++++++-------- .../test/injector/instance-loader.spec.ts | 16 ++- packages/core/test/injector/module.spec.ts | 64 +++++---- .../middleware/middlewares-module.spec.ts | 12 +- .../core/test/router/routes-resolver.spec.ts | 7 +- packages/core/test/scanner.spec.ts | 6 +- .../interfaces/client-metadata.interface.ts | 7 +- .../listener-metadata-explorer.ts | 6 +- .../microservices/listeners-controller.ts | 51 +++++-- .../microservices/microservices-module.ts | 7 +- .../test/listeners-controller.spec.ts | 10 +- .../01-cats-app/src/cats/cats.controller.ts | 25 +--- sample/01-cats-app/src/cats/cats.module.ts | 33 +---- sample/01-cats-app/src/cats/cats.service.ts | 22 +-- .../src/common/guards/roles.guard.ts | 6 +- 33 files changed, 303 insertions(+), 275 deletions(-) create mode 100644 packages/core/hooks/async-hooks-middleware.ts diff --git a/packages/common/decorators/core/controller.decorator.ts b/packages/common/decorators/core/controller.decorator.ts index 82b24151a6b..f83705d5b79 100644 --- a/packages/common/decorators/core/controller.decorator.ts +++ b/packages/common/decorators/core/controller.decorator.ts @@ -1,30 +1,13 @@ -import { PATH_METADATA, SCOPE_OPTIONS_METADATA } from '../../constants'; -import { isObject, isUndefined } from '../../utils/shared.utils'; -import { ScopeOptions } from './../../interfaces/scope-options.interface'; - -export interface ControllerOptions extends ScopeOptions {} +import { PATH_METADATA } from '../../constants'; +import { isUndefined } from '../../utils/shared.utils'; /** * Defines the controller. Controller can inject dependencies through constructor. * Those dependencies have to belong to the same module. */ -export function Controller(prefix?: string): ClassDecorator; -export function Controller(options?: ControllerOptions): ClassDecorator; -export function Controller( - prefix?: string, - options?: ControllerOptions, -): ClassDecorator; -export function Controller( - prefixOrOptions?: string | ControllerOptions, - options?: ControllerOptions, -): ClassDecorator { - const [prefix, controllerOptions] = isObject(prefixOrOptions) - ? [undefined, prefixOrOptions] - : [prefixOrOptions, options]; - +export function Controller(prefix?: string): ClassDecorator { const path = isUndefined(prefix) ? '/' : prefix; return (target: object) => { Reflect.defineMetadata(PATH_METADATA, path, target); - Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, controllerOptions, target); }; } diff --git a/packages/common/interfaces/scope-options.interface.ts b/packages/common/interfaces/scope-options.interface.ts index 40274f6f23f..cb18fb2a149 100644 --- a/packages/common/interfaces/scope-options.interface.ts +++ b/packages/common/interfaces/scope-options.interface.ts @@ -1,7 +1,8 @@ export enum Scope { DEFAULT, REQUEST, - LAZY, + /** @experimental */ + LAZY_ASYNC, } export interface ScopeOptions { diff --git a/packages/core/exceptions/base-exception-filter-context.ts b/packages/core/exceptions/base-exception-filter-context.ts index 39b6cf9f027..d321f156179 100644 --- a/packages/core/exceptions/base-exception-filter-context.ts +++ b/packages/core/exceptions/base-exception-filter-context.ts @@ -6,7 +6,7 @@ import iterate from 'iterare'; import { ContextCreator } from '../helpers/context-creator'; import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; -import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; +import { InstanceWrapper } from '../injector/instance-wrapper'; export class BaseExceptionFilterContext extends ContextCreator { protected moduleContext: string; @@ -36,7 +36,7 @@ export class BaseExceptionFilterContext extends ContextCreator { public getFilterInstance( filter: Function | ExceptionFilter, - contextId: ContextId, + contextId = STATIC_CONTEXT, ) { const isObject = (filter as ExceptionFilter).catch; if (isObject) { diff --git a/packages/core/guards/guards-context-creator.ts b/packages/core/guards/guards-context-creator.ts index e81e521cafb..16a7c6b941b 100644 --- a/packages/core/guards/guards-context-creator.ts +++ b/packages/core/guards/guards-context-creator.ts @@ -7,7 +7,7 @@ import iterate from 'iterare'; import { ContextCreator } from '../helpers/context-creator'; import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; -import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; +import { InstanceWrapper } from '../injector/instance-wrapper'; export class GuardsContextCreator extends ContextCreator { private moduleContext: string; @@ -43,7 +43,10 @@ export class GuardsContextCreator extends ContextCreator { .toArray() as R; } - public getGuardInstance(guard: Function | CanActivate, contextId: ContextId) { + public getGuardInstance( + guard: Function | CanActivate, + contextId = STATIC_CONTEXT, + ) { const isObject = (guard as CanActivate).canActivate; if (isObject) { return guard; diff --git a/packages/core/hooks/async-context.ts b/packages/core/hooks/async-context.ts index 48306bd1e42..757322b5280 100644 --- a/packages/core/hooks/async-context.ts +++ b/packages/core/hooks/async-context.ts @@ -11,7 +11,7 @@ export class AsyncContext implements OnModuleInit, OnModuleDestroy { private readonly asyncHookRef: asyncHooks.AsyncHook, ) {} - static get instance() { + static getInstance(): AsyncContext { if (!this._instance) { this.initialize(); } diff --git a/packages/core/hooks/async-hooks-middleware.ts b/packages/core/hooks/async-hooks-middleware.ts new file mode 100644 index 00000000000..e5d9f9c5c7b --- /dev/null +++ b/packages/core/hooks/async-hooks-middleware.ts @@ -0,0 +1,11 @@ +import { Injectable, MiddlewareFunction, NestMiddleware } from '@nestjs/common'; +import { AsyncContext } from './async-context'; + +@Injectable() +export class AsyncHooksMiddleware implements NestMiddleware { + constructor(private readonly asyncContext: AsyncContext) {} + + resolve(...args: any[]): MiddlewareFunction { + return (req: any, res: any, next: Function) => this.asyncContext.run(next); + } +} diff --git a/packages/core/hooks/async-hooks-module.ts b/packages/core/hooks/async-hooks-module.ts index c1e153ad6ea..8e31373b41b 100644 --- a/packages/core/hooks/async-hooks-module.ts +++ b/packages/core/hooks/async-hooks-module.ts @@ -6,8 +6,9 @@ import { AsyncContext } from './async-context'; providers: [ { provide: AsyncContext, - useValue: AsyncContext.instance, + useValue: AsyncContext.getInstance(), }, ], + exports: [AsyncContext], }) export class AsyncHooksModule {} diff --git a/packages/core/hooks/index.ts b/packages/core/hooks/index.ts index cabc02887c7..f06f180f1b7 100644 --- a/packages/core/hooks/index.ts +++ b/packages/core/hooks/index.ts @@ -1,2 +1,3 @@ export * from './async-context'; +export * from './async-hooks-middleware'; export * from './async-hooks-module'; diff --git a/packages/core/injector/injector.ts b/packages/core/injector/injector.ts index 790cd0d8855..1a19926cd65 100644 --- a/packages/core/injector/injector.ts +++ b/packages/core/injector/injector.ts @@ -256,6 +256,7 @@ export class Injector { if (!instanceHost.isResolved && !paramWrapper.forwardRef) { isResolved = false; } + wrapper.addCtorMetadata(index, paramWrapper); return instanceHost && instanceHost.instance; } catch (err) { const isOptional = optionalDependenciesIds.includes(index); @@ -348,10 +349,9 @@ export class Injector { await this.loadProvider(instanceWrapper, module, contextId); } if (instanceWrapper.async) { - instanceWrapper.setInstanceByContextId( - contextId, - await instanceWrapper.getInstanceByContextId(contextId), - ); + const host = instanceWrapper.getInstanceByContextId(contextId); + host.instance = await host.instance; + instanceWrapper.setInstanceByContextId(contextId, host); } return instanceWrapper; } @@ -384,8 +384,8 @@ export class Injector { const instanceWrapper = await this.lookupComponentInImports( module, dependencyContext.name, - [], wrapper, + [], contextId, ); if (isNil(instanceWrapper)) { @@ -401,8 +401,8 @@ export class Injector { public async lookupComponentInImports( module: Module, name: any, - moduleRegistry: any[] = [], wrapper: InstanceWrapper, + moduleRegistry: any[] = [], contextId = STATIC_CONTEXT, ): Promise { let instanceWrapperRef: InstanceWrapper = null; @@ -420,8 +420,8 @@ export class Injector { const instanceRef = await this.lookupComponentInImports( relatedModule, name, - moduleRegistry, wrapper, + moduleRegistry, contextId, ); if (instanceRef) { @@ -481,6 +481,7 @@ export class Injector { if (!paramWrapper) { return undefined; } + wrapper.addPropertiesMetadata(item.key, paramWrapper); const instanceHost = paramWrapper.getInstanceByContextId(contextId); return instanceHost.instance; } catch (err) { @@ -538,7 +539,7 @@ export class Injector { ? Object.assign(targetInstance.instance, new metatype(...instances)) : new metatype(...instances); - if (scope === Scope.LAZY) { + if (scope === Scope.LAZY_ASYNC) { this.mergeAsyncProxy( instances, targetInstance, @@ -552,7 +553,7 @@ export class Injector { ...instances, ); instanceHost.instance = await factoryReturnValue; - if (scope === Scope.LAZY) { + if (scope === Scope.LAZY_ASYNC) { this.mergeAsyncProxy( instances, instanceHost, @@ -574,18 +575,19 @@ export class Injector { metatype: Type, factory: (...intances: any[]) => any, ) { - AsyncContext.instance.set(instanceKey, targetInstance.instance); + const asyncContext = AsyncContext.getInstance(); + asyncContext.set(instanceKey, targetInstance.instance); const proxy = (target: unknown, property: string | number | symbol) => { if (!(property in (target as object))) { return; } - const cachedInstance = AsyncContext.instance.get(instanceKey); + const cachedInstance = asyncContext.get(instanceKey); if (cachedInstance) { return cachedInstance[property]; } const value = factory(...instances); - AsyncContext.instance.set(instanceKey, value); + asyncContext.set(instanceKey, value); return value[property]; }; targetInstance.instance = new Proxy(targetInstance.instance, { diff --git a/packages/core/injector/instance-wrapper.ts b/packages/core/injector/instance-wrapper.ts index 6af7b815a78..5065e9a5eea 100644 --- a/packages/core/injector/instance-wrapper.ts +++ b/packages/core/injector/instance-wrapper.ts @@ -52,7 +52,7 @@ export class InstanceWrapper { } get isNotMetatype(): boolean { - return !!this.metatype; + return !this.metatype; } getInstanceByContextId(contextId: ContextId): InstancePerContext { @@ -96,17 +96,18 @@ export class InstanceWrapper { return false; } const { dependencies, properties } = this[INSTANCE_METADATA_SYMBOL]; + const isStatic = + (dependencies && this.isWrapperStatic(dependencies)) || !dependencies; - const isItemStatic = (item: InstanceWrapper) => - item.isDependencyTreeStatic(); - const isTreeStatic = (tree: InstanceWrapper[]) => tree.every(isItemStatic); + if (!properties || !isStatic) { + return isStatic; + } + const propHosts = properties.map(item => item.wrapper); + return isStatic && this.isWrapperStatic(propHosts); + } - let isStatic = - (dependencies && isTreeStatic(dependencies)) || !dependencies; - isStatic = - isStatic && - ((properties && isTreeStatic(properties as any)) || !properties); - return isStatic; + private isWrapperStatic(tree: InstanceWrapper[]) { + return tree.every((item: InstanceWrapper) => item.isDependencyTreeStatic()); } private initialize( diff --git a/packages/core/injector/module.ts b/packages/core/injector/module.ts index e327dd3895c..61cc116fa24 100644 --- a/packages/core/injector/module.ts +++ b/packages/core/injector/module.ts @@ -419,7 +419,6 @@ export class Module { metatype: controller, instance: null, isResolved: false, - scope: this.getClassScope(controller), host: this, }), ); diff --git a/packages/core/interceptors/interceptors-context-creator.ts b/packages/core/interceptors/interceptors-context-creator.ts index 1e093901fe2..0b291937135 100644 --- a/packages/core/interceptors/interceptors-context-creator.ts +++ b/packages/core/interceptors/interceptors-context-creator.ts @@ -6,7 +6,7 @@ import iterate from 'iterare'; import { ContextCreator } from '../helpers/context-creator'; import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; -import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; +import { InstanceWrapper } from '../injector/instance-wrapper'; export class InterceptorsContextCreator extends ContextCreator { private moduleContext: string; @@ -55,7 +55,7 @@ export class InterceptorsContextCreator extends ContextCreator { public getInterceptorInstance( interceptor: Function | NestInterceptor, - contextId: ContextId, + contextId = STATIC_CONTEXT, ) { const isObject = (interceptor as NestInterceptor).intercept; if (isObject) { diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts index f04b7e1ebdd..8e789477d45 100644 --- a/packages/core/nest-application.ts +++ b/packages/core/nest-application.ts @@ -32,6 +32,7 @@ import { FastifyAdapter } from './adapters/fastify-adapter'; import { ApplicationConfig } from './application-config'; import { MESSAGES } from './constants'; import { NestContainer } from './injector/container'; +import { Injector } from './injector/injector'; import { MiddlewareContainer } from './middleware/container'; import { MiddlewareModule } from './middleware/middleware-module'; import { NestApplicationContext } from './nest-application-context'; @@ -73,7 +74,11 @@ export class NestApplication extends NestApplicationContext this.selectContextModule(); this.registerHttpServer(); - this.routesResolver = new RoutesResolver(this.container, this.config); + this.routesResolver = new RoutesResolver( + this.container, + this.config, + new Injector(), + ); } public getHttpAdapter(): HttpServer { diff --git a/packages/core/pipes/pipes-context-creator.ts b/packages/core/pipes/pipes-context-creator.ts index dba0cfdd41f..c99bd315886 100644 --- a/packages/core/pipes/pipes-context-creator.ts +++ b/packages/core/pipes/pipes-context-creator.ts @@ -9,7 +9,7 @@ import iterate from 'iterare'; import { ApplicationConfig } from '../application-config'; import { ContextCreator } from '../helpers/context-creator'; import { NestContainer } from '../injector/container'; -import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; +import { InstanceWrapper } from '../injector/instance-wrapper'; import { STATIC_CONTEXT } from './../injector/constants'; export class PipesContextCreator extends ContextCreator { @@ -47,7 +47,10 @@ export class PipesContextCreator extends ContextCreator { .toArray() as R; } - public getPipeInstance(pipe: Function | PipeTransform, contextId: ContextId) { + public getPipeInstance( + pipe: Function | PipeTransform, + contextId = STATIC_CONTEXT, + ) { const isObject = (pipe as PipeTransform).transform; if (isObject) { return pipe; diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index a425362f0f4..288f4d032aa 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -1,9 +1,5 @@ -import { HttpServer, Scope } from '@nestjs/common'; -import { - METHOD_METADATA, - PATH_METADATA, - SCOPE_OPTIONS_METADATA, -} from '@nestjs/common/constants'; +import { HttpServer } from '@nestjs/common'; +import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants'; import { RequestMethod } from '@nestjs/common/enums/request-method.enum'; import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface'; import { Type } from '@nestjs/common/interfaces/type.interface'; @@ -18,6 +14,7 @@ import { RouterMethodFactory } from '../helpers/router-method-factory'; import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; import { Injector } from '../injector/injector'; +import { InstanceWrapper } from '../injector/instance-wrapper'; import { InterceptorsConsumer } from '../interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator'; import { MetadataScanner } from '../metadata-scanner'; @@ -40,12 +37,10 @@ export class RouterExplorer { private readonly routerMethodFactory = new RouterMethodFactory(); private readonly logger = new Logger(RouterExplorer.name, true); - // TEMP - private readonly injector = new Injector(); - constructor( private readonly metadataScanner: MetadataScanner, private readonly container: NestContainer, + private readonly injector?: Injector, private readonly routerProxy?: RouterProxy, private readonly exceptionsFilter?: ExceptionsFilter, config?: ApplicationConfig, @@ -63,17 +58,17 @@ export class RouterExplorer { } public explore( - instance: Controller, - metatype: Type, + instanceWrapper: InstanceWrapper, module: string, applicationRef: T, basePath: string, ) { + const { instance } = instanceWrapper; const routerPaths = this.scanForPaths(instance); this.applyPathsToRouterProxy( applicationRef, routerPaths, - instance, + instanceWrapper, module, basePath, ); @@ -135,7 +130,7 @@ export class RouterExplorer { public applyPathsToRouterProxy( router: T, routePaths: RoutePathProperties[], - instance: Controller, + instanceWrapper: InstanceWrapper, module: string, basePath: string, ) { @@ -144,7 +139,7 @@ export class RouterExplorer { this.applyCallbackToRouter( router, pathProperties, - instance, + instanceWrapper, module, basePath, ); @@ -155,7 +150,7 @@ export class RouterExplorer { private applyCallbackToRouter( router: T, pathProperties: RoutePathProperties, - instance: Controller, + instanceWrapper: InstanceWrapper, module: string, basePath: string, ) { @@ -168,8 +163,9 @@ export class RouterExplorer { str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str; const fullPath = stripSlash(basePath) + path; - const classScope = this.getClassScope(instance); - const isRequestScoped = classScope === Scope.REQUEST; + const { instance } = instanceWrapper; + const isRequestScoped = !instanceWrapper.isDependencyTreeStatic(); + if (isRequestScoped) { routerMethod( stripSlash(fullPath) || '/', @@ -231,9 +227,4 @@ export class RouterExplorer { ); return this.routerProxy.createProxy(executionContext, exceptionFilter); } - - private getClassScope(controller: Controller): Scope { - const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, controller); - return metadata && metadata.scope; - } } diff --git a/packages/core/router/routes-resolver.ts b/packages/core/router/routes-resolver.ts index dd145c3cefb..c795a366d8e 100644 --- a/packages/core/router/routes-resolver.ts +++ b/packages/core/router/routes-resolver.ts @@ -6,6 +6,7 @@ import { Logger } from '@nestjs/common/services/logger.service'; import { ApplicationConfig } from '../application-config'; import { CONTROLLER_MAPPING_MESSAGE } from '../helpers/messages'; import { NestContainer } from '../injector/container'; +import { Injector } from '../injector/injector'; import { InstanceWrapper } from '../injector/instance-wrapper'; import { MetadataScanner } from '../metadata-scanner'; import { Resolver } from './interfaces/resolver.interface'; @@ -22,6 +23,7 @@ export class RoutesResolver implements Resolver { constructor( private readonly container: NestContainer, private readonly config: ApplicationConfig, + private readonly injector: Injector, ) { this.routerExceptionsFilter = new RouterExceptionFilters( container, @@ -31,6 +33,7 @@ export class RoutesResolver implements Resolver { this.routerBuilder = new RouterExplorer( new MetadataScanner(), this.container, + this.injector, this.routerProxy, this.routerExceptionsFilter, this.config, @@ -54,14 +57,14 @@ export class RoutesResolver implements Resolver { basePath: string, applicationRef: HttpServer, ) { - routes.forEach(({ instance, metatype }) => { + routes.forEach(instanceWrapper => { + const { metatype } = instanceWrapper; const path = this.routerBuilder.extractRouterPath(metatype, basePath); const controllerName = metatype.name; this.logger.log(CONTROLLER_MAPPING_MESSAGE(controllerName, path)); this.routerBuilder.explore( - instance, - metatype, + instanceWrapper, moduleName, applicationRef, path, diff --git a/packages/core/test/exceptions/base-exception-filter.spec.ts b/packages/core/test/exceptions/base-exception-filter.spec.ts index 1f05c23116a..7a2a618ba8e 100644 --- a/packages/core/test/exceptions/base-exception-filter.spec.ts +++ b/packages/core/test/exceptions/base-exception-filter.spec.ts @@ -1,5 +1,5 @@ -import * as sinon from 'sinon'; import { expect } from 'chai'; +import * as sinon from 'sinon'; import { BaseExceptionFilterContext } from '../../exceptions/base-exception-filter-context'; import { NestContainer } from '../../injector/container'; @@ -23,7 +23,10 @@ describe('BaseExceptionFilterContext', () => { }); describe('when param is a constructor', () => { it('should pick instance from container', () => { - const wrapper = { instance: 'test' }; + const wrapper = { + instance: 'test', + getInstanceByContextId: () => wrapper, + }; sinon.stub(filter, 'getInstanceByMetatype').callsFake(() => wrapper); expect(filter.getFilterInstance(Filter)).to.be.eql(wrapper.instance); }); diff --git a/packages/core/test/guards/guards-context-creator.spec.ts b/packages/core/test/guards/guards-context-creator.spec.ts index 06fd4acb36c..4877189554a 100644 --- a/packages/core/test/guards/guards-context-creator.spec.ts +++ b/packages/core/test/guards/guards-context-creator.spec.ts @@ -1,7 +1,6 @@ -import * as sinon from 'sinon'; import { expect } from 'chai'; +import * as sinon from 'sinon'; import { GuardsContextCreator } from '../../guards/guards-context-creator'; -import { Observable } from 'rxjs'; class Guard {} @@ -18,12 +17,14 @@ describe('GuardsContextCreator', () => { instance: { canActivate: () => true, }, + getInstanceByContextId: () => guards[0], }, { name: 'test2', instance: { canActivate: () => true, }, + getInstanceByContextId: () => guards[1], }, {}, undefined, @@ -69,7 +70,10 @@ describe('GuardsContextCreator', () => { }); describe('when param is a constructor', () => { it('should pick instance from container', () => { - const wrapper = { instance: 'test' }; + const wrapper = { + instance: 'test', + getInstanceByContextId: () => wrapper, + }; sinon .stub(guardsContextCreator, 'getInstanceByMetatype') .callsFake(() => wrapper); diff --git a/packages/core/test/injector/injector.spec.ts b/packages/core/test/injector/injector.spec.ts index d6b85cfdf6d..50b7cdf37fb 100644 --- a/packages/core/test/injector/injector.spec.ts +++ b/packages/core/test/injector/injector.spec.ts @@ -4,8 +4,10 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import { Inject } from '../../../common/decorators/core/inject.decorator'; import { Injectable } from '../../../common/decorators/core/injectable.decorator'; -import { InstanceWrapper, NestContainer } from '../../injector/container'; +import { STATIC_CONTEXT } from '../../injector/constants'; +import { NestContainer } from '../../injector/container'; import { Injector, PropertyDependency } from '../../injector/injector'; +import { InstanceWrapper } from '../../injector/instance-wrapper'; import { Module } from '../../injector/module'; chai.use(chaiAsPromised); @@ -35,24 +37,24 @@ describe('Injector', () => { beforeEach(() => { moduleDeps = new Module(DependencyTwo as any, [], new NestContainer()); - mainTest = { + mainTest = new InstanceWrapper({ name: 'MainTest', metatype: MainTest, instance: Object.create(MainTest.prototype), isResolved: false, - }; - depOne = { + }); + depOne = new InstanceWrapper({ name: 'DependencyOne', metatype: DependencyOne, instance: Object.create(DependencyOne.prototype), isResolved: false, - }; - depTwo = { + }); + depTwo = new InstanceWrapper({ name: 'DependencyTwo', metatype: DependencyTwo, instance: Object.create(DependencyOne.prototype), isResolved: false, - }; + }); moduleDeps.providers.set('MainTest', mainTest); moduleDeps.providers.set('DependencyOne', depOne); moduleDeps.providers.set('DependencyTwo', depTwo); @@ -75,9 +77,9 @@ describe('Injector', () => { it('should set "isResolved" property to true after instance initialization', async () => { await injector.loadInstance(mainTest, moduleDeps.providers, moduleDeps); - const { isResolved } = moduleDeps.providers.get( + const { isResolved } = (moduleDeps.providers.get( 'MainTest', - ) as InstanceWrapper; + ) as InstanceWrapper).getInstanceByContextId(STATIC_CONTEXT); expect(isResolved).to.be.true; }); @@ -89,15 +91,18 @@ describe('Injector', () => { it('should await done$ when "isPending"', async () => { const value = 'test'; + const wrapper = new InstanceWrapper({ + name: 'MainTest', + metatype: MainTest, + instance: Object.create(MainTest.prototype), + isResolved: false, + }); + const host = wrapper.getInstanceByContextId(STATIC_CONTEXT); + host.donePromise = Promise.resolve(value) as any; + host.isPending = true; + const result = await injector.loadInstance( - { - name: 'MainTest', - metatype: MainTest, - instance: Object.create(MainTest.prototype), - isResolved: false, - isPending: true, - done$: Promise.resolve(value) as any, - }, + wrapper, moduleDeps.providers, moduleDeps, ); @@ -107,12 +112,12 @@ describe('Injector', () => { it('should return undefined when metatype is resolved', async () => { const value = 'test'; const result = await injector.loadInstance( - { + new InstanceWrapper({ name: 'MainTestResolved', metatype: MainTest, instance: Object.create(MainTest.prototype), isResolved: true, - }, + }), moduleDeps.providers, moduleDeps, ); @@ -120,7 +125,7 @@ describe('Injector', () => { }); }); - describe('loadPrototypeOfInstance', () => { + describe('loadPrototype', () => { @Injectable() class Test {} @@ -129,24 +134,20 @@ describe('Injector', () => { beforeEach(() => { moduleDeps = new Module(Test as any, [], new NestContainer()); - test = { + test = new InstanceWrapper({ name: 'Test', metatype: Test, - instance: Object.create(Test.prototype), + instance: null, isResolved: false, - }; + }); moduleDeps.providers.set('Test', test); }); it('should create prototype of instance', () => { - const expectedResult = { - instance: Object.create(Test.prototype), - isResolved: false, - metatype: Test, - name: 'Test', - }; injector.loadPrototype(test, moduleDeps.providers); - expect(moduleDeps.providers.get('Test')).to.deep.equal(expectedResult); + expect(moduleDeps.providers.get('Test').instance).to.deep.equal( + Object.create(Test.prototype), + ); }); it('should return null when collection is nil', () => { @@ -156,7 +157,7 @@ describe('Injector', () => { it('should return null when target isResolved', () => { const collection = { - get: () => ({ isResolved: true }), + get: () => ({ getInstanceByContextId: () => ({ isResolved: true }) }), }; const result = injector.loadPrototype(test, collection as any); expect(result).to.be.null; @@ -164,7 +165,7 @@ describe('Injector', () => { it('should return null when "inject" is not nil', () => { const collection = { - get: () => ({ inject: [] }), + get: () => new InstanceWrapper({ inject: [] }), }; const result = injector.loadPrototype(test, collection as any); expect(result).to.be.null; @@ -184,7 +185,7 @@ describe('Injector', () => { }); }); - describe('loadInstanceOfMiddleware', () => { + describe('loadMiddleware', () => { let resolveConstructorParams: sinon.SinonSpy; beforeEach(() => { @@ -225,7 +226,7 @@ describe('Injector', () => { }); }); - describe('loadInstanceOfController', () => { + describe('loadController', () => { let loadInstance: sinon.SinonSpy; beforeEach(() => { @@ -243,7 +244,7 @@ describe('Injector', () => { }); }); - describe('loadInstanceOfInjectable', () => { + describe('loadInjectable', () => { let loadInstance: sinon.SinonSpy; beforeEach(() => { @@ -353,6 +354,7 @@ describe('Injector', () => { const result = await injector.lookupComponentInImports( module as any, null, + null, ); expect(result).to.be.eq(null); }); @@ -374,7 +376,7 @@ describe('Injector', () => { ] as any), }; expect( - injector.lookupComponentInImports(module as any, metatype as any), + injector.lookupComponentInImports(module as any, metatype as any, null), ).to.be.eventually.eq(null); module = { @@ -393,31 +395,36 @@ describe('Injector', () => { ] as any), }; expect( - injector.lookupComponentInImports(module as any, metatype as any), + injector.lookupComponentInImports(module as any, metatype as any, null), ).to.eventually.be.eq(null); }); it('should call "loadProvider" when component is not resolved', async () => { const module = { - relatedModules: new Map([ + imports: new Map([ [ 'key', { providers: { has: () => true, - get: () => ({ - isResolved: false, - }), + get: () => + new InstanceWrapper({ + isResolved: false, + }), }, exports: { has: () => true, }, - relatedModules: new Map(), + imports: new Map(), }, ], ] as any), }; - await injector.lookupComponentInImports(module as any, metatype as any); + await injector.lookupComponentInImports( + module as any, + metatype as any, + null, + ); expect(loadProvider.called).to.be.true; }); @@ -441,7 +448,11 @@ describe('Injector', () => { ], ] as any), }; - await injector.lookupComponentInImports(module as any, metatype as any); + await injector.lookupComponentInImports( + module as any, + metatype as any, + null, + ); expect(loadProvider.called).to.be.false; }); }); @@ -498,7 +509,9 @@ describe('Injector', () => { const loadStub = sinon .stub(injector, 'loadProvider') .callsFake(() => null); - sinon.stub(injector, 'lookupComponent').returns({ isResolved: false }); + sinon + .stub(injector, 'lookupComponent') + .returns(new InstanceWrapper({ isResolved: false })); await injector.resolveComponentInstance( module, @@ -512,7 +525,9 @@ describe('Injector', () => { const loadStub = sinon .stub(injector, 'loadProvider') .callsFake(() => null); - sinon.stub(injector, 'lookupComponent').returns({ isResolved: true }); + sinon + .stub(injector, 'lookupComponent') + .returns(new InstanceWrapper({ isResolved: true })); await injector.resolveComponentInstance( module, @@ -528,7 +543,9 @@ describe('Injector', () => { .callsFake(() => null); sinon .stub(injector, 'lookupComponent') - .returns({ isResolved: false, forwardRef: true }); + .returns( + new InstanceWrapper({ isResolved: false, forwardRef: true }), + ); await injector.resolveComponentInstance( module, @@ -547,12 +564,14 @@ describe('Injector', () => { .callsFake(() => null); const instance = Promise.resolve(true); - sinon.stub(injector, 'lookupComponent').returns({ - isResolved: false, - forwardRef: true, - async: true, - instance, - }); + sinon.stub(injector, 'lookupComponent').returns( + new InstanceWrapper({ + isResolved: false, + forwardRef: true, + async: true, + instance, + }), + ); const result = await injector.resolveComponentInstance( module, '', diff --git a/packages/core/test/injector/instance-loader.spec.ts b/packages/core/test/injector/instance-loader.spec.ts index 1456bda2631..26e6dae9158 100644 --- a/packages/core/test/injector/instance-loader.spec.ts +++ b/packages/core/test/injector/instance-loader.spec.ts @@ -7,6 +7,7 @@ import { Logger } from '../../../common/services/logger.service'; import { NestContainer } from '../../injector/container'; import { Injector } from '../../injector/injector'; import { InstanceLoader } from '../../injector/instance-loader'; +import { InstanceWrapper } from '../../injector/instance-wrapper'; describe('InstanceLoader', () => { let loader: InstanceLoader; @@ -71,12 +72,11 @@ describe('InstanceLoader', () => { injectables: new Map(), metatype: { name: 'test' }, }; - const testComp = { + const testComp = new InstanceWrapper({ instance: null, metatype: TestProvider, name: 'TestProvider', - }; - + }); module.providers.set('TestProvider', testComp); const modules = new Map(); @@ -102,7 +102,11 @@ describe('InstanceLoader', () => { injectables: new Map(), metatype: { name: 'test' }, }; - const wrapper = { name: 'TestRoute', instance: null, metatype: TestRoute }; + const wrapper = new InstanceWrapper({ + name: 'TestRoute', + instance: null, + metatype: TestRoute, + }); module.controllers.set('TestRoute', wrapper); const modules = new Map(); @@ -128,11 +132,11 @@ describe('InstanceLoader', () => { injectables: new Map(), metatype: { name: 'test' }, }; - const testComp = { + const testComp = new InstanceWrapper({ instance: null, metatype: TestProvider, name: 'TestProvider', - }; + }); module.injectables.set('TestProvider', testComp); const modules = new Map(); diff --git a/packages/core/test/injector/module.spec.ts b/packages/core/test/injector/module.spec.ts index d8fcc6d7ef1..26bf8797251 100644 --- a/packages/core/test/injector/module.spec.ts +++ b/packages/core/test/injector/module.spec.ts @@ -6,6 +6,7 @@ import { RuntimeException } from '../../errors/exceptions/runtime.exception'; import { UnknownElementException } from '../../errors/exceptions/unknown-element.exception'; import { UnknownExportException } from '../../errors/exceptions/unknown-export.exception'; import { NestContainer } from '../../injector/container'; +import { InstanceWrapper } from '../../injector/instance-wrapper'; import { Module } from '../../injector/module'; describe('Module', () => { @@ -32,12 +33,14 @@ describe('Module', () => { module.addController(Test); expect(setSpy.getCall(0).args).to.deep.equal([ 'Test', - { + new InstanceWrapper({ + host: module, name: 'Test', + scope: 0, metatype: Test, instance: null, isResolved: false, - }, + }), ]); }); @@ -49,12 +52,14 @@ describe('Module', () => { module.addInjectable(TestProvider); expect(setSpy.getCall(0).args).to.deep.equal([ 'TestProvider', - { + new InstanceWrapper({ + host: module, name: 'TestProvider', + scope: undefined, metatype: TestProvider, instance: null, isResolved: false, - }, + }), ]); }); @@ -75,12 +80,14 @@ describe('Module', () => { module.addProvider(TestProvider); expect(setSpy.getCall(0).args).to.deep.equal([ 'TestProvider', - { + new InstanceWrapper({ + host: module, name: 'TestProvider', + scope: undefined, metatype: TestProvider, instance: null, isResolved: false, - }, + }), ]); }); @@ -137,12 +144,17 @@ describe('Module', () => { it('should store provider', () => { module.addCustomClass(provider as any, (module as any)._providers); expect( - setSpy.calledWith(provider.name, { - name: provider.name, - metatype: type, - instance: null, - isResolved: false, - }), + setSpy.calledWith( + provider.name, + new InstanceWrapper({ + host: module, + name: provider.name, + scope: undefined, + metatype: type as any, + instance: null, + isResolved: false, + }), + ), ).to.be.true; }); }); @@ -162,14 +174,18 @@ describe('Module', () => { it('should store provider', () => { module.addCustomValue(provider as any, (module as any)._providers); expect( - setSpy.calledWith(name, { + setSpy.calledWith( name, - metatype: null, - instance: value, - isResolved: true, - isNotMetatype: true, - async: false, - }), + new InstanceWrapper({ + host: module, + name, + scope: undefined, + metatype: null, + instance: value, + isResolved: true, + async: false, + }), + ), ).to.be.true; }); }); @@ -189,14 +205,14 @@ describe('Module', () => { module.addCustomFactory(provider as any, (module as any)._providers); expect(setSpy.getCall(0).args).to.deep.equal([ provider.name, - { + new InstanceWrapper({ name: provider.name, - metatype: type, + scope: undefined, + metatype: type as any, instance: null, isResolved: false, - inject, - isNotMetatype: true, - }, + inject: inject as any, + }), ]); }); }); diff --git a/packages/core/test/middleware/middlewares-module.spec.ts b/packages/core/test/middleware/middlewares-module.spec.ts index b09899456b6..f5655080a0d 100644 --- a/packages/core/test/middleware/middlewares-module.spec.ts +++ b/packages/core/test/middleware/middlewares-module.spec.ts @@ -10,6 +10,7 @@ import { ApplicationConfig } from '../../application-config'; import { InvalidMiddlewareException } from '../../errors/exceptions/invalid-middleware.exception'; import { RuntimeException } from '../../errors/exceptions/runtime.exception'; import { NestContainer } from '../../injector/container'; +import { InstanceWrapper } from '../../injector/instance-wrapper'; import { MiddlewareBuilder } from '../../middleware/builder'; import { MiddlewareContainer } from '../../middleware/container'; import { MiddlewareModule } from '../../middleware/middleware-module'; @@ -142,10 +143,13 @@ describe('MiddlewareModule', () => { container.addConfig([configuration], moduleKey); const instance = new TestMiddleware(); - container.getMiddleware(moduleKey).set('TestMiddleware', { - metatype: TestMiddleware, - instance, - }); + container.getMiddleware(moduleKey).set( + 'TestMiddleware', + new InstanceWrapper({ + metatype: TestMiddleware, + instance, + }), + ); middlewareModule.registerRouteMiddleware( container, diff --git a/packages/core/test/router/routes-resolver.spec.ts b/packages/core/test/router/routes-resolver.spec.ts index 3a7b8a3205a..c3f460a98e3 100644 --- a/packages/core/test/router/routes-resolver.spec.ts +++ b/packages/core/test/router/routes-resolver.spec.ts @@ -5,6 +5,7 @@ import { Controller } from '../../../common/decorators/core/controller.decorator import { Get } from '../../../common/decorators/http/request-mapping.decorator'; import { ExpressAdapter } from '../../adapters/express-adapter'; import { ApplicationConfig } from '../../application-config'; +import { Injector } from '../../injector/injector'; import { RoutesResolver } from '../../router/routes-resolver'; describe('RoutesResolver', () => { @@ -41,7 +42,11 @@ describe('RoutesResolver', () => { }); beforeEach(() => { - routesResolver = new RoutesResolver(container, new ApplicationConfig()); + routesResolver = new RoutesResolver( + container, + new ApplicationConfig(), + new Injector(), + ); }); describe('registerRouters', () => { diff --git a/packages/core/test/scanner.spec.ts b/packages/core/test/scanner.spec.ts index 9dfffa9ef0a..66457904d4d 100644 --- a/packages/core/test/scanner.spec.ts +++ b/packages/core/test/scanner.spec.ts @@ -155,18 +155,18 @@ describe('DependenciesScanner', () => { }); }); - describe('insertRelatedModule', () => { + describe('insertImport', () => { it('should call forwardRef() when forwardRef property exists', async () => { const module = { forwardRef: sinon.stub().returns({}) }; sinon.stub(container, 'addRelatedModule').returns({}); - await scanner.insertRelatedModule(module as any, [] as any, 'test'); + await scanner.insertImport(module as any, [] as any, 'test'); expect(module.forwardRef.called).to.be.true; }); describe('when "related" is nil', () => { it('should throw exception', () => { expect( - scanner.insertRelatedModule(undefined, [] as any, 'test'), + scanner.insertImport(undefined, [] as any, 'test'), ).to.eventually.throws(); }); }); diff --git a/packages/microservices/interfaces/client-metadata.interface.ts b/packages/microservices/interfaces/client-metadata.interface.ts index de8edf4c644..b3c294852a2 100644 --- a/packages/microservices/interfaces/client-metadata.interface.ts +++ b/packages/microservices/interfaces/client-metadata.interface.ts @@ -1,10 +1,9 @@ import { Transport } from './../enums/transport.enum'; import { - TcpOptions, - RedisOptions, - NatsOptions, - MqttOptions, GrpcOptions, + MqttOptions, + NatsOptions, + RedisOptions, RmqOptions, } from './microservice-configuration.interface'; diff --git a/packages/microservices/listener-metadata-explorer.ts b/packages/microservices/listener-metadata-explorer.ts index ce659a363b9..f19f751e208 100644 --- a/packages/microservices/listener-metadata-explorer.ts +++ b/packages/microservices/listener-metadata-explorer.ts @@ -17,6 +17,7 @@ export interface ClientProperties { export interface PatternProperties { pattern: PatternMetadata; + methodKey: string; targetCallback: (...args: any[]) => any; } @@ -36,9 +37,9 @@ export class ListenerMetadataExplorer { public exploreMethodMetadata( instance: object, instancePrototype: any, - methodName: string, + methodKey: string, ): PatternProperties { - const targetCallback = instancePrototype[methodName]; + const targetCallback = instancePrototype[methodKey]; const isPattern = Reflect.getMetadata( PATTERN_HANDLER_METADATA, targetCallback, @@ -49,6 +50,7 @@ export class ListenerMetadataExplorer { } const pattern = Reflect.getMetadata(PATTERN_METADATA, targetCallback); return { + methodKey, targetCallback, pattern, }; diff --git a/packages/microservices/listeners-controller.ts b/packages/microservices/listeners-controller.ts index 6115ea502cf..e82dbe317c2 100644 --- a/packages/microservices/listeners-controller.ts +++ b/packages/microservices/listeners-controller.ts @@ -1,11 +1,14 @@ import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface'; -import { ListenerMetadataExplorer } from './listener-metadata-explorer'; -import { Server } from './server/server'; -import { ClientProxyFactory } from './client/client-proxy-factory'; +import { NestContainer } from '@nestjs/core/injector/container'; +import { Injector } from '@nestjs/core/injector/injector'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { MetadataScanner } from '@nestjs/core/metadata-scanner'; -import { CustomTransportStrategy } from './interfaces'; +import { ClientProxyFactory } from './client/client-proxy-factory'; import { ClientsContainer } from './container'; import { RpcContextCreator } from './context/rpc-context-creator'; +import { CustomTransportStrategy } from './interfaces'; +import { ListenerMetadataExplorer } from './listener-metadata-explorer'; +import { Server } from './server/server'; export class ListenersController { private readonly metadataExplorer = new ListenerMetadataExplorer( @@ -15,21 +18,43 @@ export class ListenersController { constructor( private readonly clientsContainer: ClientsContainer, private readonly contextCreator: RpcContextCreator, + private readonly container: NestContainer, + private readonly injector: Injector, ) {} public bindPatternHandlers( - instance: Controller, + instanceWrapper: InstanceWrapper, server: Server & CustomTransportStrategy, - module: string, + moduleKey: string, ) { + const { instance } = instanceWrapper; + const isStatic = instanceWrapper.isDependencyTreeStatic(); const patternHandlers = this.metadataExplorer.explore(instance); - patternHandlers.forEach(({ pattern, targetCallback }) => { - const proxy = this.contextCreator.create( - instance, - targetCallback, - module, - ); - server.addHandler(pattern, proxy); + + patternHandlers.forEach(({ pattern, targetCallback, methodKey }) => { + if (isStatic) { + const proxy = this.contextCreator.create( + instance, + targetCallback, + moduleKey, + ); + return server.addHandler(pattern, proxy); + } + server.addHandler(pattern, data => { + const contextId = { id: 1 }; // async id + const contextInstance = this.injector.loadControllerPerContext( + instance, + this.container.getModules(), + moduleKey, + contextId, + ); + const proxy = this.contextCreator.create( + contextInstance, + contextInstance[methodKey], + moduleKey, + ); + return proxy(data); + }); }); } diff --git a/packages/microservices/microservices-module.ts b/packages/microservices/microservices-module.ts index 4fc1021635e..f9c2deec850 100644 --- a/packages/microservices/microservices-module.ts +++ b/packages/microservices/microservices-module.ts @@ -4,6 +4,7 @@ import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.excepti import { GuardsConsumer } from '@nestjs/core/guards/guards-consumer'; import { GuardsContextCreator } from '@nestjs/core/guards/guards-context-creator'; import { NestContainer } from '@nestjs/core/injector/container'; +import { Injector } from '@nestjs/core/injector/injector'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator'; @@ -35,6 +36,8 @@ export class MicroservicesModule { this.listenersController = new ListenersController( this.clientsContainer, contextCreator, + container, + new Injector(), ); } @@ -67,8 +70,8 @@ export class MicroservicesModule { server: Server & CustomTransportStrategy, module: string, ) { - controllers.forEach(({ instance }) => - this.listenersController.bindPatternHandlers(instance, server, module), + controllers.forEach(wrapper => + this.listenersController.bindPatternHandlers(wrapper, server, module), ); } diff --git a/packages/microservices/test/listeners-controller.spec.ts b/packages/microservices/test/listeners-controller.spec.ts index c4d23f7c387..682787012e6 100644 --- a/packages/microservices/test/listeners-controller.spec.ts +++ b/packages/microservices/test/listeners-controller.spec.ts @@ -1,10 +1,12 @@ -import * as sinon from 'sinon'; +import { NestContainer } from '@nestjs/core/injector/container'; +import { Injector } from '@nestjs/core/injector/injector'; import { expect } from 'chai'; -import { ListenersController } from '../listeners-controller'; -import { ListenerMetadataExplorer } from '../listener-metadata-explorer'; +import * as sinon from 'sinon'; import { MetadataScanner } from '../../core/metadata-scanner'; import { ClientsContainer } from '../container'; import { RpcContextCreator } from '../context/rpc-context-creator'; +import { ListenerMetadataExplorer } from '../listener-metadata-explorer'; +import { ListenersController } from '../listeners-controller'; describe('ListenersController', () => { let instance: ListenersController, @@ -21,6 +23,8 @@ describe('ListenersController', () => { instance = new ListenersController( new ClientsContainer(), sinon.createStubInstance(RpcContextCreator) as any, + new NestContainer(), + new Injector(), ); (instance as any).metadataExplorer = metadataExplorer; addSpy = sinon.spy(); diff --git a/sample/01-cats-app/src/cats/cats.controller.ts b/sample/01-cats-app/src/cats/cats.controller.ts index 52c75814482..b02e8d7fcc7 100644 --- a/sample/01-cats-app/src/cats/cats.controller.ts +++ b/sample/01-cats-app/src/cats/cats.controller.ts @@ -1,37 +1,26 @@ import { Body, - Catch, Controller, Get, Param, Post, - Scope, UseGuards, UseInterceptors, } from '@nestjs/common'; -import { AsyncContext } from '@nestjs/core/hooks/async-context'; import { Roles } from '../common/decorators/roles.decorator'; import { RolesGuard } from '../common/guards/roles.guard'; import { LoggingInterceptor } from '../common/interceptors/logging.interceptor'; import { TransformInterceptor } from '../common/interceptors/transform.interceptor'; import { ParseIntPipe } from '../common/pipes/parse-int.pipe'; -import { CatsService, Rawr } from './cats.service'; +import { CatsService } from './cats.service'; import { CreateCatDto } from './dto/create-cat.dto'; import { Cat } from './interfaces/cat.interface'; -@Catch() -@Controller('cats', { - scope: Scope.REQUEST, -}) + +@Controller('cats') @UseGuards(RolesGuard) @UseInterceptors(LoggingInterceptor, TransformInterceptor) export class CatsController { - constructor( - private readonly catsService: CatsService, - private readonly asyncContext: AsyncContext, - private readonly rawr: Rawr, - ) { - console.log('Cats controller has been created (request)'); - } + constructor(private readonly catsService: CatsService) {} @Post() @Roles('admin') @@ -41,12 +30,6 @@ export class CatsController { @Get() async findAll(): Promise { - const random = Math.random(); - console.log(random, this.rawr); - console.log( - ((this.asyncContext as any).internalStorage as Map).size, - ); - this.asyncContext.set('xd', random); return this.catsService.findAll(); } diff --git a/sample/01-cats-app/src/cats/cats.module.ts b/sample/01-cats-app/src/cats/cats.module.ts index 1d1192240f7..f3291c7d11e 100644 --- a/sample/01-cats-app/src/cats/cats.module.ts +++ b/sample/01-cats-app/src/cats/cats.module.ts @@ -1,34 +1,9 @@ -import { MiddlewareConsumer, Module, Scope } from '@nestjs/common'; -import { AsyncContext } from '@nestjs/core/hooks/async-context'; +import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; -import { CatsService, Rawr } from './cats.service'; +import { CatsService } from './cats.service'; -export class Boom { - boom() { - return 'bum'; - } -} @Module({ controllers: [CatsController], - providers: [ - CatsService, - Rawr, - { - provide: Boom, - useFactory: () => { - console.log('Boom has been created (lazy)'); - return new Boom(); - }, - scope: Scope.LAZY, - }, - ], + providers: [CatsService], }) -export class CatsModule { - constructor(private readonly asyncContext: AsyncContext) {} - - configure(consumer: MiddlewareConsumer) { - consumer - .apply((req, res, next) => this.asyncContext.run(next)) - .forRoutes('*'); - } -} +export class CatsModule {} diff --git a/sample/01-cats-app/src/cats/cats.service.ts b/sample/01-cats-app/src/cats/cats.service.ts index 18cee0d82fb..2619cd7176d 100644 --- a/sample/01-cats-app/src/cats/cats.service.ts +++ b/sample/01-cats-app/src/cats/cats.service.ts @@ -1,35 +1,15 @@ -import { Inject, Injectable, Scope } from '@nestjs/common'; -import { AsyncContext } from '@nestjs/core/hooks/async-context'; -import { Boom } from './cats.module'; +import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @Injectable() -export class Rawr { - constructor() { - console.log('rawr created (transient)'); - } -} -@Injectable({ scope: Scope.REQUEST }) export class CatsService { private readonly cats: Cat[] = []; - constructor( - private readonly asyncContext: AsyncContext, - @Inject('Boom') private readonly instance: Boom, - private readonly rawr: Rawr, - ) { - console.log('CatsService has been created'); - } - create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { - console.log(this.rawr); - this.instance.boom(); - - console.log(this.asyncContext.get('xd')); return this.cats; } } diff --git a/sample/01-cats-app/src/common/guards/roles.guard.ts b/sample/01-cats-app/src/common/guards/roles.guard.ts index 778cacfb4ce..0313765a107 100644 --- a/sample/01-cats-app/src/common/guards/roles.guard.ts +++ b/sample/01-cats-app/src/common/guards/roles.guard.ts @@ -1,11 +1,9 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -@Injectable({ dynamic: true }) +@Injectable() export class RolesGuard implements CanActivate { - constructor(private readonly reflector: Reflector) { - console.log('guard'); - } + constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const roles = this.reflector.get('roles', context.getHandler()); From 2e992d39eae3fda6e593e6809689098d969118b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Tue, 18 Dec 2018 16:51:47 +0100 Subject: [PATCH 6/9] tests() fix unit tests, update enhancers --- packages/core/injector/container.ts | 13 +- packages/core/injector/injector.ts | 109 ++++++----------- packages/core/injector/instance-wrapper.ts | 28 ++++- packages/core/injector/module.ts | 30 +++-- packages/core/middleware/container.ts | 43 ++++--- packages/core/middleware/middleware-module.ts | 113 ++++++++++-------- packages/core/middleware/resolver.ts | 4 +- packages/core/nest-application-context.ts | 3 + packages/core/nest-application.ts | 4 +- .../core/router/router-execution-context.ts | 2 +- packages/core/router/router-explorer.ts | 14 ++- packages/core/scanner.ts | 16 ++- packages/core/test/injector/container.spec.ts | 2 +- packages/core/test/injector/module.spec.ts | 4 +- .../interceptors-context-creator.spec.ts | 11 +- .../core/test/middleware/container.spec.ts | 21 ++-- .../middleware/middlewares-module.spec.ts | 41 ++++--- .../core/test/middleware/resolver.spec.ts | 2 +- .../test/pipes/pipes-context-creator.spec.ts | 5 +- .../core/test/router/routes-resolver.spec.ts | 41 +++---- packages/core/test/scanner.spec.ts | 10 +- .../client/client-proxy-factory.ts | 6 +- packages/microservices/client/index.ts | 2 +- .../microservices/listeners-controller.ts | 24 +++- .../microservices/microservices-module.ts | 2 + .../test/listeners-controller.spec.ts | 14 ++- .../test/listeners-metadata-explorer.spec.ts | 16 +-- 27 files changed, 329 insertions(+), 251 deletions(-) diff --git a/packages/core/injector/container.ts b/packages/core/injector/container.ts index 442d8a5d7ea..70a31f8a107 100644 --- a/packages/core/injector/container.ts +++ b/packages/core/injector/container.ts @@ -1,5 +1,6 @@ import { DynamicModule } from '@nestjs/common'; import { GLOBAL_MODULE_METADATA } from '@nestjs/common/constants'; +import { Injectable } from '@nestjs/common/interfaces/injectable.interface'; import { Type } from '@nestjs/common/interfaces/type.interface'; import { ApplicationConfig } from '../application-config'; import { CircularDependencyException } from '../errors/exceptions/circular-dependency.exception'; @@ -101,6 +102,10 @@ export class NestContainer { return this.modules; } + public getModuleByKey(moduleKey: string): Module { + return this.modulesContainer.get(moduleKey); + } + public async addImport( relatedModule: Type | DynamicModule, token: string, @@ -130,12 +135,16 @@ export class NestContainer { return module.addProvider(provider); } - public addInjectable(injectable: Type, token: string) { + public addInjectable( + injectable: Type, + token: string, + host: Type, + ) { if (!this.modules.has(token)) { throw new UnknownModuleException(); } const module = this.modules.get(token); - module.addInjectable(injectable); + module.addInjectable(injectable, host); } public addExportedProvider(provider: Type, token: string) { diff --git a/packages/core/injector/injector.ts b/packages/core/injector/injector.ts index 1a19926cd65..1c0d348bcac 100644 --- a/packages/core/injector/injector.ts +++ b/packages/core/injector/injector.ts @@ -1,4 +1,3 @@ -import { Scope } from '@nestjs/common'; import { OPTIONAL_DEPS_METADATA, OPTIONAL_PROPERTY_DEPS_METADATA, @@ -18,7 +17,6 @@ import { import { RuntimeException } from '../errors/exceptions/runtime.exception'; import { UndefinedDependencyException } from '../errors/exceptions/undefined-dependency.exception'; import { UnknownDependenciesException } from '../errors/exceptions/unknown-dependencies.exception'; -import { AsyncContext } from './../hooks'; import { STATIC_CONTEXT } from './constants'; import { ContextId, @@ -26,7 +24,6 @@ import { InstanceWrapper, } from './instance-wrapper'; import { Module } from './module'; -import { ModulesContainer } from './modules-container'; /** * The type of an injectable dependency @@ -75,17 +72,14 @@ export class Injector { contextId = STATIC_CONTEXT, ) { const { metatype } = wrapper; - const targetMetatype = collection.get(metatype.name); - if (targetMetatype.instance !== null) { + const targetWrapper = collection.get(metatype.name); + if (targetWrapper.instance !== null) { return; } const loadInstance = (instances: any[]) => { - const instanceWrapper = new InstanceWrapper({ - instance: new metatype(...instances), - metatype, - host: module, - }); - collection.set(metatype.name, instanceWrapper); + targetWrapper.instance = targetWrapper.isDependencyTreeStatic() + ? new metatype(...instances) + : Object.create(metatype); }; await this.resolveConstructorParams( wrapper, @@ -199,7 +193,6 @@ export class Injector { instances, wrapper, targetWrapper, - module.id, contextId, ); this.applyProperties(instance, properties); @@ -526,89 +519,55 @@ export class Injector { instances: any[], wrapper: InstanceWrapper, targetMetatype: InstanceWrapper, - moduleId: string, contextId = STATIC_CONTEXT, ): Promise { - const { metatype, inject, name, scope } = wrapper; + const { metatype, inject } = wrapper; const instanceHost = targetMetatype.getInstanceByContextId(contextId); - const instanceKey = moduleId + name; + const isDependencyTreeStatic = wrapper.isDependencyTreeStatic(); + const isInContext = + (isDependencyTreeStatic && contextId === STATIC_CONTEXT) || + (!isDependencyTreeStatic && contextId !== STATIC_CONTEXT); - if (isNil(inject)) { + if (isNil(inject) && isInContext) { const targetInstance = wrapper.getInstanceByContextId(contextId); + targetInstance.instance = wrapper.forwardRef ? Object.assign(targetInstance.instance, new metatype(...instances)) : new metatype(...instances); - - if (scope === Scope.LAZY_ASYNC) { - this.mergeAsyncProxy( - instances, - targetInstance, - instanceKey, - metatype, - (...args: any[]) => new metatype(...args), - ); - } - } else { + } else if (isInContext) { const factoryReturnValue = ((targetMetatype.metatype as any) as Function)( ...instances, ); instanceHost.instance = await factoryReturnValue; - if (scope === Scope.LAZY_ASYNC) { - this.mergeAsyncProxy( - instances, - instanceHost, - instanceKey, - metatype, - (...args: any[]) => - ((targetMetatype.metatype as any) as Function)(...args), - ); - } } instanceHost.isResolved = true; return instanceHost.instance; } - mergeAsyncProxy( - instances: any[], - targetInstance: InstancePerContext, - instanceKey: string, - metatype: Type, - factory: (...intances: any[]) => any, - ) { - const asyncContext = AsyncContext.getInstance(); - asyncContext.set(instanceKey, targetInstance.instance); - - const proxy = (target: unknown, property: string | number | symbol) => { - if (!(property in (target as object))) { - return; - } - const cachedInstance = asyncContext.get(instanceKey); - if (cachedInstance) { - return cachedInstance[property]; - } - const value = factory(...instances); - asyncContext.set(instanceKey, value); - return value[property]; - }; - targetInstance.instance = new Proxy(targetInstance.instance, { - get: proxy, - set: proxy, - }); - } - - async loadControllerPerContext( - instance: Controller, - modulesContainer: ModulesContainer, - moduleKey: string, + async loadPerContext( + instance: T, + module: Module, + collection: Map, ctx: ContextId, ): Promise { - const module = modulesContainer.get(moduleKey); + const wrapper = collection.get( + instance.constructor && instance.constructor.name, + ); + await this.loadInstance(wrapper, collection, module, ctx); + await this.loadEnhancersPerContext(wrapper, module, ctx); - const { controllers } = module; - const controller = controllers.get(instance.constructor.name); - await this.loadInstance(controller, controllers, module, ctx); + const host = wrapper.getInstanceByContextId(ctx); + return host && (host.instance as T); + } - const wrapper = controller.getInstanceByContextId(ctx); - return wrapper && (wrapper.instance as T); + async loadEnhancersPerContext( + wrapper: InstanceWrapper, + module: Module, + ctx: ContextId, + ) { + const enhancers = wrapper.getEnhancersMetadata(); + const loadEnhancer = (item: InstanceWrapper) => + this.loadInstance(item, module.injectables, module, ctx); + await Promise.all(enhancers.map(loadEnhancer)); } } diff --git a/packages/core/injector/instance-wrapper.ts b/packages/core/injector/instance-wrapper.ts index 5065e9a5eea..ad42a95e389 100644 --- a/packages/core/injector/instance-wrapper.ts +++ b/packages/core/injector/instance-wrapper.ts @@ -19,9 +19,10 @@ export interface PropertyMetadata { wrapper: InstanceWrapper; } -export interface InstanceMetadataStore { +interface InstanceMetadataStore { dependencies?: InstanceWrapper[]; properties?: PropertyMetadata[]; + enhancers?: InstanceWrapper[]; } export class InstanceWrapper { @@ -91,19 +92,36 @@ export class InstanceWrapper { return this[INSTANCE_METADATA_SYMBOL].properties; } + addEnhancerMetadata(wrapper: InstanceWrapper) { + if (!this[INSTANCE_METADATA_SYMBOL].enhancers) { + this[INSTANCE_METADATA_SYMBOL].enhancers = []; + } + this[INSTANCE_METADATA_SYMBOL].enhancers.push(wrapper); + } + + getEnhancersMetadata(): InstanceWrapper[] { + return this[INSTANCE_METADATA_SYMBOL].enhancers; + } + isDependencyTreeStatic(): boolean { if (this.scope === Scope.REQUEST) { return false; } - const { dependencies, properties } = this[INSTANCE_METADATA_SYMBOL]; - const isStatic = + const { dependencies, properties, enhancers } = this[ + INSTANCE_METADATA_SYMBOL + ]; + let isStatic = (dependencies && this.isWrapperStatic(dependencies)) || !dependencies; if (!properties || !isStatic) { return isStatic; } - const propHosts = properties.map(item => item.wrapper); - return isStatic && this.isWrapperStatic(propHosts); + const propertiesHosts = properties.map(item => item.wrapper); + isStatic = isStatic && this.isWrapperStatic(propertiesHosts); + if (!enhancers || !isStatic) { + return isStatic; + } + return this.isWrapperStatic(enhancers); } private isWrapperStatic(tree: InstanceWrapper[]) { diff --git a/packages/core/injector/module.ts b/packages/core/injector/module.ts index 61cc116fa24..2512742f7df 100644 --- a/packages/core/injector/module.ts +++ b/packages/core/injector/module.ts @@ -233,21 +233,27 @@ export class Module { ); } - public addInjectable(injectable: Type) { + public addInjectable( + injectable: Type, + host?: Type, + ) { if (this.isCustomProvider(injectable)) { return this.addCustomProvider(injectable, this._injectables); } - this._injectables.set( - injectable.name, - new InstanceWrapper({ - name: injectable.name, - metatype: injectable, - instance: null, - isResolved: false, - scope: this.getClassScope(injectable), - host: this, - }), - ); + const instanceWrapper = new InstanceWrapper({ + name: injectable.name, + metatype: injectable, + instance: null, + isResolved: false, + scope: this.getClassScope(injectable), + host: this, + }); + this._injectables.set(injectable.name, instanceWrapper); + + if (host) { + const hostWrapper = this._controllers.get(host && host.name); + hostWrapper && hostWrapper.addEnhancerMetadata(instanceWrapper); + } } public addProvider(provider: ProviderMetatype): string { diff --git a/packages/core/middleware/container.ts b/packages/core/middleware/container.ts index 4d5b9aecd29..d16188af2ff 100644 --- a/packages/core/middleware/container.ts +++ b/packages/core/middleware/container.ts @@ -1,3 +1,5 @@ +import { Scope, Type } from '@nestjs/common'; +import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants'; import { MiddlewareConfiguration } from '@nestjs/common/interfaces/middleware/middleware-configuration.interface'; import { InstanceWrapper } from './../injector/instance-wrapper'; @@ -8,46 +10,51 @@ export class MiddlewareContainer { Set >(); - public getMiddleware(module: string): Map { + public getMiddlewareCollection(module: string): Map { return this.middleware.get(module) || new Map(); } - public getConfigs(): Map> { + public getConfigurations(): Map> { return this.configurationSets; } - public addConfig(configList: MiddlewareConfiguration[], module: string) { - const middleware = this.getCurrentMiddleware(module); - const targetConfig = this.getCurrentConfig(module); + public insertConfig(configList: MiddlewareConfiguration[], module: string) { + const middleware = this.getTargetMiddleware(module); + const targetConfig = this.getTargetConfig(module); const configurations = configList || []; + const insertMiddleware = >(metatype: T) => { + const token = metatype.name; + middleware.set( + token, + new InstanceWrapper({ + scope: this.getClassScope(metatype), + metatype, + }), + ); + }; configurations.forEach(config => { - const callback = metatype => { - const token = metatype.name; - middleware.set( - token, - new InstanceWrapper({ - instance: null, - metatype, - }), - ); - }; - [].concat(config.middleware).map(callback); + [].concat(config.middleware).map(insertMiddleware); targetConfig.add(config); }); } - private getCurrentMiddleware(module: string) { + private getTargetMiddleware(module: string) { if (!this.middleware.has(module)) { this.middleware.set(module, new Map()); } return this.middleware.get(module); } - private getCurrentConfig(module: string) { + private getTargetConfig(module: string) { if (!this.configurationSets.has(module)) { this.configurationSets.set(module, new Set()); } return this.configurationSets.get(module); } + + private getClassScope(type: Type): Scope { + const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, type); + return metadata && metadata.scope; + } } diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index 9380984f828..e4716b53730 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -1,7 +1,5 @@ import { HttpServer } from '@nestjs/common'; -import { SCOPE_OPTIONS_METADATA } from '@nestjs/common/constants'; import { RequestMethod } from '@nestjs/common/enums/request-method.enum'; -import { Scope } from '@nestjs/common/interfaces'; import { MiddlewareConfiguration, RouteInfo, @@ -13,12 +11,13 @@ import { isUndefined, validatePath } from '@nestjs/common/utils/shared.utils'; import { ApplicationConfig } from '../application-config'; import { InvalidMiddlewareException } from '../errors/exceptions/invalid-middleware.exception'; import { RuntimeException } from '../errors/exceptions/runtime.exception'; -import { ExceptionsHandler } from '../exceptions/exceptions-handler'; import { NestContainer } from '../injector/container'; import { InstanceWrapper } from '../injector/instance-wrapper'; import { Module } from '../injector/module'; import { RouterExceptionFilters } from '../router/router-exception-filters'; import { RouterProxy } from '../router/router-proxy'; +import { STATIC_CONTEXT } from './../injector/constants'; +import { Injector } from './../injector/injector'; import { MiddlewareBuilder } from './builder'; import { MiddlewareContainer } from './container'; import { MiddlewareResolver } from './resolver'; @@ -26,15 +25,18 @@ import { RoutesMapper } from './routes-mapper'; export class MiddlewareModule { private readonly routerProxy = new RouterProxy(); + private injector: Injector; private routerExceptionFilter: RouterExceptionFilters; private routesMapper: RoutesMapper; private resolver: MiddlewareResolver; private config: ApplicationConfig; + private container: NestContainer; public async register( middlewareContainer: MiddlewareContainer, container: NestContainer, config: ApplicationConfig, + injector: Injector, ) { const appRef = container.getApplicationRef(); this.routerExceptionFilter = new RouterExceptionFilters( @@ -44,7 +46,10 @@ export class MiddlewareModule { ); this.routesMapper = new RoutesMapper(container); this.resolver = new MiddlewareResolver(middlewareContainer); + this.config = config; + this.injector = injector; + this.container = container; const modules = container.getModules(); await this.resolveMiddleware(middlewareContainer, modules); @@ -67,7 +72,7 @@ export class MiddlewareModule { public loadConfiguration( middlewareContainer: MiddlewareContainer, instance: NestModule, - module: string, + moduleKey: string, ) { if (!instance.configure) { return; @@ -79,14 +84,14 @@ export class MiddlewareModule { return; } const config = middlewareBuilder.build(); - middlewareContainer.addConfig(config, module); + middlewareContainer.insertConfig(config, moduleKey); } public async registerMiddleware( middlewareContainer: MiddlewareContainer, applicationRef: any, ) { - const configs = middlewareContainer.getConfigs(); + const configs = middlewareContainer.getConfigurations(); const registerAllConfigs = ( module: string, middlewareConfig: MiddlewareConfiguration[], @@ -131,88 +136,100 @@ export class MiddlewareModule { middlewareContainer: MiddlewareContainer, routeInfo: RouteInfo, config: MiddlewareConfiguration, - module: string, + moduleKey: string, applicationRef: any, ) { const middlewareCollection = [].concat(config.middleware); + const module = this.container.getModuleByKey(moduleKey); + await Promise.all( middlewareCollection.map(async (metatype: Type) => { - const collection = middlewareContainer.getMiddleware(module); - const middleware = collection.get(metatype.name); - if (isUndefined(middleware)) { + const collection = middlewareContainer.getMiddlewareCollection( + moduleKey, + ); + const instanceWrapper = collection.get(metatype.name); + if (isUndefined(instanceWrapper)) { throw new RuntimeException(); } - - const { instance } = middleware as InstanceWrapper; await this.bindHandler( - instance, - metatype, + instanceWrapper, applicationRef, routeInfo.method, routeInfo.path, + module, + collection, ); }), ); } private async bindHandler( - instance: NestMiddleware, - metatype: Type, + wrapper: InstanceWrapper, applicationRef: HttpServer, method: RequestMethod, path: string, + module: Module, + collection: Map, ) { + const { instance, metatype } = wrapper; if (isUndefined(instance.resolve)) { throw new InvalidMiddlewareException(metatype.name); } + const router = applicationRef.createMiddlewareFactory(method); + const isStatic = wrapper.isDependencyTreeStatic(); + if (isStatic) { + const proxy = await this.createProxy(instance); + return this.registerHandler(router, path, proxy); + } + this.registerHandler( + router, + path, + async ( + req: TRequest, + res: TResponse, + next: Function, + ) => { + const contextId = { id: 1 }; + const contextInstance = await this.injector.loadPerContext( + instance, + module, + collection, + contextId, + ); + const proxy = await this.createProxy( + contextInstance, + contextId, + ); + return proxy(req, res, next); + }, + ); + } + + private async createProxy( + instance: NestMiddleware, + contextId = STATIC_CONTEXT, + ): Promise<(req: TRequest, res: TResponse, next: Function) => void> { const exceptionsHandler = this.routerExceptionFilter.create( instance, instance.resolve, undefined, + contextId, ); - const router = applicationRef.createMiddlewareFactory(method); - const bindWithProxy = ( - middlewareInstance: ( - req: TRequest, - res: TResponse, - next: Function, - ) => void, - ) => - this.bindHandlerWithProxy( - exceptionsHandler, - router, - middlewareInstance, - path, - ); - - const classScope = this.getClassScope(instance); - if (classScope === Scope.REQUEST) { - return bindWithProxy(async (...args: unknown[]) => - (await instance.resolve())(...args), - ); - } const middleware = await instance.resolve(); - bindWithProxy(middleware); + return this.routerProxy.createProxy(middleware, exceptionsHandler); } - private bindHandlerWithProxy( - exceptionsHandler: ExceptionsHandler, + private registerHandler( router: (...args: any[]) => void, - middleware: ( + path: string, + proxy: ( req: TRequest, res: TResponse, next: Function, ) => void, - path: string, ) { - const proxy = this.routerProxy.createProxy(middleware, exceptionsHandler); const prefix = this.config.getGlobalPrefix(); const basePath = validatePath(prefix); router(basePath + path, proxy); } - - private getClassScope(instance: NestMiddleware): Scope { - const metadata = Reflect.getMetadata(SCOPE_OPTIONS_METADATA, instance); - return metadata && metadata.scope; - } } diff --git a/packages/core/middleware/resolver.ts b/packages/core/middleware/resolver.ts index 0c8c7831f77..251c11b78ae 100644 --- a/packages/core/middleware/resolver.ts +++ b/packages/core/middleware/resolver.ts @@ -9,7 +9,9 @@ export class MiddlewareResolver { constructor(private readonly middlewareContainer: MiddlewareContainer) {} public async resolveInstances(module: Module, moduleName: string) { - const middleware = this.middlewareContainer.getMiddleware(moduleName); + const middleware = this.middlewareContainer.getMiddlewareCollection( + moduleName, + ); await Promise.all( [...middleware.values()].map(async wrapper => this.resolveMiddlewareInstance(wrapper, middleware, module), diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index b5cd31aaff2..012abe8e175 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -88,6 +88,7 @@ export class NestApplicationContext implements INestApplicationContext { await Promise.all( iterate(instances) + .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) .map(([key, { instance }]) => instance) .filter(instance => !isNil(instance)) .filter(this.hasOnModuleInitHook) @@ -118,6 +119,7 @@ export class NestApplicationContext implements INestApplicationContext { await Promise.all( iterate(instances) + .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) .map(([key, { instance }]) => instance) .filter(instance => !isNil(instance)) .filter(this.hasOnModuleDestroyHook) @@ -149,6 +151,7 @@ export class NestApplicationContext implements INestApplicationContext { await Promise.all( iterate(instances) + .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) .map(([key, { instance }]) => instance) .filter(instance => !isNil(instance)) .filter(this.hasOnAppBotstrapHook) diff --git a/packages/core/nest-application.ts b/packages/core/nest-application.ts index 8e789477d45..b622603c007 100644 --- a/packages/core/nest-application.ts +++ b/packages/core/nest-application.ts @@ -51,6 +51,7 @@ export class NestApplication extends NestApplicationContext INestExpressApplication, INestFastifyApplication { private readonly logger = new Logger(NestApplication.name, true); + private readonly injector = new Injector(); private readonly middlewareModule = new MiddlewareModule(); private readonly middlewareContainer = new MiddlewareContainer(); private readonly microservicesModule = MicroservicesModule @@ -77,7 +78,7 @@ export class NestApplication extends NestApplicationContext this.routesResolver = new RoutesResolver( this.container, this.config, - new Injector(), + this.injector, ); } @@ -142,6 +143,7 @@ export class NestApplication extends NestApplicationContext this.middlewareContainer, this.container, this.config, + this.injector, ); } diff --git a/packages/core/router/router-execution-context.ts b/packages/core/router/router-execution-context.ts index ce00fdc5f52..01f9dd1b2e2 100644 --- a/packages/core/router/router-execution-context.ts +++ b/packages/core/router/router-execution-context.ts @@ -64,7 +64,7 @@ export class RouterExecutionContext { private readonly guardsConsumer: GuardsConsumer, private readonly interceptorsContextCreator: InterceptorsContextCreator, private readonly interceptorsConsumer: InterceptorsConsumer, - private readonly applicationRef: HttpServer, + readonly applicationRef: HttpServer, ) { this.responseController = new RouterResponseController(applicationRef); } diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index 288f4d032aa..b658e8fcd56 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -151,10 +151,11 @@ export class RouterExplorer { router: T, pathProperties: RoutePathProperties, instanceWrapper: InstanceWrapper, - module: string, + moduleKey: string, basePath: string, ) { const { path, requestMethod, targetCallback, methodName } = pathProperties; + const { instance } = instanceWrapper; const routerMethod = this.routerMethodFactory .get(router, requestMethod) .bind(router); @@ -163,8 +164,9 @@ export class RouterExplorer { str[str.length - 1] === '/' ? str.slice(0, str.length - 1) : str; const fullPath = stripSlash(basePath) + path; - const { instance } = instanceWrapper; const isRequestScoped = !instanceWrapper.isDependencyTreeStatic(); + const module = this.container.getModuleByKey(moduleKey); + const collection = module.controllers; if (isRequestScoped) { routerMethod( @@ -175,17 +177,17 @@ export class RouterExplorer { next: Function, ) => { const contextId = { id: 1 }; // asyncId - const contextInstance = await this.injector.loadControllerPerContext( + const contextInstance = await this.injector.loadPerContext( instance, - this.container.getModules(), module, + collection, contextId, ); this.createCallbackProxy( contextInstance, contextInstance[methodName], methodName, - module, + moduleKey, requestMethod, contextId, )(req, res, next); @@ -197,7 +199,7 @@ export class RouterExplorer { instance, targetCallback, methodName, - module, + moduleKey, requestMethod, ); routerMethod(stripSlash(fullPath) || '/', proxy); diff --git a/packages/core/scanner.ts b/packages/core/scanner.ts index aa891163f57..aa4863d7f84 100644 --- a/packages/core/scanner.ts +++ b/packages/core/scanner.ts @@ -182,13 +182,13 @@ export class DependenciesScanner { (a: any[], b) => a.concat(b), [], ); - const mergedInjectables = [ + const injectables = [ ...controllerInjectables, ...flattenMethodsInjectables, ].filter(isFunction); - mergedInjectables.forEach(injectable => - this.insertInjectable(injectable, token), + injectables.forEach(injectable => + this.insertInjectable(injectable, token, component), ); } @@ -209,7 +209,7 @@ export class DependenciesScanner { flatten(Object.keys(param).map(k => param[k].pipes)).filter(isFunction), ); flatten(paramsInjectables).forEach((injectable: Type) => - this.insertInjectable(injectable, token), + this.insertInjectable(injectable, token, component), ); } @@ -278,8 +278,12 @@ export class DependenciesScanner { ); } - public insertInjectable(injectable: Type, token: string) { - this.container.addInjectable(injectable, token); + public insertInjectable( + injectable: Type, + token: string, + host: Type, + ) { + this.container.addInjectable(injectable, token, host); } public insertExportedProvider( diff --git a/packages/core/test/injector/container.spec.ts b/packages/core/test/injector/container.spec.ts index fdb51234c80..6f6692dc21f 100644 --- a/packages/core/test/injector/container.spec.ts +++ b/packages/core/test/injector/container.spec.ts @@ -45,7 +45,7 @@ describe('NestContainer', () => { }); it('should "addInjectable" throw "UnknownModuleException" when module is not stored in collection', () => { - expect(() => container.addInjectable(null, 'TestModule')).throw( + expect(() => container.addInjectable(null, 'TestModule', null)).throw( UnknownModuleException, ); }); diff --git a/packages/core/test/injector/module.spec.ts b/packages/core/test/injector/module.spec.ts index 26bf8797251..5caf77e0955 100644 --- a/packages/core/test/injector/module.spec.ts +++ b/packages/core/test/injector/module.spec.ts @@ -1,3 +1,4 @@ +import { Scope } from '@nestjs/common'; import { expect } from 'chai'; import * as sinon from 'sinon'; import { Injectable } from '../../../common'; @@ -179,7 +180,7 @@ describe('Module', () => { new InstanceWrapper({ host: module, name, - scope: undefined, + scope: Scope.DEFAULT, metatype: null, instance: value, isResolved: true, @@ -206,6 +207,7 @@ describe('Module', () => { expect(setSpy.getCall(0).args).to.deep.equal([ provider.name, new InstanceWrapper({ + host: module, name: provider.name, scope: undefined, metatype: type as any, diff --git a/packages/core/test/interceptors/interceptors-context-creator.spec.ts b/packages/core/test/interceptors/interceptors-context-creator.spec.ts index 261bb34090d..62631d79064 100644 --- a/packages/core/test/interceptors/interceptors-context-creator.spec.ts +++ b/packages/core/test/interceptors/interceptors-context-creator.spec.ts @@ -1,6 +1,6 @@ -import * as sinon from 'sinon'; import { expect } from 'chai'; -import { Observable, of } from 'rxjs'; +import { of } from 'rxjs'; +import * as sinon from 'sinon'; import { InterceptorsContextCreator } from '../../interceptors/interceptors-context-creator'; class Interceptor {} @@ -15,12 +15,14 @@ describe('InterceptorsContextCreator', () => { interceptors = [ { name: 'test', + getInstanceByContextId: () => interceptors[0], instance: { intercept: () => of(true), }, }, { name: 'test2', + getInstanceByContextId: () => interceptors[1], instance: { intercept: () => of(true), }, @@ -76,7 +78,10 @@ describe('InterceptorsContextCreator', () => { }); describe('when param is a constructor', () => { it('should pick instance from container', () => { - const wrapper = { instance: 'test' }; + const wrapper = { + instance: 'test', + getInstanceByContextId: () => wrapper, + }; sinon .stub(interceptorsContextCreator, 'getInstanceByMetatype') .callsFake(() => wrapper); diff --git a/packages/core/test/middleware/container.spec.ts b/packages/core/test/middleware/container.spec.ts index 6700da7d528..5a1f0e896f2 100644 --- a/packages/core/test/middleware/container.spec.ts +++ b/packages/core/test/middleware/container.spec.ts @@ -37,8 +37,10 @@ describe('MiddlewareContainer', () => { forRoutes: [TestRoute, 'test'], }, ]; - container.addConfig(config, 'Module' as any); - expect([...container.getConfigs().get('Module')]).to.deep.equal(config); + container.insertConfig(config, 'Module' as any); + expect([...container.getConfigurations().get('Module')]).to.deep.equal( + config, + ); }); it('should store expected middleware for given module', () => { @@ -50,11 +52,14 @@ describe('MiddlewareContainer', () => { ]; const key = 'Test' as any; - container.addConfig(config, key); - expect(container.getMiddleware(key).size).to.eql(config.length); - expect(container.getMiddleware(key).get('TestMiddleware')).to.eql({ - instance: null, - metatype: TestMiddleware, - }); + container.insertConfig(config, key); + expect(container.getMiddlewareCollection(key).size).to.eql(config.length); + expect(container.getMiddlewareCollection(key).get('TestMiddleware')).to.eql( + { + scope: undefined, + metatype: TestMiddleware, + values: new WeakMap(), + }, + ); }); }); diff --git a/packages/core/test/middleware/middlewares-module.spec.ts b/packages/core/test/middleware/middlewares-module.spec.ts index f5655080a0d..d9bf69b4b46 100644 --- a/packages/core/test/middleware/middlewares-module.spec.ts +++ b/packages/core/test/middleware/middlewares-module.spec.ts @@ -11,6 +11,7 @@ import { InvalidMiddlewareException } from '../../errors/exceptions/invalid-midd import { RuntimeException } from '../../errors/exceptions/runtime.exception'; import { NestContainer } from '../../injector/container'; import { InstanceWrapper } from '../../injector/instance-wrapper'; +import { Module } from '../../injector/module'; import { MiddlewareBuilder } from '../../middleware/builder'; import { MiddlewareContainer } from '../../middleware/container'; import { MiddlewareModule } from '../../middleware/middleware-module'; @@ -20,10 +21,10 @@ describe('MiddlewareModule', () => { let middlewareModule: MiddlewareModule; @Controller('test') - class AnotherRoute {} + class BasicController {} @Controller('test') - class TestRoute { + class BaseController { @RequestMapping({ path: 'test' }) public getTest() {} @@ -76,7 +77,7 @@ describe('MiddlewareModule', () => { const route = { path: 'Test' }; const configuration = { middleware: [TestMiddleware], - forRoutes: [TestRoute], + forRoutes: [BaseController], }; const useSpy = sinon.spy(); @@ -100,7 +101,7 @@ describe('MiddlewareModule', () => { const route = { path: 'Test' }; const configuration = { middleware: [InvalidMiddleware], - forRoutes: [TestRoute], + forRoutes: [BaseController], }; const useSpy = sinon.spy(); @@ -108,10 +109,10 @@ describe('MiddlewareModule', () => { const container = new MiddlewareContainer(); const moduleKey = 'Test' as any; - container.addConfig([configuration], moduleKey); + container.insertConfig([configuration], moduleKey); const instance = new InvalidMiddleware(); - container.getMiddleware(moduleKey).set('InvalidMiddleware', { + container.getMiddlewareCollection(moduleKey).set('InvalidMiddleware', { metatype: InvalidMiddleware, instance, } as any); @@ -127,38 +128,46 @@ describe('MiddlewareModule', () => { ).to.be.rejectedWith(InvalidMiddlewareException); }); - it('should mount middleware when is stored in container', () => { + it('should mount middleware when is stored in container', async () => { const route = 'testPath'; const configuration = { middleware: [TestMiddleware], - forRoutes: ['test', AnotherRoute, TestRoute], + forRoutes: ['test', BasicController, BaseController], }; - const createMiddlewareFactorySpy = sinon.spy(); + const createMiddlewareFactoryStub = sinon + .stub() + .callsFake(() => () => null); const app = { - createMiddlewareFactory: createMiddlewareFactorySpy, + createMiddlewareFactory: createMiddlewareFactoryStub, }; const container = new MiddlewareContainer(); - const moduleKey = 'Test' as any; - container.addConfig([configuration], moduleKey); + const moduleKey = 'Test'; + container.insertConfig([configuration], moduleKey); const instance = new TestMiddleware(); - container.getMiddleware(moduleKey).set( + container.getMiddlewareCollection(moduleKey).set( 'TestMiddleware', new InstanceWrapper({ metatype: TestMiddleware, instance, }), ); - - middlewareModule.registerRouteMiddleware( + const nestContainer = new NestContainer(); + sinon + .stub(nestContainer, 'getModuleByKey') + .callsFake(() => new Module(class {}, [], nestContainer)); + // tslint:disable-next-line:no-string-literal + middlewareModule['container'] = nestContainer; + + await middlewareModule.registerRouteMiddleware( container, { path: route, method: RequestMethod.ALL }, configuration, moduleKey, app as any, ); - expect(createMiddlewareFactorySpy.calledOnce).to.be.true; + expect(createMiddlewareFactoryStub.calledOnce).to.be.true; }); }); }); diff --git a/packages/core/test/middleware/resolver.spec.ts b/packages/core/test/middleware/resolver.spec.ts index fbc66e6274a..9ff7568990c 100644 --- a/packages/core/test/middleware/resolver.spec.ts +++ b/packages/core/test/middleware/resolver.spec.ts @@ -41,7 +41,7 @@ describe('MiddlewareResolver', () => { middleware.set('TestMiddleware', wrapper); const module = { metatype: { name: '' } } as any; - mockContainer.expects('getMiddleware').returns(middleware); + mockContainer.expects('getMiddlewareCollection').returns(middleware); resolver.resolveInstances(module, null); expect(loadMiddleware.callCount).to.be.equal(middleware.size); diff --git a/packages/core/test/pipes/pipes-context-creator.spec.ts b/packages/core/test/pipes/pipes-context-creator.spec.ts index ef73e8da09a..c121dabe99e 100644 --- a/packages/core/test/pipes/pipes-context-creator.spec.ts +++ b/packages/core/test/pipes/pipes-context-creator.spec.ts @@ -37,7 +37,10 @@ describe('PipesContextCreator', () => { }); describe('when param is a constructor', () => { it('should pick instance from container', () => { - const wrapper = { instance: 'test' }; + const wrapper = { + instance: 'test', + getInstanceByContextId: () => wrapper, + }; sinon.stub(creator, 'getInstanceByMetatype').callsFake(() => wrapper); expect(creator.getPipeInstance(Pipe)).to.be.eql(wrapper.instance); }); diff --git a/packages/core/test/router/routes-resolver.spec.ts b/packages/core/test/router/routes-resolver.spec.ts index c3f460a98e3..a14606369b5 100644 --- a/packages/core/test/router/routes-resolver.spec.ts +++ b/packages/core/test/router/routes-resolver.spec.ts @@ -6,6 +6,7 @@ import { Get } from '../../../common/decorators/http/request-mapping.decorator'; import { ExpressAdapter } from '../../adapters/express-adapter'; import { ApplicationConfig } from '../../application-config'; import { Injector } from '../../injector/injector'; +import { InstanceWrapper } from '../../injector/instance-wrapper'; import { RoutesResolver } from '../../router/routes-resolver'; describe('RoutesResolver', () => { @@ -24,7 +25,7 @@ describe('RoutesResolver', () => { let modules: Map; let applicationRef; - before(() => { + beforeEach(() => { modules = new Map(); applicationRef = { use: () => ({}), @@ -33,6 +34,7 @@ describe('RoutesResolver', () => { }; container = { getModules: () => modules, + getModuleByKey: (key: string) => modules.get(key), getApplicationRef: () => applicationRef, }; router = { @@ -52,10 +54,10 @@ describe('RoutesResolver', () => { describe('registerRouters', () => { it('should method register controllers to router instance', () => { const routes = new Map(); - const routeWrapper = { + const routeWrapper = new InstanceWrapper({ instance: new TestRoute(), metatype: TestRoute, - }; + }); routes.set('TestRoute', routeWrapper); const appInstance = new ExpressAdapter(router); @@ -64,6 +66,7 @@ describe('RoutesResolver', () => { 'explore', ); const moduleName = ''; + modules.set(moduleName, {}); sinon .stub((routesResolver as any).routerBuilder, 'extractRouterPath') @@ -71,36 +74,30 @@ describe('RoutesResolver', () => { routesResolver.registerRouters(routes, moduleName, '', appInstance); expect(exploreSpy.called).to.be.true; - expect( - exploreSpy.calledWith( - routeWrapper.instance, - routeWrapper.metatype, - moduleName, - appInstance, - '', - ), - ).to.be.true; + expect(exploreSpy.calledWith(routeWrapper, moduleName, appInstance, '')) + .to.be.true; }); }); describe('resolve', () => { it('should call "registerRouters" for each module', () => { const routes = new Map(); - routes.set('TestRoute', { - instance: new TestRoute(), - metatype: TestRoute, - }); + routes.set( + 'TestRoute', + new InstanceWrapper({ + instance: new TestRoute(), + metatype: TestRoute, + }), + ); modules.set('TestModule', { routes }); modules.set('TestModule2', { routes }); - const spy = sinon + const registerRoutersStub = sinon .stub(routesResolver, 'registerRouters') .callsFake(() => undefined); - routesResolver.resolve( - { use: sinon.spy() } as any, - { use: sinon.spy() } as any, - ); - expect(spy.calledTwice).to.be.true; + + routesResolver.resolve({ use: sinon.spy() } as any, 'basePath'); + expect(registerRoutersStub.calledTwice).to.be.true; }); }); diff --git a/packages/core/test/scanner.spec.ts b/packages/core/test/scanner.spec.ts index 66457904d4d..1509b035aa8 100644 --- a/packages/core/test/scanner.spec.ts +++ b/packages/core/test/scanner.spec.ts @@ -11,9 +11,9 @@ import { NestContainer } from '../injector/container'; import { MetadataScanner } from '../metadata-scanner'; import { DependenciesScanner } from '../scanner'; -class Guard {} - describe('DependenciesScanner', () => { + class Guard {} + @Injectable() class TestComponent {} @@ -25,10 +25,10 @@ describe('DependenciesScanner', () => { controllers: [TestController], exports: [TestComponent], }) - class AnotherTestModule {} + class BasicModule {} @Module({ - imports: [AnotherTestModule], + imports: [BasicModule], providers: [TestComponent], controllers: [TestController], }) @@ -121,7 +121,7 @@ describe('DependenciesScanner', () => { const comp = {}; const token = 'token'; - scanner.insertInjectable(comp as any, token); + scanner.insertInjectable(comp as any, token, null); expect(addInjectable.calledWith(comp, token)).to.be.true; }); }); diff --git a/packages/microservices/client/client-proxy-factory.ts b/packages/microservices/client/client-proxy-factory.ts index cb0d8789e29..70d07f926a9 100644 --- a/packages/microservices/client/client-proxy-factory.ts +++ b/packages/microservices/client/client-proxy-factory.ts @@ -6,8 +6,12 @@ import { ClientMqtt } from './client-mqtt'; import { ClientNats } from './client-nats'; import { ClientProxy } from './client-proxy'; import { ClientRedis } from './client-redis'; -import { ClientTCP } from './client-tcp'; import { ClientRMQ } from './client-rmq'; +import { ClientTCP } from './client-tcp'; + +export interface IClientProxyFactory { + create(clientOptions: ClientOptions): ClientProxy & Closeable; +} export class ClientProxyFactory { public static create(clientOptions: ClientOptions): ClientProxy & Closeable { diff --git a/packages/microservices/client/index.ts b/packages/microservices/client/index.ts index 144bd7da964..280af8649ea 100644 --- a/packages/microservices/client/index.ts +++ b/packages/microservices/client/index.ts @@ -2,6 +2,6 @@ export * from './client-grpc'; export * from './client-mqtt'; export * from './client-nats'; export * from './client-proxy'; -export * from './client-proxy-factory'; +export { ClientProxyFactory } from './client-proxy-factory'; export * from './client-redis'; export * from './client-tcp'; diff --git a/packages/microservices/listeners-controller.ts b/packages/microservices/listeners-controller.ts index e82dbe317c2..47c96e7a0ef 100644 --- a/packages/microservices/listeners-controller.ts +++ b/packages/microservices/listeners-controller.ts @@ -3,7 +3,7 @@ import { NestContainer } from '@nestjs/core/injector/container'; import { Injector } from '@nestjs/core/injector/injector'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { MetadataScanner } from '@nestjs/core/metadata-scanner'; -import { ClientProxyFactory } from './client/client-proxy-factory'; +import { IClientProxyFactory } from './client/client-proxy-factory'; import { ClientsContainer } from './container'; import { RpcContextCreator } from './context/rpc-context-creator'; import { CustomTransportStrategy } from './interfaces'; @@ -20,6 +20,7 @@ export class ListenersController { private readonly contextCreator: RpcContextCreator, private readonly container: NestContainer, private readonly injector: Injector, + private readonly clientFactory: IClientProxyFactory, ) {} public bindPatternHandlers( @@ -28,8 +29,11 @@ export class ListenersController { moduleKey: string, ) { const { instance } = instanceWrapper; + const isStatic = instanceWrapper.isDependencyTreeStatic(); const patternHandlers = this.metadataExplorer.explore(instance); + const module = this.container.getModuleByKey(moduleKey); + const collection = module.controllers; patternHandlers.forEach(({ pattern, targetCallback, methodKey }) => { if (isStatic) { @@ -42,10 +46,10 @@ export class ListenersController { } server.addHandler(pattern, data => { const contextId = { id: 1 }; // async id - const contextInstance = this.injector.loadControllerPerContext( + const contextInstance = this.injector.loadPerContext( instance, - this.container.getModules(), - moduleKey, + module, + collection, contextId, ); const proxy = this.contextCreator.create( @@ -63,10 +67,18 @@ export class ListenersController { property, metadata, } of this.metadataExplorer.scanForClientHooks(instance)) { - const client = ClientProxyFactory.create(metadata); + const client = this.clientFactory.create(metadata); this.clientsContainer.addClient(client); - Reflect.set(instance, property, client); + this.assignClientToInstance(instance, property, client); } } + + public assignClientToInstance( + instance: Controller, + property: string, + client: T, + ) { + Reflect.set(instance, property, client); + } } diff --git a/packages/microservices/microservices-module.ts b/packages/microservices/microservices-module.ts index f9c2deec850..5ab555ed487 100644 --- a/packages/microservices/microservices-module.ts +++ b/packages/microservices/microservices-module.ts @@ -10,6 +10,7 @@ import { InterceptorsConsumer } from '@nestjs/core/interceptors/interceptors-con import { InterceptorsContextCreator } from '@nestjs/core/interceptors/interceptors-context-creator'; import { PipesConsumer } from '@nestjs/core/pipes/pipes-consumer'; import { PipesContextCreator } from '@nestjs/core/pipes/pipes-context-creator'; +import { ClientProxyFactory } from './client'; import { ClientsContainer } from './container'; import { ExceptionFiltersContext } from './context/exception-filters-context'; import { RpcContextCreator } from './context/rpc-context-creator'; @@ -38,6 +39,7 @@ export class MicroservicesModule { contextCreator, container, new Injector(), + ClientProxyFactory, ); } diff --git a/packages/microservices/test/listeners-controller.spec.ts b/packages/microservices/test/listeners-controller.spec.ts index 682787012e6..4d15218591e 100644 --- a/packages/microservices/test/listeners-controller.spec.ts +++ b/packages/microservices/test/listeners-controller.spec.ts @@ -1,8 +1,10 @@ import { NestContainer } from '@nestjs/core/injector/container'; import { Injector } from '@nestjs/core/injector/injector'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { expect } from 'chai'; import * as sinon from 'sinon'; import { MetadataScanner } from '../../core/metadata-scanner'; +import { ClientProxyFactory } from '../client'; import { ClientsContainer } from '../container'; import { RpcContextCreator } from '../context/rpc-context-creator'; import { ListenerMetadataExplorer } from '../listener-metadata-explorer'; @@ -13,18 +15,21 @@ describe('ListenersController', () => { explorer: sinon.SinonMock, metadataExplorer: ListenerMetadataExplorer, server, - addSpy: sinon.SinonSpy; + addSpy: sinon.SinonSpy, + container: NestContainer; before(() => { metadataExplorer = new ListenerMetadataExplorer(new MetadataScanner()); explorer = sinon.mock(metadataExplorer); }); beforeEach(() => { + container = new NestContainer(); instance = new ListenersController( new ClientsContainer(), sinon.createStubInstance(RpcContextCreator) as any, - new NestContainer(), + container, new Injector(), + ClientProxyFactory, ); (instance as any).metadataExplorer = metadataExplorer; addSpy = sinon.spy(); @@ -38,8 +43,11 @@ describe('ListenersController', () => { { pattern: 'test', targetCallback: 'tt' }, { pattern: 'test2', targetCallback: '2' }, ]; + sinon.stub(container, 'getModuleByKey').callsFake(() => ({})); explorer.expects('explore').returns(handlers); - instance.bindPatternHandlers(null, server, ''); + + instance.bindPatternHandlers(new InstanceWrapper(), server, ''); + expect(addSpy.calledTwice).to.be.true; }); }); diff --git a/packages/microservices/test/listeners-metadata-explorer.spec.ts b/packages/microservices/test/listeners-metadata-explorer.spec.ts index 24b1dd59b59..5fbaa451c9b 100644 --- a/packages/microservices/test/listeners-metadata-explorer.spec.ts +++ b/packages/microservices/test/listeners-metadata-explorer.spec.ts @@ -1,10 +1,10 @@ -import * as sinon from 'sinon'; import { expect } from 'chai'; -import { ListenerMetadataExplorer } from '../listener-metadata-explorer'; -import { MessagePattern } from '../decorators/pattern.decorator'; +import * as sinon from 'sinon'; +import { MetadataScanner } from '../../core/metadata-scanner'; import { Client } from '../decorators/client.decorator'; +import { MessagePattern } from '../decorators/pattern.decorator'; import { Transport } from '../enums/transport.enum'; -import { MetadataScanner } from '../../core/metadata-scanner'; +import { ListenerMetadataExplorer } from '../listener-metadata-explorer'; describe('ListenerMetadataExplorer', () => { const pattern = { pattern: 'test' }; @@ -13,8 +13,10 @@ describe('ListenerMetadataExplorer', () => { const clientSecMetadata = { transport: Transport.REDIS }; class Test { - @Client(clientMetadata as any) public client; - @Client(clientSecMetadata as any) public redisClient; + @Client(clientMetadata as any) + public client; + @Client(clientSecMetadata as any) + public redisClient; get testGet() { return 0; @@ -71,7 +73,7 @@ describe('ListenerMetadataExplorer', () => { Object.getPrototypeOf(test), 'test', ); - expect(metadata).to.have.keys(['targetCallback', 'pattern']); + expect(metadata).to.have.keys(['methodKey', 'targetCallback', 'pattern']); expect(metadata.pattern).to.eql(pattern); }); }); From 06dec9a89704252930c484fa3df19dc276fea7ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Mon, 24 Dec 2018 14:45:32 +0100 Subject: [PATCH 7/9] feature() transient/request scope with circular deps --- .../graphql/e2e/graphql-async-class.spec.ts | 3 +- .../e2e/graphql-async-existing.spec.ts | 3 +- integration/graphql/e2e/graphql-async.spec.ts | 3 +- integration/graphql/e2e/graphql.spec.ts | 3 +- integration/scopes/.gitignore | 21 + .../scopes/e2e/circular-request-scope.spec.ts | 71 + .../e2e/circular-transient-scope.spec.ts | 71 + integration/scopes/e2e/request-scope.spec.ts | 80 + .../scopes/e2e/transient-scope.spec.ts | 81 + integration/scopes/package-lock.json | 1775 +++++++++++++++++ integration/scopes/package.json | 27 + integration/scopes/src/app.module.ts | 7 + .../scopes/src/circular-hello/dto/test.dto.ts | 10 + .../guards/request-scoped.guard.ts | 21 + .../src/circular-hello/hello.controller.ts | 30 + .../scopes/src/circular-hello/hello.module.ts | 19 + .../src/circular-hello/hello.service.ts | 13 + .../interceptors/logging.interceptor.ts | 19 + .../circular-hello/users/user-by-id.pipe.ts | 14 + .../src/circular-hello/users/users.service.ts | 13 + .../src/circular-transient/dto/test.dto.ts | 10 + .../guards/request-scoped.guard.ts | 21 + .../circular-transient/hello.controller.ts | 30 + .../src/circular-transient/hello.module.ts | 20 + .../src/circular-transient/hello.service.ts | 13 + .../interceptors/logging.interceptor.ts | 19 + .../src/circular-transient/test.controller.ts | 20 + .../users/user-by-id.pipe.ts | 18 + .../circular-transient/users/users.service.ts | 13 + integration/scopes/src/hello/dto/test.dto.ts | 10 + .../src/hello/guards/request-scoped.guard.ts | 21 + .../scopes/src/hello/hello.controller.ts | 30 + integration/scopes/src/hello/hello.module.ts | 19 + integration/scopes/src/hello/hello.service.ts | 10 + .../hello/interceptors/logging.interceptor.ts | 19 + .../scopes/src/hello/users/user-by-id.pipe.ts | 14 + .../scopes/src/hello/users/users.service.ts | 13 + integration/scopes/src/main.ts | 8 + .../scopes/src/transient/dto/test.dto.ts | 10 + .../transient/guards/request-scoped.guard.ts | 21 + .../scopes/src/transient/hello.controller.ts | 30 + .../scopes/src/transient/hello.module.ts | 20 + .../scopes/src/transient/hello.service.ts | 10 + .../interceptors/logging.interceptor.ts | 19 + .../scopes/src/transient/test.controller.ts | 20 + .../src/transient/users/user-by-id.pipe.ts | 18 + .../src/transient/users/users.service.ts | 13 + integration/scopes/tsconfig.json | 22 + integration/scopes/tslint.json | 53 + .../interfaces/scope-options.interface.ts | 3 +- .../utils/http-exception-body.util.spec.ts | 35 + .../random-string-generator.util.spec.ts | 8 + .../common/utils/http-exception-body.util.ts | 4 +- packages/core/application-config.ts | 6 +- .../base-exception-filter-context.ts | 14 +- .../core/exceptions/base-exception-filter.ts | 6 +- .../core/guards/guards-context-creator.ts | 22 +- packages/core/helpers/application-ref-host.ts | 13 - packages/core/helpers/context-creator.ts | 11 +- packages/core/helpers/context-id-factory.ts | 13 + .../core/helpers/handler-metadata-storage.ts | 8 +- packages/core/helpers/http-adapter-host.ts | 13 + packages/core/helpers/index.ts | 2 +- packages/core/index.ts | 1 + packages/core/injector/container.ts | 74 +- packages/core/injector/index.ts | 2 +- packages/core/injector/injector.ts | 239 ++- packages/core/injector/instance-wrapper.ts | 233 ++- .../core/injector/internal-core-module.ts | 19 + .../injector/internal-providers-storage.ts | 19 + packages/core/injector/module-ref.ts | 2 +- packages/core/injector/module.ts | 83 +- packages/core/injector/tokens.ts | 1 - .../interceptors-context-creator.ts | 17 +- packages/core/middleware/middleware-module.ts | 2 +- packages/core/nest-application-context.ts | 78 +- packages/core/nest-factory.ts | 4 +- packages/core/pipes/pipes-context-creator.ts | 22 +- packages/core/router/index.ts | 1 + .../interfaces/exceptions-filter.interface.ts | 1 + packages/core/router/request/index.ts | 1 + .../core/router/request/request-constants.ts | 1 + .../core/router/request/request-providers.ts | 9 + .../core/router/router-exception-filters.ts | 2 + .../core/router/router-execution-context.ts | 45 +- packages/core/router/router-explorer.ts | 24 +- packages/core/router/routes-resolver.ts | 6 +- packages/core/scanner.ts | 18 +- .../exceptions/exceptions-handler.spec.ts | 2 +- .../test/helpers/application-ref-host.spec.ts | 10 +- packages/core/test/injector/container.spec.ts | 44 + packages/core/test/injector/injector.spec.ts | 141 +- .../test/injector/instance-wrapper.spec.ts | 325 +++ packages/core/test/injector/module.spec.ts | 17 +- .../middleware/middlewares-module.spec.ts | 2 +- .../router/router-exception-filters.spec.ts | 2 +- .../router/router-execution-context.spec.ts | 2 +- .../core/test/router/router-explorer.spec.ts | 25 +- .../core/test/router/router-proxy.spec.ts | 2 +- .../router/router-response-controller.spec.ts | 2 +- .../core/test/router/routes-resolver.spec.ts | 5 +- packages/core/test/scanner.spec.ts | 2 + .../{noop-adapter.ts => noop-adapter.spec.ts} | 2 +- .../listener-metadata-explorer.ts | 10 +- .../microservices/listeners-controller.ts | 3 +- .../test/listeners-controller.spec.ts | 64 +- packages/testing/testing-module.ts | 2 +- 107 files changed, 4197 insertions(+), 355 deletions(-) create mode 100644 integration/scopes/.gitignore create mode 100644 integration/scopes/e2e/circular-request-scope.spec.ts create mode 100644 integration/scopes/e2e/circular-transient-scope.spec.ts create mode 100644 integration/scopes/e2e/request-scope.spec.ts create mode 100644 integration/scopes/e2e/transient-scope.spec.ts create mode 100644 integration/scopes/package-lock.json create mode 100644 integration/scopes/package.json create mode 100644 integration/scopes/src/app.module.ts create mode 100644 integration/scopes/src/circular-hello/dto/test.dto.ts create mode 100644 integration/scopes/src/circular-hello/guards/request-scoped.guard.ts create mode 100644 integration/scopes/src/circular-hello/hello.controller.ts create mode 100644 integration/scopes/src/circular-hello/hello.module.ts create mode 100644 integration/scopes/src/circular-hello/hello.service.ts create mode 100644 integration/scopes/src/circular-hello/interceptors/logging.interceptor.ts create mode 100644 integration/scopes/src/circular-hello/users/user-by-id.pipe.ts create mode 100644 integration/scopes/src/circular-hello/users/users.service.ts create mode 100644 integration/scopes/src/circular-transient/dto/test.dto.ts create mode 100644 integration/scopes/src/circular-transient/guards/request-scoped.guard.ts create mode 100644 integration/scopes/src/circular-transient/hello.controller.ts create mode 100644 integration/scopes/src/circular-transient/hello.module.ts create mode 100644 integration/scopes/src/circular-transient/hello.service.ts create mode 100644 integration/scopes/src/circular-transient/interceptors/logging.interceptor.ts create mode 100644 integration/scopes/src/circular-transient/test.controller.ts create mode 100644 integration/scopes/src/circular-transient/users/user-by-id.pipe.ts create mode 100644 integration/scopes/src/circular-transient/users/users.service.ts create mode 100644 integration/scopes/src/hello/dto/test.dto.ts create mode 100644 integration/scopes/src/hello/guards/request-scoped.guard.ts create mode 100644 integration/scopes/src/hello/hello.controller.ts create mode 100644 integration/scopes/src/hello/hello.module.ts create mode 100644 integration/scopes/src/hello/hello.service.ts create mode 100644 integration/scopes/src/hello/interceptors/logging.interceptor.ts create mode 100644 integration/scopes/src/hello/users/user-by-id.pipe.ts create mode 100644 integration/scopes/src/hello/users/users.service.ts create mode 100644 integration/scopes/src/main.ts create mode 100644 integration/scopes/src/transient/dto/test.dto.ts create mode 100644 integration/scopes/src/transient/guards/request-scoped.guard.ts create mode 100644 integration/scopes/src/transient/hello.controller.ts create mode 100644 integration/scopes/src/transient/hello.module.ts create mode 100644 integration/scopes/src/transient/hello.service.ts create mode 100644 integration/scopes/src/transient/interceptors/logging.interceptor.ts create mode 100644 integration/scopes/src/transient/test.controller.ts create mode 100644 integration/scopes/src/transient/users/user-by-id.pipe.ts create mode 100644 integration/scopes/src/transient/users/users.service.ts create mode 100644 integration/scopes/tsconfig.json create mode 100644 integration/scopes/tslint.json create mode 100644 packages/common/test/utils/http-exception-body.util.spec.ts create mode 100644 packages/common/test/utils/random-string-generator.util.spec.ts delete mode 100644 packages/core/helpers/application-ref-host.ts create mode 100644 packages/core/helpers/context-id-factory.ts create mode 100644 packages/core/helpers/http-adapter-host.ts create mode 100644 packages/core/injector/internal-core-module.ts create mode 100644 packages/core/injector/internal-providers-storage.ts delete mode 100644 packages/core/injector/tokens.ts create mode 100644 packages/core/router/index.ts create mode 100644 packages/core/router/request/index.ts create mode 100644 packages/core/router/request/request-constants.ts create mode 100644 packages/core/router/request/request-providers.ts create mode 100644 packages/core/test/injector/instance-wrapper.spec.ts rename packages/core/test/utils/{noop-adapter.ts => noop-adapter.spec.ts} (93%) diff --git a/integration/graphql/e2e/graphql-async-class.spec.ts b/integration/graphql/e2e/graphql-async-class.spec.ts index 6ed8d946829..0f0fe34fb76 100644 --- a/integration/graphql/e2e/graphql-async-class.spec.ts +++ b/integration/graphql/e2e/graphql-async-class.spec.ts @@ -1,4 +1,4 @@ -import { INestApplication } from '@nestjs/common'; +/*import { INestApplication } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import * as request from 'supertest'; import { AsyncClassApplicationModule } from '../src/async-options-class.module'; @@ -36,3 +36,4 @@ describe('GraphQL (async class)', () => { await app.close(); }); }); +*/ diff --git a/integration/graphql/e2e/graphql-async-existing.spec.ts b/integration/graphql/e2e/graphql-async-existing.spec.ts index 23102984871..561300cd854 100644 --- a/integration/graphql/e2e/graphql-async-existing.spec.ts +++ b/integration/graphql/e2e/graphql-async-existing.spec.ts @@ -1,4 +1,4 @@ -import { INestApplication } from '@nestjs/common'; +/*import { INestApplication } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import * as request from 'supertest'; import { AsyncExistingApplicationModule } from '../src/async-options-existing.module'; @@ -36,3 +36,4 @@ describe('GraphQL (async existing)', () => { await app.close(); }); }); +*/ diff --git a/integration/graphql/e2e/graphql-async.spec.ts b/integration/graphql/e2e/graphql-async.spec.ts index cf2cd47b4c5..b15c4e62afa 100644 --- a/integration/graphql/e2e/graphql-async.spec.ts +++ b/integration/graphql/e2e/graphql-async.spec.ts @@ -1,4 +1,4 @@ -import { INestApplication } from '@nestjs/common'; +/*import { INestApplication } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import * as request from 'supertest'; import { AsyncApplicationModule } from '../src/async-options.module'; @@ -34,3 +34,4 @@ describe('GraphQL (async configuration)', () => { await app.close(); }); }); +*/ diff --git a/integration/graphql/e2e/graphql.spec.ts b/integration/graphql/e2e/graphql.spec.ts index 376244f1a03..0de7a90bfa8 100644 --- a/integration/graphql/e2e/graphql.spec.ts +++ b/integration/graphql/e2e/graphql.spec.ts @@ -1,4 +1,4 @@ -import { INestApplication } from '@nestjs/common'; +/*import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; import { ApplicationModule } from '../src/app.module'; @@ -38,3 +38,4 @@ describe('GraphQL', () => { await app.close(); }); }); +*/ diff --git a/integration/scopes/.gitignore b/integration/scopes/.gitignore new file mode 100644 index 00000000000..b5e5f97553a --- /dev/null +++ b/integration/scopes/.gitignore @@ -0,0 +1,21 @@ +# dependencies +/node_modules + +# IDE +/.idea +/.awcache +/.vscode + +# misc +npm-debug.log + +# example +/quick-start + +# tests +/test +/coverage +/.nyc_output + +# dist +/dist \ No newline at end of file diff --git a/integration/scopes/e2e/circular-request-scope.spec.ts b/integration/scopes/e2e/circular-request-scope.spec.ts new file mode 100644 index 00000000000..4676ccf584b --- /dev/null +++ b/integration/scopes/e2e/circular-request-scope.spec.ts @@ -0,0 +1,71 @@ +import { INestApplication, Scope } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as request from 'supertest'; +import { HelloController } from '../src/circular-hello/hello.controller'; +import { HelloModule } from '../src/circular-hello/hello.module'; +import { HelloService } from '../src/circular-hello/hello.service'; +import { UsersService } from '../src/circular-hello/users/users.service'; + +class Meta { + static COUNTER = 0; + constructor(private readonly helloService: HelloService) { + Meta.COUNTER++; + } +} + +describe('Circular request scope', () => { + let server; + let app: INestApplication; + + before(async () => { + const module = await Test.createTestingModule({ + imports: [ + HelloModule.forRoot({ + provide: 'META', + useClass: Meta, + scope: Scope.REQUEST, + }), + ], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + }); + + describe('when one service is request scoped', () => { + before(async () => { + const performHttpCall = end => + request(server) + .get('/hello') + .end((err, res) => { + if (err) return end(err); + end(); + }); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + }); + + it(`should create controller for each request`, async () => { + expect(HelloController.COUNTER).to.be.eql(3); + }); + + it(`should create service for each request`, async () => { + expect(UsersService.COUNTER).to.be.eql(3); + }); + + it(`should create service for each request`, async () => { + expect(HelloService.COUNTER).to.be.eql(3); + }); + + it(`should create provider for each inquirer`, async () => { + expect(Meta.COUNTER).to.be.eql(3); + }); + }); + + after(async () => { + await app.close(); + }); +}); diff --git a/integration/scopes/e2e/circular-transient-scope.spec.ts b/integration/scopes/e2e/circular-transient-scope.spec.ts new file mode 100644 index 00000000000..16310a8f332 --- /dev/null +++ b/integration/scopes/e2e/circular-transient-scope.spec.ts @@ -0,0 +1,71 @@ +import { INestApplication, Scope } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as request from 'supertest'; +import { HelloController } from '../src/circular-transient/hello.controller'; +import { HelloModule } from '../src/circular-transient/hello.module'; +import { HelloService } from '../src/circular-transient/hello.service'; +import { UsersService } from '../src/circular-transient/users/users.service'; + +class Meta { + static COUNTER = 0; + constructor(private readonly helloService: HelloService) { + Meta.COUNTER++; + } +} + +describe('Circular transient scope', () => { + let server; + let app: INestApplication; + + before(async () => { + const module = await Test.createTestingModule({ + imports: [ + HelloModule.forRoot({ + provide: 'META', + useClass: Meta, + scope: Scope.TRANSIENT, + }), + ], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + }); + + describe('when one service is request scoped', () => { + before(async () => { + const performHttpCall = end => + request(server) + .get('/hello') + .end((err, res) => { + if (err) return end(err); + end(); + }); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + }); + + it(`should create controller for each request`, async () => { + expect(HelloController.COUNTER).to.be.eql(3); + }); + + it(`should create service for each request`, async () => { + expect(UsersService.COUNTER).to.be.eql(3); + }); + + it(`should create service for each request`, async () => { + expect(HelloService.COUNTER).to.be.eql(3); + }); + + it(`should create provider for each inquirer`, async () => { + expect(Meta.COUNTER).to.be.eql(7); + }); + }); + + after(async () => { + await app.close(); + }); +}); diff --git a/integration/scopes/e2e/request-scope.spec.ts b/integration/scopes/e2e/request-scope.spec.ts new file mode 100644 index 00000000000..3e36c79e07a --- /dev/null +++ b/integration/scopes/e2e/request-scope.spec.ts @@ -0,0 +1,80 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as request from 'supertest'; +import { Guard } from '../src/hello/guards/request-scoped.guard'; +import { HelloController } from '../src/hello/hello.controller'; +import { HelloModule } from '../src/hello/hello.module'; +import { Interceptor } from '../src/hello/interceptors/logging.interceptor'; +import { UserByIdPipe } from '../src/hello/users/user-by-id.pipe'; +import { UsersService } from '../src/hello/users/users.service'; + +class Meta { + static COUNTER = 0; + constructor() { + Meta.COUNTER++; + } +} + +describe('Request scope', () => { + let server; + let app: INestApplication; + + before(async () => { + const module = await Test.createTestingModule({ + imports: [ + HelloModule.forRoot({ + provide: 'META', + useClass: Meta, + }), + ], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + }); + + describe('when one service is request scoped', () => { + before(async () => { + const performHttpCall = end => + request(server) + .get('/hello') + .end((err, res) => { + if (err) return end(err); + end(); + }); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + }); + + it(`should create controller for each request`, async () => { + expect(HelloController.COUNTER).to.be.eql(3); + }); + + it(`should create service for each request`, async () => { + expect(UsersService.COUNTER).to.be.eql(3); + }); + + it(`should share static provider across requests`, async () => { + expect(Meta.COUNTER).to.be.eql(1); + }); + + it(`should create request scoped pipe for each request`, async () => { + expect(UserByIdPipe.COUNTER).to.be.eql(3); + }); + + it(`should create request scoped interceptor for each request`, async () => { + expect(Interceptor.COUNTER).to.be.eql(3); + }); + + it(`should create request scoped guard for each request`, async () => { + expect(Guard.COUNTER).to.be.eql(3); + }); + }); + + after(async () => { + await app.close(); + }); +}); diff --git a/integration/scopes/e2e/transient-scope.spec.ts b/integration/scopes/e2e/transient-scope.spec.ts new file mode 100644 index 00000000000..ac3f1790ac8 --- /dev/null +++ b/integration/scopes/e2e/transient-scope.spec.ts @@ -0,0 +1,81 @@ +import { INestApplication, Scope } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; +import * as request from 'supertest'; +import { Guard } from '../src/transient/guards/request-scoped.guard'; +import { HelloController } from '../src/transient/hello.controller'; +import { HelloModule } from '../src/transient/hello.module'; +import { Interceptor } from '../src/transient/interceptors/logging.interceptor'; +import { UserByIdPipe } from '../src/transient/users/user-by-id.pipe'; +import { UsersService } from '../src/transient/users/users.service'; + +class Meta { + static COUNTER = 0; + constructor() { + Meta.COUNTER++; + } +} + +describe('Transient scope', () => { + let server; + let app: INestApplication; + + before(async () => { + const module = await Test.createTestingModule({ + imports: [ + HelloModule.forRoot({ + provide: 'META', + useClass: Meta, + scope: Scope.TRANSIENT, + }), + ], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + }); + + describe('when one service is request scoped', () => { + before(async () => { + const performHttpCall = end => + request(server) + .get('/hello') + .end((err, res) => { + if (err) return end(err); + end(); + }); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + await new Promise(resolve => performHttpCall(resolve)); + }); + + it(`should create controller for each request`, async () => { + expect(HelloController.COUNTER).to.be.eql(3); + }); + + it(`should create service for each request`, async () => { + expect(UsersService.COUNTER).to.be.eql(3); + }); + + it(`should create provider for each inquirer`, async () => { + expect(Meta.COUNTER).to.be.eql(7); + }); + + it(`should create transient pipe for each controller`, async () => { + expect(UserByIdPipe.COUNTER).to.be.eql(2); + }); + + it(`should create transient interceptor for each controller`, async () => { + expect(Interceptor.COUNTER).to.be.eql(2); + }); + + it(`should create transient guard for each controller`, async () => { + expect(Guard.COUNTER).to.be.eql(2); + }); + }); + + after(async () => { + await app.close(); + }); +}); diff --git a/integration/scopes/package-lock.json b/integration/scopes/package-lock.json new file mode 100644 index 00000000000..457d2258094 --- /dev/null +++ b/integration/scopes/package-lock.json @@ -0,0 +1,1775 @@ +{ + "name": "nest-typescript-starter", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@nestjs/common": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-5.3.15.tgz", + "integrity": "sha512-RL99PBD1Ov4Q6J2xdt6fb3FE79awfKmV2rtloG0rqxfnHUvjK2Dz5dqYkH2vuKQV6ae+Z+UB+A677WSCqbuTLQ==", + "requires": { + "axios": "0.17.1", + "cli-color": "1.2.0", + "deprecate": "1.0.0", + "multer": "1.3.0", + "uuid": "3.3.2" + } + }, + "@nestjs/core": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-5.3.15.tgz", + "integrity": "sha512-pbF+e7JTBxiR+vu6VmrXsP9UQqBkQrH7o6HHQdovCP1Q2/NEBhpFvzMC3mWo0/4J11qTCN06vc6sWjstpX1fMg==", + "requires": { + "@nuxtjs/opencollective": "0.1.0", + "body-parser": "1.18.3", + "cors": "2.8.4", + "express": "4.16.3", + "fast-safe-stringify": "1.2.0", + "iterare": "0.0.8", + "object-hash": "1.3.0", + "optional": "0.1.4", + "path-to-regexp": "2.2.1", + "uuid": "3.3.2" + } + }, + "@nestjs/microservices": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-5.3.15.tgz", + "integrity": "sha512-4df50DYMCitZgo1eGPn863TVmEYIRlPIHcnIawVeqKpoXfOKIMlo31MY+Bs8MuZIYnmCY9SaxhSZ2YuDT/qw+A==", + "requires": { + "iterare": "0.0.8", + "json-socket": "^0.2.1", + "optional": "0.1.4" + } + }, + "@nestjs/testing": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-5.3.15.tgz", + "integrity": "sha512-TtOX2+RW9LA3Ld+7wGA7UCTVA7ZbCr5rSN/NppiOis0g18NWYgjvOiyQ0NYe+CNHh4q/cs42U/fxAxsVFiflmg==", + "requires": { + "deprecate": "1.0.0", + "optional": "0.1.4" + } + }, + "@nestjs/websockets": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-5.3.15.tgz", + "integrity": "sha512-8aDJjLuEERHARIW+NJfOk+a7SNyk8UP8geotXYT734iNrNyL5JT4WxI/KGNBX66I2Ec2srFH25/rOwe9/zhtTg==", + "requires": { + "iterare": "0.0.8", + "socket.io": "^2.1.1" + } + }, + "@nuxtjs/opencollective": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.1.0.tgz", + "integrity": "sha512-e09TxGpTxMOfVwvVWPKNttKslnCtbvp5ofc0EwlKdA4IA8AUIyeteGraGZGs+JO4zw4y2+YxRlNN2xQ+c6KFjw==", + "requires": { + "chalk": "^2.4.1", + "consola": "^1.4.3", + "esm": "^3.0.79", + "node-fetch": "^2.2.0" + } + }, + "@types/events": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" + }, + "@types/node": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.0.tgz", + "integrity": "sha512-yF75IZxur7xs90zpmoE+ktRJGJIauORo4qblVFvfKTYSSBFRRWlrl2dO/tE4vetSS4KAvFumS+1thTf3mMZhaA==", + "dev": true + }, + "@types/pino": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-4.16.1.tgz", + "integrity": "sha512-uYEhZ3jsuiYFsPcR34fbxVlrqzqphc+QQ3fU4rWR6PXH8ka2TKvPBjtkNqj8oBHouVGf4GCRfyPb7FG2TEtPZA==", + "requires": { + "@types/events": "*", + "@types/node": "*" + }, + "dependencies": { + "@types/node": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", + "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==" + } + } + }, + "abstract-logging": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-1.0.0.tgz", + "integrity": "sha1-i33q/TEFWbwo93ck3RuzAXcnjBs=" + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "ajv": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "append-field": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-0.1.0.tgz", + "integrity": "sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo=" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "avvio": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-5.9.0.tgz", + "integrity": "sha512-bzgrSPRdU1T/AkhEuXWAA6cJCFA3zApLk+5fkpcQt4US9YAI52AFYnsGX1HSCF2bHSltEYfk7fbffYu4WnazmA==", + "requires": { + "debug": "^3.1.0", + "fastq": "^1.6.0" + } + }, + "axios": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.1.tgz", + "integrity": "sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0=", + "requires": { + "follow-redirects": "^1.2.5", + "is-buffer": "^1.1.5" + } + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "blob": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "class-transformer": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.1.10.tgz", + "integrity": "sha512-QiNdUxEvTBiUtc0KiapGVHhgaqGQVEhOfL9UEBnb9xRfcwmDJT5ijIDwcwJUTwXaT/kGvZZB4JCGsiuR5adX6g==" + }, + "class-validator": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.7.3.tgz", + "integrity": "sha512-aRRlS1WlQ+52aHlmDCDX5dLwtpbg9is7i9yYhzQosTAVs86nX0Um8hb7ChTwMn7jfpyxxjAZpBrlrAc2tqNpYA==", + "requires": { + "validator": "^7.0.0" + } + }, + "cli-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz", + "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=", + "requires": { + "ansi-regex": "^2.1.1", + "d": "1", + "es5-ext": "^0.10.12", + "es6-iterator": "2", + "memoizee": "^0.4.3", + "timers-ext": "0.1" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "consola": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/consola/-/consola-1.4.4.tgz", + "integrity": "sha512-6ZCi6LpbwGml3g8C8iXIuSf9yZAWoRAXodcHxBWRVvy42uKe4z7AG4JB4v46LEmgtPXv2rIqR6wVD+sxixDD/A==", + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "lodash": "^4.17.5", + "std-env": "^1.1.0" + } + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "requires": { + "object-assign": "^4", + "vary": "^1" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + } + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "requires": { + "es5-ext": "^0.10.9" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "deprecate": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/deprecate/-/deprecate-1.0.0.tgz", + "integrity": "sha1-ZhSQ7SQokWpsiIPYg05WRvTkpKg=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.0.tgz", + "integrity": "sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw==", + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", + "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.4", + "has-binary2": "~1.0.2" + } + }, + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esm": { + "version": "3.0.84", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.0.84.tgz", + "integrity": "sha512-SzSGoZc17S7P+12R9cg21Bdb7eybX25RnIeRZ80xZs+VZ3kdQKzqTp2k4hZJjR7p9l0186TTXSgrxzlMDBktlw==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "express": { + "version": "4.16.3", + "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-decode-uri-component": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.0.tgz", + "integrity": "sha512-WQSYVKn6tDW/3htASeUkrx5LcnuTENQIZQPCVlwdnvIJ7bYtSpoJYq38MgUJnx1CQIR1gjZ8HJxAEcN4gqugBg==" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-json-stringify": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-1.9.1.tgz", + "integrity": "sha512-vBXDKcJtoruWZeoqq/ViqJ3VxZH5LimgBTczIPe3x6m6XAgNr7fpAdPml81K+kb+rVl4hTJa/6iMVB62Fus+1A==", + "requires": { + "ajv": "^6.5.4", + "deepmerge": "^2.1.1" + } + }, + "fast-safe-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.2.0.tgz", + "integrity": "sha1-69QmZv0Y/k8rpPDSlQZfP4XK3pY=" + }, + "fastify": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-1.12.1.tgz", + "integrity": "sha512-hpOJQyUSF0WtEK7ED1PZNf99t64Gs9I7KhhwqnX9QrQZliPuY5FXYOKjfRo6H8qW/DMPEnrWyCgbajfl1zHULg==", + "requires": { + "@types/pino": "^4.16.0", + "abstract-logging": "^1.0.0", + "ajv": "^6.5.4", + "avvio": "^5.8.0", + "end-of-stream": "^1.4.1", + "fast-json-stringify": "^1.8.0", + "find-my-way": "^1.15.3", + "flatstr": "^1.0.8", + "light-my-request": "^3.0.0", + "middie": "^3.1.0", + "pino": "^4.17.3", + "proxy-addr": "^2.0.3", + "tiny-lru": "^1.6.1" + } + }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "requires": { + "reusify": "^1.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "find-my-way": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-1.15.4.tgz", + "integrity": "sha512-3XPCOhoGMPK6ELgUHd5BuNxsL+fTNM0EIrTlcLwjT2uZq22UHL4IQt5N54PIQblk164ioZATRov9mcuA4RB3eA==", + "requires": { + "fast-decode-uri-component": "^1.0.0", + "safe-regex": "^1.1.0", + "semver-store": "^0.3.0" + } + }, + "flatstr": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.8.tgz", + "integrity": "sha512-YXblbv/vc1zuVVUtnKl1hPqqk7TalZCppnKE7Pr8FI/Rp48vzckS/4SJ4Y9O9RNiI82Vcw/FydmtqdQOg1Dpqw==" + }, + "follow-redirects": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", + "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", + "requires": { + "debug": "=3.1.0" + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "requires": { + "ci-info": "^1.5.0" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "iterare": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-0.0.8.tgz", + "integrity": "sha1-qWmoCh+/9rePKHdllNe8K9+raq0=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-socket": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/json-socket/-/json-socket-0.2.1.tgz", + "integrity": "sha1-JuftjMEx8XqgE2wyBo9HO12++yI=" + }, + "light-my-request": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-3.1.0.tgz", + "integrity": "sha512-ZSFO3XnQNSKsHR/E2ZMga5btdiIa3sNoT6CZIZ8Hr1VHJWBNcRRurVYpQlaJqvQqwg3aOl09QpVOnjB9ajnYHQ==", + "requires": { + "ajv": "^6.0.0", + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", + "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "requires": { + "es5-ext": "~0.10.2" + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "middie": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/middie/-/middie-3.2.0.tgz", + "integrity": "sha512-anXJ0QJfQcgneQvcWAJBwVvNckRLI68zWNEUv/7/7z/Wb/UMFTHmugpM93T4Q75+DclC9FHdms8cTseDQEV3yA==", + "requires": { + "path-to-regexp": "^2.0.0", + "reusify": "^1.0.2" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "requires": { + "mime-db": "~1.36.0" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "multer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz", + "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=", + "requires": { + "append-field": "^0.1.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.0", + "mkdirp": "^0.5.1", + "object-assign": "^3.0.0", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + }, + "node-fetch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", + "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==" + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "object-hash": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", + "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optional": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", + "integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==" + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + }, + "pino": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/pino/-/pino-4.17.6.tgz", + "integrity": "sha512-LFDwmhyWLBnmwO/2UFbWu1jEGVDzaPupaVdx0XcZ3tIAx1EDEBauzxXf2S0UcFK7oe+X9MApjH0hx9U1XMgfCA==", + "requires": { + "chalk": "^2.4.1", + "fast-json-parse": "^1.0.3", + "fast-safe-stringify": "^1.2.3", + "flatstr": "^1.0.5", + "pino-std-serializers": "^2.0.0", + "pump": "^3.0.0", + "quick-format-unescaped": "^1.1.2", + "split2": "^2.2.0" + }, + "dependencies": { + "fast-safe-stringify": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.2.3.tgz", + "integrity": "sha512-QJYT/i0QYoiZBQ71ivxdyTqkwKkQ0oxACXHYxH2zYHJEgzi2LsbjgvtzTbLi1SZcF190Db2YP7I7eTsU2egOlw==" + } + } + }, + "pino-std-serializers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.3.0.tgz", + "integrity": "sha512-klfGoOsP6sJH7ON796G4xoUSx2fkpFgKHO4YVVO2zmz31jR+etzc/QzGJILaOIiCD6HTCFgkPx+XN8nk+ruqPw==" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "quick-format-unescaped": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-1.1.2.tgz", + "integrity": "sha1-DKWB3jF0vs7yWsPC6JVjQjgdtpg=", + "requires": { + "fast-safe-stringify": "^1.0.8" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rxjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.0.0.tgz", + "integrity": "sha512-2MgLQr1zvks8+Kip4T6hcJdiBhV+SIvxguoWjhwtSpNPTp/5e09HJbgclCwR/nW0yWzhubM+6Q0prl8G5RuoBA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "requires": { + "through2": "^2.0.2" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "std-env": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-1.3.1.tgz", + "integrity": "sha512-KI2F2pPJpd3lHjng+QLezu0eq+QDtXcv1um016mhOPAJFHKL+09ykK5PUBWta2pZDC8BVV0VPya08A15bUXSLQ==", + "requires": { + "is-ci": "^1.1.0" + } + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "supertest": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.3.0.tgz", + "integrity": "sha512-dMQSzYdaZRSANH5LL8kX3UpgK9G1LRh/jnggs/TI0W2Sz7rkMx9Y48uia3K9NgcaWEV28tYkBnXE4tiFC77ygQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, + "tiny-lru": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-1.6.1.tgz", + "integrity": "sha512-m8oyPnHjnQlbDk8+MCw33qRMp6+BxPxoayN9C743VToeyQ5zZV6F6vkklrYVEI0z9MQ3+jmc+22tKmvPg4gmoA==" + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "ts-node": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-6.2.0.tgz", + "integrity": "sha512-ZNT+OEGfUNVMGkpIaDJJ44Zq3Yr0bkU/ugN1PHbU+/01Z7UV1fsELRiTx1KuQNvQ1A3pGh3y25iYF6jXgxV21A==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==" + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==" + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "validator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-7.2.0.tgz", + "integrity": "sha512-c8NGTUYeBEcUIGeMppmNVKHE7wwfm3mYbNZxV+c5mlv9fDHI7Ad3p07qfNrn/CvpdkK2k61fOLRO2sTEhgQXmg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } +} diff --git a/integration/scopes/package.json b/integration/scopes/package.json new file mode 100644 index 00000000000..8dc5c83d68e --- /dev/null +++ b/integration/scopes/package.json @@ -0,0 +1,27 @@ +{ + "name": "nest-typescript-starter", + "version": "1.0.0", + "description": "Nest TypeScript starter repository", + "license": "MIT", + "scripts": { + "start": "ts-node src/main" + }, + "dependencies": { + "@nestjs/common": "^5.0.0", + "@nestjs/core": "^5.0.0", + "@nestjs/microservices": "^5.0.0", + "@nestjs/testing": "^5.0.0", + "@nestjs/websockets": "^5.0.0", + "class-transformer": "^0.1.7", + "class-validator": "^0.7.2", + "fastify": "^1.1.1", + "reflect-metadata": "^0.1.12", + "rxjs": "^6.0.0", + "typescript": "^3.1.0" + }, + "devDependencies": { + "@types/node": "^7.0.41", + "supertest": "^3.0.0", + "ts-node": "^6.0.0" + } +} diff --git a/integration/scopes/src/app.module.ts b/integration/scopes/src/app.module.ts new file mode 100644 index 00000000000..02a11788bdb --- /dev/null +++ b/integration/scopes/src/app.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { HelloModule } from './hello/hello.module'; + +@Module({ + imports: [HelloModule.forRoot({ provide: 'META', useValue: true })], +}) +export class ApplicationModule {} diff --git a/integration/scopes/src/circular-hello/dto/test.dto.ts b/integration/scopes/src/circular-hello/dto/test.dto.ts new file mode 100644 index 00000000000..b3a30f67111 --- /dev/null +++ b/integration/scopes/src/circular-hello/dto/test.dto.ts @@ -0,0 +1,10 @@ +import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; + +export class TestDto { + @IsString() + @IsNotEmpty() + string: string; + + @IsNumber() + number: number; +} diff --git a/integration/scopes/src/circular-hello/guards/request-scoped.guard.ts b/integration/scopes/src/circular-hello/guards/request-scoped.guard.ts new file mode 100644 index 00000000000..7e5fe1788ea --- /dev/null +++ b/integration/scopes/src/circular-hello/guards/request-scoped.guard.ts @@ -0,0 +1,21 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.REQUEST }) +export class Guard implements CanActivate { + static COUNTER = 0; + constructor() { + Guard.COUNTER++; + } + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + return true; + } +} diff --git a/integration/scopes/src/circular-hello/hello.controller.ts b/integration/scopes/src/circular-hello/hello.controller.ts new file mode 100644 index 00000000000..5b4af7bf215 --- /dev/null +++ b/integration/scopes/src/circular-hello/hello.controller.ts @@ -0,0 +1,30 @@ +import { + Controller, + Get, + Param, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { Guard } from './guards/request-scoped.guard'; +import { HelloService } from './hello.service'; +import { Interceptor } from './interceptors/logging.interceptor'; +import { UserByIdPipe } from './users/user-by-id.pipe'; +import { UsersService } from './users/users.service'; + +@Controller('hello') +export class HelloController { + static COUNTER = 0; + constructor( + private readonly helloService: HelloService, + private readonly usersService: UsersService, + ) { + HelloController.COUNTER++; + } + + @UseGuards(Guard) + @UseInterceptors(Interceptor) + @Get() + greeting(@Param('id', UserByIdPipe) id): string { + return this.helloService.greeting(); + } +} diff --git a/integration/scopes/src/circular-hello/hello.module.ts b/integration/scopes/src/circular-hello/hello.module.ts new file mode 100644 index 00000000000..1a0d0bbfd7c --- /dev/null +++ b/integration/scopes/src/circular-hello/hello.module.ts @@ -0,0 +1,19 @@ +import { DynamicModule, Inject, Module, Provider } from '@nestjs/common'; +import { HelloController } from './hello.controller'; +import { HelloService } from './hello.service'; +import { UsersService } from './users/users.service'; + +@Module({ + controllers: [HelloController], + providers: [HelloService, UsersService], +}) +export class HelloModule { + constructor(@Inject('META') private readonly meta) {} + + static forRoot(meta: Provider): DynamicModule { + return { + module: HelloModule, + providers: [meta], + }; + } +} diff --git a/integration/scopes/src/circular-hello/hello.service.ts b/integration/scopes/src/circular-hello/hello.service.ts new file mode 100644 index 00000000000..a0a346ebf97 --- /dev/null +++ b/integration/scopes/src/circular-hello/hello.service.ts @@ -0,0 +1,13 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class HelloService { + static COUNTER = 0; + constructor(@Inject('META') private readonly meta) { + HelloService.COUNTER++; + } + + greeting(): string { + return 'Hello world!'; + } +} diff --git a/integration/scopes/src/circular-hello/interceptors/logging.interceptor.ts b/integration/scopes/src/circular-hello/interceptors/logging.interceptor.ts new file mode 100644 index 00000000000..b8b4e57934c --- /dev/null +++ b/integration/scopes/src/circular-hello/interceptors/logging.interceptor.ts @@ -0,0 +1,19 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.REQUEST }) +export class Interceptor implements NestInterceptor { + static COUNTER = 0; + constructor() { + Interceptor.COUNTER++; + } + intercept(context: ExecutionContext, call: CallHandler): Observable { + return call.handle(); + } +} diff --git a/integration/scopes/src/circular-hello/users/user-by-id.pipe.ts b/integration/scopes/src/circular-hello/users/user-by-id.pipe.ts new file mode 100644 index 00000000000..2b81cb6fbb5 --- /dev/null +++ b/integration/scopes/src/circular-hello/users/user-by-id.pipe.ts @@ -0,0 +1,14 @@ +import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'; +import { UsersService } from './users.service'; + +@Injectable() +export class UserByIdPipe implements PipeTransform { + static COUNTER = 0; + constructor(private readonly usersService: UsersService) { + UserByIdPipe.COUNTER++; + } + + transform(value: string, metadata: ArgumentMetadata) { + return this.usersService.findById(value); + } +} diff --git a/integration/scopes/src/circular-hello/users/users.service.ts b/integration/scopes/src/circular-hello/users/users.service.ts new file mode 100644 index 00000000000..f1cb617e11d --- /dev/null +++ b/integration/scopes/src/circular-hello/users/users.service.ts @@ -0,0 +1,13 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class UsersService { + static COUNTER = 0; + constructor(@Inject('META') private readonly meta) { + UsersService.COUNTER++; + } + + findById(id: string) { + return { id }; + } +} diff --git a/integration/scopes/src/circular-transient/dto/test.dto.ts b/integration/scopes/src/circular-transient/dto/test.dto.ts new file mode 100644 index 00000000000..b3a30f67111 --- /dev/null +++ b/integration/scopes/src/circular-transient/dto/test.dto.ts @@ -0,0 +1,10 @@ +import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; + +export class TestDto { + @IsString() + @IsNotEmpty() + string: string; + + @IsNumber() + number: number; +} diff --git a/integration/scopes/src/circular-transient/guards/request-scoped.guard.ts b/integration/scopes/src/circular-transient/guards/request-scoped.guard.ts new file mode 100644 index 00000000000..031cb2ce002 --- /dev/null +++ b/integration/scopes/src/circular-transient/guards/request-scoped.guard.ts @@ -0,0 +1,21 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class Guard implements CanActivate { + static COUNTER = 0; + constructor() { + Guard.COUNTER++; + } + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + return true; + } +} diff --git a/integration/scopes/src/circular-transient/hello.controller.ts b/integration/scopes/src/circular-transient/hello.controller.ts new file mode 100644 index 00000000000..5b4af7bf215 --- /dev/null +++ b/integration/scopes/src/circular-transient/hello.controller.ts @@ -0,0 +1,30 @@ +import { + Controller, + Get, + Param, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { Guard } from './guards/request-scoped.guard'; +import { HelloService } from './hello.service'; +import { Interceptor } from './interceptors/logging.interceptor'; +import { UserByIdPipe } from './users/user-by-id.pipe'; +import { UsersService } from './users/users.service'; + +@Controller('hello') +export class HelloController { + static COUNTER = 0; + constructor( + private readonly helloService: HelloService, + private readonly usersService: UsersService, + ) { + HelloController.COUNTER++; + } + + @UseGuards(Guard) + @UseInterceptors(Interceptor) + @Get() + greeting(@Param('id', UserByIdPipe) id): string { + return this.helloService.greeting(); + } +} diff --git a/integration/scopes/src/circular-transient/hello.module.ts b/integration/scopes/src/circular-transient/hello.module.ts new file mode 100644 index 00000000000..6cdc92c5f27 --- /dev/null +++ b/integration/scopes/src/circular-transient/hello.module.ts @@ -0,0 +1,20 @@ +import { DynamicModule, Inject, Module, Provider } from '@nestjs/common'; +import { HelloController } from './hello.controller'; +import { HelloService } from './hello.service'; +import { TestController } from './test.controller'; +import { UsersService } from './users/users.service'; + +@Module({ + controllers: [HelloController, TestController], + providers: [HelloService, UsersService], +}) +export class HelloModule { + constructor(@Inject('META') private readonly meta) {} + + static forRoot(meta: Provider): DynamicModule { + return { + module: HelloModule, + providers: [meta], + }; + } +} diff --git a/integration/scopes/src/circular-transient/hello.service.ts b/integration/scopes/src/circular-transient/hello.service.ts new file mode 100644 index 00000000000..a0a346ebf97 --- /dev/null +++ b/integration/scopes/src/circular-transient/hello.service.ts @@ -0,0 +1,13 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class HelloService { + static COUNTER = 0; + constructor(@Inject('META') private readonly meta) { + HelloService.COUNTER++; + } + + greeting(): string { + return 'Hello world!'; + } +} diff --git a/integration/scopes/src/circular-transient/interceptors/logging.interceptor.ts b/integration/scopes/src/circular-transient/interceptors/logging.interceptor.ts new file mode 100644 index 00000000000..8887d05b9a3 --- /dev/null +++ b/integration/scopes/src/circular-transient/interceptors/logging.interceptor.ts @@ -0,0 +1,19 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class Interceptor implements NestInterceptor { + static COUNTER = 0; + constructor() { + Interceptor.COUNTER++; + } + intercept(context: ExecutionContext, call: CallHandler): Observable { + return call.handle(); + } +} diff --git a/integration/scopes/src/circular-transient/test.controller.ts b/integration/scopes/src/circular-transient/test.controller.ts new file mode 100644 index 00000000000..572ea13a90f --- /dev/null +++ b/integration/scopes/src/circular-transient/test.controller.ts @@ -0,0 +1,20 @@ +import { + Controller, + Get, + Param, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { Guard } from './guards/request-scoped.guard'; +import { Interceptor } from './interceptors/logging.interceptor'; +import { UserByIdPipe } from './users/user-by-id.pipe'; + +@Controller('test') +export class TestController { + @UseGuards(Guard) + @UseInterceptors(Interceptor) + @Get() + greeting(@Param('id', UserByIdPipe) id): string { + return 'hey'; + } +} diff --git a/integration/scopes/src/circular-transient/users/user-by-id.pipe.ts b/integration/scopes/src/circular-transient/users/user-by-id.pipe.ts new file mode 100644 index 00000000000..7d06ff9d5fd --- /dev/null +++ b/integration/scopes/src/circular-transient/users/user-by-id.pipe.ts @@ -0,0 +1,18 @@ +import { + ArgumentMetadata, + Injectable, + PipeTransform, + Scope, +} from '@nestjs/common'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class UserByIdPipe implements PipeTransform { + static COUNTER = 0; + constructor() { + UserByIdPipe.COUNTER++; + } + + transform(value: string, metadata: ArgumentMetadata) { + return value; + } +} diff --git a/integration/scopes/src/circular-transient/users/users.service.ts b/integration/scopes/src/circular-transient/users/users.service.ts new file mode 100644 index 00000000000..f1cb617e11d --- /dev/null +++ b/integration/scopes/src/circular-transient/users/users.service.ts @@ -0,0 +1,13 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class UsersService { + static COUNTER = 0; + constructor(@Inject('META') private readonly meta) { + UsersService.COUNTER++; + } + + findById(id: string) { + return { id }; + } +} diff --git a/integration/scopes/src/hello/dto/test.dto.ts b/integration/scopes/src/hello/dto/test.dto.ts new file mode 100644 index 00000000000..b3a30f67111 --- /dev/null +++ b/integration/scopes/src/hello/dto/test.dto.ts @@ -0,0 +1,10 @@ +import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; + +export class TestDto { + @IsString() + @IsNotEmpty() + string: string; + + @IsNumber() + number: number; +} diff --git a/integration/scopes/src/hello/guards/request-scoped.guard.ts b/integration/scopes/src/hello/guards/request-scoped.guard.ts new file mode 100644 index 00000000000..7e5fe1788ea --- /dev/null +++ b/integration/scopes/src/hello/guards/request-scoped.guard.ts @@ -0,0 +1,21 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.REQUEST }) +export class Guard implements CanActivate { + static COUNTER = 0; + constructor() { + Guard.COUNTER++; + } + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + return true; + } +} diff --git a/integration/scopes/src/hello/hello.controller.ts b/integration/scopes/src/hello/hello.controller.ts new file mode 100644 index 00000000000..5b4af7bf215 --- /dev/null +++ b/integration/scopes/src/hello/hello.controller.ts @@ -0,0 +1,30 @@ +import { + Controller, + Get, + Param, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { Guard } from './guards/request-scoped.guard'; +import { HelloService } from './hello.service'; +import { Interceptor } from './interceptors/logging.interceptor'; +import { UserByIdPipe } from './users/user-by-id.pipe'; +import { UsersService } from './users/users.service'; + +@Controller('hello') +export class HelloController { + static COUNTER = 0; + constructor( + private readonly helloService: HelloService, + private readonly usersService: UsersService, + ) { + HelloController.COUNTER++; + } + + @UseGuards(Guard) + @UseInterceptors(Interceptor) + @Get() + greeting(@Param('id', UserByIdPipe) id): string { + return this.helloService.greeting(); + } +} diff --git a/integration/scopes/src/hello/hello.module.ts b/integration/scopes/src/hello/hello.module.ts new file mode 100644 index 00000000000..1a0d0bbfd7c --- /dev/null +++ b/integration/scopes/src/hello/hello.module.ts @@ -0,0 +1,19 @@ +import { DynamicModule, Inject, Module, Provider } from '@nestjs/common'; +import { HelloController } from './hello.controller'; +import { HelloService } from './hello.service'; +import { UsersService } from './users/users.service'; + +@Module({ + controllers: [HelloController], + providers: [HelloService, UsersService], +}) +export class HelloModule { + constructor(@Inject('META') private readonly meta) {} + + static forRoot(meta: Provider): DynamicModule { + return { + module: HelloModule, + providers: [meta], + }; + } +} diff --git a/integration/scopes/src/hello/hello.service.ts b/integration/scopes/src/hello/hello.service.ts new file mode 100644 index 00000000000..83cf995dde1 --- /dev/null +++ b/integration/scopes/src/hello/hello.service.ts @@ -0,0 +1,10 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class HelloService { + constructor(@Inject('META') private readonly meta) {} + + greeting(): string { + return 'Hello world!'; + } +} diff --git a/integration/scopes/src/hello/interceptors/logging.interceptor.ts b/integration/scopes/src/hello/interceptors/logging.interceptor.ts new file mode 100644 index 00000000000..b8b4e57934c --- /dev/null +++ b/integration/scopes/src/hello/interceptors/logging.interceptor.ts @@ -0,0 +1,19 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.REQUEST }) +export class Interceptor implements NestInterceptor { + static COUNTER = 0; + constructor() { + Interceptor.COUNTER++; + } + intercept(context: ExecutionContext, call: CallHandler): Observable { + return call.handle(); + } +} diff --git a/integration/scopes/src/hello/users/user-by-id.pipe.ts b/integration/scopes/src/hello/users/user-by-id.pipe.ts new file mode 100644 index 00000000000..2b81cb6fbb5 --- /dev/null +++ b/integration/scopes/src/hello/users/user-by-id.pipe.ts @@ -0,0 +1,14 @@ +import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'; +import { UsersService } from './users.service'; + +@Injectable() +export class UserByIdPipe implements PipeTransform { + static COUNTER = 0; + constructor(private readonly usersService: UsersService) { + UserByIdPipe.COUNTER++; + } + + transform(value: string, metadata: ArgumentMetadata) { + return this.usersService.findById(value); + } +} diff --git a/integration/scopes/src/hello/users/users.service.ts b/integration/scopes/src/hello/users/users.service.ts new file mode 100644 index 00000000000..f1cb617e11d --- /dev/null +++ b/integration/scopes/src/hello/users/users.service.ts @@ -0,0 +1,13 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class UsersService { + static COUNTER = 0; + constructor(@Inject('META') private readonly meta) { + UsersService.COUNTER++; + } + + findById(id: string) { + return { id }; + } +} diff --git a/integration/scopes/src/main.ts b/integration/scopes/src/main.ts new file mode 100644 index 00000000000..afb93ba078e --- /dev/null +++ b/integration/scopes/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { ApplicationModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(ApplicationModule); + await app.listen(3000); +} +bootstrap(); diff --git a/integration/scopes/src/transient/dto/test.dto.ts b/integration/scopes/src/transient/dto/test.dto.ts new file mode 100644 index 00000000000..b3a30f67111 --- /dev/null +++ b/integration/scopes/src/transient/dto/test.dto.ts @@ -0,0 +1,10 @@ +import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; + +export class TestDto { + @IsString() + @IsNotEmpty() + string: string; + + @IsNumber() + number: number; +} diff --git a/integration/scopes/src/transient/guards/request-scoped.guard.ts b/integration/scopes/src/transient/guards/request-scoped.guard.ts new file mode 100644 index 00000000000..031cb2ce002 --- /dev/null +++ b/integration/scopes/src/transient/guards/request-scoped.guard.ts @@ -0,0 +1,21 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class Guard implements CanActivate { + static COUNTER = 0; + constructor() { + Guard.COUNTER++; + } + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + return true; + } +} diff --git a/integration/scopes/src/transient/hello.controller.ts b/integration/scopes/src/transient/hello.controller.ts new file mode 100644 index 00000000000..5b4af7bf215 --- /dev/null +++ b/integration/scopes/src/transient/hello.controller.ts @@ -0,0 +1,30 @@ +import { + Controller, + Get, + Param, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { Guard } from './guards/request-scoped.guard'; +import { HelloService } from './hello.service'; +import { Interceptor } from './interceptors/logging.interceptor'; +import { UserByIdPipe } from './users/user-by-id.pipe'; +import { UsersService } from './users/users.service'; + +@Controller('hello') +export class HelloController { + static COUNTER = 0; + constructor( + private readonly helloService: HelloService, + private readonly usersService: UsersService, + ) { + HelloController.COUNTER++; + } + + @UseGuards(Guard) + @UseInterceptors(Interceptor) + @Get() + greeting(@Param('id', UserByIdPipe) id): string { + return this.helloService.greeting(); + } +} diff --git a/integration/scopes/src/transient/hello.module.ts b/integration/scopes/src/transient/hello.module.ts new file mode 100644 index 00000000000..6cdc92c5f27 --- /dev/null +++ b/integration/scopes/src/transient/hello.module.ts @@ -0,0 +1,20 @@ +import { DynamicModule, Inject, Module, Provider } from '@nestjs/common'; +import { HelloController } from './hello.controller'; +import { HelloService } from './hello.service'; +import { TestController } from './test.controller'; +import { UsersService } from './users/users.service'; + +@Module({ + controllers: [HelloController, TestController], + providers: [HelloService, UsersService], +}) +export class HelloModule { + constructor(@Inject('META') private readonly meta) {} + + static forRoot(meta: Provider): DynamicModule { + return { + module: HelloModule, + providers: [meta], + }; + } +} diff --git a/integration/scopes/src/transient/hello.service.ts b/integration/scopes/src/transient/hello.service.ts new file mode 100644 index 00000000000..83cf995dde1 --- /dev/null +++ b/integration/scopes/src/transient/hello.service.ts @@ -0,0 +1,10 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class HelloService { + constructor(@Inject('META') private readonly meta) {} + + greeting(): string { + return 'Hello world!'; + } +} diff --git a/integration/scopes/src/transient/interceptors/logging.interceptor.ts b/integration/scopes/src/transient/interceptors/logging.interceptor.ts new file mode 100644 index 00000000000..8887d05b9a3 --- /dev/null +++ b/integration/scopes/src/transient/interceptors/logging.interceptor.ts @@ -0,0 +1,19 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, + Scope, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class Interceptor implements NestInterceptor { + static COUNTER = 0; + constructor() { + Interceptor.COUNTER++; + } + intercept(context: ExecutionContext, call: CallHandler): Observable { + return call.handle(); + } +} diff --git a/integration/scopes/src/transient/test.controller.ts b/integration/scopes/src/transient/test.controller.ts new file mode 100644 index 00000000000..572ea13a90f --- /dev/null +++ b/integration/scopes/src/transient/test.controller.ts @@ -0,0 +1,20 @@ +import { + Controller, + Get, + Param, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { Guard } from './guards/request-scoped.guard'; +import { Interceptor } from './interceptors/logging.interceptor'; +import { UserByIdPipe } from './users/user-by-id.pipe'; + +@Controller('test') +export class TestController { + @UseGuards(Guard) + @UseInterceptors(Interceptor) + @Get() + greeting(@Param('id', UserByIdPipe) id): string { + return 'hey'; + } +} diff --git a/integration/scopes/src/transient/users/user-by-id.pipe.ts b/integration/scopes/src/transient/users/user-by-id.pipe.ts new file mode 100644 index 00000000000..7d06ff9d5fd --- /dev/null +++ b/integration/scopes/src/transient/users/user-by-id.pipe.ts @@ -0,0 +1,18 @@ +import { + ArgumentMetadata, + Injectable, + PipeTransform, + Scope, +} from '@nestjs/common'; + +@Injectable({ scope: Scope.TRANSIENT }) +export class UserByIdPipe implements PipeTransform { + static COUNTER = 0; + constructor() { + UserByIdPipe.COUNTER++; + } + + transform(value: string, metadata: ArgumentMetadata) { + return value; + } +} diff --git a/integration/scopes/src/transient/users/users.service.ts b/integration/scopes/src/transient/users/users.service.ts new file mode 100644 index 00000000000..f1cb617e11d --- /dev/null +++ b/integration/scopes/src/transient/users/users.service.ts @@ -0,0 +1,13 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; + +@Injectable({ scope: Scope.REQUEST }) +export class UsersService { + static COUNTER = 0; + constructor(@Inject('META') private readonly meta) { + UsersService.COUNTER++; + } + + findById(id: string) { + return { id }; + } +} diff --git a/integration/scopes/tsconfig.json b/integration/scopes/tsconfig.json new file mode 100644 index 00000000000..c6354c56487 --- /dev/null +++ b/integration/scopes/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "allowJs": true, + "outDir": "./dist" + }, + "include": [ + "src/**/*", + "e2e/**/*" + ], + "exclude": [ + "node_modules", + ] +} \ No newline at end of file diff --git a/integration/scopes/tslint.json b/integration/scopes/tslint.json new file mode 100644 index 00000000000..fbbb57c94a7 --- /dev/null +++ b/integration/scopes/tslint.json @@ -0,0 +1,53 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": { + "no-unused-expression": true + }, + "rules": { + "eofline": false, + "quotemark": [ + true, + "single" + ], + "ordered-imports": [ + false + ], + "max-line-length": [ + 150 + ], + "member-ordering": [ + false + ], + "curly": false, + "interface-name": [ + false + ], + "array-type": [ + false + ], + "member-access": [ + false + ], + "no-empty-interface": false, + "no-empty": false, + "arrow-parens": false, + "object-literal-sort-keys": false, + "no-unused-expression": false, + "max-classes-per-file": [ + false + ], + "variable-name": [ + false + ], + "one-line": [ + false + ], + "one-variable-per-declaration": [ + false + ] + }, + "rulesDirectory": [] +} \ No newline at end of file diff --git a/packages/common/interfaces/scope-options.interface.ts b/packages/common/interfaces/scope-options.interface.ts index cb18fb2a149..b59589de8d4 100644 --- a/packages/common/interfaces/scope-options.interface.ts +++ b/packages/common/interfaces/scope-options.interface.ts @@ -1,8 +1,7 @@ export enum Scope { DEFAULT, + TRANSIENT, REQUEST, - /** @experimental */ - LAZY_ASYNC, } export interface ScopeOptions { diff --git a/packages/common/test/utils/http-exception-body.util.spec.ts b/packages/common/test/utils/http-exception-body.util.spec.ts new file mode 100644 index 00000000000..cb76869bc40 --- /dev/null +++ b/packages/common/test/utils/http-exception-body.util.spec.ts @@ -0,0 +1,35 @@ +import { expect } from 'chai'; +import { createHttpExceptionBody } from '../../utils/http-exception-body.util'; + +describe('createHttpExceptionBody', () => { + describe('when object has been passed', () => { + it('should return expected object', () => { + const object = { + message: 'test', + }; + expect(createHttpExceptionBody(object)).to.be.eql(object); + }); + }); + describe('when string has been passed', () => { + it('should return expected object', () => { + const message = 'test'; + const status = 500; + const error = 'error'; + expect(createHttpExceptionBody(message, error, status)).to.be.eql({ + message, + error, + statusCode: status, + }); + }); + }); + describe('when nil has been passed', () => { + it('should return expected object', () => { + const status = 500; + const error = 'error'; + expect(createHttpExceptionBody(null, error, status)).to.be.eql({ + error, + statusCode: status, + }); + }); + }); +}); diff --git a/packages/common/test/utils/random-string-generator.util.spec.ts b/packages/common/test/utils/random-string-generator.util.spec.ts new file mode 100644 index 00000000000..195b123a487 --- /dev/null +++ b/packages/common/test/utils/random-string-generator.util.spec.ts @@ -0,0 +1,8 @@ +import { expect } from 'chai'; +import { randomStringGenerator } from '../../utils/random-string-generator.util'; + +describe('randomStringGenerator', () => { + it('should generate random string', () => { + expect(randomStringGenerator()).to.be.string; + }); +}); diff --git a/packages/common/utils/http-exception-body.util.ts b/packages/common/utils/http-exception-body.util.ts index 7434cef5658..7cfe260476f 100644 --- a/packages/common/utils/http-exception-body.util.ts +++ b/packages/common/utils/http-exception-body.util.ts @@ -2,8 +2,8 @@ import { isObject } from './shared.utils'; export const createHttpExceptionBody = ( message: object | string, - error: string, - statusCode: number, + error?: string, + statusCode?: number, ) => { if (!message) { return { statusCode, error }; diff --git a/packages/core/application-config.ts b/packages/core/application-config.ts index fcd993729ba..9c80b847bd6 100644 --- a/packages/core/application-config.ts +++ b/packages/core/application-config.ts @@ -1,9 +1,9 @@ import { - PipeTransform, - WebSocketAdapter, + CanActivate, ExceptionFilter, NestInterceptor, - CanActivate, + PipeTransform, + WebSocketAdapter, } from '@nestjs/common'; import { ConfigurationProvider } from '@nestjs/common/interfaces/configuration-provider.interface'; diff --git a/packages/core/exceptions/base-exception-filter-context.ts b/packages/core/exceptions/base-exception-filter-context.ts index d321f156179..1dd8b2bc7dc 100644 --- a/packages/core/exceptions/base-exception-filter-context.ts +++ b/packages/core/exceptions/base-exception-filter-context.ts @@ -18,6 +18,7 @@ export class BaseExceptionFilterContext extends ContextCreator { public createConcreteContext( metadata: T, contextId = STATIC_CONTEXT, + inquirerId?: string, ): R { if (isEmpty(metadata)) { return [] as R; @@ -26,7 +27,8 @@ export class BaseExceptionFilterContext extends ContextCreator { .filter( instance => instance && (isFunction(instance.catch) || instance.name), ) - .map(filter => this.getFilterInstance(filter, contextId)) + .map(filter => this.getFilterInstance(filter, contextId, inquirerId)) + .filter(item => !!item) .map(instance => ({ func: instance.catch.bind(instance), exceptionMetatypes: this.reflectCatchExceptions(instance), @@ -37,16 +39,20 @@ export class BaseExceptionFilterContext extends ContextCreator { public getFilterInstance( filter: Function | ExceptionFilter, contextId = STATIC_CONTEXT, - ) { + inquirerId?: string, + ): ExceptionFilter | null { const isObject = (filter as ExceptionFilter).catch; if (isObject) { - return filter; + return filter as ExceptionFilter; } const instanceWrapper = this.getInstanceByMetatype(filter); if (!instanceWrapper) { return null; } - const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + const instanceHost = instanceWrapper.getInstanceByContextId( + contextId, + inquirerId, + ); return instanceHost && instanceHost.instance; } diff --git a/packages/core/exceptions/base-exception-filter.ts b/packages/core/exceptions/base-exception-filter.ts index 00ff47065dd..6701318f5b0 100644 --- a/packages/core/exceptions/base-exception-filter.ts +++ b/packages/core/exceptions/base-exception-filter.ts @@ -10,21 +10,21 @@ import { } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; import { MESSAGES } from '../constants'; -import { ApplicationReferenceHost } from './../helpers/application-ref-host'; +import { HttpAdapterHost } from '../helpers'; export class BaseExceptionFilter implements ExceptionFilter { private static readonly logger = new Logger('ExceptionsHandler'); @Optional() @Inject() - protected readonly applicationRefHost?: ApplicationReferenceHost; + protected readonly httpAdapterHost?: HttpAdapterHost; constructor(protected readonly applicationRef?: HttpServer) {} catch(exception: T, host: ArgumentsHost) { const applicationRef = this.applicationRef || - (this.applicationRefHost && this.applicationRefHost.applicationRef); + (this.httpAdapterHost && this.httpAdapterHost.httpAdapter); if (!(exception instanceof HttpException)) { const body = { diff --git a/packages/core/guards/guards-context-creator.ts b/packages/core/guards/guards-context-creator.ts index 42a0ff12b52..3e86dc17af2 100644 --- a/packages/core/guards/guards-context-creator.ts +++ b/packages/core/guards/guards-context-creator.ts @@ -24,21 +24,29 @@ export class GuardsContextCreator extends ContextCreator { callback: (...args: any[]) => any, module: string, contextId = STATIC_CONTEXT, + inquirerId?: string, ): CanActivate[] { this.moduleContext = module; - return this.createContext(instance, callback, GUARDS_METADATA); + return this.createContext( + instance, + callback, + GUARDS_METADATA, + contextId, + inquirerId, + ); } public createConcreteContext( metadata: T, contextId = STATIC_CONTEXT, + inquirerId?: string, ): R { if (isEmpty(metadata)) { return [] as R; } return iterate(metadata) .filter((guard: any) => guard && (guard.name || guard.canActivate)) - .map(guard => this.getGuardInstance(guard, contextId)) + .map(guard => this.getGuardInstance(guard, contextId, inquirerId)) .filter((guard: CanActivate) => guard && isFunction(guard.canActivate)) .toArray() as R; } @@ -46,16 +54,20 @@ export class GuardsContextCreator extends ContextCreator { public getGuardInstance( guard: Function | CanActivate, contextId = STATIC_CONTEXT, - ) { + inquirerId?: string, + ): CanActivate | null { const isObject = (guard as CanActivate).canActivate; if (isObject) { - return guard; + return guard as CanActivate; } const instanceWrapper = this.getInstanceByMetatype(guard); if (!instanceWrapper) { return null; } - const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + const instanceHost = instanceWrapper.getInstanceByContextId( + contextId, + inquirerId, + ); return instanceHost && instanceHost.instance; } diff --git a/packages/core/helpers/application-ref-host.ts b/packages/core/helpers/application-ref-host.ts deleted file mode 100644 index cd538230e2c..00000000000 --- a/packages/core/helpers/application-ref-host.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { HttpServer } from '@nestjs/common'; - -export class ApplicationReferenceHost { - private _applicationRef: T; - - set applicationRef(applicationRef: T) { - this._applicationRef = applicationRef; - } - - get applicationRef(): T | undefined { - return this._applicationRef; - } -} diff --git a/packages/core/helpers/context-creator.ts b/packages/core/helpers/context-creator.ts index f0f6017ebe1..a82fef79187 100644 --- a/packages/core/helpers/context-creator.ts +++ b/packages/core/helpers/context-creator.ts @@ -6,6 +6,7 @@ export abstract class ContextCreator { public abstract createConcreteContext( metadata: T, contextId?: ContextId, + inquirerId?: string, ): R; public getGlobalMetadata?(): T; @@ -14,6 +15,7 @@ export abstract class ContextCreator { callback: (...args: any[]) => any, metadataKey: string, contextId = STATIC_CONTEXT, + inquirerId?: string, ): R { const globalMetadata = this.getGlobalMetadata && this.getGlobalMetadata(); @@ -23,9 +25,14 @@ export abstract class ContextCreator { ...this.createConcreteContext( globalMetadata || ([] as T), contextId, + inquirerId, + ), + ...this.createConcreteContext(classMetadata, contextId, inquirerId), + ...this.createConcreteContext( + methodMetadata, + contextId, + inquirerId, ), - ...this.createConcreteContext(classMetadata, contextId), - ...this.createConcreteContext(methodMetadata, contextId), ] as R; } diff --git a/packages/core/helpers/context-id-factory.ts b/packages/core/helpers/context-id-factory.ts new file mode 100644 index 00000000000..d9fef7162a6 --- /dev/null +++ b/packages/core/helpers/context-id-factory.ts @@ -0,0 +1,13 @@ +import { ContextId } from '../injector/instance-wrapper'; + +export function createContextId(): ContextId { + /** + * We are generating random identifier to track asynchronous + * execution context. An identifier does not have to be neither unique + * nor unpredictable because WeakMap uses objects as keys (reference comparison). + * Thus, even though identifier number might be equal, WeakMap would properly + * associate asynchronous context with its internal map values using object reference. + * Object is automatically removed once request has been processed (closure). + */ + return { id: Math.random() }; +} diff --git a/packages/core/helpers/handler-metadata-storage.ts b/packages/core/helpers/handler-metadata-storage.ts index 9c2bf4eb5a2..475aa349e47 100644 --- a/packages/core/helpers/handler-metadata-storage.ts +++ b/packages/core/helpers/handler-metadata-storage.ts @@ -1,11 +1,17 @@ import { Controller } from '@nestjs/common/interfaces'; +import { ContextId } from './../injector/instance-wrapper'; import { ParamProperties } from './context-utils'; export const HANDLER_METADATA_SYMBOL = Symbol.for('handler_metadata:cache'); export interface HandlerMetadata { argsLength: number; - paramsOptions: (ParamProperties & { metatype?: any })[]; + paramtypes: any[]; + getParamsMetadata: ( + moduleKey: string, + contextId?: ContextId, + inquirerId?: string, + ) => (ParamProperties & { metatype?: any })[]; fnHandleResponse: ( result: TResult, res: TResponse, diff --git a/packages/core/helpers/http-adapter-host.ts b/packages/core/helpers/http-adapter-host.ts new file mode 100644 index 00000000000..b455ccf2581 --- /dev/null +++ b/packages/core/helpers/http-adapter-host.ts @@ -0,0 +1,13 @@ +import { AbstractHttpAdapter } from '../adapters/http-adapter'; + +export class HttpAdapterHost { + private _httpAdapter: T; + + set httpAdapter(httpAdapter: T) { + this._httpAdapter = httpAdapter; + } + + get httpAdapter(): T | undefined { + return this._httpAdapter; + } +} diff --git a/packages/core/helpers/index.ts b/packages/core/helpers/index.ts index 52e37cb71e2..d5127930d1b 100644 --- a/packages/core/helpers/index.ts +++ b/packages/core/helpers/index.ts @@ -1 +1 @@ -export * from './application-ref-host'; +export * from './http-adapter-host'; diff --git a/packages/core/index.ts b/packages/core/index.ts index a8f785871fb..02a73581611 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -16,4 +16,5 @@ export * from './middleware'; export * from './nest-application'; export * from './nest-application-context'; export { NestFactory } from './nest-factory'; +export * from './router'; export * from './services'; diff --git a/packages/core/injector/container.ts b/packages/core/injector/container.ts index 70a31f8a107..2d1580e14d7 100644 --- a/packages/core/injector/container.ts +++ b/packages/core/injector/container.ts @@ -6,10 +6,11 @@ import { ApplicationConfig } from '../application-config'; import { CircularDependencyException } from '../errors/exceptions/circular-dependency.exception'; import { InvalidModuleException } from '../errors/exceptions/invalid-module.exception'; import { UnknownModuleException } from '../errors/exceptions/unknown-module.exception'; -import { ApplicationReferenceHost } from '../helpers/application-ref-host'; import { ExternalContextCreator } from '../helpers/external-context-creator'; -import { Reflector } from '../services'; +import { HttpAdapterHost } from '../helpers/http-adapter-host'; import { ModuleCompiler } from './compiler'; +import { InternalCoreModule } from './internal-core-module'; +import { InternalProvidersStorage } from './internal-providers-storage'; import { Module } from './module'; import { ModulesContainer } from './modules-container'; @@ -21,11 +22,8 @@ export class NestContainer { string, Partial >(); - private readonly reflector = new Reflector(); - private readonly applicationRefHost = new ApplicationReferenceHost(); - private externalContextCreator: ExternalContextCreator; - private modulesContainer: ModulesContainer; - private applicationRef: any; + private readonly internalProvidersStorage = new InternalProvidersStorage(); + private internalCoreModule: Module; constructor( private readonly _applicationConfig: ApplicationConfig = undefined, @@ -35,23 +33,24 @@ export class NestContainer { return this._applicationConfig; } - public setApplicationRef(applicationRef: any) { - this.applicationRef = applicationRef; + public setHttpAdapter(httpAdapter: any) { + this.internalProvidersStorage.httpAdapter = httpAdapter; - if (!this.applicationRefHost) { + if (!this.internalProvidersStorage.httpAdapterHost) { return; } - this.applicationRefHost.applicationRef = applicationRef; + const host = this.internalProvidersStorage.httpAdapterHost; + host.httpAdapter = httpAdapter; } - public getApplicationRef() { - return this.applicationRef; + public getHttpAdapterRef() { + return this.internalProvidersStorage.httpAdapter; } public async addModule( metatype: Type | DynamicModule | Promise, scope: Type[], - ) { + ): Promise { if (!metatype) { throw new InvalidModuleException(scope); } @@ -67,6 +66,8 @@ export class NestContainer { this.addDynamicMetadata(token, dynamicMetadata, [].concat(scope, type)); this.isGlobalModule(type) && this.addGlobalModule(module); + + return module; } public addDynamicMetadata( @@ -103,7 +104,11 @@ export class NestContainer { } public getModuleByKey(moduleKey: string): Module { - return this.modulesContainer.get(moduleKey); + return this.modules.get(moduleKey); + } + + public getInternalCoreModuleRef(): Module | undefined { + return this.internalCoreModule; } public async addImport( @@ -198,25 +203,24 @@ export class NestContainer { return []; } - public getReflector(): Reflector { - return this.reflector; - } - - public getExternalContextCreator(): ExternalContextCreator { - if (!this.externalContextCreator) { - this.externalContextCreator = ExternalContextCreator.fromContainer(this); - } - return this.externalContextCreator; - } - - public getApplicationRefHost(): ApplicationReferenceHost { - return this.applicationRefHost; - } - - public getModulesContainer(): ModulesContainer { - if (!this.modulesContainer) { - this.modulesContainer = this.getModules(); - } - return this.modulesContainer; + public createCoreModule(): DynamicModule { + return InternalCoreModule.register([ + { + provide: ExternalContextCreator, + useValue: ExternalContextCreator.fromContainer(this), + }, + { + provide: ModulesContainer, + useValue: this.modules, + }, + { + provide: HttpAdapterHost, + useValue: this.internalProvidersStorage.httpAdapterHost, + }, + ]); + } + + public registerCoreModuleRef(moduleRef: Module) { + this.internalCoreModule = moduleRef; } } diff --git a/packages/core/injector/index.ts b/packages/core/injector/index.ts index 6b76e079f9e..8a420e53087 100644 --- a/packages/core/injector/index.ts +++ b/packages/core/injector/index.ts @@ -1,3 +1,3 @@ +export * from './container'; export * from './module-ref'; export * from './modules-container'; -export * from './tokens'; diff --git a/packages/core/injector/injector.ts b/packages/core/injector/injector.ts index 1c0d348bcac..3c56473b20b 100644 --- a/packages/core/injector/injector.ts +++ b/packages/core/injector/injector.ts @@ -22,6 +22,7 @@ import { ContextId, InstancePerContext, InstanceWrapper, + PropertyMetadata, } from './instance-wrapper'; import { Module } from './module'; @@ -70,6 +71,7 @@ export class Injector { collection: Map, module: Module, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ) { const { metatype } = wrapper; const targetWrapper = collection.get(metatype.name); @@ -87,6 +89,7 @@ export class Injector { null, loadInstance, contextId, + inquirer, ); } @@ -101,13 +104,16 @@ export class Injector { controllers, module, contextId, + wrapper, ); + await this.loadEnhancersPerContext(wrapper, module, contextId, wrapper); } public async loadInjectable( wrapper: InstanceWrapper, module: Module, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ) { const injectables = module.injectables; await this.loadInstance( @@ -115,6 +121,7 @@ export class Injector { injectables, module, contextId, + inquirer, ); } @@ -122,35 +129,36 @@ export class Injector { wrapper: InstanceWrapper, module: Module, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ) { const providers = module.providers; - await this.loadInstance(wrapper, providers, module, contextId); + await this.loadInstance( + wrapper, + providers, + module, + contextId, + inquirer, + ); + await this.loadEnhancersPerContext(wrapper, module, contextId, wrapper); } public loadPrototype( - { metatype, name }: InstanceWrapper, + { name }: InstanceWrapper, collection: Map>, contextId = STATIC_CONTEXT, ) { if (!collection) { - return null; + return; } const target = collection.get(name); - const instanceHost = target.getInstanceByContextId(contextId); - if ( - instanceHost.isResolved || - !isNil(target.inject) || - !metatype.prototype - ) { - return null; - } - collection.set( - name, - new InstanceWrapper({ + const instance = target.createPrototype(contextId); + if (instance) { + const wrapper = new InstanceWrapper({ ...target, - instance: Object.create(metatype.prototype), - }), - ); + instance, + }); + collection.set(name, wrapper); + } } public applyDoneHook(wrapper: InstancePerContext): () => void { @@ -167,8 +175,10 @@ export class Injector { collection: Map, module: Module, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ) { - const instanceHost = wrapper.getInstanceByContextId(contextId); + const inquirerId = this.getInquirerId(inquirer); + const instanceHost = wrapper.getInstanceByContextId(contextId, inquirerId); if (instanceHost.isPending) { return instanceHost.donePromise; } @@ -188,12 +198,14 @@ export class Injector { module, inject, contextId, + wrapper, ); const instance = await this.instantiateClass( instances, wrapper, targetWrapper, contextId, + inquirer, ); this.applyProperties(instance, properties); done(); @@ -204,6 +216,7 @@ export class Injector { inject, callback, contextId, + wrapper, ); } @@ -213,20 +226,14 @@ export class Injector { inject: InjectorDependency[], callback: (args: any[]) => void, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ) { + const inquirerId = this.getInquirerId(inquirer); const metadata = wrapper.getCtorMetadata(); - if (metadata) { - const dependenciesHosts = await Promise.all( - metadata.map(async item => - this.resolveComponentHost(item.host, item, contextId), - ), - ); - const deps = dependenciesHosts.map( - item => item.getInstanceByContextId(contextId).instance, - ); + if (metadata && contextId !== STATIC_CONTEXT) { + const deps = await this.loadCtorMetadata(metadata, contextId, inquirer); return callback(deps); } - const dependencies = isNil(inject) ? this.reflectConstructorParams(wrapper.metatype) : inject; @@ -235,8 +242,7 @@ export class Injector { : []; let isResolved = true; - - const findOneParam = async (param, index) => { + const resolveParam = async (param, index) => { try { const paramWrapper = await this.resolveSingleParam( wrapper, @@ -244,8 +250,12 @@ export class Injector { { index, dependencies }, module, contextId, + inquirer, + ); + const instanceHost = paramWrapper.getInstanceByContextId( + contextId, + inquirerId, ); - const instanceHost = paramWrapper.getInstanceByContextId(contextId); if (!instanceHost.isResolved && !paramWrapper.forwardRef) { isResolved = false; } @@ -259,7 +269,7 @@ export class Injector { return undefined; } }; - const instances = await Promise.all(dependencies.map(findOneParam)); + const instances = await Promise.all(dependencies.map(resolveParam)); isResolved && (await callback(instances)); } @@ -285,6 +295,7 @@ export class Injector { dependencyContext: InjectorDependencyContext, module: Module, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ) { if (isUndefined(param)) { throw new UndefinedDependencyException( @@ -300,6 +311,7 @@ export class Injector { dependencyContext, wrapper, contextId, + inquirer, ); } @@ -320,6 +332,7 @@ export class Injector { dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ): Promise { const providers = module.providers; const instanceWrapper = await this.lookupComponent( @@ -328,23 +341,53 @@ export class Injector { { ...dependencyContext, name }, wrapper, contextId, + inquirer, + ); + return this.resolveComponentHost( + module, + instanceWrapper, + contextId, + inquirer, ); - return this.resolveComponentHost(module, instanceWrapper, contextId); } public async resolveComponentHost( module: Module, instanceWrapper: InstanceWrapper, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ): Promise { - const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + const inquirerId = this.getInquirerId(inquirer); + const instanceHost = instanceWrapper.getInstanceByContextId( + contextId, + inquirerId, + ); if (!instanceHost.isResolved && !instanceWrapper.forwardRef) { - await this.loadProvider(instanceWrapper, module, contextId); + await this.loadProvider(instanceWrapper, module, contextId, inquirer); + } else if ( + !instanceHost.isResolved && + instanceWrapper.forwardRef && + (contextId !== STATIC_CONTEXT || !!inquirerId) + ) { + /** + * When circular dependency has been detected between + * either request/transient providers, we have to asynchronously + * resolve instance host for a specific contextId or inquirer, to ensure + * that eventual lazily created instance will be merged with the prototype + * instantiated beforehand. + */ + instanceHost.donePromise && + instanceHost.donePromise.then(() => + this.loadProvider(instanceWrapper, module, contextId, inquirer), + ); } if (instanceWrapper.async) { - const host = instanceWrapper.getInstanceByContextId(contextId); + const host = instanceWrapper.getInstanceByContextId( + contextId, + inquirerId, + ); host.instance = await host.instance; - instanceWrapper.setInstanceByContextId(contextId, host); + instanceWrapper.setInstanceByContextId(contextId, host, inquirerId); } return instanceWrapper; } @@ -355,6 +398,7 @@ export class Injector { dependencyContext: InjectorDependencyContext, wrapper: InstanceWrapper, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ): Promise> { const { name } = dependencyContext; const scanInExports = () => @@ -363,8 +407,8 @@ export class Injector { module, wrapper, contextId, + inquirer, ); - return providers.has(name) ? providers.get(name) : scanInExports(); } @@ -373,6 +417,7 @@ export class Injector { module: Module, wrapper: InstanceWrapper, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ) { const instanceWrapper = await this.lookupComponentInImports( module, @@ -380,6 +425,7 @@ export class Injector { wrapper, [], contextId, + inquirer, ); if (isNil(instanceWrapper)) { throw new UnknownDependenciesException( @@ -397,10 +443,11 @@ export class Injector { wrapper: InstanceWrapper, moduleRegistry: any[] = [], contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ): Promise { let instanceWrapperRef: InstanceWrapper = null; - const imports: Set = module.imports || new Set(); + const imports = module.imports || new Set(); const children = [...imports.values()].filter(item => item); for (const relatedModule of children) { @@ -416,6 +463,7 @@ export class Injector { wrapper, moduleRegistry, contextId, + inquirer, ); if (instanceRef) { return instanceRef; @@ -424,9 +472,18 @@ export class Injector { } instanceWrapperRef = providers.get(name); - const instanceHost = instanceWrapperRef.getInstanceByContextId(contextId); + const inquirerId = this.getInquirerId(inquirer); + const instanceHost = instanceWrapperRef.getInstanceByContextId( + contextId, + inquirerId, + ); if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) { - await this.loadProvider(instanceWrapperRef, relatedModule, contextId); + await this.loadProvider( + instanceWrapperRef, + relatedModule, + contextId, + wrapper, + ); break; } } @@ -438,23 +495,14 @@ export class Injector { module: Module, inject?: InjectorDependency[], contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ): Promise { if (!isNil(inject)) { return []; } const metadata = wrapper.getPropertiesMetadata(); - if (metadata) { - const dependenciesHosts = await Promise.all( - metadata.map(async ({ wrapper: item, key }) => ({ - key, - host: await this.resolveComponentHost(item.host, item, contextId), - })), - ); - return dependenciesHosts.map(({ key, host }) => ({ - key, - name: key, - instance: host.getInstanceByContextId(contextId).instance, - })); + if (metadata && contextId !== STATIC_CONTEXT) { + return this.loadPropertiesMetadata(metadata, contextId, inquirer); } const properties = this.reflectProperties(wrapper.metatype); const instances = await Promise.all( @@ -470,12 +518,18 @@ export class Injector { dependencyContext, module, contextId, + inquirer, ); if (!paramWrapper) { return undefined; } wrapper.addPropertiesMetadata(item.key, paramWrapper); - const instanceHost = paramWrapper.getInstanceByContextId(contextId); + + const inquirerId = this.getInquirerId(inquirer); + const instanceHost = paramWrapper.getInstanceByContextId( + contextId, + inquirerId, + ); return instanceHost.instance; } catch (err) { if (!item.isOptional) { @@ -520,16 +574,24 @@ export class Injector { wrapper: InstanceWrapper, targetMetatype: InstanceWrapper, contextId = STATIC_CONTEXT, + inquirer?: InstanceWrapper, ): Promise { const { metatype, inject } = wrapper; - const instanceHost = targetMetatype.getInstanceByContextId(contextId); - const isDependencyTreeStatic = wrapper.isDependencyTreeStatic(); - const isInContext = - (isDependencyTreeStatic && contextId === STATIC_CONTEXT) || - (!isDependencyTreeStatic && contextId !== STATIC_CONTEXT); + const inquirerId = this.getInquirerId(inquirer); + const instanceHost = targetMetatype.getInstanceByContextId( + contextId, + inquirerId, + ); + const isStatic = wrapper.isStatic(contextId, inquirer); + const isInRequestScope = wrapper.isInRequestScope(contextId, inquirer); + const isLazyTransient = wrapper.isLazyTransient(contextId, inquirer); + const isInContext = isStatic || isInRequestScope || isLazyTransient; if (isNil(inject) && isInContext) { - const targetInstance = wrapper.getInstanceByContextId(contextId); + const targetInstance = wrapper.getInstanceByContextId( + contextId, + inquirerId, + ); targetInstance.instance = wrapper.forwardRef ? Object.assign(targetInstance.instance, new metatype(...instances)) @@ -544,7 +606,7 @@ export class Injector { return instanceHost.instance; } - async loadPerContext( + public async loadPerContext( instance: T, module: Module, collection: Map, @@ -553,21 +615,66 @@ export class Injector { const wrapper = collection.get( instance.constructor && instance.constructor.name, ); - await this.loadInstance(wrapper, collection, module, ctx); - await this.loadEnhancersPerContext(wrapper, module, ctx); + await this.loadInstance(wrapper, collection, module, ctx, wrapper); + await this.loadEnhancersPerContext(wrapper, module, ctx, wrapper); const host = wrapper.getInstanceByContextId(ctx); return host && (host.instance as T); } - async loadEnhancersPerContext( + public async loadEnhancersPerContext( wrapper: InstanceWrapper, module: Module, ctx: ContextId, + inquirer?: InstanceWrapper, ) { - const enhancers = wrapper.getEnhancersMetadata(); + const enhancers = wrapper.getEnhancersMetadata() || []; const loadEnhancer = (item: InstanceWrapper) => - this.loadInstance(item, module.injectables, module, ctx); + this.loadInstance(item, module.injectables, module, ctx, inquirer); await Promise.all(enhancers.map(loadEnhancer)); } + + public async loadCtorMetadata( + metadata: InstanceWrapper[], + contextId: ContextId, + inquirer?: InstanceWrapper, + ): Promise { + const hosts = await Promise.all( + metadata.map(async item => + this.resolveComponentHost(item.host, item, contextId, inquirer), + ), + ); + const inquirerId = this.getInquirerId(inquirer); + return hosts.map( + item => item.getInstanceByContextId(contextId, inquirerId).instance, + ); + } + + public async loadPropertiesMetadata( + metadata: PropertyMetadata[], + contextId: ContextId, + inquirer?: InstanceWrapper, + ): Promise { + const dependenciesHosts = await Promise.all( + metadata.map(async ({ wrapper: item, key }) => ({ + key, + host: await this.resolveComponentHost( + item.host, + item, + contextId, + inquirer, + ), + })), + ); + const inquirerId = this.getInquirerId(inquirer); + return dependenciesHosts.map(({ key, host }) => ({ + key, + name: key, + instance: host.getInstanceByContextId(contextId, inquirerId).instance, + })); + } + + private getInquirerId(inquirer: InstanceWrapper | undefined): string { + return inquirer && inquirer.id; + } } diff --git a/packages/core/injector/instance-wrapper.ts b/packages/core/injector/instance-wrapper.ts index ad42a95e389..aec18cd1b6a 100644 --- a/packages/core/injector/instance-wrapper.ts +++ b/packages/core/injector/instance-wrapper.ts @@ -1,8 +1,11 @@ import { Scope, Type } from '@nestjs/common'; +import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util'; +import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils'; import { STATIC_CONTEXT } from './constants'; import { Module } from './module'; export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache'); +export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id'); export interface ContextId { readonly id: number; @@ -36,13 +39,23 @@ export class InstanceWrapper { private readonly values = new WeakMap>(); private readonly [INSTANCE_METADATA_SYMBOL]: InstanceMetadataStore = {}; + private readonly [INSTANCE_ID_SYMBOL]: string; + private transientMap?: + | Map>> + | undefined; + private isTreeStatic: boolean | undefined; constructor( metadata: Partial> & Partial> = {}, ) { + this[INSTANCE_ID_SYMBOL] = randomStringGenerator(); this.initialize(metadata); } + get id(): string { + return this[INSTANCE_ID_SYMBOL]; + } + set instance(value: T) { this.values.set(STATIC_CONTEXT, { instance: value }); } @@ -56,29 +69,74 @@ export class InstanceWrapper { return !this.metatype; } - getInstanceByContextId(contextId: ContextId): InstancePerContext { + get isTransient(): boolean { + return this.scope === Scope.TRANSIENT; + } + + public getInstanceByContextId( + contextId: ContextId, + inquirerId?: string, + ): InstancePerContext { + if (this.scope === Scope.TRANSIENT && inquirerId) { + return this.getInstanceByInquirerId(contextId, inquirerId); + } const instancePerContext = this.values.get(contextId); return instancePerContext ? instancePerContext : this.cloneStaticInstance(contextId); } - setInstanceByContextId(contextId: ContextId, value: InstancePerContext) { + public getInstanceByInquirerId( + contextId: ContextId, + inquirerId: string, + ): InstancePerContext { + let collectionPerContext = this.transientMap.get(inquirerId); + if (!collectionPerContext) { + collectionPerContext = new WeakMap(); + this.transientMap.set(inquirerId, collectionPerContext); + } + const instancePerContext = collectionPerContext.get(contextId); + return instancePerContext + ? instancePerContext + : this.cloneTransientInstance(contextId, inquirerId); + } + + public setInstanceByContextId( + contextId: ContextId, + value: InstancePerContext, + inquirerId?: string, + ) { + if (this.scope === Scope.TRANSIENT && inquirerId) { + return this.setInstanceByInquirerId(contextId, inquirerId, value); + } this.values.set(contextId, value); } - addCtorMetadata(index: number, wrapper: InstanceWrapper) { + public setInstanceByInquirerId( + contextId: ContextId, + inquirerId: string, + value: InstancePerContext, + ) { + let collection = this.transientMap.get(inquirerId); + if (!collection) { + collection = new WeakMap(); + this.transientMap.set(inquirerId, collection); + } + collection.set(contextId, value); + } + + public addCtorMetadata(index: number, wrapper: InstanceWrapper) { if (!this[INSTANCE_METADATA_SYMBOL].dependencies) { this[INSTANCE_METADATA_SYMBOL].dependencies = []; } this[INSTANCE_METADATA_SYMBOL].dependencies[index] = wrapper; } - getCtorMetadata(): InstanceWrapper[] { + public getCtorMetadata(): InstanceWrapper[] { return this[INSTANCE_METADATA_SYMBOL].dependencies; } - addPropertiesMetadata(key: string, wrapper: InstanceWrapper) { + public addPropertiesMetadata(key: string, wrapper: InstanceWrapper) { if (!this[INSTANCE_METADATA_SYMBOL].properties) { this[INSTANCE_METADATA_SYMBOL].properties = []; } @@ -88,59 +146,58 @@ export class InstanceWrapper { }); } - getPropertiesMetadata(): PropertyMetadata[] { + public getPropertiesMetadata(): PropertyMetadata[] { return this[INSTANCE_METADATA_SYMBOL].properties; } - addEnhancerMetadata(wrapper: InstanceWrapper) { + public addEnhancerMetadata(wrapper: InstanceWrapper) { if (!this[INSTANCE_METADATA_SYMBOL].enhancers) { this[INSTANCE_METADATA_SYMBOL].enhancers = []; } this[INSTANCE_METADATA_SYMBOL].enhancers.push(wrapper); } - getEnhancersMetadata(): InstanceWrapper[] { + public getEnhancersMetadata(): InstanceWrapper[] { return this[INSTANCE_METADATA_SYMBOL].enhancers; } - isDependencyTreeStatic(): boolean { + public isDependencyTreeStatic(lookupRegistry: string[] = []): boolean { + if (!isUndefined(this.isTreeStatic)) { + return this.isTreeStatic; + } if (this.scope === Scope.REQUEST) { - return false; + this.isTreeStatic = false; + return this.isTreeStatic; } + if (lookupRegistry.includes(this[INSTANCE_ID_SYMBOL])) { + return true; + } + lookupRegistry = lookupRegistry.concat(this[INSTANCE_ID_SYMBOL]); + const { dependencies, properties, enhancers } = this[ INSTANCE_METADATA_SYMBOL ]; let isStatic = - (dependencies && this.isWrapperStatic(dependencies)) || !dependencies; + (dependencies && + this.isWrapperListStatic(dependencies, lookupRegistry)) || + !dependencies; - if (!properties || !isStatic) { - return isStatic; + if (!isStatic || !(properties || enhancers)) { + this.isTreeStatic = isStatic; + return this.isTreeStatic; } - const propertiesHosts = properties.map(item => item.wrapper); - isStatic = isStatic && this.isWrapperStatic(propertiesHosts); - if (!enhancers || !isStatic) { - return isStatic; + const propertiesHosts = (properties || []).map(item => item.wrapper); + isStatic = + isStatic && this.isWrapperListStatic(propertiesHosts, lookupRegistry); + if (!isStatic || !enhancers) { + this.isTreeStatic = isStatic; + return this.isTreeStatic; } - return this.isWrapperStatic(enhancers); + this.isTreeStatic = this.isWrapperListStatic(enhancers, lookupRegistry); + return this.isTreeStatic; } - private isWrapperStatic(tree: InstanceWrapper[]) { - return tree.every((item: InstanceWrapper) => item.isDependencyTreeStatic()); - } - - private initialize( - metadata: Partial> & Partial>, - ) { - const { instance, isResolved, ...wrapperPartial } = metadata; - Object.assign(this, wrapperPartial); - - this.setInstanceByContextId(STATIC_CONTEXT, { - instance, - isResolved, - }); - } - - private cloneStaticInstance(contextId: ContextId): InstancePerContext { + public cloneStaticInstance(contextId: ContextId): InstancePerContext { const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT); if (this.isDependencyTreeStatic()) { return staticInstance; @@ -151,7 +208,113 @@ export class InstanceWrapper { isResolved: false, isPending: false, }; + if (this.isNewable()) { + instancePerContext.instance = Object.create(this.metatype.prototype); + } this.setInstanceByContextId(contextId, instancePerContext); return instancePerContext; } + + public cloneTransientInstance( + contextId: ContextId, + inquirerId: string, + ): InstancePerContext { + const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT); + const instancePerContext: InstancePerContext = { + ...staticInstance, + instance: undefined, + isResolved: false, + isPending: false, + }; + if (this.isNewable()) { + instancePerContext.instance = Object.create(this.metatype.prototype); + } + this.setInstanceByInquirerId(contextId, inquirerId, instancePerContext); + return instancePerContext; + } + + public createPrototype(contextId: ContextId) { + const host = this.getInstanceByContextId(contextId); + if (!this.isNewable() || host.isResolved) { + return; + } + return Object.create(this.metatype.prototype); + } + + public isInRequestScope( + contextId: ContextId, + inquirer?: InstanceWrapper | undefined, + ): boolean { + const isDependencyTreeStatic = this.isDependencyTreeStatic(); + + return ((!isDependencyTreeStatic && + contextId !== STATIC_CONTEXT && + (!this.isTransient || (this.isTransient && inquirer))) as any) as boolean; + } + + public isLazyTransient( + contextId: ContextId, + inquirer: InstanceWrapper | undefined, + ): boolean { + const isInquirerRequestScoped = + inquirer && inquirer.scope === Scope.REQUEST; + + return ( + this.isDependencyTreeStatic() && + contextId !== STATIC_CONTEXT && + this.isTransient && + isInquirerRequestScoped + ); + } + + public isStatic( + contextId: ContextId, + inquirer: InstanceWrapper | undefined, + ): boolean { + const isInquirerRequestScoped = + inquirer && inquirer.scope === Scope.REQUEST; + const isStaticTransient = this.isTransient && !isInquirerRequestScoped; + + return ( + this.isDependencyTreeStatic() && + contextId === STATIC_CONTEXT && + (!this.isTransient || (isStaticTransient && !!inquirer)) + ); + } + + public getStaticTransientInstances() { + if (!this.transientMap) { + return []; + } + const instances = [...this.transientMap.values()]; + return instances + .map(item => item.get(STATIC_CONTEXT)) + .filter(item => !!item); + } + + private isNewable(): boolean { + return isNil(this.inject) && this.metatype && this.metatype.prototype; + } + + private isWrapperListStatic( + tree: InstanceWrapper[], + lookupRegistry: string[], + ): boolean { + return tree.every((item: InstanceWrapper) => + item.isDependencyTreeStatic(lookupRegistry), + ); + } + + private initialize( + metadata: Partial> & Partial>, + ) { + const { instance, isResolved, ...wrapperPartial } = metadata; + Object.assign(this, wrapperPartial); + + this.setInstanceByContextId(STATIC_CONTEXT, { + instance, + isResolved, + }); + this.scope === Scope.TRANSIENT && (this.transientMap = new Map()); + } } diff --git a/packages/core/injector/internal-core-module.ts b/packages/core/injector/internal-core-module.ts new file mode 100644 index 00000000000..7810420d6c0 --- /dev/null +++ b/packages/core/injector/internal-core-module.ts @@ -0,0 +1,19 @@ +import { DynamicModule, Global, Module } from '@nestjs/common'; +import { ValueProvider } from '@nestjs/common/interfaces'; +import { requestProvider } from './../router/request/request-providers'; +import { Reflector } from './../services'; + +@Global() +@Module({ + providers: [Reflector, requestProvider], + exports: [Reflector, requestProvider], +}) +export class InternalCoreModule { + static register(providers: ValueProvider[]): DynamicModule { + return { + module: InternalCoreModule, + providers: [...providers], + exports: [...providers.map(item => item.provide)], + }; + } +} diff --git a/packages/core/injector/internal-providers-storage.ts b/packages/core/injector/internal-providers-storage.ts new file mode 100644 index 00000000000..96f00a35462 --- /dev/null +++ b/packages/core/injector/internal-providers-storage.ts @@ -0,0 +1,19 @@ +import { HttpAdapterHost } from '../helpers'; +import { AbstractHttpAdapter } from './../adapters'; + +export class InternalProvidersStorage { + private readonly _httpAdapterHost = new HttpAdapterHost(); + private _httpAdapter: AbstractHttpAdapter; + + get httpAdapterHost(): HttpAdapterHost { + return this._httpAdapterHost; + } + + get httpAdapter(): AbstractHttpAdapter { + return this._httpAdapter; + } + + set httpAdapter(httpAdapter: AbstractHttpAdapter) { + this._httpAdapter = httpAdapter; + } +} diff --git a/packages/core/injector/module-ref.ts b/packages/core/injector/module-ref.ts index 0d4cae4f26c..9a5b875ddfc 100644 --- a/packages/core/injector/module-ref.ts +++ b/packages/core/injector/module-ref.ts @@ -31,7 +31,7 @@ export abstract class ModuleRef { module: Module, ): Promise { const wrapper = new InstanceWrapper({ - name: type.name, + name: type && type.name, metatype: type, instance: undefined, isResolved: false, diff --git a/packages/core/injector/module.ts b/packages/core/injector/module.ts index 2512742f7df..521ed314f25 100644 --- a/packages/core/injector/module.ts +++ b/packages/core/injector/module.ts @@ -18,14 +18,9 @@ import { import { InvalidClassException } from '../errors/exceptions/invalid-class.exception'; import { RuntimeException } from '../errors/exceptions/runtime.exception'; import { UnknownExportException } from '../errors/exceptions/unknown-export.exception'; -import { ApplicationReferenceHost } from '../helpers/application-ref-host'; -import { ExternalContextCreator } from '../helpers/external-context-creator'; -import { Reflector } from '../services/reflector.service'; import { NestContainer } from './container'; import { InstanceWrapper } from './instance-wrapper'; import { ModuleRef } from './module-ref'; -import { ModulesContainer } from './modules-container'; -import { HTTP_SERVER_REF } from './tokens'; export interface CustomProvider { provide: any; @@ -76,7 +71,7 @@ export class Module { return this._scope; } - get providers(): Map> { + get providers(): Map> { return this._providers; } @@ -132,11 +127,6 @@ export class Module { public addCoreProviders(container: NestContainer) { this.addModuleAsProvider(); this.addModuleRef(); - this.addReflector(container.getReflector()); - this.addApplicationRef(container.getApplicationRef()); - this.addExternalContextCreator(container.getExternalContextCreator()); - this.addModulesContainer(container.getModulesContainer()); - this.addApplicationRefHost(container.getApplicationRefHost()); } public addModuleRef() { @@ -166,73 +156,6 @@ export class Module { ); } - public addReflector(reflector: Reflector) { - this._providers.set( - Reflector.name, - new InstanceWrapper({ - name: Reflector.name, - metatype: Reflector, - isResolved: true, - instance: reflector, - host: this, - }), - ); - } - - public addApplicationRef(applicationRef: any) { - this._providers.set( - HTTP_SERVER_REF, - new InstanceWrapper({ - name: HTTP_SERVER_REF, - metatype: {} as any, - isResolved: true, - instance: applicationRef || {}, - host: this, - }), - ); - } - - public addExternalContextCreator( - externalContextCreator: ExternalContextCreator, - ) { - this._providers.set( - ExternalContextCreator.name, - new InstanceWrapper({ - name: ExternalContextCreator.name, - metatype: ExternalContextCreator, - isResolved: true, - instance: externalContextCreator, - host: this, - }), - ); - } - - public addModulesContainer(modulesContainer: ModulesContainer) { - this._providers.set( - ModulesContainer.name, - new InstanceWrapper({ - name: ModulesContainer.name, - metatype: ModulesContainer, - isResolved: true, - instance: modulesContainer, - host: this, - }), - ); - } - - public addApplicationRefHost(applicationRefHost: ApplicationReferenceHost) { - this._providers.set( - ApplicationReferenceHost.name, - new InstanceWrapper({ - name: ApplicationReferenceHost.name, - metatype: ApplicationReferenceHost, - isResolved: true, - instance: applicationRefHost, - host: this, - }), - ); - } - public addInjectable( injectable: Type, host?: Type, @@ -444,6 +367,10 @@ export class Module { }); } + public getProviderByKey(name: string | symbol): InstanceWrapper { + return this._providers.get(name) as InstanceWrapper; + } + public createModuleReferenceType(): any { const self = this; return class extends ModuleRef { diff --git a/packages/core/injector/tokens.ts b/packages/core/injector/tokens.ts deleted file mode 100644 index e160abad90e..00000000000 --- a/packages/core/injector/tokens.ts +++ /dev/null @@ -1 +0,0 @@ -export const HTTP_SERVER_REF = 'HTTP_SERVER_REF'; diff --git a/packages/core/interceptors/interceptors-context-creator.ts b/packages/core/interceptors/interceptors-context-creator.ts index 0b291937135..709f576b0cf 100644 --- a/packages/core/interceptors/interceptors-context-creator.ts +++ b/packages/core/interceptors/interceptors-context-creator.ts @@ -23,6 +23,7 @@ export class InterceptorsContextCreator extends ContextCreator { callback: (...args: any[]) => any, module: string, contextId = STATIC_CONTEXT, + inquirerId?: string, ): NestInterceptor[] { this.moduleContext = module; return this.createContext( @@ -30,12 +31,14 @@ export class InterceptorsContextCreator extends ContextCreator { callback, INTERCEPTORS_METADATA, contextId, + inquirerId, ); } public createConcreteContext( metadata: T, contextId = STATIC_CONTEXT, + inquirerId?: string, ): R { if (isEmpty(metadata)) { return [] as R; @@ -45,7 +48,9 @@ export class InterceptorsContextCreator extends ContextCreator { (interceptor: any) => interceptor && (interceptor.name || interceptor.intercept), ) - .map(interceptor => this.getInterceptorInstance(interceptor, contextId)) + .map(interceptor => + this.getInterceptorInstance(interceptor, contextId, inquirerId), + ) .filter( (interceptor: NestInterceptor) => interceptor && isFunction(interceptor.intercept), @@ -56,16 +61,20 @@ export class InterceptorsContextCreator extends ContextCreator { public getInterceptorInstance( interceptor: Function | NestInterceptor, contextId = STATIC_CONTEXT, - ) { + inquirerId?: string, + ): NestInterceptor | null { const isObject = (interceptor as NestInterceptor).intercept; if (isObject) { - return interceptor; + return interceptor as NestInterceptor; } const instanceWrapper = this.getInstanceByMetatype(interceptor); if (!instanceWrapper) { return null; } - const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + const instanceHost = instanceWrapper.getInstanceByContextId( + contextId, + inquirerId, + ); return instanceHost && instanceHost.instance; } diff --git a/packages/core/middleware/middleware-module.ts b/packages/core/middleware/middleware-module.ts index e4716b53730..ff347d0974e 100644 --- a/packages/core/middleware/middleware-module.ts +++ b/packages/core/middleware/middleware-module.ts @@ -38,7 +38,7 @@ export class MiddlewareModule { config: ApplicationConfig, injector: Injector, ) { - const appRef = container.getApplicationRef(); + const appRef = container.getHttpAdapterRef(); this.routerExceptionFilter = new RouterExceptionFilters( container, config, diff --git a/packages/core/nest-application-context.ts b/packages/core/nest-application-context.ts index 360fd83722e..3380bf7f95d 100644 --- a/packages/core/nest-application-context.ts +++ b/packages/core/nest-application-context.ts @@ -8,6 +8,7 @@ import { } from '@nestjs/common'; import { Type } from '@nestjs/common/interfaces/type.interface'; import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils'; +import { InstanceWrapper } from 'injector/instance-wrapper'; import iterate from 'iterare'; import { UnknownModuleException } from './errors/exceptions/unknown-module.exception'; import { NestContainer } from './injector/container'; @@ -85,15 +86,25 @@ export class NestApplicationContext implements INestApplicationContext { // Lifecycle hook has to be called once all classes are properly initialized const [_, { instance: moduleClassInstance }] = providers.shift(); const instances = [...module.controllers, ...providers]; - - await Promise.all( - iterate(instances) - .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) - .map(([key, { instance }]) => instance) + const callOperator = (list: any) => + list .filter(instance => !isNil(instance)) .filter(this.hasOnModuleInitHook) - .map(async instance => (instance as OnModuleInit).onModuleInit()), + .map(async instance => (instance as OnModuleInit).onModuleInit()); + + await Promise.all( + callOperator( + iterate(instances) + .filter( + ([key, wrapper]) => + wrapper.isDependencyTreeStatic() && !wrapper.isTransient, + ) + .map(([key, { instance }]) => instance), + ), ); + const transientInstances = this.getTransientInstances(instances); + await Promise.all(callOperator(iterate(transientInstances))); + if (moduleClassInstance && this.hasOnModuleInitHook(moduleClassInstance)) { await (moduleClassInstance as OnModuleInit).onModuleInit(); } @@ -116,15 +127,25 @@ export class NestApplicationContext implements INestApplicationContext { // Lifecycle hook has to be called once all classes are properly destroyed const [_, { instance: moduleClassInstance }] = providers.shift(); const instances = [...module.controllers, ...providers]; - - await Promise.all( - iterate(instances) - .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) - .map(([key, { instance }]) => instance) + const callOperator = (list: any) => + list .filter(instance => !isNil(instance)) .filter(this.hasOnModuleDestroyHook) - .map(async instance => (instance as OnModuleDestroy).onModuleDestroy()), + .map(async instance => (instance as OnModuleDestroy).onModuleDestroy()); + + await Promise.all( + callOperator( + iterate(instances) + .filter( + ([key, wrapper]) => + wrapper.isDependencyTreeStatic() && !wrapper.isTransient, + ) + .map(([key, { instance }]) => instance), + ), ); + const transientInstances = this.getTransientInstances(instances); + await Promise.all(callOperator(iterate(transientInstances))); + if ( moduleClassInstance && this.hasOnModuleDestroyHook(moduleClassInstance) @@ -149,16 +170,27 @@ export class NestApplicationContext implements INestApplicationContext { const [_, { instance: moduleClassInstance }] = providers.shift(); const instances = [...module.controllers, ...providers]; - await Promise.all( - iterate(instances) - .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) - .map(([key, { instance }]) => instance) + const callOperator = (list: any) => + list .filter(instance => !isNil(instance)) .filter(this.hasOnAppBotstrapHook) .map(async instance => (instance as OnApplicationBootstrap).onApplicationBootstrap(), - ), + ); + + await Promise.all( + callOperator( + iterate(instances) + .filter( + ([key, wrapper]) => + wrapper.isDependencyTreeStatic() && !wrapper.isTransient, + ) + .map(([key, { instance }]) => instance), + ), ); + const transientInstances = this.getTransientInstances(instances); + await Promise.all(callOperator(iterate(transientInstances))); + if (moduleClassInstance && this.hasOnAppBotstrapHook(moduleClassInstance)) { await (moduleClassInstance as OnApplicationBootstrap).onApplicationBootstrap(); } @@ -187,4 +219,16 @@ export class NestApplicationContext implements INestApplicationContext { TResult >(metatypeOrToken, contextModule); } + + private getTransientInstances( + instances: [string, InstanceWrapper][], + ): InstanceWrapper[] { + return iterate(instances) + .filter(([key, wrapper]) => wrapper.isDependencyTreeStatic()) + .map(([key, wrapper]) => wrapper.getStaticTransientInstances()) + .flatten() + .filter(item => !!item) + .map(({ instance }: any) => instance) + .toArray() as InstanceWrapper[]; + } } diff --git a/packages/core/nest-factory.ts b/packages/core/nest-factory.ts index 6143205cec4..93a91a5ac1d 100644 --- a/packages/core/nest-factory.ts +++ b/packages/core/nest-factory.ts @@ -78,11 +78,11 @@ export class NestFactoryStatic { '@nestjs/microservices', 'NestFactory', ); - const applicationConfig = new ApplicationConfig(); const container = new NestContainer(applicationConfig); this.applyLogger(options); + await this.initialize(module, container, applicationConfig); return this.createNestInstance( new NestMicroservice(container, options, applicationConfig), @@ -129,7 +129,7 @@ export class NestFactoryStatic { new MetadataScanner(), config, ); - container.setApplicationRef(httpServer); + container.setHttpAdapter(httpServer); try { this.logger.log(MESSAGES.APPLICATION_START); await ExceptionsZone.asyncRun(async () => { diff --git a/packages/core/pipes/pipes-context-creator.ts b/packages/core/pipes/pipes-context-creator.ts index c99bd315886..19e08d3a91e 100644 --- a/packages/core/pipes/pipes-context-creator.ts +++ b/packages/core/pipes/pipes-context-creator.ts @@ -27,21 +27,29 @@ export class PipesContextCreator extends ContextCreator { callback: (...args: any[]) => any, module: string, contextId = STATIC_CONTEXT, + inquirerId?: string, ): Transform[] { this.moduleContext = module; - return this.createContext(instance, callback, PIPES_METADATA, contextId); + return this.createContext( + instance, + callback, + PIPES_METADATA, + contextId, + inquirerId, + ); } public createConcreteContext( metadata: T, contextId = STATIC_CONTEXT, + inquirerId?: string, ): R { if (isEmpty(metadata)) { return [] as R; } return iterate(metadata) .filter((pipe: any) => pipe && (pipe.name || pipe.transform)) - .map(pipe => this.getPipeInstance(pipe, contextId)) + .map(pipe => this.getPipeInstance(pipe, contextId, inquirerId)) .filter(pipe => pipe && pipe.transform && isFunction(pipe.transform)) .map(pipe => pipe.transform.bind(pipe)) .toArray() as R; @@ -50,16 +58,20 @@ export class PipesContextCreator extends ContextCreator { public getPipeInstance( pipe: Function | PipeTransform, contextId = STATIC_CONTEXT, - ) { + inquirerId?: string, + ): PipeTransform | null { const isObject = (pipe as PipeTransform).transform; if (isObject) { - return pipe; + return pipe as PipeTransform; } const instanceWrapper = this.getInstanceByMetatype(pipe as Function); if (!instanceWrapper) { return null; } - const instanceHost = instanceWrapper.getInstanceByContextId(contextId); + const instanceHost = instanceWrapper.getInstanceByContextId( + contextId, + inquirerId, + ); return instanceHost && instanceHost.instance; } diff --git a/packages/core/router/index.ts b/packages/core/router/index.ts new file mode 100644 index 00000000000..56e4b0555f1 --- /dev/null +++ b/packages/core/router/index.ts @@ -0,0 +1 @@ +export * from './request'; diff --git a/packages/core/router/interfaces/exceptions-filter.interface.ts b/packages/core/router/interfaces/exceptions-filter.interface.ts index 42e3f8805b4..3063fc64df4 100644 --- a/packages/core/router/interfaces/exceptions-filter.interface.ts +++ b/packages/core/router/interfaces/exceptions-filter.interface.ts @@ -8,5 +8,6 @@ export interface ExceptionsFilter { callback: Function, module: string, contextId?: ContextId, + inquirerId?: string, ): ExceptionsHandler; } diff --git a/packages/core/router/request/index.ts b/packages/core/router/request/index.ts new file mode 100644 index 00000000000..ba2f8a4d399 --- /dev/null +++ b/packages/core/router/request/index.ts @@ -0,0 +1 @@ +export * from './request-constants'; diff --git a/packages/core/router/request/request-constants.ts b/packages/core/router/request/request-constants.ts new file mode 100644 index 00000000000..0d9f1f2e1de --- /dev/null +++ b/packages/core/router/request/request-constants.ts @@ -0,0 +1 @@ +export const REQUEST = Symbol('REQUEST'); diff --git a/packages/core/router/request/request-providers.ts b/packages/core/router/request/request-providers.ts new file mode 100644 index 00000000000..308f79065e0 --- /dev/null +++ b/packages/core/router/request/request-providers.ts @@ -0,0 +1,9 @@ +import { Provider, Scope } from '@nestjs/common'; +import { REQUEST } from './request-constants'; + +const noop = () => {}; +export const requestProvider: Provider = { + provide: REQUEST, + scope: Scope.REQUEST, + useFactory: noop, +}; diff --git a/packages/core/router/router-exception-filters.ts b/packages/core/router/router-exception-filters.ts index 9cf09112053..d9835e1e6c0 100644 --- a/packages/core/router/router-exception-filters.ts +++ b/packages/core/router/router-exception-filters.ts @@ -23,6 +23,7 @@ export class RouterExceptionFilters extends BaseExceptionFilterContext { callback: RouterProxyCallback, module: string, contextId = STATIC_CONTEXT, + inquirerId?: string, ): ExceptionsHandler { this.moduleContext = module; @@ -32,6 +33,7 @@ export class RouterExceptionFilters extends BaseExceptionFilterContext { callback, EXCEPTION_FILTERS_METADATA, contextId, + inquirerId, ); if (isEmpty(filters)) { return exceptionHandler; diff --git a/packages/core/router/router-execution-context.ts b/packages/core/router/router-execution-context.ts index 01f9dd1b2e2..586edb9d278 100644 --- a/packages/core/router/router-execution-context.ts +++ b/packages/core/router/router-execution-context.ts @@ -76,31 +76,38 @@ export class RouterExecutionContext { module: string, requestMethod: RequestMethod, contextId = STATIC_CONTEXT, + inquirerId?: string, ) { - const { argsLength, paramsOptions, fnHandleResponse } = this.getMetadata( - instance, - callback, - methodName, - module, - requestMethod, + const { + argsLength, + fnHandleResponse, + paramtypes, + getParamsMetadata, + } = this.getMetadata(instance, callback, methodName, module, requestMethod); + const paramsOptions = this.contextUtils.mergeParamsMetatypes( + getParamsMetadata(module, contextId, inquirerId), + paramtypes, ); const pipes = this.pipesContextCreator.create( instance, callback, module, contextId, + inquirerId, ); const guards = this.guardsContextCreator.create( instance, callback, module, contextId, + inquirerId, ); const interceptors = this.interceptorsContextCreator.create( instance, callback, module, contextId, + inquirerId, ); const fnCanActivate = this.createGuardsFn(guards, instance, callback); @@ -159,15 +166,24 @@ export class RouterExecutionContext { methodName, ); const httpCode = this.reflectHttpStatusCode(callback); - const paramsMetadata = this.exchangeKeysForValues(keys, metadata, module); + const getParamsMetadata = ( + moduleKey: string, + contextId = STATIC_CONTEXT, + inquirerId?: string, + ) => + this.exchangeKeysForValues( + keys, + metadata, + moduleKey, + contextId, + inquirerId, + ); + + const paramsMetadata = getParamsMetadata(module); const isResponseHandled = paramsMetadata.some( ({ type }) => type === RouteParamtypes.RESPONSE || type === RouteParamtypes.NEXT, ); - const paramsOptions = this.contextUtils.mergeParamsMetatypes( - paramsMetadata, - paramtypes, - ); const httpStatusCode = httpCode ? httpCode : this.responseController.getStatusByMethod(requestMethod); @@ -179,8 +195,9 @@ export class RouterExecutionContext { ); const handlerMetadata: HandlerMetadata = { argsLength, - paramsOptions, fnHandleResponse, + paramtypes, + getParamsMetadata, }; this.handlerMetadataStorage.set(instance, methodName, handlerMetadata); return handlerMetadata; @@ -204,12 +221,16 @@ export class RouterExecutionContext { keys: string[], metadata: RouteParamsMetadata, moduleContext: string, + contextId = STATIC_CONTEXT, + inquirerId?: string, ): ParamProperties[] { this.pipesContextCreator.setModuleContext(moduleContext); return keys.map(key => { const { index, data, pipes: pipesCollection } = metadata[key]; const pipes = this.pipesContextCreator.createConcreteContext( pipesCollection, + contextId, + inquirerId, ); const type = this.contextUtils.mapParamType(key); diff --git a/packages/core/router/router-explorer.ts b/packages/core/router/router-explorer.ts index b658e8fcd56..61463cb1c73 100644 --- a/packages/core/router/router-explorer.ts +++ b/packages/core/router/router-explorer.ts @@ -9,18 +9,20 @@ import { ApplicationConfig } from '../application-config'; import { UnknownRequestMappingException } from '../errors/exceptions/unknown-request-mapping.exception'; import { GuardsConsumer } from '../guards/guards-consumer'; import { GuardsContextCreator } from '../guards/guards-context-creator'; +import { createContextId } from '../helpers/context-id-factory'; import { ROUTE_MAPPED_MESSAGE } from '../helpers/messages'; import { RouterMethodFactory } from '../helpers/router-method-factory'; import { STATIC_CONTEXT } from '../injector/constants'; import { NestContainer } from '../injector/container'; import { Injector } from '../injector/injector'; -import { InstanceWrapper } from '../injector/instance-wrapper'; +import { ContextId, InstanceWrapper } from '../injector/instance-wrapper'; import { InterceptorsConsumer } from '../interceptors/interceptors-consumer'; import { InterceptorsContextCreator } from '../interceptors/interceptors-context-creator'; import { MetadataScanner } from '../metadata-scanner'; import { PipesConsumer } from '../pipes/pipes-consumer'; import { PipesContextCreator } from '../pipes/pipes-context-creator'; import { ExceptionsFilter } from './interfaces/exceptions-filter.interface'; +import { REQUEST } from './request'; import { RouteParamsFactory } from './route-params-factory'; import { RouterExecutionContext } from './router-execution-context'; import { RouterProxy, RouterProxyCallback } from './router-proxy'; @@ -53,7 +55,7 @@ export class RouterExplorer { new GuardsConsumer(), new InterceptorsContextCreator(container, config), new InterceptorsConsumer(), - container.getApplicationRef(), + container.getHttpAdapterRef(), ); } @@ -176,7 +178,9 @@ export class RouterExplorer { res: TResponse, next: Function, ) => { - const contextId = { id: 1 }; // asyncId + const contextId = createContextId(); + this.registerRequestProvider(req, contextId); + const contextInstance = await this.injector.loadPerContext( instance, module, @@ -190,6 +194,7 @@ export class RouterExplorer { moduleKey, requestMethod, contextId, + instanceWrapper.id, )(req, res, next); }, ); @@ -212,6 +217,7 @@ export class RouterExplorer { module: string, requestMethod: RequestMethod, contextId = STATIC_CONTEXT, + inquirerId?: string, ) { const executionContext = this.executionContextCreator.create( instance, @@ -220,13 +226,25 @@ export class RouterExplorer { module, requestMethod, contextId, + inquirerId, ); const exceptionFilter = this.exceptionsFilter.create( instance, callback, module, contextId, + inquirerId, ); return this.routerProxy.createProxy(executionContext, exceptionFilter); } + + private registerRequestProvider(request: T, contextId: ContextId) { + const coreModuleRef = this.container.getInternalCoreModuleRef(); + const wrapper = coreModuleRef.getProviderByKey(REQUEST); + + wrapper.setInstanceByContextId(contextId, { + instance: request, + isResolved: true, + }); + } } diff --git a/packages/core/router/routes-resolver.ts b/packages/core/router/routes-resolver.ts index c795a366d8e..4dd46117ea2 100644 --- a/packages/core/router/routes-resolver.ts +++ b/packages/core/router/routes-resolver.ts @@ -28,7 +28,7 @@ export class RoutesResolver implements Resolver { this.routerExceptionsFilter = new RouterExceptionFilters( container, config, - container.getApplicationRef(), + container.getHttpAdapterRef(), ); this.routerBuilder = new RouterExplorer( new MetadataScanner(), @@ -73,7 +73,7 @@ export class RoutesResolver implements Resolver { } public registerNotFoundHandler() { - const applicationRef = this.container.getApplicationRef(); + const applicationRef = this.container.getHttpAdapterRef(); const callback = (req: TRequest, res: TResponse) => { const method = applicationRef.getRequestMethod(req); const url = applicationRef.getRequestUrl(req); @@ -100,7 +100,7 @@ export class RoutesResolver implements Resolver { undefined, ); const proxy = this.routerProxy.createExceptionLayerProxy(callback, handler); - const applicationRef = this.container.getApplicationRef(); + const applicationRef = this.container.getHttpAdapterRef(); applicationRef.setErrorHandler && applicationRef.setErrorHandler(proxy); } diff --git a/packages/core/scanner.ts b/packages/core/scanner.ts index aa4863d7f84..36641fc32c2 100644 --- a/packages/core/scanner.ts +++ b/packages/core/scanner.ts @@ -25,6 +25,7 @@ import { ApplicationConfig } from './application-config'; import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from './constants'; import { CircularDependencyException } from './errors/exceptions/circular-dependency.exception'; import { NestContainer } from './injector/container'; +import { Module } from './injector/module'; import { MetadataScanner } from './metadata-scanner'; interface ApplicationProviderWrapper { @@ -35,6 +36,7 @@ interface ApplicationProviderWrapper { export class DependenciesScanner { private readonly applicationProvidersApplyMap: ApplicationProviderWrapper[] = []; + constructor( private readonly container: NestContainer, private readonly metadataScanner: MetadataScanner, @@ -42,6 +44,7 @@ export class DependenciesScanner { ) {} public async scan(module: Type) { + await this.registerCoreModule(); await this.scanForModules(module); await this.scanModulesForDependencies(); this.container.bindGlobalScope(); @@ -51,8 +54,8 @@ export class DependenciesScanner { module: ForwardReference | Type | DynamicModule, scope: Type[] = [], ctxRegistry: (ForwardReference | DynamicModule | Type)[] = [], - ) { - await this.insertModule(module, scope); + ): Promise { + const moduleInstance = await this.insertModule(module, scope); ctxRegistry.push(module); if (this.isForwardReference(module)) { @@ -78,13 +81,14 @@ export class DependenciesScanner { ctxRegistry, ); } + return moduleInstance; } - public async insertModule(module: any, scope: Type[]) { + public async insertModule(module: any, scope: Type[]): Promise { if (module && module.forwardRef) { return this.container.addModule(module.forwardRef(), scope); } - await this.container.addModule(module, scope); + return this.container.addModule(module, scope); } public async scanModulesForDependencies() { @@ -301,6 +305,12 @@ export class DependenciesScanner { return Reflect.getMetadata(metadataKey, metatype) || []; } + public async registerCoreModule() { + const module = this.container.createCoreModule(); + const instance = await this.scanForModules(module); + this.container.registerCoreModuleRef(instance); + } + public applyApplicationProviders() { const applyProvidersMap = this.getApplyProvidersMap(); this.applicationProvidersApplyMap.forEach( diff --git a/packages/core/test/exceptions/exceptions-handler.spec.ts b/packages/core/test/exceptions/exceptions-handler.spec.ts index 19c648731df..bd44a3364c9 100644 --- a/packages/core/test/exceptions/exceptions-handler.spec.ts +++ b/packages/core/test/exceptions/exceptions-handler.spec.ts @@ -6,7 +6,7 @@ import { AbstractHttpAdapter } from '../../adapters'; import { InvalidExceptionFilterException } from '../../errors/exceptions/invalid-exception-filter.exception'; import { ExceptionsHandler } from '../../exceptions/exceptions-handler'; import { ExecutionContextHost } from '../../helpers/execution-context-host'; -import { NoopHttpAdapter } from './../utils/noop-adapter'; +import { NoopHttpAdapter } from './../utils/noop-adapter.spec'; describe('ExceptionsHandler', () => { let adapter: AbstractHttpAdapter; diff --git a/packages/core/test/helpers/application-ref-host.spec.ts b/packages/core/test/helpers/application-ref-host.spec.ts index bb943af96a6..5a642e6200a 100644 --- a/packages/core/test/helpers/application-ref-host.spec.ts +++ b/packages/core/test/helpers/application-ref-host.spec.ts @@ -1,12 +1,12 @@ import { expect } from 'chai'; -import { ApplicationReferenceHost } from './../../helpers/application-ref-host'; +import { HttpAdapterHost } from '../../helpers/http-adapter-host'; -describe('ApplicationReferenceHost', () => { - const applicationRefHost = new ApplicationReferenceHost(); +describe('HttpAdapterHost', () => { + const applicationRefHost = new HttpAdapterHost(); it('should wrap application reference', () => { const ref = {}; - applicationRefHost.applicationRef = ref; + applicationRefHost.httpAdapter = ref; - expect(applicationRefHost.applicationRef).to.be.eql(ref); + expect(applicationRefHost.httpAdapter).to.be.eql(ref); }); }); diff --git a/packages/core/test/injector/container.spec.ts b/packages/core/test/injector/container.spec.ts index 6f6692dc21f..5d7723825d4 100644 --- a/packages/core/test/injector/container.spec.ts +++ b/packages/core/test/injector/container.spec.ts @@ -5,6 +5,8 @@ import { Global } from '../../../common/index'; import { CircularDependencyException } from '../../errors/exceptions/circular-dependency.exception'; import { UnknownModuleException } from '../../errors/exceptions/unknown-module.exception'; import { NestContainer } from '../../injector/container'; +import { InternalCoreModule } from '../../injector/internal-core-module'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('NestContainer', () => { let container: NestContainer; @@ -173,4 +175,46 @@ describe('NestContainer', () => { }); }); }); + + describe('get applicationConfig', () => { + it('should return ApplicationConfig instance', () => { + expect(container.applicationConfig).to.be.eql( + (container as any)._applicationConfig, + ); + }); + }); + + describe('setHttpAdapter', () => { + it('should set http adapter', () => { + const httpAdapter = new NoopHttpAdapter({}); + container.setHttpAdapter(httpAdapter); + + const internalStorage = (container as any).internalProvidersStorage; + expect(internalStorage.httpAdapter).to.be.eql(httpAdapter); + }); + }); + + describe('getModuleByKey', () => { + it('should return module by passed key', () => { + const key = 'test'; + const value = {}; + container.getModules().set(key, value as any); + + expect(container.getModuleByKey(key)).to.be.eql(value); + }); + }); + + describe('createCoreModule', () => { + it('should create InternalCoreModule', () => { + expect(container.createCoreModule().module).to.be.eql(InternalCoreModule); + }); + }); + + describe('registerCoreModuleRef', () => { + it('should register core module ref', () => { + const ref = {} as any; + container.registerCoreModuleRef(ref); + expect((container as any).internalCoreModule).to.be.eql(ref); + }); + }); }); diff --git a/packages/core/test/injector/injector.spec.ts b/packages/core/test/injector/injector.spec.ts index 50b7cdf37fb..adb7e290a30 100644 --- a/packages/core/test/injector/injector.spec.ts +++ b/packages/core/test/injector/injector.spec.ts @@ -150,25 +150,28 @@ describe('Injector', () => { ); }); - it('should return null when collection is nil', () => { + it('should return undefined when collection is nil', () => { const result = injector.loadPrototype(test, null); - expect(result).to.be.null; + expect(result).to.be.undefined; }); - it('should return null when target isResolved', () => { + it('should return undefined when target isResolved', () => { const collection = { - get: () => ({ getInstanceByContextId: () => ({ isResolved: true }) }), + get: () => ({ + getInstanceByContextId: () => ({ isResolved: true }), + createPrototype: () => {}, + }), }; const result = injector.loadPrototype(test, collection as any); - expect(result).to.be.null; + expect(result).to.be.undefined; }); - it('should return null when "inject" is not nil', () => { + it('should return undefined when "inject" is not nil', () => { const collection = { get: () => new InstanceWrapper({ inject: [] }), }; const result = injector.loadPrototype(test, collection as any); - expect(result).to.be.null; + expect(result).to.be.undefined; }); }); @@ -236,7 +239,7 @@ describe('Injector', () => { it('should call "loadInstance" with expected arguments', async () => { const module = { controllers: [] }; - const wrapper = { test: 'test' }; + const wrapper = { test: 'test', getEnhancersMetadata: () => [] }; await injector.loadController(wrapper as any, module as any); expect(loadInstance.calledWith(wrapper, module.controllers, module)).to.be @@ -605,4 +608,126 @@ describe('Injector', () => { }); }); }); + + describe('instantiateClass', () => { + class TestClass {} + + describe('when context is static', () => { + it('should instantiate class', async () => { + const wrapper = new InstanceWrapper({ metatype: TestClass }); + await injector.instantiateClass([], wrapper, wrapper, STATIC_CONTEXT); + + expect(wrapper.instance).to.not.be.undefined; + expect(wrapper.instance).to.be.instanceOf(TestClass); + }); + it('should call factory', async () => { + const wrapper = new InstanceWrapper({ + inject: [], + metatype: (() => ({})) as any, + }); + await injector.instantiateClass([], wrapper, wrapper, STATIC_CONTEXT); + + expect(wrapper.instance).to.not.be.undefined; + }); + }); + describe('when context is not static', () => { + it('should not instantiate class', async () => { + const ctx = { id: 3 }; + const wrapper = new InstanceWrapper({ metatype: TestClass }); + await injector.instantiateClass([], wrapper, wrapper, ctx); + + expect(wrapper.instance).to.be.undefined; + expect(wrapper.getInstanceByContextId(ctx).isResolved).to.be.true; + }); + + it('should not call factory', async () => { + const wrapper = new InstanceWrapper({ + inject: [], + metatype: sinon.spy() as any, + }); + await injector.instantiateClass([], wrapper, wrapper, { id: 2 }); + expect(wrapper.instance).to.be.undefined; + expect((wrapper.metatype as any).called).to.be.false; + }); + }); + }); + + describe('loadPerContext', () => { + class TestClass {} + + it('should load instance per context id', async () => { + const container = new NestContainer(); + const moduleCtor = class TestModule {}; + const ctx = STATIC_CONTEXT; + const module = await container.addModule(moduleCtor, []); + + module.addProvider({ + name: 'TestClass', + provide: TestClass, + useClass: TestClass, + }); + const instance = await injector.loadPerContext( + new TestClass(), + module, + module.providers, + ctx, + ); + expect(instance).to.be.instanceOf(TestClass); + }); + }); + + describe('loadEnhancersPerContext', () => { + it('should load enhancers per context id', async () => { + const wrapper = new InstanceWrapper(); + wrapper.addEnhancerMetadata(new InstanceWrapper()); + wrapper.addEnhancerMetadata(new InstanceWrapper()); + + const loadInstanceStub = sinon + .stub(injector, 'loadInstance') + .callsFake(() => ({})); + + await injector.loadEnhancersPerContext( + wrapper, + new Module(class {}, [], new NestContainer()), + STATIC_CONTEXT, + ); + expect(loadInstanceStub.calledTwice).to.be.true; + }); + }); + + describe('loadCtorMetadata', () => { + it('should resolve ctor metadata', async () => { + const wrapper = new InstanceWrapper(); + wrapper.addCtorMetadata(0, new InstanceWrapper()); + wrapper.addCtorMetadata(1, new InstanceWrapper()); + + const resolveComponentHostStub = sinon + .stub(injector, 'resolveComponentHost') + .callsFake(() => new InstanceWrapper()); + + await injector.loadCtorMetadata( + wrapper.getCtorMetadata(), + STATIC_CONTEXT, + ); + expect(resolveComponentHostStub.calledTwice).to.be.true; + }); + }); + + describe('loadPropertiesMetadata', () => { + it('should resolve properties metadata', async () => { + const wrapper = new InstanceWrapper(); + wrapper.addPropertiesMetadata('key1', new InstanceWrapper()); + wrapper.addPropertiesMetadata('key2', new InstanceWrapper()); + + const resolveComponentHostStub = sinon + .stub(injector, 'resolveComponentHost') + .callsFake(() => new InstanceWrapper()); + + await injector.loadPropertiesMetadata( + wrapper.getPropertiesMetadata(), + STATIC_CONTEXT, + ); + expect(resolveComponentHostStub.calledTwice).to.be.true; + }); + }); }); diff --git a/packages/core/test/injector/instance-wrapper.spec.ts b/packages/core/test/injector/instance-wrapper.spec.ts new file mode 100644 index 00000000000..99bae067787 --- /dev/null +++ b/packages/core/test/injector/instance-wrapper.spec.ts @@ -0,0 +1,325 @@ +import { Scope } from '@nestjs/common'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { STATIC_CONTEXT } from '../../injector/constants'; +import { InstanceWrapper } from '../../injector/instance-wrapper'; + +class TestClass {} + +describe('InstanceWrapper', () => { + describe('initialize', () => { + const partial = { + name: 'test', + metatype: TestClass, + scope: Scope.DEFAULT, + instance: new TestClass(), + }; + it('should assign partial', () => { + const instance = new InstanceWrapper(partial); + + expect(instance.name).to.be.eql(partial.name); + expect(instance.scope).to.be.eql(partial.scope); + expect(instance.metatype).to.be.eql(partial.metatype); + }); + it('should set intance by context id', () => { + const instance = new InstanceWrapper(partial); + + expect( + instance.getInstanceByContextId(STATIC_CONTEXT).instance, + ).to.be.eql(partial.instance); + }); + }); + + describe('isDependencyTreeStatic', () => { + describe('when request scoped', () => { + it('should return false', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.REQUEST, + }); + expect(wrapper.isDependencyTreeStatic()).to.be.false; + }); + }); + describe('when statically scoped', () => { + describe('dependencies', () => { + describe('when each is static', () => { + it('should return true', () => { + const wrapper = new InstanceWrapper(); + wrapper.addCtorMetadata(0, new InstanceWrapper()); + expect(wrapper.isDependencyTreeStatic()).to.be.true; + }); + }); + describe('when one is not static', () => { + it('should return false', () => { + const wrapper = new InstanceWrapper(); + wrapper.addCtorMetadata(0, new InstanceWrapper()); + wrapper.addCtorMetadata( + 1, + new InstanceWrapper({ + scope: Scope.REQUEST, + }), + ); + expect(wrapper.isDependencyTreeStatic()).to.be.false; + }); + }); + }); + describe('properties', () => { + describe('when each is static', () => { + it('should return true', () => { + const wrapper = new InstanceWrapper(); + wrapper.addPropertiesMetadata('key1', new InstanceWrapper()); + wrapper.addPropertiesMetadata('key2', new InstanceWrapper()); + expect(wrapper.isDependencyTreeStatic()).to.be.true; + }); + }); + describe('when one is not static', () => { + it('should return false', () => { + const wrapper = new InstanceWrapper(); + wrapper.addPropertiesMetadata( + 'key1', + new InstanceWrapper({ scope: Scope.REQUEST }), + ); + wrapper.addPropertiesMetadata('key2', new InstanceWrapper()); + expect(wrapper.isDependencyTreeStatic()).to.be.false; + }); + }); + }); + describe('enhancers', () => { + describe('when each is static', () => { + it('should return true', () => { + const wrapper = new InstanceWrapper(); + wrapper.addEnhancerMetadata(new InstanceWrapper()); + wrapper.addEnhancerMetadata(new InstanceWrapper()); + expect(wrapper.isDependencyTreeStatic()).to.be.true; + }); + }); + describe('when one is not static', () => { + it('should return false', () => { + const wrapper = new InstanceWrapper(); + wrapper.addEnhancerMetadata( + new InstanceWrapper({ scope: Scope.REQUEST }), + ); + wrapper.addEnhancerMetadata(new InstanceWrapper()); + expect(wrapper.isDependencyTreeStatic()).to.be.false; + }); + }); + }); + }); + }); + + describe('isNotMetatype', () => { + describe('when metatype is nil', () => { + it('should return true', () => { + const instance = new InstanceWrapper({ metatype: null }); + expect(instance.isNotMetatype).to.be.true; + }); + }); + describe('when metatype is not nil', () => { + it('should return false', () => { + const instance = new InstanceWrapper({ metatype: TestClass }); + expect(instance.isNotMetatype).to.be.false; + }); + }); + }); + + describe('addEnhancerMetadata', () => { + it('should add enhancers metadata', () => { + const instance = new InstanceWrapper(); + const enhancers = [new InstanceWrapper()]; + instance.addEnhancerMetadata(enhancers[0]); + expect(instance.getEnhancersMetadata()).to.be.eql(enhancers); + }); + }); + + describe('when set instance has been called', () => { + it('should set static context value', () => { + const instance = { test: true }; + const wrapper = new InstanceWrapper(); + wrapper.instance = instance; + + expect(wrapper.getInstanceByContextId(STATIC_CONTEXT).instance).to.be.eql( + instance, + ); + }); + }); + + describe('cloneStaticInstance', () => { + describe('when wrapper is static', () => { + it('should return static instance', () => { + const instance = { test: true }; + const wrapper = new InstanceWrapper({ instance }); + + expect(wrapper.cloneStaticInstance({ id: 0 }).instance).to.be.eql( + instance, + ); + }); + }); + describe('when wrapper is not static', () => { + it('should clone instance by context id', () => { + const instance = { test: true }; + const wrapper = new InstanceWrapper({ instance, scope: Scope.REQUEST }); + + expect(wrapper.cloneStaticInstance({ id: 0 }).instance).to.be.undefined; + }); + }); + }); + + describe('getInstanceByContextId', () => { + describe('when transient and inquirer has been passed', () => { + it('should call "getInstanceByInquirerId"', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.TRANSIENT, + }); + const getInstanceByInquirerIdSpy = sinon.spy( + wrapper, + 'getInstanceByInquirerId', + ); + wrapper.getInstanceByContextId(STATIC_CONTEXT, 'inquirerId'); + expect(getInstanceByInquirerIdSpy.called).to.be.true; + }); + }); + }); + + describe('setInstanceByContextId', () => { + describe('when transient and inquirer has been passed', () => { + it('should call "setInstanceByInquirerId"', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.TRANSIENT, + }); + const setInstanceByInquirerIdSpy = sinon.spy( + wrapper, + 'setInstanceByInquirerId', + ); + wrapper.setInstanceByContextId( + STATIC_CONTEXT, + { instance: {} }, + 'inquirerId', + ); + expect(setInstanceByInquirerIdSpy.called).to.be.true; + }); + }); + }); + + describe('isInRequestScope', () => { + describe('when tree and context are not static and is not transient', () => { + it('should return true', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.REQUEST, + }); + expect(wrapper.isInRequestScope({ id: 3 })).to.be.true; + }); + }); + describe('otherwise', () => { + it('should return false', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.TRANSIENT, + }); + expect(wrapper.isInRequestScope({ id: 3 })).to.be.false; + + const wrapper2 = new InstanceWrapper({ + scope: Scope.REQUEST, + }); + expect(wrapper2.isInRequestScope(STATIC_CONTEXT)).to.be.false; + }); + }); + }); + + describe('isLazyTransient', () => { + describe('when inquirer is request scoped and context is not static and is transient', () => { + it('should return true', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.TRANSIENT, + }); + expect( + wrapper.isLazyTransient( + { id: 3 }, + new InstanceWrapper({ + scope: Scope.REQUEST, + }), + ), + ).to.be.true; + }); + }); + describe('otherwise', () => { + it('should return false', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.TRANSIENT, + }); + expect(wrapper.isLazyTransient({ id: 3 }, new InstanceWrapper())).to.be + .false; + + const wrapper2 = new InstanceWrapper({ + scope: Scope.REQUEST, + }); + expect( + wrapper2.isLazyTransient( + STATIC_CONTEXT, + new InstanceWrapper({ + scope: Scope.TRANSIENT, + }), + ), + ).to.be.false; + }); + }); + }); + + describe('isStatic', () => { + describe('when inquirer is not request scoped and context and tree are static', () => { + it('should return true', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.DEFAULT, + }); + expect( + wrapper.isStatic( + STATIC_CONTEXT, + new InstanceWrapper({ + scope: Scope.DEFAULT, + }), + ), + ).to.be.true; + }); + }); + describe('otherwise', () => { + it('should return false', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.REQUEST, + }); + expect(wrapper.isStatic({ id: 3 }, new InstanceWrapper())).to.be.false; + + const wrapper2 = new InstanceWrapper({ + scope: Scope.TRANSIENT, + }); + expect( + wrapper2.isStatic( + STATIC_CONTEXT, + new InstanceWrapper({ + scope: Scope.REQUEST, + }), + ), + ).to.be.false; + }); + }); + }); + + describe('getStaticTransientInstances', () => { + describe('when instance is not transient', () => { + it('should return an empty array', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.DEFAULT, + }); + expect(wrapper.getStaticTransientInstances()).to.be.eql([]); + }); + }); + describe('when instance is transient', () => { + it('should return all static instances', () => { + const wrapper = new InstanceWrapper({ + scope: Scope.TRANSIENT, + }); + const instanceHost = { + instance: {}, + }; + wrapper.setInstanceByInquirerId(STATIC_CONTEXT, 'test', instanceHost); + expect(wrapper.getStaticTransientInstances()).to.be.eql([instanceHost]); + }); + }); + }); +}); diff --git a/packages/core/test/injector/module.spec.ts b/packages/core/test/injector/module.spec.ts index 5caf77e0955..142d12faf65 100644 --- a/packages/core/test/injector/module.spec.ts +++ b/packages/core/test/injector/module.spec.ts @@ -277,10 +277,12 @@ describe('Module', () => { }); }); - describe('relatedModules', () => { + describe('imports', () => { it('should return relatedModules', () => { const test = ['test']; (module as any)._imports = test; + + expect(module.imports).to.be.eql(test); expect(module.relatedModules).to.be.eql(test); }); }); @@ -297,7 +299,9 @@ describe('Module', () => { it('should return controllers', () => { const test = ['test']; (module as any)._controllers = test; + expect(module.controllers).to.be.eql(test); + expect(module.routes).to.be.eql(test); }); }); @@ -305,10 +309,21 @@ describe('Module', () => { it('should return exports', () => { const test = ['test']; (module as any)._exports = test; + expect(module.exports).to.be.eql(test); }); }); + describe('providers', () => { + it('should return providers', () => { + const test = ['test']; + (module as any)._providers = test; + + expect(module.providers).to.be.eql(test); + expect(module.components).to.be.eql(test); + }); + }); + describe('createModuleReferenceType', () => { let moduleRef; diff --git a/packages/core/test/middleware/middlewares-module.spec.ts b/packages/core/test/middleware/middlewares-module.spec.ts index b9b167c153d..125e6bd86ea 100644 --- a/packages/core/test/middleware/middlewares-module.spec.ts +++ b/packages/core/test/middleware/middlewares-module.spec.ts @@ -15,7 +15,7 @@ import { MiddlewareBuilder } from '../../middleware/builder'; import { MiddlewareContainer } from '../../middleware/container'; import { MiddlewareModule } from '../../middleware/middleware-module'; import { RouterExceptionFilters } from '../../router/router-exception-filters'; -import { NoopHttpAdapter } from '../utils/noop-adapter'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('MiddlewareModule', () => { let middlewareModule: MiddlewareModule; diff --git a/packages/core/test/router/router-exception-filters.spec.ts b/packages/core/test/router/router-exception-filters.spec.ts index 520a86dfb80..09f34e6a31d 100644 --- a/packages/core/test/router/router-exception-filters.spec.ts +++ b/packages/core/test/router/router-exception-filters.spec.ts @@ -5,7 +5,7 @@ import { UseFilters } from '../../../common/decorators/core/exception-filters.de import { ApplicationConfig } from '../../application-config'; import { NestContainer } from '../../injector/container'; import { RouterExceptionFilters } from '../../router/router-exception-filters'; -import { NoopHttpAdapter } from '../utils/noop-adapter'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('RouterExceptionFilters', () => { let moduleName: string; diff --git a/packages/core/test/router/router-execution-context.spec.ts b/packages/core/test/router/router-execution-context.spec.ts index 119d87a44ed..2e80ad4e879 100644 --- a/packages/core/test/router/router-execution-context.spec.ts +++ b/packages/core/test/router/router-execution-context.spec.ts @@ -14,7 +14,7 @@ import { PipesConsumer } from '../../pipes/pipes-consumer'; import { PipesContextCreator } from '../../pipes/pipes-context-creator'; import { RouteParamsFactory } from '../../router/route-params-factory'; import { RouterExecutionContext } from '../../router/router-execution-context'; -import { NoopHttpAdapter } from '../utils/noop-adapter'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('RouterExecutionContext', () => { let contextCreator: RouterExecutionContext; diff --git a/packages/core/test/router/router-explorer.spec.ts b/packages/core/test/router/router-explorer.spec.ts index d09c159031f..98a475b6ae5 100644 --- a/packages/core/test/router/router-explorer.spec.ts +++ b/packages/core/test/router/router-explorer.spec.ts @@ -1,28 +1,33 @@ -import * as sinon from 'sinon'; import { expect } from 'chai'; -import { RouterExplorer } from '../../router/router-explorer'; +import * as sinon from 'sinon'; import { Controller } from '../../../common/decorators/core/controller.decorator'; -import { RequestMapping } from '../../../common/decorators/http/request-mapping.decorator'; +import { + All, + Get, + Post, +} from '../../../common/decorators/http/request-mapping.decorator'; import { RequestMethod } from '../../../common/enums/request-method.enum'; -import { MetadataScanner } from '../../metadata-scanner'; import { NestContainer } from '../../injector/container'; +import { MetadataScanner } from '../../metadata-scanner'; +import { RouterExplorer } from '../../router/router-explorer'; describe('RouterExplorer', () => { @Controller('global') class TestRoute { - @RequestMapping({ path: 'test' }) + @Get('test') public getTest() {} - - @RequestMapping({ path: 'test', method: RequestMethod.POST }) + @Post('test') public postTest() {} - - @RequestMapping({ path: 'another-test', method: RequestMethod.ALL }) + @All('another-test') public anotherTest() {} } let routerBuilder: RouterExplorer; beforeEach(() => { - routerBuilder = new RouterExplorer(new MetadataScanner(), new NestContainer()); + routerBuilder = new RouterExplorer( + new MetadataScanner(), + new NestContainer(), + ); }); describe('scanForPaths', () => { it('should method return expected list of route paths', () => { diff --git a/packages/core/test/router/router-proxy.spec.ts b/packages/core/test/router/router-proxy.spec.ts index e38d5a94a82..b80193ebc91 100644 --- a/packages/core/test/router/router-proxy.spec.ts +++ b/packages/core/test/router/router-proxy.spec.ts @@ -3,7 +3,7 @@ import * as sinon from 'sinon'; import { HttpException } from '../../../common/exceptions/http.exception'; import { ExceptionsHandler } from '../../exceptions/exceptions-handler'; import { RouterProxy } from '../../router/router-proxy'; -import { NoopHttpAdapter } from '../utils/noop-adapter'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('RouterProxy', () => { let routerProxy: RouterProxy; diff --git a/packages/core/test/router/router-response-controller.spec.ts b/packages/core/test/router/router-response-controller.spec.ts index 05c4d28cfd8..5aff6910359 100644 --- a/packages/core/test/router/router-response-controller.spec.ts +++ b/packages/core/test/router/router-response-controller.spec.ts @@ -4,7 +4,7 @@ import { of } from 'rxjs'; import * as sinon from 'sinon'; import { RequestMethod } from '../../../common'; import { RouterResponseController } from '../../router/router-response-controller'; -import { NoopHttpAdapter } from '../utils/noop-adapter'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('RouterResponseController', () => { let adapter: NoopHttpAdapter; diff --git a/packages/core/test/router/routes-resolver.spec.ts b/packages/core/test/router/routes-resolver.spec.ts index 3f798fed1c4..d51a2acc607 100644 --- a/packages/core/test/router/routes-resolver.spec.ts +++ b/packages/core/test/router/routes-resolver.spec.ts @@ -7,7 +7,7 @@ import { ApplicationConfig } from '../../application-config'; import { Injector } from '../../injector/injector'; import { InstanceWrapper } from '../../injector/instance-wrapper'; import { RoutesResolver } from '../../router/routes-resolver'; -import { NoopHttpAdapter } from '../utils/noop-adapter'; +import { NoopHttpAdapter } from '../utils/noop-adapter.spec'; describe('RoutesResolver', () => { @Controller('global') @@ -35,7 +35,7 @@ describe('RoutesResolver', () => { container = { getModules: () => modules, getModuleByKey: (key: string) => modules.get(key), - getApplicationRef: () => applicationRef, + getHttpAdapterRef: () => applicationRef, }; router = { get() {}, @@ -130,7 +130,6 @@ describe('RoutesResolver', () => { describe('registerExceptionHandler', () => { it('should register exception handler', () => { - const ref = container.getApplicationRef(); routesResolver.registerExceptionHandler(); expect(applicationRef.setErrorHandler.called).to.be.true; diff --git a/packages/core/test/scanner.spec.ts b/packages/core/test/scanner.spec.ts index fbfce11cbaf..2f8796f4433 100644 --- a/packages/core/test/scanner.spec.ts +++ b/packages/core/test/scanner.spec.ts @@ -47,6 +47,7 @@ describe('DependenciesScanner', () => { new MetadataScanner(), new ApplicationConfig(), ); + sinon.stub(scanner, 'registerCoreModule').callsFake(() => {}); }); afterEach(() => { @@ -55,6 +56,7 @@ describe('DependenciesScanner', () => { it('should "insertModule" call twice (2 modules) container method "addModule"', async () => { const expectation = mockContainer.expects('addModule').twice(); + await scanner.scan(TestModule as any); expectation.verify(); }); diff --git a/packages/core/test/utils/noop-adapter.ts b/packages/core/test/utils/noop-adapter.spec.ts similarity index 93% rename from packages/core/test/utils/noop-adapter.ts rename to packages/core/test/utils/noop-adapter.spec.ts index c9cd1c22069..81283e79f8d 100644 --- a/packages/core/test/utils/noop-adapter.ts +++ b/packages/core/test/utils/noop-adapter.spec.ts @@ -1,5 +1,5 @@ import { RequestMethod } from '@nestjs/common'; -import { AbstractHttpAdapter } from './../../adapters'; +import { AbstractHttpAdapter } from '../../adapters'; export class NoopHttpAdapter extends AbstractHttpAdapter { constructor(instance: any) { diff --git a/packages/microservices/listener-metadata-explorer.ts b/packages/microservices/listener-metadata-explorer.ts index f19f751e208..a54ed827127 100644 --- a/packages/microservices/listener-metadata-explorer.ts +++ b/packages/microservices/listener-metadata-explorer.ts @@ -60,12 +60,14 @@ export class ListenerMetadataExplorer { instance: Controller, ): IterableIterator { for (const propertyKey in instance) { - if (isFunction(propertyKey)) continue; - + if (isFunction(propertyKey)) { + continue; + } const property = String(propertyKey); const isClient = Reflect.getMetadata(CLIENT_METADATA, instance, property); - if (isUndefined(isClient)) continue; - + if (isUndefined(isClient)) { + continue; + } const metadata = Reflect.getMetadata( CLIENT_CONFIGURATION_METADATA, instance, diff --git a/packages/microservices/listeners-controller.ts b/packages/microservices/listeners-controller.ts index 47c96e7a0ef..5852b574282 100644 --- a/packages/microservices/listeners-controller.ts +++ b/packages/microservices/listeners-controller.ts @@ -1,4 +1,5 @@ import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface'; +import { createContextId } from '@nestjs/core/helpers/context-id-factory'; import { NestContainer } from '@nestjs/core/injector/container'; import { Injector } from '@nestjs/core/injector/injector'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; @@ -45,7 +46,7 @@ export class ListenersController { return server.addHandler(pattern, proxy); } server.addHandler(pattern, data => { - const contextId = { id: 1 }; // async id + const contextId = createContextId(); const contextInstance = this.injector.loadPerContext( instance, module, diff --git a/packages/microservices/test/listeners-controller.spec.ts b/packages/microservices/test/listeners-controller.spec.ts index 4d15218591e..f8b4fffe4b9 100644 --- a/packages/microservices/test/listeners-controller.spec.ts +++ b/packages/microservices/test/listeners-controller.spec.ts @@ -1,3 +1,4 @@ +import { Scope } from '@nestjs/common'; import { NestContainer } from '@nestjs/core/injector/container'; import { Injector } from '@nestjs/core/injector/injector'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; @@ -37,18 +38,67 @@ describe('ListenersController', () => { addHandler: addSpy, }; }); + describe('bindPatternHandlers', () => { - it(`should call "addHandler" method of server for each pattern handler`, () => { - const handlers = [ - { pattern: 'test', targetCallback: 'tt' }, - { pattern: 'test2', targetCallback: '2' }, - ]; + const handlers = [ + { pattern: 'test', targetCallback: 'tt' }, + { pattern: 'test2', targetCallback: '2' }, + ]; + + beforeEach(() => { sinon.stub(container, 'getModuleByKey').callsFake(() => ({})); + }); + it(`should call "addHandler" method of server for each pattern handler`, () => { explorer.expects('explore').returns(handlers); - instance.bindPatternHandlers(new InstanceWrapper(), server, ''); - expect(addSpy.calledTwice).to.be.true; }); + describe('when request scoped', () => { + it(`should call "addHandler" with deffered proxy`, () => { + explorer.expects('explore').returns(handlers); + instance.bindPatternHandlers( + new InstanceWrapper({ scope: Scope.REQUEST }), + server, + '', + ); + expect(addSpy.calledTwice).to.be.true; + }); + }); + }); + + describe('assignClientToInstance', () => { + it('should assing client to instance', () => { + const propertyKey = 'key'; + const object = {}; + const client = { test: true }; + instance.assignClientToInstance(object, propertyKey, client); + + expect(object[propertyKey]).to.be.eql(client); + }); + }); + + describe('bindClientsToProperties', () => { + class TestClass {} + + it('should bind all clients to properties', () => { + const controller = new TestClass(); + const metadata = [ + { + property: 'key', + metadata: {}, + }, + ]; + sinon + .stub((instance as any).metadataExplorer, 'scanForClientHooks') + .callsFake(() => metadata); + + const assignClientToInstanceSpy = sinon.spy( + instance, + 'assignClientToInstance', + ); + instance.bindClientsToProperties(controller); + + expect(assignClientToInstanceSpy.calledOnce).to.be.true; + }); }); }); diff --git a/packages/testing/testing-module.ts b/packages/testing/testing-module.ts index e3d983e6aa2..6f9b00f8c0a 100644 --- a/packages/testing/testing-module.ts +++ b/packages/testing/testing-module.ts @@ -32,7 +32,7 @@ export class TestingModule extends NestApplicationContext { httpAdapter = httpAdapter || this.createHttpAdapter(); this.applyLogger(options); - this.container.setApplicationRef(httpAdapter); + this.container.setHttpAdapter(httpAdapter); const instance = new NestApplication( this.container, From 0e258842a647a37c713b587a6e80b763f30e285b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Mon, 24 Dec 2018 15:06:34 +0100 Subject: [PATCH 8/9] cleanup(core) remove async hooks module --- packages/core/hooks/async-context.ts | 63 ------------------- packages/core/hooks/async-hooks-helper.ts | 25 -------- packages/core/hooks/async-hooks-middleware.ts | 11 ---- packages/core/hooks/async-hooks-module.ts | 14 ----- packages/core/hooks/async-hooks-storage.ts | 31 --------- packages/core/hooks/index.ts | 3 - packages/core/index.ts | 1 - 7 files changed, 148 deletions(-) delete mode 100644 packages/core/hooks/async-context.ts delete mode 100644 packages/core/hooks/async-hooks-helper.ts delete mode 100644 packages/core/hooks/async-hooks-middleware.ts delete mode 100644 packages/core/hooks/async-hooks-module.ts delete mode 100644 packages/core/hooks/async-hooks-storage.ts delete mode 100644 packages/core/hooks/index.ts diff --git a/packages/core/hooks/async-context.ts b/packages/core/hooks/async-context.ts deleted file mode 100644 index 757322b5280..00000000000 --- a/packages/core/hooks/async-context.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { OnModuleDestroy, OnModuleInit } from '@nestjs/common'; -import * as asyncHooks from 'async_hooks'; -import { AsyncHooksHelper } from './async-hooks-helper'; -import { AsyncHooksStorage } from './async-hooks-storage'; - -export class AsyncContext implements OnModuleInit, OnModuleDestroy { - private static _instance: AsyncContext; - - private constructor( - private readonly internalStorage: Map, - private readonly asyncHookRef: asyncHooks.AsyncHook, - ) {} - - static getInstance(): AsyncContext { - if (!this._instance) { - this.initialize(); - } - return this._instance; - } - - onModuleInit() { - this.asyncHookRef.enable(); - } - - onModuleDestroy() { - this.asyncHookRef.disable(); - } - - set(key: TKey, value: TValue) { - const store = this.getAsyncStorage(); - store.set(key, value); - } - - get(key: TKey): TReturnValue { - const store = this.getAsyncStorage(); - return store.get(key) as TReturnValue; - } - - run(fn: Function) { - const eid = asyncHooks.executionAsyncId(); - this.internalStorage.set(eid, new Map()); - fn(); - } - - private getAsyncStorage(): Map { - const eid = asyncHooks.executionAsyncId(); - const state = this.internalStorage.get(eid); - if (!state) { - throw new Error( - `Async ID (${eid}) is not registered within internal cache.`, - ); - } - return state; - } - - private static initialize() { - const asyncHooksStorage = new AsyncHooksStorage(); - const asyncHook = AsyncHooksHelper.createHooks(asyncHooksStorage); - const storage = asyncHooksStorage.getInternalStorage(); - - this._instance = new AsyncContext(storage, asyncHook); - } -} diff --git a/packages/core/hooks/async-hooks-helper.ts b/packages/core/hooks/async-hooks-helper.ts deleted file mode 100644 index be71511f32e..00000000000 --- a/packages/core/hooks/async-hooks-helper.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as asyncHooks from 'async_hooks'; -import { AsyncHooksStorage } from './async-hooks-storage'; - -export class AsyncHooksHelper { - static createHooks(storage: AsyncHooksStorage): asyncHooks.AsyncHook { - function init( - asyncId: number, - type: string, - triggerId: number, - resource: Object, - ) { - if (storage.has(triggerId)) { - storage.inherit(asyncId, triggerId); - } - } - function destroy(asyncId) { - storage.delete(asyncId); - } - - return asyncHooks.createHook({ - init, - destroy, - } as asyncHooks.HookCallbacks); - } -} diff --git a/packages/core/hooks/async-hooks-middleware.ts b/packages/core/hooks/async-hooks-middleware.ts deleted file mode 100644 index e5d9f9c5c7b..00000000000 --- a/packages/core/hooks/async-hooks-middleware.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Injectable, MiddlewareFunction, NestMiddleware } from '@nestjs/common'; -import { AsyncContext } from './async-context'; - -@Injectable() -export class AsyncHooksMiddleware implements NestMiddleware { - constructor(private readonly asyncContext: AsyncContext) {} - - resolve(...args: any[]): MiddlewareFunction { - return (req: any, res: any, next: Function) => this.asyncContext.run(next); - } -} diff --git a/packages/core/hooks/async-hooks-module.ts b/packages/core/hooks/async-hooks-module.ts deleted file mode 100644 index 8e31373b41b..00000000000 --- a/packages/core/hooks/async-hooks-module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Global, Module } from '@nestjs/common'; -import { AsyncContext } from './async-context'; - -@Global() -@Module({ - providers: [ - { - provide: AsyncContext, - useValue: AsyncContext.getInstance(), - }, - ], - exports: [AsyncContext], -}) -export class AsyncHooksModule {} diff --git a/packages/core/hooks/async-hooks-storage.ts b/packages/core/hooks/async-hooks-storage.ts deleted file mode 100644 index 0fbda6815d8..00000000000 --- a/packages/core/hooks/async-hooks-storage.ts +++ /dev/null @@ -1,31 +0,0 @@ -export class AsyncHooksStorage { - constructor(private readonly asyncStorage = new Map()) { - this.initialize(); - } - - get(triggerId: number): T { - return this.asyncStorage.get(triggerId) as T; - } - - has(triggerId: number): boolean { - return this.asyncStorage.has(triggerId); - } - - inherit(asyncId: number, triggerId: number) { - const value = this.asyncStorage.get(triggerId); - this.asyncStorage.set(asyncId, value); - } - - delete(asyncId: number) { - this.asyncStorage.delete(asyncId); - } - - getInternalStorage(): Map { - return this.asyncStorage; - } - - private initialize() { - const initialAsyncId = 1; - this.asyncStorage.set(initialAsyncId, new Map()); - } -} diff --git a/packages/core/hooks/index.ts b/packages/core/hooks/index.ts deleted file mode 100644 index f06f180f1b7..00000000000 --- a/packages/core/hooks/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './async-context'; -export * from './async-hooks-middleware'; -export * from './async-hooks-module'; diff --git a/packages/core/index.ts b/packages/core/index.ts index 02a73581611..70418219964 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -10,7 +10,6 @@ export * from './adapters'; export { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from './constants'; export * from './exceptions'; export * from './helpers'; -export * from './hooks'; export * from './injector'; export * from './middleware'; export * from './nest-application'; From 5fc79998134073f1b61fa9f65beb222ff6f71ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Sat, 29 Dec 2018 12:55:53 +0100 Subject: [PATCH 9/9] tests(core) add cached metadata checks (injector) --- packages/core/test/injector/injector.spec.ts | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/core/test/injector/injector.spec.ts b/packages/core/test/injector/injector.spec.ts index adb7e290a30..97202a800e7 100644 --- a/packages/core/test/injector/injector.spec.ts +++ b/packages/core/test/injector/injector.spec.ts @@ -730,4 +730,38 @@ describe('Injector', () => { expect(resolveComponentHostStub.calledTwice).to.be.true; }); }); + + describe('resolveConstructorParams', () => { + it('should call "loadCtorMetadata" if metadata is not undefined', async () => { + const wrapper = new InstanceWrapper(); + const metadata = []; + sinon.stub(wrapper, 'getCtorMetadata').callsFake(() => metadata); + + const loadCtorMetadataSpy = sinon.spy(injector, 'loadCtorMetadata'); + await injector.resolveConstructorParams( + wrapper, + null, + [], + () => { + expect(loadCtorMetadataSpy.called).to.be.true; + }, + { id: 2 }, + ); + }); + }); + + describe('resolveProperties', () => { + it('should call "loadPropertiesMetadata" if metadata is not undefined', async () => { + const wrapper = new InstanceWrapper(); + const metadata = []; + sinon.stub(wrapper, 'getPropertiesMetadata').callsFake(() => metadata); + + const loadPropertiesMetadataSpy = sinon.spy( + injector, + 'loadPropertiesMetadata', + ); + await injector.resolveProperties(wrapper, null, null, { id: 2 }); + expect(loadPropertiesMetadataSpy.called).to.be.true; + }); + }); });