Skip to content
This repository has been archived by the owner on Sep 3, 2021. It is now read-only.

Commit

Permalink
Merge pull request #125 from michaeldgraham/master
Browse files Browse the repository at this point in the history
Reflexive relation types, fixes for #113 and #124
  • Loading branch information
johnymontana authored Oct 10, 2018
2 parents eecc274 + 22f04aa commit aa038eb
Show file tree
Hide file tree
Showing 10 changed files with 1,028 additions and 293 deletions.
128 changes: 70 additions & 58 deletions src/augment.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,27 +84,27 @@ const augmentQueryArguments = typeMap => {
};

export const augmentResolvers = (
queryResolvers,
mutationResolvers,
typeMap
augmentedTypeMap,
resolvers
) => {
let resolvers = {};
const queryMap = createOperationMap(typeMap.Query);
queryResolvers = possiblyAddResolvers(queryMap, queryResolvers);
let queryResolvers = resolvers && resolvers.Query ? resolvers.Query : {};
let mutationResolvers = resolvers && resolvers.Mutation ? resolvers.Mutation : {};
const generatedQueryMap = createOperationMap(augmentedTypeMap.Query);
queryResolvers = possiblyAddResolvers(generatedQueryMap, queryResolvers);
if (Object.keys(queryResolvers).length > 0) {
resolvers.Query = queryResolvers;
}
const mutationMap = createOperationMap(typeMap.Mutation);
mutationResolvers = possiblyAddResolvers(mutationMap, mutationResolvers);
const generatedMutationMap = createOperationMap(augmentedTypeMap.Mutation);
mutationResolvers = possiblyAddResolvers(generatedMutationMap, mutationResolvers);
if (Object.keys(mutationResolvers).length > 0) {
resolvers.Mutation = mutationResolvers;
}

// must implement __resolveInfo for every Interface type
// we use "FRAGMENT_TYPE" key to identify the Interface implementation
// type at runtime, so grab this value
const interfaceTypes = Object.keys(typeMap).filter(
e => typeMap[e].kind === 'InterfaceTypeDefinition'
const interfaceTypes = Object.keys(augmentedTypeMap).filter(
e => augmentedTypeMap[e].kind === 'InterfaceTypeDefinition'
);
interfaceTypes.map(e => {
resolvers[e] = {};
Expand Down Expand Up @@ -357,13 +357,19 @@ const possiblyAddRelationMutations = (
relatedAstNode,
true
);
// TODO refactor the getRelationTypeDirectiveArgs stuff in here,
// TODO out of it, and make it use the above, already obtained values...
typeMap = possiblyAddNonSymmetricRelationshipType(
relatedAstNode,
capitalizedFieldName,
typeName,
typeMap
typeMap,
field
);
// TODO probably put replaceRelationTypeValue above into possiblyAddNonSymmetricRelationshipType, after you refactor it
fields[fieldIndex] = replaceRelationTypeValue(
fromName,
toName,
field,
capitalizedFieldName,
typeName
Expand Down Expand Up @@ -629,16 +635,19 @@ const possiblyAddTypeMutation = (namePrefix, astNode, typeMap, mutationMap) => {
return typeMap;
};

const replaceRelationTypeValue = (field, capitalizedFieldName, typeName) => {
const replaceRelationTypeValue = (fromName, toName, field, capitalizedFieldName, typeName) => {
const isList = isListType(field);
// TODO persist a required inner type, and required list type
let type = {
kind: 'NamedType',
name: {
kind: 'Name',
value: `_${typeName}${capitalizedFieldName}`
value: `_${typeName}${capitalizedFieldName}${
fromName === toName ? 'Directions' : ''
}`
}
};
if (isList) {
if (isList && fromName !== toName) {
type = {
kind: 'ListType',
type: type
Expand All @@ -652,16 +661,19 @@ const possiblyAddNonSymmetricRelationshipType = (
relationAstNode,
capitalizedFieldName,
typeName,
typeMap
typeMap,
field
) => {
const fieldTypeName = `_${typeName}${capitalizedFieldName}`;
if (!typeMap[fieldTypeName]) {
let fieldName = '';
let fieldValueName = '';
let fromField = {};
let toField = {};
let fromValue = '';
let toValue = '';
let _fromField = {};
let _toField = {};
let fromValue = undefined;
let toValue = undefined;
let fields = relationAstNode.fields;
const relationTypeDirective = getRelationTypeDirectiveArgs(relationAstNode);
if (relationTypeDirective) {
Expand All @@ -683,53 +695,53 @@ const possiblyAddNonSymmetricRelationshipType = (
return acc;
}, [])
.join('\n');
if(fromValue && fromValue === toValue) {
// If field is a list type, then make .from and .to list types
const fieldIsList = isListType(field);

typeMap[fieldTypeName] = parse(`
type ${fieldTypeName} ${print(relationAstNode.directives)} {
${relationPropertyFields}
${getRelatedTypeSelectionFields(
typeName,
fromValue,
fromField,
toValue,
toField
)}
typeMap[`${fieldTypeName}Directions`] = parse(`
type ${fieldTypeName}Directions ${print(relationAstNode.directives)} {
from${getFieldArgumentsFromAst(
field,
typeName,
)}: ${fieldIsList ? '[' : ''}${fieldTypeName}${fieldIsList ? ']' : ''}
to${getFieldArgumentsFromAst(
field,
typeName,
)}: ${fieldIsList ? '[' : ''}${fieldTypeName}${fieldIsList ? ']' : ''}
}`);

typeMap[fieldTypeName] = parse(`
type ${fieldTypeName} ${print(relationAstNode.directives)} {
${relationPropertyFields}
${fromValue}: ${fromValue}
}
`);

// remove arguments on field
field.arguments = [];
}
`);
}
else {
// Non-reflexive case, (User)-[RATED]->(Movie)
typeMap[fieldTypeName] = parse(`
type ${fieldTypeName} ${print(relationAstNode.directives)} {
${relationPropertyFields}
${
typeName === toValue
? // If this is the from, the allow selecting the to
`${fromValue}: ${fromValue}`
: // else this is the to, so allow selecting the from
typeName === fromValue
? `${toValue}: ${toValue}`
: ''}
}
`);
}
}
}
return typeMap;
};

const getRelatedTypeSelectionFields = (
typeName,
fromValue,
fromField,
toValue,
toField
) => {
// TODO identify and handle ambiguity of relation type symmetry, Person FRIEND_OF Person, etc.
// if(typeName === fromValue && typeName === toValue) {
// return `
// from${fromField.arguments.length > 0
// ? `(${getFieldArgumentsFromAst(fromField)})`
// : ''}: ${fromValue}
// to${toField.arguments.length > 0
// ? `(${getFieldArgumentsFromAst(toField)})`
// : ''}: ${toValue}`;
// }
return typeName === fromValue
? // If this is the from, the allow selecting the to
`${toValue}(${getFieldArgumentsFromAst(toField, toValue)}): ${toValue}`
: // else this is the to, so allow selecting the from
typeName === toValue
? `${fromValue}(${getFieldArgumentsFromAst(
fromField,
fromValue
)}): ${fromValue}`
: '';
};

const addOrReplaceNodeIdField = (astNode, valueType) => {
const fields = astNode ? astNode.fields : [];
const index = fields.findIndex(e => e.name.value === '_id');
Expand Down
59 changes: 44 additions & 15 deletions src/augmentSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
augmentResolvers
} from "./augment";

export const augmentedSchema = (typeMap, queryResolvers, mutationResolvers) => {
export const augmentedSchema = (typeMap, resolvers) => {
const augmentedTypeMap = augmentTypeMap(typeMap);
const augmentedResolvers = augmentResolvers(queryResolvers, mutationResolvers, augmentedTypeMap);
const augmentedResolvers = augmentResolvers(augmentedTypeMap, resolvers);
// TODO extract and persist logger and schemaDirectives, at least
return makeExecutableSchema({
typeDefs: printTypeMap(augmentedTypeMap),
Expand All @@ -34,9 +34,7 @@ export const makeAugmentedExecutableSchema = ({
}) => {
const typeMap = extractTypeMapFromTypeDefs(typeDefs);
const augmentedTypeMap = augmentTypeMap(typeMap);
const queryResolvers = resolvers && resolvers.Query ? resolvers.Query : {};
const mutationResolvers = resolvers && resolvers.Mutation ? resolvers.Mutation : {};
const augmentedResolvers = augmentResolvers(queryResolvers, mutationResolvers, augmentedTypeMap);
const augmentedResolvers = augmentResolvers(augmentedTypeMap, resolvers);
resolverValidationOptions.requireResolversForResolveType = false;
return makeExecutableSchema({
typeDefs: printTypeMap(augmentedTypeMap),
Expand Down Expand Up @@ -73,15 +71,46 @@ export const extractTypeMapFromSchema = (schema) => {
}, {});
}

export const extractResolvers = (operationType) => {
const operationTypeFields = operationType ? operationType.getFields() : {};
const operations = Object.keys(operationTypeFields);
let resolver = {};
return operations.length > 0
? operations.reduce((acc, t) => {
resolver = operationTypeFields[t].resolve;
if(resolver !== undefined) acc[t] = resolver;
export const extractResolversFromSchema = (schema) => {
const _typeMap = schema && schema._typeMap ? schema._typeMap : {};
const types = Object.keys(_typeMap);
let type = {};
let schemaTypeResolvers = {};
return types.reduce( (acc, t) => {
// prevent extraction from schema introspection system keys
if(t !== "__Schema"
&& t !== "__Type"
&& t !== "__TypeKind"
&& t !== "__Field"
&& t !== "__InputValue"
&& t !== "__EnumValue"
&& t !== "__Directive") {
type = _typeMap[t];
// resolvers are stored on the field level at a .resolve key
schemaTypeResolvers = extractFieldResolversFromSchemaType(type);
// do not add unless there exists at least one field resolver for type
if(schemaTypeResolvers) {
acc[t] = schemaTypeResolvers;
}
}
return acc;
}, {})
}

const extractFieldResolversFromSchemaType = (type) => {
const fields = type._fields;
const fieldKeys = fields ? Object.keys(fields) : [];
const fieldResolvers = fieldKeys.length > 0
? fieldKeys.reduce( (acc, t) => {
// do not add entry for this field unless it has resolver
if(fields[t].resolve !== undefined) {
acc[t] = fields[t].resolve;
}
return acc;
}, {})
: {};
}, {})
: undefined;
// do not return value unless there exists at least 1 field resolver
return fieldResolvers && Object.keys(fieldResolvers).length > 0
? fieldResolvers
: undefined;
}
39 changes: 21 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
isMutation,
lowFirstLetter,
typeIdentifiers,
parameterizeRelationFields
parameterizeRelationFields,
getFieldValueType
} from './utils';
import { buildCypherSelection } from './selections';
import {
extractTypeMapFromSchema,
extractResolvers,
extractResolversFromSchema,
augmentedSchema,
makeAugmentedExecutableSchema
} from './augmentSchema';
Expand Down Expand Up @@ -146,8 +147,8 @@ export function cypherQuery(

query =
`MATCH (${variableName}:${typeName} ${argString}) ${predicate}` +
// ${variableName} { ${selection} } as ${variableName}`;
`RETURN ${variableName} {${subQuery}} AS ${variableName}${orderByValue} ${outerSkipLimit}`;

}

return [query, { ...nonNullParams, ...subParams }];
Expand Down Expand Up @@ -243,11 +244,11 @@ export function cypherMutation(
resolveInfo.fieldName
].astNode.arguments;

const firstIdArg = args.find(e => getNamedType(e).type.name.value);
const firstIdArg = args.find(e => getFieldValueType(e) === "ID");
if (firstIdArg) {
const argName = firstIdArg.name.value;
if (params.params[argName] === undefined) {
query += `SET ${variableName}.${argName} = apoc.create.uuid() `;
const firstIdArgFieldName = firstIdArg.name.value;
if (params.params[firstIdArgFieldName] === undefined) {
query += `SET ${variableName}.${firstIdArgFieldName} = apoc.create.uuid() `;
}
}

Expand Down Expand Up @@ -323,11 +324,14 @@ export function cypherMutation(
initial: '',
selections,
variableName: lowercased,
fromVar,
toVar,
schemaType,
resolveInfo,
paramIndex: 1
paramIndex: 1,
rootVariableNames: {
from: `${fromVar}`,
to: `${toVar}`,
},
variableName: schemaType.name === fromType ? `${toVar}` : `${fromVar}`
});
params = { ...params, ...subParams };
query = `
Expand Down Expand Up @@ -449,11 +453,11 @@ RETURN ${variableName}`;
schemaType,
resolveInfo,
paramIndex: 1,
rootNodes: {
rootVariableNames: {
from: `_${fromVar}`,
to: `_${toVar}`
},
variableName: schemaType.name === fromType ? `_${toVar}` : `_${fromVar}`
variableName: schemaType.name === fromType ? `_${toVar}` : `_${fromVar}`,
});
params = { ...params, ...subParams };

Expand All @@ -468,7 +472,7 @@ RETURN ${variableName}`;
OPTIONAL MATCH (${fromVar})-[${fromVar +
toVar}:${relationshipName}]->(${toVar})
DELETE ${fromVar + toVar}
WITH COUNT(*) AS scope, ${fromVar} AS _${fromVar}_from, ${toVar} AS _${toVar}_to
WITH COUNT(*) AS scope, ${fromVar} AS _${fromVar}, ${toVar} AS _${toVar}
RETURN {${subQuery}} AS ${schemaType};
`;
} else {
Expand All @@ -480,11 +484,10 @@ RETURN ${variableName}`;
return [query, params];
}

export const augmentSchema = schema => {
let typeMap = extractTypeMapFromSchema(schema);
let queryResolvers = extractResolvers(schema.getQueryType());
let mutationResolvers = extractResolvers(schema.getMutationType());
return augmentedSchema(typeMap, queryResolvers, mutationResolvers);
export const augmentSchema = (schema) => {
const typeMap = extractTypeMapFromSchema(schema);
const resolvers = extractResolversFromSchema(schema);
return augmentedSchema(typeMap, resolvers);
};

export const makeAugmentedSchema = ({
Expand Down
Loading

0 comments on commit aa038eb

Please sign in to comment.