Skip to content

Commit 223937c

Browse files
authored
Merge pull request #46 from indigotech/feature/interface-decorator
Feature - @InterfaceType decorator
2 parents 2cc4305 + 0b2bbcf commit 223937c

22 files changed

+264
-23
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Apart from the decorators listed on the original documentation, we have added si
1717
- @Query: It can be used multiple times on the same file. This way we make it possible to break queries into different folders.
1818
- @Mutation: It can be used multiple times on the same file. This way we make it possible to break queries into different folders.
1919
- @UseContainer: Sets the IoC container to be used in order to instantiate the decorated clas.
20-
- @Uniontype: It can be used to create `GraphQLUnionType` objects.
20+
- @UnionType: It can be used to create `GraphQLUnionType` objects.
21+
- @InterfaceType: It can be used to create `GraphQLInterfaceType` objects.
2122

2223
#### GraphQL Decorator Examples
2324

src/array.utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Concats two arrays
3+
* @param itemsA first array argument
4+
* @param itemsB second array argument
5+
*/
6+
export const concat = (itemsA: any[], itemsB: any[]) => itemsA.concat(itemsB);
7+
8+
/**
9+
* Executes a flatMap modifier function to each elements of the array
10+
* @param λ the flatMap function
11+
* @param collection the array to apply the flatMap funtion to
12+
*/
13+
export const flatMap = (λ: (item: any) => any, collection: any[]) => collection.map(λ).reduce(concat, []);
14+
15+
/**
16+
* Flattens an array with nested arrays
17+
* @param collection the array argument
18+
*/
19+
export const flatten = (collection: any[]) => flatMap(items => {
20+
if (items.constructor === Array) {
21+
return flatMap(item => item, items);
22+
} else {
23+
return items;
24+
}
25+
}, collection);

src/decorator/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './field.decorator';
99
export * from './order-by.decorator';
1010
export * from './root.decorator';
1111
export * from './before.decorator';
12+
export * from './interface-type.decorator';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { getMetadataArgsStorage } from '../metadata-builder';
2+
import { InterfaceOption } from '../index';
3+
4+
/**
5+
* Interface Type.
6+
* See [GraphQL Documentation - Interfaces]{@link http://graphql.org/learn/schema/#interfaces}
7+
*
8+
* @param option Options for an Interface definition
9+
*/
10+
export function InterfaceType<T>(option: InterfaceOption<T>) {
11+
return function (target: any) {
12+
getMetadataArgsStorage().interfaces.push({
13+
target: target,
14+
name: target.name,
15+
resolver: option.resolver,
16+
description: option.description,
17+
});
18+
} as Function;
19+
}

src/decorator/object-type.decorator.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { SchemaFactoryError, SchemaFactoryErrorType } from '../type-factory';
2+
13
import { ObjectOption } from '../metadata/options';
24
import { getMetadataArgsStorage } from '../metadata-builder';
35

@@ -23,11 +25,20 @@ export function InputObjectType(option?: ObjectOption) {
2325

2426
function CreateObjectType(isInput: boolean, option?: ObjectOption) {
2527
return function (target: any) {
28+
29+
if (isInput && option && option.interfaces) {
30+
throw new SchemaFactoryError(`Input types are not allowed to have interfaces: '${target.name}'`,
31+
SchemaFactoryErrorType.INPUT_FIELD_SHOULD_NOT_HAVE_INTERFACE);
32+
}
33+
2634
getMetadataArgsStorage().objects.push({
2735
target: target,
2836
name: target.name,
2937
description: option ? option.description : null,
3038
isInput: isInput,
39+
interfaces: option && option.interfaces ? (
40+
option.interfaces.constructor !== Array ? [option.interfaces as Function] : option.interfaces as Function[]
41+
) : [],
3142
});
3243
};
3344
}

src/metadata-builder/metadata-args.storage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
RootArg,
1313
SchemaArg,
1414
UnionTypeArg,
15+
InterfaceTypeArg,
1516
} from '../metadata/args';
1617

1718
import { MetadataUtils } from './metadata.utils';
@@ -29,6 +30,7 @@ export class MetadataArgsStorage {
2930
roots: RootArg[] = [];
3031
orderBys: OrderByArg[] = [];
3132
befores: BeforeArg[] = [];
33+
interfaces: InterfaceTypeArg[] = [];
3234

3335
filterEnumsByClass(target: any): EnumTypeArg[] {
3436
return this.enums.filter(item => item.target === target);
@@ -42,6 +44,10 @@ export class MetadataArgsStorage {
4244
return this.union.filter(item => item.target === target);
4345
}
4446

47+
filterInterfaceTypeByClass(target: any): InterfaceTypeArg[] {
48+
return this.interfaces.filter(item => item.target === target);
49+
}
50+
4551
filterObjectTypeByClass(target: any): ObjectTypeArg[] {
4652
return this.objects.filter(item => item.target === target);
4753
}

src/metadata-builder/metadata.builder.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import {
1111
RootMetadata,
1212
SchemaMetadata,
1313
UnionTypeMetadata,
14+
InterfaceTypeMetadata,
1415
} from '../metadata/types';
1516

17+
import { flatten } from '../array.utils';
1618
import { EntryType } from '../metadata/args';
1719
import { getMetadataArgsStorage } from './metadata-args.storage';
1820

@@ -41,6 +43,17 @@ export class MetadataBuilder {
4143
}));
4244
}
4345

46+
buildInterfaceTypeMetadata(target: any): InterfaceTypeMetadata[] | undefined {
47+
return getMetadataArgsStorage()
48+
.filterInterfaceTypeByClass(target)
49+
.map(arg => ({
50+
target: arg.target,
51+
name: arg.name,
52+
resolver: arg.resolver,
53+
description: arg.description,
54+
}));
55+
}
56+
4457
buildObjectTypeMetadata(target: any): ObjectTypeMetadata[] | undefined {
4558
return getMetadataArgsStorage()
4659
.filterObjectTypeByClass(target)
@@ -49,6 +62,7 @@ export class MetadataBuilder {
4962
name: arg.name,
5063
description: arg.description,
5164
isInput: arg.isInput,
65+
interfaces: flatten(arg.interfaces.map(this.buildInterfaceTypeMetadata)),
5266
}));
5367
}
5468

src/metadata/args/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './context.arg';
99
export * from './root.arg';
1010
export * from './order-by.arg';
1111
export * from './before.arg';
12+
export * from './interface-type.arg';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Argument } from './argument';
2+
3+
export interface InterfaceTypeArg extends Argument {
4+
resolver: (obj: any, context: any, info: any) => Promise<string> | string | null;
5+
}

src/metadata/args/object-type.arg.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ import { Argument } from './argument';
22

33
export interface ObjectTypeArg extends Argument {
44
isInput: boolean;
5+
interfaces: Function[];
56
}

0 commit comments

Comments
 (0)