Skip to content

Commit

Permalink
fixes for latest graphql-js support
Browse files Browse the repository at this point in the history
  • Loading branch information
lirown committed Jun 21, 2018
1 parent 41532e2 commit 52d2619
Show file tree
Hide file tree
Showing 5 changed files with 399 additions and 221 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "graphql-custom-directive",
"version": "0.1.9",
"version": "0.2.0",
"description": "A custom directive for GraphQL which hooks the query or schema execution",
"main": "dist/index.js",
"scripts": {
Expand Down Expand Up @@ -47,6 +47,7 @@
"eslint-config-google": "^0.7.0",
"graphql": "*",
"mocha": "^3.1.2",
"nyc": "^8.3.1"
"nyc": "^8.3.1",
"prettier": "^1.13.5"
}
}
214 changes: 138 additions & 76 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GraphQLDirective } from 'graphql/type/directives';
import { GraphQLSchema, parse } from 'graphql';
import {GraphQLDirective} from 'graphql/type/directives';
import {GraphQLSchema, parse} from 'graphql';

const DEFAULT_DIRECTIVES = ['skip', 'include'];

Expand All @@ -10,129 +10,191 @@ const DEFAULT_DIRECTIVES = ['skip', 'include'];
* of calling that function.
*/
function defaultResolveFn(source, args, context, info) {
var fieldName = info.fieldName;
// ensure source is a value for which property access is acceptable.
if (typeof source === 'object' || typeof source === 'function') {
return typeof source[fieldName] === 'function' ? source[fieldName]() : source[fieldName];
}
var fieldName = info.fieldName;
// ensure source is a value for which property access is acceptable.
if (typeof source === 'object' || typeof source === 'function') {
return typeof source[fieldName] === 'function'
? source[fieldName]()
: source[fieldName];
}
}

/**
* resolving field using directive resolver
*/
function resolveWithDirective(resolve, source, directive, context, info) {
let directiveConfig = info.schema._directives.filter(d => directive.name.value === d.name)[0];
source = source || ((info || {}).variableValues || {}).input_0 || {};
let directiveConfig = info.schema._directives.filter(
d => directive.name.value === d.name,
)[0];

let args = {};
let args = {};

for (let arg of directive.arguments) {
args[arg.name.value] = arg.value.value;
}
for (let arg of directive.arguments) {
args[arg.name.value] = arg.value.value;
}

return directiveConfig.resolve(resolve, source, args, context, info);
};
return directiveConfig.resolve(resolve, source, args, context, info);
}

/**
* parse directives from a schema defenition form them as graphql directive structure
*/
function parseSchemaDirectives(directives) {
let schemaDirectives = [];

if (!directives || !(directives instanceof Object) || Object.keys(directives).length === 0) {
return [];
let schemaDirectives = [];

if (
!directives ||
!(directives instanceof Object) ||
Object.keys(directives).length === 0
) {
return [];
}

for (let directiveName in directives) {
let argsList = [],
args = '';

Object.keys(directives[directiveName]).map(key => {
argsList.push(`${key}:"${directives[directiveName][key]}"`);
});

if (argsList.length > 0) {
args = `(${argsList.join(',')})`;
}

for (let directiveName in directives) {
let argsList = [], args = '';

Object.keys(directives[directiveName]).map(key => {
argsList.push(`${key}:"${directives[directiveName][key]}"`)
});
schemaDirectives.push(`@${directiveName}${args}`);
}

if (argsList.length > 0) {
args = `(${argsList.join(',')})`;
}

schemaDirectives.push(`@${directiveName}${args}`);
}

return parse(`{ a: String ${schemaDirectives.join(' ')} }`).definitions[0].selectionSet.selections[0].directives;
};
return parse(`{ a: String ${schemaDirectives.join(' ')} }`).definitions[0]
.selectionSet.selections[0].directives;
}

/**
* If the directive is defined on a field it will execute the custom directive
* resolve right after executing the resolve of the field otherwise it will execute
* the original resolve of the field
*/
function resolveMiddlewareWrapper(resolve = defaultResolveFn, directives = {}) {
const serverDirectives = parseSchemaDirectives(directives);

return (source, args, context, info) => {
const directives = serverDirectives.concat((info.fieldASTs || info.fieldNodes)[0].directives);
const directive = directives.filter(d => DEFAULT_DIRECTIVES.indexOf(d.name.value) === -1)[0];

if (!directive) {
return resolve(source, args, context, info);
}

let defer = resolveWithDirective(() => Promise.resolve(resolve(source, args, context, info)), source, directive, context, info);
const serverDirectives = parseSchemaDirectives(directives);

return (source, args, context, info) => {
const directives = serverDirectives.concat(
(info.fieldASTs || info.fieldNodes)[0].directives,
);
const directive = directives.filter(
d => DEFAULT_DIRECTIVES.indexOf(d.name.value) === -1,
)[0];

if (!directive) {
return resolve(source, args, context, info);
}

if (directives.length <= 1) {
return defer;
}
let defer = resolveWithDirective(
() => Promise.resolve(resolve(source, args, context, info)),
source,
directive,
context,
info,
);
defer.catch(e =>
resolveWithDirective(
() => Promise.reject(e),
source,
directive,
context,
info,
),
);

if (directives.length <= 1) {
return defer;
}

for (let directiveNext of directives.slice(1)) {
defer = defer.then(result => resolveWithDirective(() => Promise.resolve(result), source, directiveNext, context, info));
}
for (let directiveNext of directives.slice(1)) {
defer = defer.then(result =>
resolveWithDirective(
() => Promise.resolve(result),
source,
directiveNext,
context,
info,
),
);
defer.catch(e =>
resolveWithDirective(
() => Promise.reject(e),
source,
directiveNext,
context,
info,
),
);
}

return defer;
};
};
return defer;
};
}

/**
* Scanning the shema and wrapping the resolve of each field with the support
* of the graphql custom directives resolve execution
*/
function wrapFieldsWithMiddleware(fields) {

for (let label in fields) {
let field = fields.hasOwnProperty(label) ? fields[label] : null;

if (!!field && typeof field == 'object') {
field.resolve = resolveMiddlewareWrapper(field.resolve, field.directives);
if (field.type._fields) {
wrapFieldsWithMiddleware(field.type._fields)
} else if (field.type.ofType && field.type.ofType._fields) {
wrapFieldsWithMiddleware(field.type.ofType._fields);
}
function wrapFieldsWithMiddleware(type, deepWrap = true, typeMet = {}) {
if (!type) {
return;
}

let fields = type._fields;
typeMet[type.name] = true;
for (let label in fields) {
let field = fields[label];
if (field && !typeMet[field.type.name]) {
if (!!field && typeof field == 'object') {
field.resolve = resolveMiddlewareWrapper(
field.resolve,
field.directives,
);
if (field.type._fields && deepWrap) {
wrapFieldsWithMiddleware(field.type, deepWrap, typeMet);
} else if (field.type.ofType && field.type.ofType._fields && deepWrap) {
let child = field.type;
while (child.ofType) {
child = child.ofType;
}
if (child._fields) {
wrapFieldsWithMiddleware(child._fields);
}
}
}
}
}
}

/**
* create a new graphql custom directive which contain a resolve
* function for altering the execution of the graphql
*/
exports.GraphQLCustomDirective = function(config) {
const directive = new GraphQLDirective(config);
const directive = new GraphQLDirective(config);

if (config.resolve) {
directive.resolve = config.resolve;
}
if (config.resolve) {
directive.resolve = config.resolve;
}

return directive;
return directive;
};

/**
* Apply custom directives support in the graphql schema
*/
exports.applySchemaCustomDirectives = function(schema) {
if (!(schema instanceof GraphQLSchema)) {
throw new Error('Schema must be instanceof GraphQLSchema');
}

if (!(schema instanceof GraphQLSchema)) {
throw new Error('Schema must be instanceof GraphQLSchema');
}

wrapFieldsWithMiddleware(schema._queryType._fields);
wrapFieldsWithMiddleware(schema._queryType);
wrapFieldsWithMiddleware(schema._mutationType, false);

return true;
return true;
};
Loading

0 comments on commit 52d2619

Please sign in to comment.