Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}