From c5a4bbd02046e93cb5ded31a938a4359ea8fecb7 Mon Sep 17 00:00:00 2001 From: Kong Ki Pan Date: Thu, 29 Sep 2022 17:06:09 +0800 Subject: [PATCH] fix(): intersect more than 4 classes --- lib/intersection-type.helper.ts | 45 +++++++------------ .../intersection-type-multiple.helper.spec.ts | 24 +++++++++- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/lib/intersection-type.helper.ts b/lib/intersection-type.helper.ts index dd10af03..cd4e5753 100644 --- a/lib/intersection-type.helper.ts +++ b/lib/intersection-type.helper.ts @@ -6,49 +6,38 @@ import { inheritValidationMetadata, } from './type-helpers.utils'; -export function IntersectionType( - target: Type, - source: Type, -): MappedType; +// https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( + k: infer I, +) => void + ? I + : never; -export function IntersectionType( - target: Type, - sourceB: Type, - sourceC: Type, -): MappedType; +type ClassRefsToConstructors = { + [U in keyof T]: T[U] extends Type ? V : never; +}; -export function IntersectionType( - target: Type, - sourceB: Type, - sourceC: Type, - sourceD: Type, -): MappedType; - -export function IntersectionType( - classA: Type, - ...classRefs: T -): MappedType { - const allClassRefs = [classA, ...classRefs]; +type Intersection = Type< + UnionToIntersection[number]> +>; +export function IntersectionType(...classRefs: T) { abstract class IntersectionClassType { constructor() { - allClassRefs.forEach((classRef) => { + classRefs.forEach((classRef) => { inheritPropertyInitializers(this, classRef); }); } } - allClassRefs.forEach((classRef) => { + classRefs.forEach((classRef) => { inheritValidationMetadata(classRef, IntersectionClassType); inheritTransformationMetadata(classRef, IntersectionClassType); }); - const intersectedNames = allClassRefs.reduce( - (prev, ref) => prev + ref.name, - '', - ); + const intersectedNames = classRefs.reduce((prev, ref) => prev + ref.name, ''); Object.defineProperty(IntersectionClassType, 'name', { value: `Intersection${intersectedNames}`, }); - return IntersectionClassType as MappedType; + return IntersectionClassType as Intersection; } diff --git a/tests/intersection-type-multiple.helper.spec.ts b/tests/intersection-type-multiple.helper.spec.ts index b8239efd..ecd9b2f1 100644 --- a/tests/intersection-type-multiple.helper.spec.ts +++ b/tests/intersection-type-multiple.helper.spec.ts @@ -31,7 +31,23 @@ describe('IntersectionType', () => { patronymic!: string; } - class UpdateUserDto extends IntersectionType(ClassA, ClassB, ClassC) {} + class ClassD { + @IsString() + alpha = 'defaultStringAlpha'; + } + + class ClassE { + @IsString() + beta = 'defaultStringBeta'; + } + + class UpdateUserDto extends IntersectionType( + ClassA, + ClassB, + ClassC, + ClassD, + ClassE, + ) {} describe('Validation metadata', () => { it('should inherit metadata for all properties from class A and class B', () => { @@ -45,6 +61,8 @@ describe('IntersectionType', () => { 'lastName', 'hash', 'patronymic', + 'alpha', + 'beta', ]); }); describe('when object does not fulfil validation rules', () => { @@ -74,6 +92,8 @@ describe('IntersectionType', () => { updateDto.lastName = 'lastNameTest'; updateDto.login = 'mylogintesttest'; updateDto.patronymic = 'patronymicTest'; + updateDto.alpha = 'alphaTest'; + updateDto.beta = 'betaTest'; const validationErrors = await validate(updateDto); expect(validationErrors.length).toEqual(0); @@ -105,6 +125,8 @@ describe('IntersectionType', () => { expect(updateUserDto.login).toEqual('defaultLoginWithMin10Chars'); expect(updateUserDto.firstName).toEqual('defaultFirst'); expect(updateUserDto.hash).toEqual('defaultHashWithMin5Chars'); + expect(updateUserDto.alpha).toEqual('defaultStringAlpha'); + expect(updateUserDto.beta).toEqual('defaultStringBeta'); }); }); });