diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 893900a103b..d5a6d80a6cc 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -144,3 +144,13 @@ export type VisitType = ( export type ResolveType = (type: T) => T; export type Operation = 'query' | 'mutation' | 'subscription'; + +export type Request = { + document: DocumentNode; + variables: Record; + extensions?: Record; +}; + +export type Result = ExecutionResult & { + extensions?: Record; +}; diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index a13e426a7fd..77affc620a1 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -2,40 +2,26 @@ import { DocumentNode, FieldNode, FragmentDefinitionNode, - FragmentSpreadNode, - // GraphQLField, - // GraphQLInputType, - GraphQLInterfaceType, - GraphQLList, - GraphQLNamedType, - GraphQLNonNull, - GraphQLObjectType, GraphQLResolveInfo, GraphQLSchema, - GraphQLType, - GraphQLUnionType, - InlineFragmentNode, Kind, OperationDefinitionNode, SelectionSetNode, SelectionNode, - TypeNameMetaFieldDef, - // TypeNode, - VariableNode, - // execute, - visit, subscribe, graphql, print, VariableDefinitionNode, } from 'graphql'; -import { Operation } from '../Interfaces'; -import { checkResultAndHandleErrors } from './errors'; +import { Operation, Request } from '../Interfaces'; import { Transform, - applyDocumentTransforms, + applyRequestTransforms, applyResultTransforms, -} from './transforms'; +} from '../transforms'; +import AddArgumentsAsVariables from '../transforms/AddArgumentsAsVariables'; +import FilterToSchema from '../transforms/FilterToSchema'; +import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors'; export default async function delegateToSchema( targetSchema: GraphQLSchema, @@ -56,22 +42,29 @@ export default async function delegateToSchema( info.operation.variableDefinitions, ); + const rawRequest: Request = { + document: rawDocument, + variables: info.variableValues as Record, + }; + transforms = [ ...transforms, - // AddArgumentsAsVariablesTransform(args), - FilterToSchemaTransform(targetSchema), - CheckResultAndHandleErrorsTransform(info), + AddArgumentsAsVariables(targetSchema, args), + FilterToSchema(targetSchema), + CheckResultAndHandleErrors(info), ]; - const processedDocument = applyDocumentTransforms(rawDocument, transforms); + const processedRequest = applyRequestTransforms(rawRequest, transforms); + + console.log(print(processedRequest.document), processedRequest.variables); if (targetOperation === 'query' || targetOperation === 'mutation') { const rawResult = await graphql( targetSchema, - print(processedDocument), + print(processedRequest.document), info.rootValue, context, - args, + processedRequest.variables, ); const result = applyResultTransforms(rawResult, transforms); @@ -82,10 +75,10 @@ export default async function delegateToSchema( // apply result processing ??? return subscribe( targetSchema, - processedDocument, + processedRequest.document, info.rootValue, context, - args, + processedRequest.variables, ); } } @@ -126,289 +119,6 @@ export function createDocument( }; } -function CheckResultAndHandleErrorsTransform(info: GraphQLResolveInfo) { - return { - transformResult(result: any): any { - return checkResultAndHandleErrors(result, info); - }, - }; -} - -function FilterToSchemaTransform(targetSchema: GraphQLSchema): Transform { - return { - transformDocument(document: DocumentNode): DocumentNode { - return filterDocumentToSchema(targetSchema, document); - }, - }; -} - -function filterDocumentToSchema( - targetSchema: GraphQLSchema, - document: DocumentNode, -): DocumentNode { - const operations: Array< - OperationDefinitionNode - > = document.definitions.filter( - def => def.kind === Kind.OPERATION_DEFINITION, - ) as Array; - const fragments: Array = document.definitions.filter( - def => def.kind === Kind.FRAGMENT_DEFINITION, - ) as Array; - - let usedVariables: Array = []; - let usedFragments: Array = []; - const newOperations: Array = []; - let newFragments: Array = []; - - const validFragments: Array = fragments - .filter((fragment: FragmentDefinitionNode) => { - const typeName = fragment.typeCondition.name.value; - const type = targetSchema.getType(typeName); - return Boolean(type); - }) - .map((fragment: FragmentDefinitionNode) => fragment.name.value); - - fragments.forEach((fragment: FragmentDefinitionNode) => { - const name = fragment.name.value; - const typeName = fragment.typeCondition.name.value; - const type = targetSchema.getType(typeName); - const { - selectionSet, - usedFragments: fragmentUsedFragments, - usedVariables: fragmentUsedVariables, - } = filterSelectionSet( - targetSchema, - type, - validFragments, - fragment.selectionSet, - ); - usedFragments = union(usedFragments, fragmentUsedFragments); - usedVariables = union(usedVariables, fragmentUsedVariables); - - newFragments.push({ - kind: Kind.FRAGMENT_DEFINITION, - name: { - kind: Kind.NAME, - value: name, - }, - typeCondition: fragment.typeCondition, - selectionSet, - }); - }); - - operations.forEach((operation: OperationDefinitionNode) => { - let type; - if (operation.operation === 'subscription') { - type = targetSchema.getSubscriptionType(); - } else if (operation.operation === 'mutation') { - type = targetSchema.getMutationType(); - } else { - type = targetSchema.getQueryType(); - } - const { - selectionSet, - usedFragments: operationUsedFragments, - usedVariables: operationUsedVariables, - } = filterSelectionSet( - targetSchema, - type, - validFragments, - operation.selectionSet, - ); - - usedFragments = union(usedFragments, operationUsedFragments); - const fullUsedVariables = union(usedVariables, operationUsedVariables); - - const variableDefinitions = operation.variableDefinitions.filter( - (variable: VariableDefinitionNode) => - fullUsedVariables.indexOf(variable.variable.name.value) !== -1, - ); - - newOperations.push({ - kind: Kind.OPERATION_DEFINITION, - operation: operation.operation, - name: operation.name, - directives: operation.directives, - variableDefinitions, - selectionSet, - }); - }); - - newFragments = newFragments.filter( - (fragment: FragmentDefinitionNode) => - usedFragments.indexOf(fragment.name.value) !== -1, - ); - - return { - kind: Kind.DOCUMENT, - definitions: [...newOperations, ...newFragments], - }; -} - -function filterSelectionSet( - schema: GraphQLSchema, - type: GraphQLType, - validFragments: Array, - selectionSet: SelectionSetNode, -) { - const usedFragments: Array = []; - const usedVariables: Array = []; - const typeStack: Array = [type]; - - const filteredSelectionSet = visit(selectionSet, { - [Kind.FIELD]: { - enter(node: FieldNode): null | undefined | FieldNode { - let parentType: GraphQLNamedType = resolveType( - typeStack[typeStack.length - 1], - ); - if ( - parentType instanceof GraphQLObjectType || - parentType instanceof GraphQLInterfaceType - ) { - const fields = parentType.getFields(); - const field = - node.name.value === '__typename' - ? TypeNameMetaFieldDef - : fields[node.name.value]; - if (!field) { - return null; - } else { - typeStack.push(field.type); - } - } else if ( - parentType instanceof GraphQLUnionType && - node.name.value === '__typename' - ) { - typeStack.push(TypeNameMetaFieldDef.type); - } - }, - leave() { - typeStack.pop(); - }, - }, - [Kind.FRAGMENT_SPREAD](node: FragmentSpreadNode): null | undefined { - if (validFragments.indexOf(node.name.value) !== -1) { - usedFragments.push(node.name.value); - } else { - return null; - } - }, - [Kind.INLINE_FRAGMENT]: { - enter(node: InlineFragmentNode): null | undefined { - if (node.typeCondition) { - const innerType = schema.getType(node.typeCondition.name.value); - const parentType: GraphQLNamedType = resolveType( - typeStack[typeStack.length - 1], - ); - if (implementsAbstractType(parentType, innerType)) { - typeStack.push(innerType); - } else { - return null; - } - } - }, - leave(node: InlineFragmentNode): null | undefined { - if (node.typeCondition) { - const innerType = schema.getType(node.typeCondition.name.value); - if (innerType) { - typeStack.pop(); - } else { - return null; - } - } - }, - }, - [Kind.VARIABLE](node: VariableNode) { - usedVariables.push(node.name.value); - }, - }); - - return { - selectionSet: filteredSelectionSet, - usedFragments, - usedVariables, - }; -} - -function resolveType(type: GraphQLType): GraphQLNamedType { - let lastType = type; - while ( - lastType instanceof GraphQLNonNull || - lastType instanceof GraphQLList - ) { - lastType = lastType.ofType; - } - return lastType; -} - -function implementsAbstractType( - parent: GraphQLType, - child: GraphQLType, - bail: boolean = false, -): boolean { - if (parent === child) { - return true; - } else if ( - parent instanceof GraphQLInterfaceType && - child instanceof GraphQLObjectType - ) { - return child.getInterfaces().indexOf(parent) !== -1; - } else if ( - parent instanceof GraphQLUnionType && - child instanceof GraphQLObjectType - ) { - return parent.getTypes().indexOf(child) !== -1; - } else if (parent instanceof GraphQLObjectType && !bail) { - return implementsAbstractType(child, parent, true); - } - - return false; -} - -// function typeToAst(type: GraphQLInputType): TypeNode { -// if (type instanceof GraphQLNonNull) { -// const innerType = typeToAst(type.ofType); -// if ( -// innerType.kind === Kind.LIST_TYPE || -// innerType.kind === Kind.NAMED_TYPE -// ) { -// return { -// kind: Kind.NON_NULL_TYPE, -// type: innerType, -// }; -// } else { -// throw new Error('Incorrent inner non-null type'); -// } -// } else if (type instanceof GraphQLList) { -// return { -// kind: Kind.LIST_TYPE, -// type: typeToAst(type.ofType), -// }; -// } else { -// return { -// kind: Kind.NAMED_TYPE, -// name: { -// kind: Kind.NAME, -// value: type.toString(), -// }, -// }; -// } -// } - -function union(...arrays: Array>): Array { - const cache: { [key: string]: Boolean } = {}; - const result: Array = []; - arrays.forEach(array => { - array.forEach(item => { - if (!cache[item]) { - cache[item] = true; - result.push(item); - } - }); - }); - return result; -} - // function difference( // from: Array, // ...arrays: Array> diff --git a/src/stitching/makeTransformSchema.ts b/src/stitching/makeTransformSchema.ts index 696dcfebbab..8d19d4e22f0 100644 --- a/src/stitching/makeTransformSchema.ts +++ b/src/stitching/makeTransformSchema.ts @@ -12,7 +12,7 @@ import { addResolveFunctionsToSchema } from '../schemaGenerator'; import { recreateCompositeType, createResolveType } from './schemaRecreation'; import { IResolvers, Operation } from '../Interfaces'; import delegateToSchema from './delegateToSchema'; -import { Transform, applySchemaTransforms } from './transforms'; +import { Transform, applySchemaTransforms } from '../transforms'; export default function makeTransformSchema( schema: GraphQLSchema, diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts new file mode 100644 index 00000000000..ef055cb2f72 --- /dev/null +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -0,0 +1,196 @@ +import { + ArgumentNode, + DocumentNode, + FragmentDefinitionNode, + GraphQLArgument, + GraphQLInputType, + GraphQLList, + GraphQLField, + GraphQLNonNull, + GraphQLObjectType, + GraphQLSchema, + Kind, + OperationDefinitionNode, + SelectionNode, + TypeNode, + VariableDefinitionNode, +} from 'graphql'; +import { Request } from '../Interfaces'; +import { Transform } from './index'; + +export default function AddArgumentsAsVariablesTransform( + schema: GraphQLSchema, + args: { [key: string]: any }, +): Transform { + return { + transformRequest(originalRequest: Request): Request { + const { document, newVariables } = addVariablesToRootField( + schema, + originalRequest.document, + args, + ); + const variables = { + ...originalRequest.variables, + ...newVariables, + }; + return { + document, + variables, + }; + }, + }; +} + +function addVariablesToRootField( + targetSchema: GraphQLSchema, + document: DocumentNode, + args: { [key: string]: any }, +): { + document: DocumentNode; + newVariables: { [key: string]: any }; +} { + const operations: Array< + OperationDefinitionNode + > = document.definitions.filter( + def => def.kind === Kind.OPERATION_DEFINITION, + ) as Array; + const fragments: Array = document.definitions.filter( + def => def.kind === Kind.FRAGMENT_DEFINITION, + ) as Array; + + const variableNames = {}; + + const newOperations = operations.map((operation: OperationDefinitionNode) => { + let existingVariables = operation.variableDefinitions.map( + (variableDefinition: VariableDefinitionNode) => + variableDefinition.variable.name.value, + ); + + let variableCounter = 0; + const variables = {}; + + const generateVariableName = (argName: string) => { + let varName; + do { + varName = `_v${variableCounter}_${argName}`; + variableCounter++; + } while (existingVariables.indexOf(varName) !== -1); + return varName; + }; + + let type: GraphQLObjectType; + if (operation.operation === 'subscription') { + type = targetSchema.getSubscriptionType(); + } else if (operation.operation === 'mutation') { + type = targetSchema.getMutationType(); + } else { + type = targetSchema.getQueryType(); + } + + const newSelectionSet: Array = []; + + operation.selectionSet.selections.forEach((selection: SelectionNode) => { + if (selection.kind === Kind.FIELD) { + let newArgs: { [name: string]: ArgumentNode } = {}; + selection.arguments.forEach((argument: ArgumentNode) => { + newArgs[argument.name.value] = argument; + }); + const name: string = selection.name.value; + const field: GraphQLField = type.getFields()[name]; + field.args.forEach((argument: GraphQLArgument) => { + if (argument.name in args) { + const variableName = generateVariableName(argument.name); + variableNames[argument.name] = variableName; + newArgs[argument.name] = { + kind: Kind.ARGUMENT, + name: { + kind: Kind.NAME, + value: argument.name, + }, + value: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: variableName, + }, + }, + }; + existingVariables.push(variableName); + variables[variableName] = { + kind: Kind.VARIABLE_DEFINITION, + variable: { + kind: Kind.VARIABLE, + name: { + kind: Kind.NAME, + value: variableName, + }, + }, + type: typeToAst(argument.type), + }; + } + }); + + newSelectionSet.push({ + ...selection, + arguments: Object.keys(newArgs).map(argName => newArgs[argName]), + }); + } else { + newSelectionSet.push(selection); + } + }); + + return { + ...operation, + variableDefinitions: operation.variableDefinitions.concat( + Object.keys(variables).map(varName => variables[varName]), + ), + selectionSet: { + kind: Kind.SELECTION_SET, + selections: newSelectionSet, + }, + }; + }); + + const newVariables = {}; + Object.keys(variableNames).forEach(name => { + newVariables[variableNames[name]] = args[name]; + }); + + return { + document: { + ...document, + definitions: [...newOperations, ...fragments], + }, + newVariables, + }; +} + +function typeToAst(type: GraphQLInputType): TypeNode { + if (type instanceof GraphQLNonNull) { + const innerType = typeToAst(type.ofType); + if ( + innerType.kind === Kind.LIST_TYPE || + innerType.kind === Kind.NAMED_TYPE + ) { + return { + kind: Kind.NON_NULL_TYPE, + type: innerType, + }; + } else { + throw new Error('Incorrent inner non-null type'); + } + } else if (type instanceof GraphQLList) { + return { + kind: Kind.LIST_TYPE, + type: typeToAst(type.ofType), + }; + } else { + return { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: type.toString(), + }, + }; + } +} diff --git a/src/transforms/CheckResultAndHandleErrors.ts b/src/transforms/CheckResultAndHandleErrors.ts new file mode 100644 index 00000000000..b00258a5e54 --- /dev/null +++ b/src/transforms/CheckResultAndHandleErrors.ts @@ -0,0 +1,13 @@ +import { GraphQLResolveInfo } from 'graphql'; +import { checkResultAndHandleErrors } from '../stitching/errors'; +import { Transform } from './index'; + +export default function CheckResultAndHandleErrors( + info: GraphQLResolveInfo, +): Transform { + return { + transformResult(result: any): any { + return checkResultAndHandleErrors(result, info); + }, + }; +} diff --git a/src/transforms/FilterToSchema.ts b/src/transforms/FilterToSchema.ts new file mode 100644 index 00000000000..dc3fa4aa89f --- /dev/null +++ b/src/transforms/FilterToSchema.ts @@ -0,0 +1,269 @@ +import { + DocumentNode, + FieldNode, + FragmentDefinitionNode, + FragmentSpreadNode, + GraphQLInterfaceType, + GraphQLList, + GraphQLNamedType, + GraphQLNonNull, + GraphQLObjectType, + GraphQLSchema, + GraphQLType, + GraphQLUnionType, + InlineFragmentNode, + Kind, + OperationDefinitionNode, + SelectionSetNode, + TypeNameMetaFieldDef, + VariableNode, + visit, + VariableDefinitionNode, +} from 'graphql'; +import { Request } from '../Interfaces'; +import { Transform } from './index'; + +export default function FilterToSchema(targetSchema: GraphQLSchema): Transform { + return { + transformRequest(originalRequest: Request): Request { + const document = filterDocumentToSchema( + targetSchema, + originalRequest.document, + ); + return { + ...originalRequest, + document, + }; + }, + }; +} + +function filterDocumentToSchema( + targetSchema: GraphQLSchema, + document: DocumentNode, +): DocumentNode { + const operations: Array< + OperationDefinitionNode + > = document.definitions.filter( + def => def.kind === Kind.OPERATION_DEFINITION, + ) as Array; + const fragments: Array = document.definitions.filter( + def => def.kind === Kind.FRAGMENT_DEFINITION, + ) as Array; + + let usedVariables: Array = []; + let usedFragments: Array = []; + const newOperations: Array = []; + let newFragments: Array = []; + + const validFragments: Array = fragments + .filter((fragment: FragmentDefinitionNode) => { + const typeName = fragment.typeCondition.name.value; + const type = targetSchema.getType(typeName); + return Boolean(type); + }) + .map((fragment: FragmentDefinitionNode) => fragment.name.value); + + fragments.forEach((fragment: FragmentDefinitionNode) => { + const name = fragment.name.value; + const typeName = fragment.typeCondition.name.value; + const type = targetSchema.getType(typeName); + const { + selectionSet, + usedFragments: fragmentUsedFragments, + usedVariables: fragmentUsedVariables, + } = filterSelectionSet( + targetSchema, + type, + validFragments, + fragment.selectionSet, + ); + usedFragments = union(usedFragments, fragmentUsedFragments); + usedVariables = union(usedVariables, fragmentUsedVariables); + + newFragments.push({ + kind: Kind.FRAGMENT_DEFINITION, + name: { + kind: Kind.NAME, + value: name, + }, + typeCondition: fragment.typeCondition, + selectionSet, + }); + }); + + operations.forEach((operation: OperationDefinitionNode) => { + let type; + if (operation.operation === 'subscription') { + type = targetSchema.getSubscriptionType(); + } else if (operation.operation === 'mutation') { + type = targetSchema.getMutationType(); + } else { + type = targetSchema.getQueryType(); + } + const { + selectionSet, + usedFragments: operationUsedFragments, + usedVariables: operationUsedVariables, + } = filterSelectionSet( + targetSchema, + type, + validFragments, + operation.selectionSet, + ); + + usedFragments = union(usedFragments, operationUsedFragments); + const fullUsedVariables = union(usedVariables, operationUsedVariables); + + const variableDefinitions = operation.variableDefinitions.filter( + (variable: VariableDefinitionNode) => + fullUsedVariables.indexOf(variable.variable.name.value) !== -1, + ); + + newOperations.push({ + kind: Kind.OPERATION_DEFINITION, + operation: operation.operation, + name: operation.name, + directives: operation.directives, + variableDefinitions, + selectionSet, + }); + }); + + newFragments = newFragments.filter( + (fragment: FragmentDefinitionNode) => + usedFragments.indexOf(fragment.name.value) !== -1, + ); + + return { + kind: Kind.DOCUMENT, + definitions: [...newOperations, ...newFragments], + }; +} + +function filterSelectionSet( + schema: GraphQLSchema, + type: GraphQLType, + validFragments: Array, + selectionSet: SelectionSetNode, +) { + const usedFragments: Array = []; + const usedVariables: Array = []; + const typeStack: Array = [type]; + + const filteredSelectionSet = visit(selectionSet, { + [Kind.FIELD]: { + enter(node: FieldNode): null | undefined | FieldNode { + let parentType: GraphQLNamedType = resolveType( + typeStack[typeStack.length - 1], + ); + if ( + parentType instanceof GraphQLObjectType || + parentType instanceof GraphQLInterfaceType + ) { + const fields = parentType.getFields(); + const field = + node.name.value === '__typename' + ? TypeNameMetaFieldDef + : fields[node.name.value]; + if (!field) { + return null; + } else { + typeStack.push(field.type); + } + } else if ( + parentType instanceof GraphQLUnionType && + node.name.value === '__typename' + ) { + typeStack.push(TypeNameMetaFieldDef.type); + } + }, + leave() { + typeStack.pop(); + }, + }, + [Kind.FRAGMENT_SPREAD](node: FragmentSpreadNode): null | undefined { + if (validFragments.indexOf(node.name.value) !== -1) { + usedFragments.push(node.name.value); + } else { + return null; + } + }, + [Kind.INLINE_FRAGMENT]: { + enter(node: InlineFragmentNode): null | undefined { + if (node.typeCondition) { + const innerType = schema.getType(node.typeCondition.name.value); + const parentType: GraphQLNamedType = resolveType( + typeStack[typeStack.length - 1], + ); + if (implementsAbstractType(parentType, innerType)) { + typeStack.push(innerType); + } else { + return null; + } + } + }, + leave(node: InlineFragmentNode): null | undefined { + typeStack.pop(); + }, + }, + [Kind.VARIABLE](node: VariableNode) { + usedVariables.push(node.name.value); + }, + }); + + return { + selectionSet: filteredSelectionSet, + usedFragments, + usedVariables, + }; +} + +function resolveType(type: GraphQLType): GraphQLNamedType { + let lastType = type; + while ( + lastType instanceof GraphQLNonNull || + lastType instanceof GraphQLList + ) { + lastType = lastType.ofType; + } + return lastType; +} + +function implementsAbstractType( + parent: GraphQLType, + child: GraphQLType, + bail: boolean = false, +): boolean { + if (parent === child) { + return true; + } else if ( + parent instanceof GraphQLInterfaceType && + child instanceof GraphQLObjectType + ) { + return child.getInterfaces().indexOf(parent) !== -1; + } else if ( + parent instanceof GraphQLUnionType && + child instanceof GraphQLObjectType + ) { + return parent.getTypes().indexOf(child) !== -1; + } else if (parent instanceof GraphQLObjectType && !bail) { + return implementsAbstractType(child, parent, true); + } + + return false; +} + +function union(...arrays: Array>): Array { + const cache: { [key: string]: Boolean } = {}; + const result: Array = []; + arrays.forEach(array => { + array.forEach(item => { + if (!cache[item]) { + cache[item] = true; + result.push(item); + } + }); + }); + return result; +} diff --git a/src/transforms/ReplaceFieldWithFragment.ts b/src/transforms/ReplaceFieldWithFragment.ts new file mode 100644 index 00000000000..646b749d5e2 --- /dev/null +++ b/src/transforms/ReplaceFieldWithFragment.ts @@ -0,0 +1,102 @@ +export default function ReplaceFieldWithFragment( + mapping: FieldToFragmentMapping, +): Transform { + return { + transformRequest(originalRequest: Request): Request { + const document = replaceFieldsWithFragments( + originalRequest.document, + mapping, + ); + return { + ...originalRequest, + document, + }; + }, + }; +} + +function replaceFieldsWithFragments( + document: DocumentNode, + mapping: FieldToFragmentMapping, +): DocumentNode { + const typeStack: Array = [type]; + return visit(document, { + // [Kind.FIELD]: { + // enter(node: FieldNode): null | undefined | FieldNode { + // let parentType: GraphQLNamedType = resolveType( + // typeStack[typeStack.length - 1], + // ); + // if ( + // parentType instanceof GraphQLObjectType || + // parentType instanceof GraphQLInterfaceType + // ) { + // const fields = parentType.getFields(); + // const field = + // node.name.value === '__typename' + // ? TypeNameMetaFieldDef + // : fields[node.name.value]; + // if (!field) { + // return null; + // } else { + // typeStack.push(field.type); + // } + // } else if ( + // parentType instanceof GraphQLUnionType && + // node.name.value === '__typename' + // ) { + // typeStack.push(TypeNameMetaFieldDef.type); + // } + // }, + // leave() { + // typeStack.pop(); + // }, + // }, + [Kind.SELECTION_SET]( + node: SelectionSetNode, + ): SelectionSetNode | null | undefined { + const parentType: GraphQLType = resolveType( + typeStack[typeStack.length - 1], + ); + const parentTypeName = parentType.name; + + if (fragmentReplacements[parentTypeName]) { + selections.forEach(selection => { + if (selection.kind === Kind.FIELD) { + const name = selection.name.value; + const fragment = fragmentReplacements[parentTypeName][name]; + if (fragment) { + selections = selections.concat(fragment); + } + } + }); + } + + if (selections !== node.selections) { + return { + ...node, + selections, + }; + } + }, + [Kind.INLINE_FRAGMENT]: { + enter(node: InlineFragmentNode): null | undefined { + if (node.typeCondition) { + const innerType = schema.getType(node.typeCondition.name.value); + const parentType: GraphQLNamedType = resolveType( + typeStack[typeStack.length - 1], + ); + if (implementsAbstractType(parentType, innerType)) { + typeStack.push(innerType); + } else { + return null; + } + } + }, + leave(node: InlineFragmentNode): null | undefined { + + typeStack.pop(); + } + }, + }, + }); +} diff --git a/src/stitching/transforms.ts b/src/transforms/index.ts similarity index 62% rename from src/stitching/transforms.ts rename to src/transforms/index.ts index 6e18560e4df..459f40071a4 100644 --- a/src/stitching/transforms.ts +++ b/src/transforms/index.ts @@ -1,9 +1,10 @@ -import { GraphQLSchema, DocumentNode } from 'graphql'; +import { GraphQLSchema } from 'graphql'; +import { Request, Result } from '../Interfaces'; export type Transform = { transformSchema?: (schema: GraphQLSchema) => GraphQLSchema; - transformDocument?: (originalDocument: DocumentNode) => DocumentNode; - transformResult?: (result: any) => any; + transformRequest?: (originalRequest: Request) => Request; + transformResult?: (result: Result) => Result; }; export function applySchemaTransforms( @@ -17,17 +18,17 @@ export function applySchemaTransforms( ); } -export function applyDocumentTransforms( - originalDocument: DocumentNode, +export function applyRequestTransforms( + originalRequest: Request, transforms: Array, -): DocumentNode { +): Request { return transforms.reduce( - (document: DocumentNode, transform: Transform) => - transform.transformDocument - ? transform.transformDocument(document) - : document, + (request: Request, transform: Transform) => + transform.transformRequest + ? transform.transformRequest(request) + : request, - originalDocument, + originalRequest, ); }