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 PARAMTYPES_METADATA = 'design:paramtypes';
export const SELF_DECLARED_DEPS_METADATA = 'self:paramtypes';
export const OPTIONAL_DEPS_METADATA = 'optional:paramtypes';
export const METHOD_METADATA = 'method';
export const ROUTE_ARGS_METADATA = '__routeArguments__';
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 './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 './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 './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 { ValidatorOptions } from '../interfaces/external/validator-options.interface';
import { PipeTransform } from '../interfaces/features/pipe-transform.interface';
@@ -19,7 +20,7 @@ export class ValidationPipe implements PipeTransform<any> {
protected isDetailedOutputDisabled: boolean;
protected validatorOptions: ValidatorOptions;
constructor(options?: ValidationPipeOptions) {
constructor(@Optional() options?: ValidationPipeOptions) {
options = options || {};
const { transform, disableErrorMessages, ...validatorOptions } = options;
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 { Injectable } from '@nestjs/common/interfaces/injectable.interface';
import { Type } from '@nestjs/common/interfaces/type.interface';
@@ -99,7 +99,7 @@ export class Injector {
await this.loadInstance<Injectable>(wrapper, components, module);
}
public applyDoneSubject<T>(wrapper: InstanceWrapper<T>): () => void {
public applyDoneHook<T>(wrapper: InstanceWrapper<T>): () => void {
let done: () => void;
wrapper.done$ = new Promise<void>((resolve, reject) => {
done = resolve;
@@ -116,13 +116,15 @@ export class Injector {
if (wrapper.isPending) {
return await wrapper.done$;
}
const done = this.applyDoneSubject(wrapper);
const done = this.applyDoneHook(wrapper);
const { metatype, name, inject } = wrapper;
const currentMetatype = collection.get(name);
if (isUndefined(currentMetatype)) {
throw new RuntimeException();
}
if (currentMetatype.isResolved) return null;
if (currentMetatype.isResolved) {
return void 0;
}
await this.resolveConstructorParams<T>(
wrapper,
@@ -153,22 +155,34 @@ export class Injector {
callback: (args) => void,
) {
let isResolved = true;
const dependencies = isNil(inject)
? this.reflectConstructorParams(wrapper.metatype)
: inject;
const optionalDependenciesIds = isNil(inject)
? this.reflectOptionalParams(wrapper.metatype)
: [];
const instances = await Promise.all(
dependencies.map(async (param, index) => {
const paramWrapper = await this.resolveSingleParam<T>(
wrapper,
param,
{ index, dependencies },
module,
);
if (!paramWrapper.isResolved && !paramWrapper.forwardRef) {
isResolved = false;
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 null;
}
return paramWrapper.instance;
}),
);
isResolved && (await callback(instances));
@@ -182,6 +196,10 @@ export class Injector {
return paramtypes;
}
public reflectOptionalParams<T>(type: Type<T>): any[] {
return Reflect.getMetadata(OPTIONAL_DEPS_METADATA, type) || [];
}
public reflectSelfParams<T>(type: Type<T>): any[] {
return Reflect.getMetadata(SELF_DECLARED_DEPS_METADATA, type) || [];
}
@@ -313,4 +331,4 @@ export class Injector {
};
return modules.concat.apply(modules, modules.map(flatten));
}
}
}

View File

@@ -101,7 +101,7 @@ describe('Injector', () => {
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 result = await injector.loadInstance(
{
@@ -113,7 +113,7 @@ describe('Injector', () => {
moduleDeps.components,
moduleDeps,
);
expect(result).to.be.null;
expect(result).to.be.undefined;
});
});

View File

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