Skip to content

Commit

Permalink
fix: inherit directive and extensions in type helpers, compile unique…
Browse files Browse the repository at this point in the history
… directives
  • Loading branch information
madzhup committed Feb 5, 2021
1 parent 05b5a1c commit 8c721d0
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 35 deletions.
52 changes: 33 additions & 19 deletions lib/schema-builder/storages/type-metadata.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,13 @@ export class TypeMetadataStorageHost {
item.properties = this.getClassFieldsByPredicate(belongsToClass);
}
if (!item.directives) {
item.directives = this.classDirectives.filter(belongsToClass);
item.directives = this.classDirectives
.filter(belongsToClass)
.reduce(
(acc, curr) =>
acc.some((item) => item.sdl === curr.sdl) ? acc : [...acc, curr],
[],
);
}
if (!item.extensions) {
item.extensions = this.classExtensions
Expand All @@ -293,12 +299,8 @@ export class TypeMetadataStorageHost {
field.methodArgs = this.params.filter(
(param) => isHostEqual(param) && field.name === param.methodName,
);
field.directives = this.fieldDirectives.filter(
this.isFieldDirectiveOrExtension.bind(this, field),
);
field.extensions = this.fieldExtensions
.filter(this.isFieldDirectiveOrExtension.bind(this, field))
.reduce((curr, acc) => ({ ...curr, ...acc.value }), {});
field.directives = this.getDirectives(field);
field.extensions = this.getExtensions(field);
});
return fields;
}
Expand All @@ -312,12 +314,8 @@ export class TypeMetadataStorageHost {
item.methodArgs = this.params.filter(
(param) => isTypeEqual(param) && item.methodName === param.methodName,
);
item.directives = this.fieldDirectives.filter(
this.isFieldDirectiveOrExtension.bind(this, item),
);
item.extensions = this.fieldExtensions
.filter(this.isFieldDirectiveOrExtension.bind(this, item))
.reduce((curr, acc) => ({ ...curr, ...acc.value }), {});
item.directives = this.getDirectives(item);
item.extensions = this.getExtensions(item);
});
}

Expand All @@ -326,12 +324,8 @@ export class TypeMetadataStorageHost {

metadata.forEach((item) => {
const belongsToClass = isTargetEqual.bind(undefined, item);
item.directives = this.fieldDirectives.filter(
this.isFieldDirectiveOrExtension.bind(this, item),
);
item.extensions = this.fieldExtensions
.filter(this.isFieldDirectiveOrExtension.bind(this, item))
.reduce((curr, acc) => ({ ...curr, ...acc.value }), {});
item.directives = this.getDirectives(item);
item.extensions = this.getExtensions(item);

item.objectTypeFn =
item.kind === 'external'
Expand Down Expand Up @@ -438,6 +432,26 @@ export class TypeMetadataStorageHost {
});
}

private getDirectives(
field: PropertyMetadata | BaseResolverMetadata,
): PropertyDirectiveMetadata[] {
return this.fieldDirectives
.filter(this.isFieldDirectiveOrExtension.bind(this, field))
.reduce(
(acc, curr) =>
acc.some((item) => item.sdl === curr.sdl) ? acc : [...acc, curr],
[],
);
}

private getExtensions(
field: PropertyMetadata | BaseResolverMetadata,
): Record<string, unknown> {
return this.fieldExtensions
.filter(this.isFieldDirectiveOrExtension.bind(this, field))
.reduce((curr, acc) => ({ ...curr, ...acc.value }), {});
}

private isFieldDirectiveOrExtension(
host: Record<'target' | 'methodName' | 'name', any>,
metadata: PropertyDirectiveMetadata | PropertyExtensionsMetadata,
Expand Down
2 changes: 2 additions & 0 deletions lib/type-helpers/intersection-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<A, B>(
classARef: Type<A>,
Expand Down Expand Up @@ -41,6 +42,7 @@ export function IntersectionType<A, B>(
IntersectionObjectType.prototype,
item.name,
);
applyFieldDecorators(IntersectionObjectType, item);
});

Object.defineProperty(IntersectionObjectType, 'name', {
Expand Down
2 changes: 2 additions & 0 deletions lib/type-helpers/omit-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, K extends keyof T>(
classRef: Type<T>,
Expand Down Expand Up @@ -47,6 +48,7 @@ export function OmitType<T, K extends keyof T>(
OmitObjectType.prototype,
item.name,
);
applyFieldDecorators(OmitObjectType, item);
});
return OmitObjectType as Type<Omit<T, typeof keys[number]>>;
}
2 changes: 2 additions & 0 deletions lib/type-helpers/partial-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(
classRef: Type<T>,
Expand Down Expand Up @@ -44,6 +45,7 @@ export function PartialType<T>(
item.name,
);
applyIsOptionalDecorator(PartialObjectType, item.name);
applyFieldDecorators(PartialObjectType, item);
});

if (PartialObjectType[METADATA_FACTORY_NAME]) {
Expand Down
2 changes: 2 additions & 0 deletions lib/type-helpers/pick-type.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, K extends keyof T>(
classRef: Type<T>,
Expand Down Expand Up @@ -48,6 +49,7 @@ export function PickType<T, K extends keyof T>(
PickObjectType.prototype,
item.name,
);
applyFieldDecorators(PickObjectType, item);
});
return PickObjectType as Type<Pick<T, typeof keys[number]>>;
}
16 changes: 16 additions & 0 deletions lib/type-helpers/type-helpers.utils.ts
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
29 changes: 25 additions & 4 deletions tests/type-helpers/intersection-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -7,6 +7,8 @@ describe('IntersectionType', () => {
@ObjectType()
class ClassA {
@Field({ nullable: true })
@Directive('@upper')
@Extensions({ extension: true })
login: string;

@Field()
Expand All @@ -16,6 +18,8 @@ describe('IntersectionType', () => {
@ObjectType()
class ClassB {
@Field()
@Directive('@upper')
@Extensions({ extension: true })
lastName: string;

firstName?: string;
Expand All @@ -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,
});
});
});
18 changes: 14 additions & 4 deletions tests/type-helpers/omit-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -14,6 +14,8 @@ describe('OmitType', () => {
@Transform((str) => str + '_transformed')
@MinLength(10)
@Field()
@Directive('@upper')
@Extensions({ extension: true })
password: string;

@Field({ name: 'id' })
Expand All @@ -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,
});
});
});
18 changes: 14 additions & 4 deletions tests/type-helpers/partial-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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';

describe('PartialType', () => {
@ObjectType({ isAbstract: true })
abstract class BaseType {
@Field()
@Directive('@upper')
@Extensions({ extension: true })
id: string;

@Field()
Expand All @@ -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(
Expand All @@ -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,
});
});
});
22 changes: 18 additions & 4 deletions tests/type-helpers/pick-type.helper.spec.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -10,13 +16,15 @@ describe('PickType', () => {
@Transform((str) => str + '_transformed')
@MinLength(10)
@Field({ nullable: true })
@Directive('@upper')
login: string;

@MinLength(10)
@Field()
password: string;

@Field({ name: 'id' })
@Extensions({ extension: true })
_id: string;
}

Expand All @@ -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', () => {
Expand All @@ -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()
Expand Down

0 comments on commit 8c721d0

Please sign in to comment.