Skip to content

Commit

Permalink
Add schema directives support
Browse files Browse the repository at this point in the history
Signed-off-by: Arda TANRIKULU <ardatanrikulu@gmail.com>
  • Loading branch information
ardatan committed Nov 7, 2018
1 parent ef146af commit 76b2d15
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 18 deletions.
75 changes: 59 additions & 16 deletions packages/core/src/graphql-module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IResolvers, makeExecutableSchema } from 'graphql-tools';
import { IResolvers, makeExecutableSchema, SchemaDirectiveVisitor } from 'graphql-tools';
import { mergeGraphQLSchemas, mergeResolvers } from '@graphql-modules/epoxy';
import { Provider, ModuleContext, Injector } from './di';
import { DocumentNode, print, GraphQLSchema } from 'graphql';
Expand All @@ -15,6 +15,12 @@ export type BuildContextFn<Request, Context> = (
injector: Injector,
) => Promise<Context> | Context;

export interface ISchemaDirectives {
[name: string]: typeof SchemaDirectiveVisitor;
}

export type ModulesMap<Request> = Map<string, GraphQLModule<any, Request, any>>;

/**
* Defines the structure of a dependency as it declared in each module's `dependencies` field.
*/
Expand Down Expand Up @@ -62,6 +68,7 @@ export interface GraphQLModuleOptions<Config, Request, Context> {
providers?: Provider[] | ((config: Config) => Provider[]);
/** Object map between `Type.field` to a function(s) that will wrap the resolver of the field */
resolversComposition?: IResolversComposerMapping | ((config: Config) => IResolversComposerMapping);
schemaDirectives?: ISchemaDirectives | ((config: Config) => ISchemaDirectives);
}

/**
Expand All @@ -80,8 +87,9 @@ export interface ModuleCache<Request, Context> {
schema: GraphQLSchema;
typeDefs: string;
resolvers: IResolvers;
schemaDirectives: ISchemaDirectives;
contextBuilder: (req: Request) => Promise<Context>;
modulesMap: Map<string, GraphQLModule<any, Request, any>>;
modulesMap: ModulesMap<Request>;
}

/**
Expand All @@ -98,6 +106,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
schema: null,
typeDefs: null,
resolvers: null,
schemaDirectives: null,
contextBuilder: null,
modulesMap: null,
};
Expand Down Expand Up @@ -165,14 +174,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
return this._cache.typeDefs;
}

get resolvers(): IResolvers {
if (!this._cache.resolvers) {
this.buildSchemaAndInjector(this.modulesMap);
}
return this._cache.resolvers;
}

private buildTypeDefs(modulesMap: Map<string, GraphQLModule<any, Request, any>>) {
private buildTypeDefs(modulesMap: ModulesMap<Request>) {
const typeDefsArr = [];
const selfImports = this.selfImports;
for (let module of selfImports) {
Expand All @@ -191,6 +193,20 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
this._cache.typeDefs = mergeGraphQLSchemas(typeDefsArr);
}

get resolvers(): IResolvers {
if (!this._cache.resolvers) {
this.buildSchemaAndInjector(this.modulesMap);
}
return this._cache.resolvers;
}

get schemaDirectives(): ISchemaDirectives {
if (!this._cache.schemaDirectives) {
this.buildSchemaAndInjector(this.modulesMap);
}
return this._cache.schemaDirectives;
}

/**
* Returns the GraphQL type definitions of the module
* @return a `string` with the merged type definitions
Expand Down Expand Up @@ -284,12 +300,26 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
return resolversComposition;
}

private buildSchemaAndInjector(modulesMap: Map<string, GraphQLModule<any, Request, any>>) {
get selfSchemaDirectives(): ISchemaDirectives {
let schemaDirectives: ISchemaDirectives = {};
const schemaDirectivesDefinitions = this._options.schemaDirectives;
if (schemaDirectivesDefinitions) {
if (typeof schemaDirectivesDefinitions === 'function') {
schemaDirectives = schemaDirectivesDefinitions(this._moduleConfig);
} else {
schemaDirectives = schemaDirectivesDefinitions;
}
}
return schemaDirectives;
}

private buildSchemaAndInjector(modulesMap: ModulesMap<Request>) {
const imports = this.selfImports;
const importsTypeDefs = new Array<string>();
const importsResolvers = new Array<IResolvers>();
const importsInjectors = new Array<Injector>();
const importsContextBuilders = new Array<(req: Request) => Promise<Context>>();
let importsSchemaDirectives: ISchemaDirectives = {};
for (let module of imports) {
const moduleName = typeof module === 'string' ? module : module.name;
module = modulesMap.get(moduleName);
Expand All @@ -304,13 +334,15 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
const resolvers = module._cache.resolvers;
const injector = module._cache.injector;
const contextBuilder = module._cache.contextBuilder;
const schemaDirectives = module._cache.schemaDirectives;

if (typeDefs && typeDefs.length) {
importsTypeDefs.push(typeDefs);
}
importsResolvers.push(resolvers);
importsInjectors.push(injector);
importsContextBuilders.push(contextBuilder);
importsSchemaDirectives = { ...importsSchemaDirectives, ...schemaDirectives };
}

const injector = new Injector();
Expand Down Expand Up @@ -358,15 +390,25 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
const allTypeDefs = [...importsTypeDefs];

const selfTypeDefs = this.selfTypeDefs;
if (selfTypeDefs && selfTypeDefs) {
if (selfTypeDefs) {
allTypeDefs.push(selfTypeDefs);
}

const mergedTypeDefs = mergeGraphQLSchemas(
allTypeDefs,
);

this._cache.typeDefs = mergedTypeDefs;

const mergedSchemaDirectives = {
...importsSchemaDirectives,
...this.selfSchemaDirectives,
};

this._cache.schemaDirectives = mergedSchemaDirectives;

this._cache.schema = {} as GraphQLSchema;
if (allTypeDefs.length) {
const mergedTypeDefs = mergeGraphQLSchemas(
allTypeDefs,
);
try {
this._cache.schema = makeExecutableSchema({
typeDefs: mergedTypeDefs,
Expand All @@ -378,6 +420,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
requireResolversForResolveType: false,
allowResolversNotInSchema: true,
},
schemaDirectives: mergedSchemaDirectives,
});
} catch (e) {
if (e.message !== 'Must provide typeDefs') {
Expand Down Expand Up @@ -470,7 +513,7 @@ export class GraphQLModule<Config = any, Request = any, Context = any> {
return modulesMap;
}

private checkAndFixModulesMap(modulesMap: Map<string, GraphQLModule<any, Request, any>>): Map<string, GraphQLModule<any, Request, any>> {
private checkAndFixModulesMap(modulesMap: ModulesMap<Request>): Map<string, GraphQLModule<any, Request, any>> {
const graph = new DepGraph<GraphQLModule<any, Request, any>>();

modulesMap.forEach(module => {
Expand Down
64 changes: 62 additions & 2 deletions packages/core/tests/graphql-module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
ModuleContext,
OnRequest,
} from '../src';
import { execute, GraphQLSchema, printSchema } from 'graphql';
import { execute, GraphQLSchema, printSchema, GraphQLField, GraphQLEnumValue, GraphQLString, defaultFieldResolver } from 'graphql';
import { stripWhitespaces } from './utils';
import gql from 'graphql-tag';
import { DependencyProviderNotFoundError, Injectable } from '../src';
import { SchemaDirectiveVisitor } from 'graphql-tools';

describe('GraphQLModule', () => {
// A
Expand Down Expand Up @@ -522,7 +523,7 @@ describe('GraphQLModule', () => {
interface MyBase {
id: String
}
type MyType implements MyBase {
id: String
}
Expand Down Expand Up @@ -556,4 +557,63 @@ describe('GraphQLModule', () => {
expect(hasInjector).toBeTruthy();
});
});
describe('Schema Directives', async () => {
it('should handle schema directives', async () => {

const typeDefs = `
directive @date on FIELD_DEFINITION
scalar Date
type Query {
today: Date @date
}`;

class FormattableDateDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;

field.args.push({
name: 'format',
type: GraphQLString,
});

field.resolve = async function(
source,
args,
context,
info,
) {
const date = await resolve.call(this, source, args, context, info);
return date.toLocaleDateString();
};

field.type = GraphQLString;
}
}

const { schema, context } = new GraphQLModule({
typeDefs,
resolvers: {
Query: {
today: () => new Date(),
},
},
schemaDirectives: {
date: FormattableDateDirective,
},
});

const contextValue = await context({ req: {} });

const result = await execute({
schema,
document: gql`query { today }`,
contextValue,
});

expect(result.data['today']).toEqual(new Date().toLocaleDateString());

});
});
});

0 comments on commit 76b2d15

Please sign in to comment.