mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 23:11:44 +00:00
feature(@nestjs/core) add @Optional() decorator #847
This commit is contained in:
@@ -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__';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
12
packages/common/decorators/core/optional.decorator.ts
Normal file
12
packages/common/decorators/core/optional.decorator.ts
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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/**/*",
|
||||||
|
|||||||
Reference in New Issue
Block a user