feature(@nestjs/core) add @Optional() decorator #847

This commit is contained in:
Kamil Myśliwiec
2018-08-01 22:31:39 +02:00
parent 9f77d5f550
commit 96da5bd9c5
8 changed files with 98 additions and 62 deletions

View File

@@ -12,6 +12,7 @@ export const GLOBAL_MODULE_METADATA = '__globalModule__';
export const PATH_METADATA = 'path'; export const PATH_METADATA = 'path';
export const PARAMTYPES_METADATA = 'design:paramtypes'; export const PARAMTYPES_METADATA = 'design:paramtypes';
export const SELF_DECLARED_DEPS_METADATA = 'self:paramtypes'; export const SELF_DECLARED_DEPS_METADATA = 'self:paramtypes';
export const OPTIONAL_DEPS_METADATA = 'optional:paramtypes';
export const METHOD_METADATA = 'method'; export const METHOD_METADATA = 'method';
export const ROUTE_ARGS_METADATA = '__routeArguments__'; export const ROUTE_ARGS_METADATA = '__routeArguments__';
export const CUSTOM_ROUTE_AGRS_METADATA = '__customRouteArgs__'; export const CUSTOM_ROUTE_AGRS_METADATA = '__customRouteArgs__';

View File

@@ -1,11 +1,12 @@
export * from './controller.decorator';
export * from './component.decorator';
export * from './dependencies.decorator';
export * from './inject.decorator';
export * from './catch.decorator';
export * from './exception-filters.decorator';
export * from './use-pipes.decorator';
export * from './use-guards.decorator';
export * from './reflect-metadata.decorator';
export * from './use-interceptors.decorator';
export * from './bind.decorator'; export * from './bind.decorator';
export * from './catch.decorator';
export * from './component.decorator';
export * from './controller.decorator';
export * from './dependencies.decorator';
export * from './exception-filters.decorator';
export * from './inject.decorator';
export * from './optional.decorator';
export * from './reflect-metadata.decorator';
export * from './use-guards.decorator';
export * from './use-interceptors.decorator';
export * from './use-pipes.decorator';

View File

@@ -0,0 +1,12 @@
import 'reflect-metadata';
import { OPTIONAL_DEPS_METADATA } from '../../constants';
/**
* Sets dependency as an optional one.
*/
export function Optional(): ParameterDecorator {
return (target, key, index) => {
const args = Reflect.getMetadata(OPTIONAL_DEPS_METADATA, target) || [];
Reflect.defineMetadata(OPTIONAL_DEPS_METADATA, [...args, index], target);
};
}

View File

@@ -7,39 +7,39 @@
export * from './decorators'; export * from './decorators';
export * from './enums'; export * from './enums';
export {
NestModule,
INestApplication,
INestMicroservice,
NestMiddleware,
MiddlewareFunction,
MiddlewareConsumer,
OnModuleInit,
ExceptionFilter,
WebSocketAdapter,
PipeTransform,
Paramtype,
ArgumentMetadata,
OnModuleDestroy,
ExecutionContext,
CanActivate,
RpcExceptionFilter,
WsExceptionFilter,
NestInterceptor,
DynamicModule,
INestApplicationContext,
HttpServer,
Provider,
Type,
HttpServerFactory,
ArgumentsHost,
INestExpressApplication,
INestFastifyApplication,
ForwardReference,
} from './interfaces';
export * from './interceptors';
export * from './services/logger.service';
export * from './pipes';
export * from './utils';
export * from './exceptions'; export * from './exceptions';
export * from './http'; export * from './http';
export * from './interceptors';
export {
ArgumentMetadata,
ArgumentsHost,
CanActivate,
DynamicModule,
ExceptionFilter,
ExecutionContext,
ForwardReference,
HttpServer,
HttpServerFactory,
INestApplication,
INestApplicationContext,
INestExpressApplication,
INestFastifyApplication,
INestMicroservice,
MiddlewareConsumer,
MiddlewareFunction,
NestInterceptor,
NestMiddleware,
NestModule,
OnModuleDestroy,
OnModuleInit,
Paramtype,
PipeTransform,
Provider,
RpcExceptionFilter,
Type,
WebSocketAdapter,
WsExceptionFilter,
} from './interfaces';
export * from './pipes';
export * from './services/logger.service';
export * from './utils';

View File

@@ -1,3 +1,4 @@
import { Optional } from '../decorators';
import { ArgumentMetadata, BadRequestException } from '../index'; import { ArgumentMetadata, BadRequestException } from '../index';
import { ValidatorOptions } from '../interfaces/external/validator-options.interface'; import { ValidatorOptions } from '../interfaces/external/validator-options.interface';
import { PipeTransform } from '../interfaces/features/pipe-transform.interface'; import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
@@ -19,7 +20,7 @@ export class ValidationPipe implements PipeTransform<any> {
protected isDetailedOutputDisabled: boolean; protected isDetailedOutputDisabled: boolean;
protected validatorOptions: ValidatorOptions; protected validatorOptions: ValidatorOptions;
constructor(options?: ValidationPipeOptions) { constructor(@Optional() options?: ValidationPipeOptions) {
options = options || {}; options = options || {};
const { transform, disableErrorMessages, ...validatorOptions } = options; const { transform, disableErrorMessages, ...validatorOptions } = options;
this.isTransformEnabled = !!transform; this.isTransformEnabled = !!transform;

View File

@@ -1,4 +1,4 @@
import { PARAMTYPES_METADATA, SELF_DECLARED_DEPS_METADATA } from '@nestjs/common/constants'; import { OPTIONAL_DEPS_METADATA, PARAMTYPES_METADATA, SELF_DECLARED_DEPS_METADATA } from '@nestjs/common/constants';
import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface'; import { Controller } from '@nestjs/common/interfaces/controllers/controller.interface';
import { Injectable } from '@nestjs/common/interfaces/injectable.interface'; import { Injectable } from '@nestjs/common/interfaces/injectable.interface';
import { Type } from '@nestjs/common/interfaces/type.interface'; import { Type } from '@nestjs/common/interfaces/type.interface';
@@ -99,7 +99,7 @@ export class Injector {
await this.loadInstance<Injectable>(wrapper, components, module); await this.loadInstance<Injectable>(wrapper, components, module);
} }
public applyDoneSubject<T>(wrapper: InstanceWrapper<T>): () => void { public applyDoneHook<T>(wrapper: InstanceWrapper<T>): () => void {
let done: () => void; let done: () => void;
wrapper.done$ = new Promise<void>((resolve, reject) => { wrapper.done$ = new Promise<void>((resolve, reject) => {
done = resolve; done = resolve;
@@ -116,13 +116,15 @@ export class Injector {
if (wrapper.isPending) { if (wrapper.isPending) {
return await wrapper.done$; return await wrapper.done$;
} }
const done = this.applyDoneSubject(wrapper); const done = this.applyDoneHook(wrapper);
const { metatype, name, inject } = wrapper; const { metatype, name, inject } = wrapper;
const currentMetatype = collection.get(name); const currentMetatype = collection.get(name);
if (isUndefined(currentMetatype)) { if (isUndefined(currentMetatype)) {
throw new RuntimeException(); throw new RuntimeException();
} }
if (currentMetatype.isResolved) return null; if (currentMetatype.isResolved) {
return void 0;
}
await this.resolveConstructorParams<T>( await this.resolveConstructorParams<T>(
wrapper, wrapper,
@@ -153,22 +155,34 @@ export class Injector {
callback: (args) => void, callback: (args) => void,
) { ) {
let isResolved = true; let isResolved = true;
const dependencies = isNil(inject) const dependencies = isNil(inject)
? this.reflectConstructorParams(wrapper.metatype) ? this.reflectConstructorParams(wrapper.metatype)
: inject; : inject;
const optionalDependenciesIds = isNil(inject)
? this.reflectOptionalParams(wrapper.metatype)
: [];
const instances = await Promise.all( const instances = await Promise.all(
dependencies.map(async (param, index) => { dependencies.map(async (param, index) => {
const paramWrapper = await this.resolveSingleParam<T>( try {
wrapper, const paramWrapper = await this.resolveSingleParam<T>(
param, wrapper,
{ index, dependencies }, param,
module, { index, dependencies },
); module,
if (!paramWrapper.isResolved && !paramWrapper.forwardRef) { );
isResolved = false; if (!paramWrapper.isResolved && !paramWrapper.forwardRef) {
isResolved = false;
}
return paramWrapper.instance;
} catch (err) {
const isOptional = optionalDependenciesIds.includes(index);
if (!isOptional) {
throw err;
}
return null;
} }
return paramWrapper.instance;
}), }),
); );
isResolved && (await callback(instances)); isResolved && (await callback(instances));
@@ -182,6 +196,10 @@ export class Injector {
return paramtypes; return paramtypes;
} }
public reflectOptionalParams<T>(type: Type<T>): any[] {
return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
}
public reflectSelfParams<T>(type: Type<T>): any[] { public reflectSelfParams<T>(type: Type<T>): any[] {
return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || []; return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
} }
@@ -313,4 +331,4 @@ export class Injector {
}; };
return modules.concat.apply(modules, modules.map(flatten)); return modules.concat.apply(modules, modules.map(flatten));
} }
} }

View File

@@ -101,7 +101,7 @@ describe('Injector', () => {
expect(result).to.be.eql(value); expect(result).to.be.eql(value);
}); });
it('should return null when metatype is resolved', async () => { it('should return undefined when metatype is resolved', async () => {
const value = 'test'; const value = 'test';
const result = await injector.loadInstance( const result = await injector.loadInstance(
{ {
@@ -113,7 +113,7 @@ describe('Injector', () => {
moduleDeps.components, moduleDeps.components,
moduleDeps, moduleDeps,
); );
expect(result).to.be.null; expect(result).to.be.undefined;
}); });
}); });

View File

@@ -11,7 +11,10 @@
"target": "es6", "target": "es6",
"sourceMap": true, "sourceMap": true,
"allowJs": true, "allowJs": true,
"outDir": "dist" "outDir": "dist",
"lib": [
"es7"
]
}, },
"include": [ "include": [
"packages/**/*", "packages/**/*",