Skip to content

Commit

Permalink
Merge pull request #21 from indigotech/feature/union
Browse files Browse the repository at this point in the history
Feature - Union Type
  • Loading branch information
felipesabino authored Oct 25, 2017
2 parents def85da + eac7b48 commit f6a7a09
Show file tree
Hide file tree
Showing 16 changed files with 153 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Apart from the decorators listed on the original documentation, we have added si
- @Query: It can be used multiple times on the same file. This way we make it possible to break queries into different folders.
- @Mutation: It can be used multiple times on the same file. This way we make it possible to break queries into different folders.
- @UseContainer: Sets the IoC container to be used in order to instantiate the decorated clas.
- @Uniontype: It can be used to create `GraphQLUnionType` objects.

#### GraphQL Decorator Examples

Expand Down
5 changes: 3 additions & 2 deletions src/decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { OrderByTypeFactory } from './order-by.type-factory';
import { PageInfo } from './page-info.type';
import { PaginationResponse } from './pagination.type';

export * from './decorator/';
export * from './metadata/options';

export const GQ_QUERY_KEY = 'gq_query';
export const GQ_MUTATION_KEY = 'gq_mutation';
export const GQ_SUBSCRIPTION_KEY = 'gq_subscription';
Expand All @@ -17,8 +20,6 @@ export const GQ_OBJECT_METADATA_KEY = 'gq_object_type';
export const GQ_ENUM_METADATA_KEY = 'gq_enum_type';
export const GQ_DESCRIPTION_KEY = 'gq_description';



export interface TypeMetadata {
name?: string;
description?: string;
Expand Down
1 change: 1 addition & 0 deletions src/decorator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './union-type.decorator';
17 changes: 17 additions & 0 deletions src/decorator/union-type.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MetadataStorage } from '../metadata-storage';
import { UnionOption } from '../metadata';

/**
* Union Type. ref: http://graphql.org/learn/schema/#union-types
* @param option Options for a Union Type
*/
export function UnionType<T>(option: UnionOption<T>) {
return function (target: any) {
MetadataStorage.addUnionMetadata({
name: target.name,
types: option.types,
resolver: option.resolver,
description: option.description,
});
} as Function;
}
15 changes: 11 additions & 4 deletions src/field_type_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import {
RootMetadata,
TypeMetadata,
} from './decorator';
import { MetadataStorage } from './metadata-storage';

import { SchemaFactoryError, SchemaFactoryErrorType } from './schema_factory';

import { IoCContainer } from './ioc-container';
import { OrderByTypeFactory } from './order-by.type-factory';
import { PaginationType } from './pagination.type';
import { enumTypeFactory } from './enum.type-factory';
import { objectTypeFactory } from './object_type_factory';
import { unionTypeFactory } from './type-factory';


export interface ResolverHolder {
fn: Function;
Expand All @@ -43,12 +47,15 @@ function convertType(typeFn: Function, metadata: TypeMetadata, isInput: boolean,
} else if (typeFn === Boolean) {
returnType = graphql.GraphQLBoolean;
} else if (typeFn && typeFn.prototype && Reflect.hasMetadata(GQ_OBJECT_METADATA_KEY, typeFn.prototype)) {
// recursively call objectFactory
returnType = objectTypeFactory(typeFn, isInput);
// recursively call objectFactory
returnType = objectTypeFactory(typeFn, isInput);
}
} else {
} else {
returnType = metadata.explicitType;
if (returnType && returnType.prototype && Reflect.hasMetadata(GQ_OBJECT_METADATA_KEY, returnType.prototype)) {

if (returnType && returnType.prototype && MetadataStorage.containsUnionMetadata(returnType.name)) {
returnType = unionTypeFactory(returnType.name, isInput);
} else if (returnType && returnType.prototype && Reflect.hasMetadata(GQ_OBJECT_METADATA_KEY, returnType.prototype)) {
// recursively call objectFactory
returnType = objectTypeFactory(returnType, isInput);
} else if (returnType && returnType.prototype && Reflect.hasMetadata(GQ_ENUM_METADATA_KEY, returnType.prototype)) {
Expand Down
1 change: 1 addition & 0 deletions src/metadata-storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './metadata-storage';
18 changes: 18 additions & 0 deletions src/metadata-storage/metadata-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { UnionTypeMetadata } from '../metadata/types/union.metadata';

const unionMetadata: UnionTypeMetadata[] = [];

const MetadataStorage = {

addUnionMetadata: function(metadata: UnionTypeMetadata) {
unionMetadata.push(metadata);
},
getUnionMetadata: function(): UnionTypeMetadata[] {
return unionMetadata;
},
containsUnionMetadata: function(name: string) {
return unionMetadata.some(metadata => metadata.name === name);
},
};

export { MetadataStorage };
2 changes: 2 additions & 0 deletions src/metadata/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './options';
export * from './types';
1 change: 1 addition & 0 deletions src/metadata/options/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './union.option';
19 changes: 19 additions & 0 deletions src/metadata/options/union.option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Arguments for a Union type on graphql schema
*/
export interface UnionOption<T> {
/**
* (Optional) Description
*/
description?: string;

/**
* Concrete object types
*/
types: any[];

/**
* Resolver function to inform schema what type should be returned based on the value provided
*/
resolver: (obj: T, context: any, info: any) => Promise<string> | string | null;
}
1 change: 1 addition & 0 deletions src/metadata/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './union.metadata';
6 changes: 6 additions & 0 deletions src/metadata/types/union.metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface UnionTypeMetadata {
name: string;
description?: string;
types: any[];
resolver: (obj: any, context: any, info: any) => Promise<string> | string | null;
}
50 changes: 50 additions & 0 deletions src/schema_factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,54 @@ describe('schemaFactory', function() {

});

describe('UnionType', () => {

it('creates schema with union type', () => {

@D.ObjectType()
class ObjA { @D.Field() fieldA: string; }

@D.ObjectType()
class ObjB { @D.Field() fieldB: string; }

type MyType = ObjA | ObjB;
@D.UnionType<MyType>({
types: [ObjA, ObjB],
resolver: (obj: any): string | null => {
if (obj.fieldA) { return ObjA.name; }
if (obj.fieldB) { return ObjB.name; }
return null;
},
})
class MyUnionType { }


@D.ObjectType() class Query {
@D.Field({ type: MyUnionType })
async aQuery(): Promise<MyType> {
return { fieldA: '' };
}
}
@D.Schema() class Schema { @D.Query() query: Query; }
const schema = schemaFactory(Schema);
const ast = parse(`
query {
aQuery {
...on ObjA {
fieldA
}
...on ObjB {
fieldB
}
}
}`);

assert.deepEqual(validate(schema, ast), []);

});

});



});
2 changes: 1 addition & 1 deletion src/schema_factory.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as graphql from 'graphql';

import { FieldTypeMetadata, GQ_FIELDS_KEY, GQ_MUTATION_KEY, GQ_OBJECT_METADATA_KEY, GQ_QUERY_KEY, GQ_SUBSCRIPTION_KEY } from './decorator';
import { GraphQLObjectType, GraphQLSchema } from 'graphql';
import { mutationObjectTypeFactory, queryObjectTypeFactory, subscriptionObjectTypeFactory } from './object_type_factory';

import { GraphQLSchema, GraphQLObjectType } from 'graphql';
import { fieldTypeFactory } from './field_type_factory';

export enum SchemaFactoryErrorType {
Expand Down
1 change: 1 addition & 0 deletions src/type-factory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './union.type-factory';
20 changes: 20 additions & 0 deletions src/type-factory/union.type-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as graphql from 'graphql';
import { MetadataStorage } from '../metadata-storage';
import { UnionTypeMetadata } from '../metadata';
import { objectTypeFactory } from '../object_type_factory';

export function unionTypeFactory(name: string, isInput: boolean): graphql.GraphQLUnionType | undefined {
return MetadataStorage.getUnionMetadata()
.filter(union => union.name === name)
.map(union => {
return new graphql.GraphQLUnionType({
description: union.description,
name: union.name,
resolveType: union.resolver,
types: union.types
.map(type => objectTypeFactory(type, isInput))
.filter(_ => _), //filter null values
});
})
.find((_, index) => index === 0);
}

0 comments on commit f6a7a09

Please sign in to comment.