diff --git a/lib/intersection-type.helper.ts b/lib/intersection-type.helper.ts index 6e385ccb..36260bd1 100644 --- a/lib/intersection-type.helper.ts +++ b/lib/intersection-type.helper.ts @@ -1,5 +1,6 @@ import { Type } from '@nestjs/common'; import { + inheritPropertyInitializers, inheritTransformationMetadata, inheritValidationMetadata, } from './type-helpers.utils'; @@ -8,7 +9,12 @@ export function IntersectionType( classARef: Type, classBRef: Type, ): Type { - abstract class IntersectionClassType {} + abstract class IntersectionClassType { + constructor() { + inheritPropertyInitializers(this, classARef); + inheritPropertyInitializers(this, classBRef); + } + } inheritValidationMetadata(classARef, IntersectionClassType); inheritValidationMetadata(classBRef, IntersectionClassType); diff --git a/lib/omit-type.helper.ts b/lib/omit-type.helper.ts index 1a2f5abc..63b29ac3 100644 --- a/lib/omit-type.helper.ts +++ b/lib/omit-type.helper.ts @@ -1,5 +1,6 @@ import { Type } from '@nestjs/common'; import { + inheritPropertyInitializers, inheritTransformationMetadata, inheritValidationMetadata, } from './type-helpers.utils'; @@ -8,10 +9,15 @@ export function OmitType( classRef: Type, keys: readonly K[], ): Type> { - abstract class OmitClassType {} - const isInheritedPredicate = (propertyKey: string) => !keys.includes(propertyKey as K); + + abstract class OmitClassType { + constructor() { + inheritPropertyInitializers(this, classRef, isInheritedPredicate); + } + } + inheritValidationMetadata(classRef, OmitClassType, isInheritedPredicate); inheritTransformationMetadata(classRef, OmitClassType, isInheritedPredicate); diff --git a/lib/partial-type.helper.ts b/lib/partial-type.helper.ts index 1864c182..1bff140f 100644 --- a/lib/partial-type.helper.ts +++ b/lib/partial-type.helper.ts @@ -1,18 +1,23 @@ import { Type } from '@nestjs/common'; import { applyIsOptionalDecorator, + inheritPropertyInitializers, inheritTransformationMetadata, inheritValidationMetadata, } from './type-helpers.utils'; export function PartialType(classRef: Type): Type> { - abstract class PartialClassType {} + abstract class PartialClassType { + constructor() { + inheritPropertyInitializers(this, classRef); + } + } const propertyKeys = inheritValidationMetadata(classRef, PartialClassType); inheritTransformationMetadata(classRef, PartialClassType); if (propertyKeys) { - propertyKeys.forEach(key => { + propertyKeys.forEach((key) => { applyIsOptionalDecorator(PartialClassType, key); }); } diff --git a/lib/pick-type.helper.ts b/lib/pick-type.helper.ts index 56297357..10c2f0be 100644 --- a/lib/pick-type.helper.ts +++ b/lib/pick-type.helper.ts @@ -1,5 +1,6 @@ import { Type } from '@nestjs/common'; import { + inheritPropertyInitializers, inheritTransformationMetadata, inheritValidationMetadata, } from './type-helpers.utils'; @@ -8,10 +9,14 @@ export function PickType( classRef: Type, keys: readonly K[], ): Type> { - abstract class PickClassType {} - const isInheritedPredicate = (propertyKey: string) => keys.includes(propertyKey as K); + + abstract class PickClassType { + constructor() { + inheritPropertyInitializers(this, classRef, isInheritedPredicate); + } + } inheritValidationMetadata(classRef, PickClassType, isInheritedPredicate); inheritTransformationMetadata(classRef, PickClassType, isInheritedPredicate); diff --git a/lib/type-helpers.utils.ts b/lib/type-helpers.utils.ts index 4cc46035..ed354b40 100644 --- a/lib/type-helpers.utils.ts +++ b/lib/type-helpers.utils.ts @@ -155,3 +155,26 @@ function isClassTransformerAvailable() { return false; } } + +export function inheritPropertyInitializers( + target: Record, + sourceClass: Type, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isPropertyInherited = (key: string) => true, +) { + try { + const tempInstance = new sourceClass(); + const propertyNames = Object.getOwnPropertyNames(tempInstance); + + propertyNames + .filter( + (propertyName) => + typeof tempInstance[propertyName] !== 'undefined' && + typeof target[propertyName] === 'undefined', + ) + .filter((propertyName) => isPropertyInherited(propertyName)) + .forEach((propertyName) => { + target[propertyName] = tempInstance[propertyName]; + }); + } catch {} +} diff --git a/tests/intersection-type.helper.spec.ts b/tests/intersection-type.helper.spec.ts index 8467eea8..3d9d4c2a 100644 --- a/tests/intersection-type.helper.spec.ts +++ b/tests/intersection-type.helper.spec.ts @@ -6,18 +6,18 @@ import { getValidationMetadataByTarget } from './type-helpers.test-utils'; describe('IntersectionType', () => { class ClassA { @MinLength(10) - login!: string; + login = 'defaultLoginWithMin10Chars'; - @Transform(str => str + '_transformed') + @Transform((str) => str + '_transformed') @MinLength(10) password!: string; } class ClassB { @IsString() - firstName!: string; + firstName = 'defaultFirst'; - @Transform(str => str + '_transformed') + @Transform((str) => str + '_transformed') @MinLength(5) lastName!: string; } @@ -27,7 +27,7 @@ describe('IntersectionType', () => { describe('Validation metadata', () => { it('should inherit metadata for all properties from class A and class B', () => { const validationKeys = getValidationMetadataByTarget(UpdateUserDto).map( - item => item.propertyName, + (item) => item.propertyName, ); expect(validationKeys).toEqual([ 'login', @@ -43,17 +43,11 @@ describe('IntersectionType', () => { const validationErrors = await validate(updateDto); - expect(validationErrors.length).toEqual(4); + expect(validationErrors.length).toEqual(2); expect(validationErrors[0].constraints).toEqual({ - minLength: 'login must be longer than or equal to 10 characters', - }); - expect(validationErrors[1].constraints).toEqual({ minLength: 'password must be longer than or equal to 10 characters', }); - expect(validationErrors[2].constraints).toEqual({ - isString: 'firstName must be a string', - }); - expect(validationErrors[3].constraints).toEqual({ + expect(validationErrors[1].constraints).toEqual({ minLength: 'lastName must be longer than or equal to 5 characters', }); }); @@ -86,4 +80,12 @@ describe('IntersectionType', () => { expect(transformedDto.password).toEqual(password + '_transformed'); }); }); + + describe('Property initializers', () => { + it('should inherit property initializers', () => { + const updateUserDto = new UpdateUserDto(); + expect(updateUserDto.login).toEqual('defaultLoginWithMin10Chars'); + expect(updateUserDto.firstName).toEqual('defaultFirst'); + }); + }); }); diff --git a/tests/omit-type.helper.spec.ts b/tests/omit-type.helper.spec.ts index baf56ab7..cbde0005 100644 --- a/tests/omit-type.helper.spec.ts +++ b/tests/omit-type.helper.spec.ts @@ -10,7 +10,7 @@ describe('OmitType', () => { @Transform((str) => str + '_transformed') @MinLength(10) - password!: string; + password = 'defaultPassword'; } class UpdateUserDto extends OmitType(CreateUserDto, ['login']) {} @@ -56,4 +56,12 @@ describe('OmitType', () => { expect(transformedDto.password).toEqual(password + '_transformed'); }); }); + + describe('Property initializers', () => { + it('should inherit property initializers', () => { + const updateUserDto = new UpdateUserDto(); + expect((updateUserDto as any)['login']).toBeUndefined(); + expect(updateUserDto.password).toEqual('defaultPassword'); + }); + }); }); diff --git a/tests/partial-type.helper.spec.ts b/tests/partial-type.helper.spec.ts index f050713d..64dd273e 100644 --- a/tests/partial-type.helper.spec.ts +++ b/tests/partial-type.helper.spec.ts @@ -12,7 +12,7 @@ describe('PartialType', () => { } class CreateUserDto extends BaseUserDto { - login!: string; + login: string = 'defaultLogin'; @Expose() @Transform((str) => str + '_transformed') @@ -74,4 +74,11 @@ describe('PartialType', () => { ); }); }); + + describe('Property initializers', () => { + it('should inherit property initializers', () => { + const updateUserDto = new UpdateUserDto(); + expect(updateUserDto.login).toEqual('defaultLogin'); + }); + }); }); diff --git a/tests/pick-type.helper.spec.ts b/tests/pick-type.helper.spec.ts index 5e7cd992..1c797a85 100644 --- a/tests/pick-type.helper.spec.ts +++ b/tests/pick-type.helper.spec.ts @@ -5,9 +5,9 @@ import { getValidationMetadataByTarget } from './type-helpers.test-utils'; describe('PickType', () => { class CreateUserDto { - @Transform(str => str + '_transformed') + @Transform((str) => str + '_transformed') @MinLength(10) - login!: string; + login = 'defaultLogin'; @MinLength(10) password!: string; @@ -18,7 +18,7 @@ describe('PickType', () => { describe('Validation metadata', () => { it('should inherit metadata with "password" property excluded', () => { const validationKeys = getValidationMetadataByTarget(UpdateUserDto).map( - item => item.propertyName, + (item) => item.propertyName, ); expect(validationKeys).toEqual(['login']); }); @@ -56,4 +56,12 @@ describe('PickType', () => { expect(transformedDto.login).toEqual(login + '_transformed'); }); }); + + describe('Property initializers', () => { + it('should inherit property initializers', () => { + const updateUserDto = new UpdateUserDto(); + expect((updateUserDto as any)['password']).toBeUndefined(); + expect(updateUserDto.login).toEqual('defaultLogin'); + }); + }); });