feature() wip: DI scopes

This commit is contained in:
Kamil Myśliwiec
2018-12-15 21:22:36 +01:00
parent 15bdbacc4b
commit 17e0ae6c63
51 changed files with 1206 additions and 494 deletions

View File

@@ -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

View File

@@ -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());
});

34
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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__';

View File

@@ -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);
};
}

View File

@@ -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<any>) {

View File

@@ -39,6 +39,7 @@ export {
PipeTransform,
Provider,
RpcExceptionFilter,
Scope,
Type,
ValidationError,
WebSocketAdapter,

View File

@@ -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';

View File

@@ -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<any>;
scope?: Scope;
}
export interface ValueProvider {
@@ -20,4 +22,5 @@ export interface FactoryProvider {
provide: any;
useFactory: (...args: any[]) => any;
inject?: Array<Type<any> | string | any>;
scope?: Scope;
}

View File

@@ -0,0 +1,9 @@
export enum Scope {
DEFAULT,
REQUEST,
LAZY,
}
export interface ScopeOptions {
scope?: Scope;
}

View File

@@ -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<T extends any[], R extends any[]>(
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<T extends Record<string, any>>(
filter: T,
): { instance: any } | undefined {
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return undefined;
}

View File

@@ -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<T extends any[], R extends any[]>(
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<T extends Record<string, any>>(
guard: T,
): { instance: any } | undefined {
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return undefined;
}

View File

@@ -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<T extends any[], R extends any[]>(
metadata: T,
contextId?: ContextId,
): R;
public getGlobalMetadata?<T extends any[]>(): 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<T>();
const classMetadata = this.reflectClassMetadata<T>(instance, metadataKey);
const methodMetadata = this.reflectMethodMetadata<T>(callback, metadataKey);
return [
...this.createConcreteContext<T, R>(globalMetadata || ([] as T)),
...this.createConcreteContext<T, R>(classMetadata),
...this.createConcreteContext<T, R>(methodMetadata),
...this.createConcreteContext<T, R>(
globalMetadata || ([] as T),
contextId,
),
...this.createConcreteContext<T, R>(classMetadata, contextId),
...this.createConcreteContext<T, R>(methodMetadata, contextId),
] as R;
}

View File

@@ -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<number, any>,
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<TKey = any, TValue = any>(key: TKey, value: TValue) {
const store = this.getAsyncStorage();
store.set(key, value);
}
get<TKey = any, TReturnValue = any>(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<unknown, unknown> {
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);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,31 @@
export class AsyncHooksStorage {
constructor(private readonly asyncStorage = new Map<number, unknown>()) {
this.initialize();
}
get<T = any>(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<number, unknown> {
return this.asyncStorage;
}
private initialize() {
const initialAsyncId = 1;
this.asyncStorage.set(initialAsyncId, new Map());
}
}

View File

@@ -0,0 +1 @@
export * from './async-context';

View File

@@ -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';

View File

@@ -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,
});

View File

@@ -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<any>).instance;
return (instanceWrapper as InstanceWrapper).instance;
}
private initFlatContainer(): void {

View File

@@ -101,7 +101,7 @@ export class NestContainer {
return this.modules;
}
public async addRelatedModule(
public async addImport(
relatedModule: Type<any> | 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<T> {
name: any;
metatype: Type<T>;
instance: T;
isResolved: boolean;
isPending?: boolean;
done$?: Promise<void>;
inject?: Type<any>[];
isNotMetatype?: boolean;
forwardRef?: boolean;
async?: boolean;
}

View File

@@ -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<any> | Function | string;
export type InjectorDependency = Type<any> | Function | string | symbol;
/**
* The property-based dependency
@@ -61,73 +68,100 @@ export interface InjectorDependencyContext {
}
export class Injector {
public async loadInstanceOfMiddleware(
wrapper: MiddlewareWrapper,
collection: Map<string, MiddlewareWrapper>,
public async loadMiddleware(
wrapper: InstanceWrapper,
collection: Map<string, InstanceWrapper>,
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<Controller>,
module: Module,
contextId = STATIC_CONTEXT,
) {
const controllers = module.controllers;
await this.loadInstance<Controller>(wrapper, controllers, module);
await this.loadInstance<Controller>(
wrapper,
controllers,
module,
contextId,
);
}
public async loadInstanceOfInjectable(
public async loadInjectable(
wrapper: InstanceWrapper<Controller>,
module: Module,
contextId = STATIC_CONTEXT,
) {
const injectables = module.injectables;
await this.loadInstance<Controller>(wrapper, injectables, module);
await this.loadInstance<Controller>(
wrapper,
injectables,
module,
contextId,
);
}
public loadPrototypeOfInstance<T>(
public async loadProvider(
wrapper: InstanceWrapper<Injectable>,
module: Module,
contextId = STATIC_CONTEXT,
) {
const providers = module.providers;
await this.loadInstance<Injectable>(wrapper, providers, module, contextId);
}
public loadPrototype<T>(
{ metatype, name }: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper<T>>,
): 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),
});
collection.set(
name,
new InstanceWrapper({
...target,
instance: Object.create(metatype.prototype),
}),
);
}
public async loadInstanceOfComponent(
wrapper: InstanceWrapper<Injectable>,
module: Module,
) {
const providers = module.providers;
await this.loadInstance<Injectable>(wrapper, providers, module);
}
public applyDoneHook<T>(wrapper: InstanceWrapper<T>): () => void {
public applyDoneHook<T>(wrapper: InstancePerContext<T>): () => void {
let done: () => void;
wrapper.done$ = new Promise<void>((resolve, reject) => {
wrapper.donePromise = new Promise<void>((resolve, reject) => {
done = resolve;
});
wrapper.isPending = true;
@@ -136,33 +170,48 @@ export class Injector {
public async loadInstance<T>(
wrapper: InstanceWrapper<T>,
collection: Map<string, InstanceWrapper<any>>,
collection: Map<string, InstanceWrapper>,
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<T>(wrapper, module, inject, callback);
await this.resolveConstructorParams<T>(
wrapper,
module,
inject,
callback,
contextId,
);
}
public async resolveConstructorParams<T>(
@@ -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<T>(
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<T>(
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<any> | 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<any>).name : token,
dependencyContext,
wrapper,
contextId,
);
}
@@ -258,32 +325,53 @@ export class Injector {
name: any,
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
) {
contextId = STATIC_CONTEXT,
): Promise<InstanceWrapper> {
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<T>(
module: Module,
instanceWrapper: InstanceWrapper<T>,
contextId = STATIC_CONTEXT,
): Promise<InstanceWrapper> {
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<T = any>(
providers: Map<string, any>,
providers: Map<string, InstanceWrapper>,
module: Module,
dependencyContext: InjectorDependencyContext,
wrapper: InstanceWrapper<T>,
) {
contextId = STATIC_CONTEXT,
): Promise<InstanceWrapper<T>> {
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<T>,
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<any> {
let componentRef = null;
let instanceWrapperRef: InstanceWrapper = null;
const imports: Set<Module> = module.imports || new Set();
const children = [...imports.values()].filter(item => item);
const relatedModules: Set<Module> = 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<T>(
wrapper: InstanceWrapper<T>,
module: Module,
inject?: InjectorDependency[],
contextId = STATIC_CONTEXT,
): Promise<PropertyDependency[]> {
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<T = any>(
instances: any[],
wrapper: InstanceWrapper<any>,
targetMetatype: InstanceWrapper<any>,
wrapper: InstanceWrapper,
targetMetatype: InstanceWrapper,
moduleId: string,
contextId = STATIC_CONTEXT,
): Promise<T> {
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<any>,
instanceKey: string,
metatype: Type<any>,
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<T>(
instance: Controller,
modulesContainer: ModulesContainer,
moduleKey: string,
ctx: ContextId,
): Promise<T> {
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);
}
}

View File

@@ -41,52 +41,49 @@ export class InstanceLoader {
}
private createPrototypesOfProviders(module: Module) {
module.providers.forEach(wrapper => {
this.injector.loadPrototypeOfInstance<Injectable>(
wrapper,
module.providers,
);
});
const { providers } = module;
providers.forEach(wrapper =>
this.injector.loadPrototype<Injectable>(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<Controller>(
wrapper,
module.controllers,
);
});
const { controllers } = module;
controllers.forEach(wrapper =>
this.injector.loadPrototype<Controller>(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<Controller>(
wrapper,
module.injectables,
);
});
const { injectables } = module;
injectables.forEach(wrapper =>
this.injector.loadPrototype<Controller>(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),
),
);
}

View File

@@ -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<T> {
instance: T;
isResolved?: boolean;
isPending?: boolean;
donePromise?: Promise<void>;
}
export interface PropertyMetadata {
key: string;
wrapper: InstanceWrapper;
}
export interface InstanceMetadataStore {
dependencies?: InstanceWrapper[];
properties?: PropertyMetadata[];
}
export class InstanceWrapper<T = any> {
public readonly name: any;
public readonly metatype: Type<T>;
public readonly inject?: (string | symbol | Function | Type<any>)[];
public readonly async?: boolean;
public readonly host?: Module;
public readonly scope?: Scope = Scope.DEFAULT;
public forwardRef?: boolean;
private readonly values = new WeakMap<ContextId, InstancePerContext<T>>();
private readonly [INSTANCE_METADATA_SYMBOL]: InstanceMetadataStore = {};
constructor(
metadata: Partial<InstanceWrapper<T>> & Partial<InstancePerContext<T>> = {},
) {
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<T> {
const instancePerContext = this.values.get(contextId);
return instancePerContext
? instancePerContext
: this.cloneStaticInstance(contextId);
}
setInstanceByContextId(contextId: ContextId, value: InstancePerContext<T>) {
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<InstanceWrapper<T>> & Partial<InstancePerContext<T>>,
) {
const { instance, isResolved, ...wrapperPartial } = metadata;
Object.assign(this, wrapperPartial);
this.setInstanceByContextId(STATIC_CONTEXT, {
instance,
isResolved,
});
}
private cloneStaticInstance(contextId: ContextId): InstancePerContext<T> {
const staticInstance = this.getInstanceByContextId(STATIC_CONTEXT);
if (this.isDependencyTreeStatic()) {
return staticInstance;
}
const instancePerContext: InstancePerContext<T> = {
...staticInstance,
instance: undefined,
isResolved: false,
isPending: false,
};
this.setInstanceByContextId(contextId, instancePerContext);
return instancePerContext;
}
}

View File

@@ -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<T>,
module: Module,
): Promise<T> {
const wrapper: InstanceWrapper<any> = {
const wrapper = new InstanceWrapper({
name: type.name,
metatype: type,
instance: undefined,
isResolved: false,
};
host: module,
});
return new Promise<T>(async (resolve, reject) => {
try {
const callback = async (instances: any[]) => {

View File

@@ -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(

View File

@@ -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<any>;
export type CustomClass = CustomProvider & { useClass: Type<any> };
export type OpaqueToken = string | symbol | Type<any>;
export type CustomClass = CustomProvider & {
useClass: Type<any>;
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<Module>();
private readonly _imports = new Set<Module>();
private readonly _providers = new Map<any, InstanceWrapper<Injectable>>();
private readonly _injectables = new Map<any, InstanceWrapper<Injectable>>();
private readonly _controllers = new Map<
@@ -69,14 +77,21 @@ export class Module {
return this._scope;
}
get relatedModules(): Set<Module> {
return this._relatedModules;
}
get providers(): Map<string, InstanceWrapper<Injectable>> {
return this._providers;
}
get imports(): Set<Module> {
return this._imports;
}
/**
* Left for backward-compatibility reasons
*/
get relatedModules(): Set<Module> {
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<T = Injectable>(injectable: Type<T>) {
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<Injectable>).name, {
name: (provider as Type<Injectable>).name,
metatype: provider as Type<Injectable>,
instance: null,
isResolved: false,
});
this._providers.set(
(provider as Type<Injectable>).name,
new InstanceWrapper({
name: (provider as Type<Injectable>).name,
metatype: provider as Type<Injectable>,
instance: null,
isResolved: false,
scope: this.getClassScope(provider),
host: this,
}),
);
return (provider as Type<Injectable>).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<string, any>) {
const { name, useClass } = provider;
collection.set(name, {
public addCustomClass(
provider: CustomClass,
collection: Map<string, InstanceWrapper>,
) {
const { name, useClass, scope } = provider;
collection.set(
name,
metatype: useClass,
instance: null,
isResolved: false,
});
new InstanceWrapper({
name,
metatype: useClass,
instance: null,
isResolved: false,
scope,
host: this,
}),
);
}
public addCustomValue(provider: CustomValue, collection: Map<string, any>) {
public addCustomValue(
provider: CustomValue,
collection: Map<string, InstanceWrapper>,
) {
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<string, any>,
collection: Map<string, InstanceWrapper>,
) {
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<Controller>) {
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<any>, 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;
}
}

View File

@@ -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<T extends any[], R extends any[]>(
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<T extends Record<string, any> = any>(
metatype: T,
): { instance: any } | undefined {
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return undefined;
}

View File

@@ -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<string, MiddlewareWrapper>
>();
private readonly middleware = new Map<string, Map<string, InstanceWrapper>>();
private readonly configurationSets = new Map<
string,
Set<MiddlewareConfiguration>
>();
public getMiddleware(module: string): Map<string, MiddlewareWrapper> {
public getMiddleware(module: string): Map<string, InstanceWrapper> {
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<string, MiddlewareWrapper>());
this.middleware.set(module, new Map<string, InstanceWrapper>());
}
return this.middleware.get(module);
}
@@ -51,8 +51,3 @@ export class MiddlewareContainer {
return this.configurationSets.get(module);
}
}
export interface MiddlewareWrapper {
instance: NestMiddleware;
metatype: Type<NestMiddleware>;
}

View File

@@ -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);
}

View File

@@ -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<string, MiddlewareWrapper>,
wrapper: InstanceWrapper,
middleware: Map<string, InstanceWrapper>,
module: Module,
) {
await this.instanceLoader.loadInstanceOfMiddleware(
wrapper,
middleware,
module,
);
await this.instanceLoader.loadMiddleware(wrapper, middleware, module);
}
}

View File

@@ -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<any>[] {
this.moduleContext = module;
return this.createContext(instance, callback, PIPES_METADATA);
return this.createContext(instance, callback, PIPES_METADATA, contextId);
}
public createConcreteContext<T extends any[], R extends any[]>(
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<T extends { name: string } = any>(
metatype: T,
): { instance: any } | undefined {
): InstanceWrapper | undefined {
if (!this.moduleContext) {
return undefined;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 <TRequest, TResponse>(
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);
}

View File

@@ -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';

View File

@@ -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<any>,
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(

View File

@@ -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;

View File

@@ -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);

View File

@@ -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(

View File

@@ -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);

View File

@@ -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();
});
});

View File

@@ -115,7 +115,7 @@ export class ClientRedis extends ClientProxy {
return this.getOptionsProp<RedisOptions>(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,

View File

@@ -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';

View File

@@ -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';

View File

@@ -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<Cat[]> {
const random = Math.random();
console.log(random, this.rawr);
console.log(
((this.asyncContext as any).internalStorage as Map<any, any>).size,
);
this.asyncContext.set('xd', random);
return this.catsService.findAll();
}

View File

@@ -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('*');
}
}

View File

@@ -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;
}
}

View File

@@ -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<string[]>('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();
}
}