Nestjs - Use applyDecorators inside createParamDecorator

Viewed 396

I'm using Nestjs decorators and am trying to make the most of custom decorators. I'm trying to write my own custom @Body param decorator that validates and applies multiple decorators at the same time.

Does anyone know if the below is possible? I'm having difficulty getting the second argument in the transform call of the pipes to have metadata: ArgumentMetadata.

export const MyParamDecorator = <T>(myDto: T) => {
  return createParamDecorator(
    (data: unknown, ctx: ExecutionContext) => {
      applyDecorators( // also get SetMeta and Pipes to validate DTO
        SetMetadata('thisWorks', true)
        UsePipes(CustomValidationPipe, OtherPipe), // + add MyDTO - type T somehow..
      );
      return doAsyncWork()
    },
  )();
}

@Controller('users')
export class UsersController {
  @Patch(':id')
  update(@MyParamDecorator() asyncWork: Promise<any>) { // <------ Promise<any> is custom async opperation that will be handled. (So I can't type the DTO here..)
    return reqBody;
  }
}
1 Answers

I ran across this question because I needed a similar answer. Hopefully what I've found is helpful.

Part 1. You can do async processing in a decorator.

And, it would resolve for you, so your controller would look like:

  update(@MyParamDecorator() asyncWork: any) {

Notice that Promise<any> is just any.

Part 2. You can get ArgumentMetadata using an enhancer.

Here is a quick example, let's assume METADATA__PARAM_TYPE is myType.

param-type.enhancer.ts

export const paramTypeEnhancer: ParamDecoratorEnhancer = (
  target: Record<string, unknown>,
  propertyKey: string,
  parameterIndex: number,
): void => {
  const paramTypes = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey);
  const metatype = paramTypes[parameterIndex];
  Reflect.defineMetadata(METADATA__PARAM_TYPE, metatype, target[propertyKey]);
};

my-decorator.ts

import { paramTypeEnhancer } from './param-type.enhancer';

export const MyDecorator = createParamDecorator(
  async (data: unknown, ctx: ExecutionContext): Promise<any> => {
    const metatype = Reflect.getOwnMetadata(METADATA__PARAM_TYPE, ctx.getHandler());
    const argument: ArgumentMetadata = {
      type: 'custom',
      data: undefined,
      metatype: metatype,
    };
    // Do processing here... You can return a promise.
  },
  [paramTypeEnhancer],
);

See this gist for a full annotated version: https://gist.github.com/josephdpurcell/fc04cfd428a6ee9d7ffb64685e4fe3a6

Related