diff --git a/lib/schema-builder/storages/type-metadata.storage.ts b/lib/schema-builder/storages/type-metadata.storage.ts index b84b903f2..6b2f7a810 100644 --- a/lib/schema-builder/storages/type-metadata.storage.ts +++ b/lib/schema-builder/storages/type-metadata.storage.ts @@ -157,11 +157,28 @@ export class TypeMetadataStorageHost { } addDirectiveMetadata(metadata: ClassDirectiveMetadata) { - this.classDirectives.push(metadata); + const exist = this.fieldDirectives.some((directiveMetadata) => { + return ( + directiveMetadata.sdl === metadata.sdl && + directiveMetadata.target === metadata.target + ); + }); + if (!exist) { + this.classDirectives.push(metadata); + } } addDirectivePropertyMetadata(metadata: PropertyDirectiveMetadata) { - this.fieldDirectives.push(metadata); + const exist = this.fieldDirectives.some((directiveMetadata) => { + return ( + directiveMetadata.fieldName === metadata.fieldName && + directiveMetadata.sdl === metadata.sdl && + directiveMetadata.target === metadata.target + ); + }); + if (!exist) { + this.fieldDirectives.push(metadata); + } } addExtensionsMetadata(metadata: ClassExtensionsMetadata) { diff --git a/lib/type-helpers/intersection-type.helper.ts b/lib/type-helpers/intersection-type.helper.ts index bee42662a..2457f19d5 100644 --- a/lib/type-helpers/intersection-type.helper.ts +++ b/lib/type-helpers/intersection-type.helper.ts @@ -7,6 +7,7 @@ import { import { Field } from '../decorators'; import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface'; import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util'; +import { applyFieldDecorators } from './type-helpers.utils'; export function IntersectionType( classARef: Type, @@ -41,6 +42,7 @@ export function IntersectionType( IntersectionObjectType.prototype, item.name, ); + applyFieldDecorators(IntersectionObjectType, item); }); Object.defineProperty(IntersectionObjectType, 'name', { diff --git a/lib/type-helpers/omit-type.helper.ts b/lib/type-helpers/omit-type.helper.ts index 7f4ae543a..5d4a4f8ee 100644 --- a/lib/type-helpers/omit-type.helper.ts +++ b/lib/type-helpers/omit-type.helper.ts @@ -8,6 +8,7 @@ import { import { Field } from '../decorators'; import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface'; import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util'; +import { applyFieldDecorators } from './type-helpers.utils'; export function OmitType( classRef: Type, @@ -47,6 +48,7 @@ export function OmitType( OmitObjectType.prototype, item.name, ); + applyFieldDecorators(OmitObjectType, item); }); return OmitObjectType as Type>; } diff --git a/lib/type-helpers/partial-type.helper.ts b/lib/type-helpers/partial-type.helper.ts index 5742a33c8..814cea620 100644 --- a/lib/type-helpers/partial-type.helper.ts +++ b/lib/type-helpers/partial-type.helper.ts @@ -10,6 +10,7 @@ import { Field } from '../decorators'; import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface'; import { METADATA_FACTORY_NAME } from '../plugin/plugin-constants'; import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util'; +import { applyFieldDecorators } from './type-helpers.utils'; export function PartialType( classRef: Type, @@ -44,6 +45,7 @@ export function PartialType( item.name, ); applyIsOptionalDecorator(PartialObjectType, item.name); + applyFieldDecorators(PartialObjectType, item); }); if (PartialObjectType[METADATA_FACTORY_NAME]) { diff --git a/lib/type-helpers/pick-type.helper.ts b/lib/type-helpers/pick-type.helper.ts index d05e7148c..08ef6d430 100644 --- a/lib/type-helpers/pick-type.helper.ts +++ b/lib/type-helpers/pick-type.helper.ts @@ -8,6 +8,7 @@ import { import { Field } from '../decorators'; import { ClassDecoratorFactory } from '../interfaces/class-decorator-factory.interface'; import { getFieldsAndDecoratorForType } from '../schema-builder/utils/get-fields-and-decorator.util'; +import { applyFieldDecorators } from './type-helpers.utils'; export function PickType( classRef: Type, @@ -48,6 +49,7 @@ export function PickType( PickObjectType.prototype, item.name, ); + applyFieldDecorators(PickObjectType, item); }); return PickObjectType as Type>; } diff --git a/lib/type-helpers/type-helpers.utils.ts b/lib/type-helpers/type-helpers.utils.ts new file mode 100644 index 000000000..ee2d5aa57 --- /dev/null +++ b/lib/type-helpers/type-helpers.utils.ts @@ -0,0 +1,16 @@ +import { Directive, Extensions } from '../decorators'; +import { PropertyMetadata } from '../schema-builder/metadata'; + +export function applyFieldDecorators( + targetClass: Function, + item: PropertyMetadata, +) { + if (item.extensions) { + Extensions(item.extensions)(targetClass.prototype, item.name); + } + if (item.directives?.length) { + item.directives.map((directive) => { + Directive(directive.sdl)(targetClass.prototype, item.name); + }); + } +} diff --git a/tests/type-helpers/intersection-type.helper.spec.ts b/tests/type-helpers/intersection-type.helper.spec.ts index b9cc0da13..15274cd38 100644 --- a/tests/type-helpers/intersection-type.helper.spec.ts +++ b/tests/type-helpers/intersection-type.helper.spec.ts @@ -1,4 +1,4 @@ -import { Field, ObjectType } from '../../lib/decorators'; +import { Directive, Extensions, Field, ObjectType } from '../../lib/decorators'; import { METADATA_FACTORY_NAME } from '../../lib/plugin/plugin-constants'; import { getFieldsAndDecoratorForType } from '../../lib/schema-builder/utils/get-fields-and-decorator.util'; import { IntersectionType } from '../../lib/type-helpers'; @@ -7,6 +7,8 @@ describe('IntersectionType', () => { @ObjectType() class ClassA { @Field({ nullable: true }) + @Directive('@upper') + @Extensions({ extension: true }) login: string; @Field() @@ -16,6 +18,8 @@ describe('IntersectionType', () => { @ObjectType() class ClassB { @Field() + @Directive('@upper') + @Extensions({ extension: true }) lastName: string; firstName?: string; @@ -30,13 +34,30 @@ describe('IntersectionType', () => { class UpdateUserDto extends IntersectionType(ClassA, ClassB) {} it('should inherit all fields from two types', () => { - const { fields } = getFieldsAndDecoratorForType( - Object.getPrototypeOf(UpdateUserDto), - ); + const prototype = Object.getPrototypeOf(UpdateUserDto); + const { fields } = getFieldsAndDecoratorForType(prototype); expect(fields.length).toEqual(4); expect(fields[0].name).toEqual('login'); expect(fields[1].name).toEqual('password'); expect(fields[2].name).toEqual('lastName'); expect(fields[3].name).toEqual('firstName'); + expect(fields[0].directives.length).toEqual(1); + expect(fields[0].directives).toContainEqual({ + fieldName: 'login', + sdl: '@upper', + target: prototype, + }); + expect(fields[0].extensions).toEqual({ + extension: true, + }); + expect(fields[2].directives.length).toEqual(1); + expect(fields[2].directives).toContainEqual({ + fieldName: 'lastName', + sdl: '@upper', + target: prototype, + }); + expect(fields[2].extensions).toEqual({ + extension: true, + }); }); }); diff --git a/tests/type-helpers/omit-type.helper.spec.ts b/tests/type-helpers/omit-type.helper.spec.ts index ce67abc2f..587dbb8cf 100644 --- a/tests/type-helpers/omit-type.helper.spec.ts +++ b/tests/type-helpers/omit-type.helper.spec.ts @@ -1,6 +1,6 @@ import { Transform } from 'class-transformer'; import { MinLength } from 'class-validator'; -import { Field, ObjectType } from '../../lib/decorators'; +import { Directive, Extensions, Field, ObjectType } from '../../lib/decorators'; import { getFieldsAndDecoratorForType } from '../../lib/schema-builder/utils/get-fields-and-decorator.util'; import { OmitType } from '../../lib/type-helpers'; @@ -14,6 +14,8 @@ describe('OmitType', () => { @Transform((str) => str + '_transformed') @MinLength(10) @Field() + @Directive('@upper') + @Extensions({ extension: true }) password: string; @Field({ name: 'id' }) @@ -23,10 +25,18 @@ describe('OmitType', () => { class UpdateUserDto extends OmitType(CreateUserDto, ['login', '_id']) {} it('should inherit all fields except for "login" and "_id"', () => { - const { fields } = getFieldsAndDecoratorForType( - Object.getPrototypeOf(UpdateUserDto), - ); + const prototype = Object.getPrototypeOf(UpdateUserDto); + const { fields } = getFieldsAndDecoratorForType(prototype); expect(fields.length).toEqual(1); expect(fields[0].name).toEqual('password'); + expect(fields[0].directives.length).toEqual(1); + expect(fields[0].directives).toContainEqual({ + fieldName: 'password', + sdl: '@upper', + target: prototype, + }); + expect(fields[0].extensions).toEqual({ + extension: true, + }); }); }); diff --git a/tests/type-helpers/partial-type.helper.spec.ts b/tests/type-helpers/partial-type.helper.spec.ts index 644a99994..0bfc9c8ef 100644 --- a/tests/type-helpers/partial-type.helper.spec.ts +++ b/tests/type-helpers/partial-type.helper.spec.ts @@ -1,6 +1,6 @@ import { Expose, Transform } from 'class-transformer'; import { IsString } from 'class-validator'; -import { Field, ObjectType } from '../../lib/decorators'; +import { Directive, Extensions, Field, ObjectType } from '../../lib/decorators'; import { getFieldsAndDecoratorForType } from '../../lib/schema-builder/utils/get-fields-and-decorator.util'; import { PartialType } from '../../lib/type-helpers'; @@ -8,6 +8,8 @@ describe('PartialType', () => { @ObjectType({ isAbstract: true }) abstract class BaseType { @Field() + @Directive('@upper') + @Extensions({ extension: true }) id: string; @Field() @@ -32,9 +34,8 @@ describe('PartialType', () => { class UpdateUserDto extends PartialType(CreateUserDto) {} it('should inherit all fields and set "nullable" to true', () => { - const { fields } = getFieldsAndDecoratorForType( - Object.getPrototypeOf(UpdateUserDto), - ); + const prototype = Object.getPrototypeOf(UpdateUserDto); + const { fields } = getFieldsAndDecoratorForType(prototype); expect(fields.length).toEqual(5); expect(fields).toEqual( @@ -61,5 +62,14 @@ describe('PartialType', () => { }), ]), ); + expect(fields[0].directives.length).toEqual(1); + expect(fields[0].directives).toContainEqual({ + fieldName: 'id', + sdl: '@upper', + target: prototype, + }); + expect(fields[0].extensions).toEqual({ + extension: true, + }); }); }); diff --git a/tests/type-helpers/pick-type.helper.spec.ts b/tests/type-helpers/pick-type.helper.spec.ts index fd5d4407c..ee500b249 100644 --- a/tests/type-helpers/pick-type.helper.spec.ts +++ b/tests/type-helpers/pick-type.helper.spec.ts @@ -1,6 +1,12 @@ import { Transform } from 'class-transformer'; import { MinLength } from 'class-validator'; -import { ArgsType, Field, ObjectType } from '../../lib/decorators'; +import { + ArgsType, + Directive, + Extensions, + Field, + ObjectType, +} from '../../lib/decorators'; import { getFieldsAndDecoratorForType } from '../../lib/schema-builder/utils/get-fields-and-decorator.util'; import { PickType } from '../../lib/type-helpers'; @@ -10,6 +16,7 @@ describe('PickType', () => { @Transform((str) => str + '_transformed') @MinLength(10) @Field({ nullable: true }) + @Directive('@upper') login: string; @MinLength(10) @@ -17,6 +24,7 @@ describe('PickType', () => { password: string; @Field({ name: 'id' }) + @Extensions({ extension: true }) _id: string; } @@ -25,11 +33,16 @@ describe('PickType', () => { class UpdateUserWithIdDto extends PickType(CreateUserDto, ['_id']) {} it('should inherit "login" field', () => { - const { fields } = getFieldsAndDecoratorForType( - Object.getPrototypeOf(UpdateUserDto), - ); + const prototype = Object.getPrototypeOf(UpdateUserDto); + const { fields } = getFieldsAndDecoratorForType(prototype); expect(fields.length).toEqual(1); expect(fields[0].name).toEqual('login'); + expect(fields[0].directives.length).toEqual(1); + expect(fields[0].directives).toContainEqual({ + fieldName: 'login', + sdl: '@upper', + target: prototype, + }); }); it('should inherit renamed "_id" field', () => { @@ -38,6 +51,7 @@ describe('PickType', () => { ); expect(fields.length).toEqual(1); expect(fields[0].name).toEqual('_id'); + expect(fields[0].extensions).toEqual({ extension: true }); }); @ArgsType()