UNPKG

@graphql-tools/delegate

Version:

A set of utils for faster development of GraphQL tools

318 lines (317 loc) • 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.finalizeGatewayRequest = void 0; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const getDocumentMetadata_js_1 = require("./getDocumentMetadata.js"); function finalizeGatewayDocument(targetSchema, fragments, operations) { var _a; let usedVariables = []; let usedFragments = []; const newOperations = []; let newFragments = []; const validFragments = []; const validFragmentsWithType = Object.create(null); for (const fragment of fragments) { const typeName = fragment.typeCondition.name.value; const type = targetSchema.getType(typeName); if (type != null) { validFragments.push(fragment); validFragmentsWithType[fragment.name.value] = type; } } let fragmentSet = Object.create(null); for (const operation of operations) { const type = (0, utils_1.getDefinedRootType)(targetSchema, operation.operation); const { selectionSet, usedFragments: operationUsedFragments, usedVariables: operationUsedVariables, } = finalizeSelectionSet(targetSchema, type, validFragmentsWithType, operation.selectionSet); usedFragments = union(usedFragments, operationUsedFragments); const { usedVariables: collectedUsedVariables, newFragments: collectedNewFragments, fragmentSet: collectedFragmentSet, } = collectFragmentVariables(targetSchema, fragmentSet, validFragments, validFragmentsWithType, usedFragments); const operationOrFragmentVariables = union(operationUsedVariables, collectedUsedVariables); usedVariables = union(usedVariables, operationOrFragmentVariables); newFragments = collectedNewFragments; fragmentSet = collectedFragmentSet; const variableDefinitions = ((_a = operation.variableDefinitions) !== null && _a !== void 0 ? _a : []).filter((variable) => operationOrFragmentVariables.indexOf(variable.variable.name.value) !== -1); newOperations.push({ kind: graphql_1.Kind.OPERATION_DEFINITION, operation: operation.operation, name: operation.name, directives: operation.directives, variableDefinitions, selectionSet, }); } const newDocument = { kind: graphql_1.Kind.DOCUMENT, definitions: [...newOperations, ...newFragments], }; return { usedVariables, newDocument, }; } function finalizeGatewayRequest(originalRequest, delegationContext) { let { document, variables } = originalRequest; let { operations, fragments } = (0, getDocumentMetadata_js_1.getDocumentMetadata)(document); const { targetSchema, args } = delegationContext; if (args) { const requestWithNewVariables = addVariablesToRootFields(targetSchema, operations, args); operations = requestWithNewVariables.newOperations; variables = Object.assign({}, variables !== null && variables !== void 0 ? variables : {}, requestWithNewVariables.newVariables); } const { usedVariables, newDocument } = finalizeGatewayDocument(targetSchema, fragments, operations); const newVariables = {}; if (variables != null) { for (const variableName of usedVariables) { const variableValue = variables[variableName]; if (variableValue !== undefined) { newVariables[variableName] = variableValue; } } } return { ...originalRequest, document: newDocument, variables: newVariables, }; } exports.finalizeGatewayRequest = finalizeGatewayRequest; function addVariablesToRootFields(targetSchema, operations, args) { const newVariables = Object.create(null); const newOperations = operations.map((operation) => { var _a, _b; const variableDefinitionMap = ((_a = operation.variableDefinitions) !== null && _a !== void 0 ? _a : []).reduce((prev, def) => ({ ...prev, [def.variable.name.value]: def, }), {}); const type = (0, utils_1.getDefinedRootType)(targetSchema, operation.operation); const newSelections = []; for (const selection of operation.selectionSet.selections) { if (selection.kind === graphql_1.Kind.FIELD) { const argumentNodes = (_b = selection.arguments) !== null && _b !== void 0 ? _b : []; const argumentNodeMap = argumentNodes.reduce((prev, argument) => ({ ...prev, [argument.name.value]: argument, }), {}); const targetField = type.getFields()[selection.name.value]; // excludes __typename if (targetField != null) { updateArguments(targetField, argumentNodeMap, variableDefinitionMap, newVariables, args); } newSelections.push({ ...selection, arguments: Object.values(argumentNodeMap), }); } else { newSelections.push(selection); } } const newSelectionSet = { kind: graphql_1.Kind.SELECTION_SET, selections: newSelections, }; return { ...operation, variableDefinitions: Object.values(variableDefinitionMap), selectionSet: newSelectionSet, }; }); return { newOperations, newVariables, }; } function updateArguments(targetField, argumentNodeMap, variableDefinitionMap, variableValues, newArgs) { const generateVariableName = (0, utils_1.createVariableNameGenerator)(variableDefinitionMap); for (const argument of targetField.args) { const argName = argument.name; const argType = argument.type; if (argName in newArgs) { (0, utils_1.updateArgument)(argumentNodeMap, variableDefinitionMap, variableValues, argName, generateVariableName(argName), argType, (0, utils_1.serializeInputValue)(argType, newArgs[argName])); } } } function collectFragmentVariables(targetSchema, fragmentSet, validFragments, validFragmentsWithType, usedFragments) { let remainingFragments = usedFragments.slice(); let usedVariables = []; const newFragments = []; while (remainingFragments.length !== 0) { const nextFragmentName = remainingFragments.pop(); const fragment = validFragments.find(fr => fr.name.value === nextFragmentName); if (fragment != null) { const name = nextFragmentName; const typeName = fragment.typeCondition.name.value; const type = targetSchema.getType(typeName); if (type == null) { throw new Error(`Fragment reference type "${typeName}", but the type is not contained within the target schema.`); } const { selectionSet, usedFragments: fragmentUsedFragments, usedVariables: fragmentUsedVariables, } = finalizeSelectionSet(targetSchema, type, validFragmentsWithType, fragment.selectionSet); remainingFragments = union(remainingFragments, fragmentUsedFragments); usedVariables = union(usedVariables, fragmentUsedVariables); if (name && !(name in fragmentSet)) { fragmentSet[name] = true; newFragments.push({ kind: graphql_1.Kind.FRAGMENT_DEFINITION, name: { kind: graphql_1.Kind.NAME, value: name, }, typeCondition: fragment.typeCondition, selectionSet, }); } } } return { usedVariables, newFragments, fragmentSet, }; } const filteredSelectionSetVisitorKeys = { SelectionSet: ['selections'], Field: ['selectionSet'], InlineFragment: ['selectionSet'], FragmentDefinition: ['selectionSet'], }; const variablesVisitorKeys = { SelectionSet: ['selections'], Field: ['arguments', 'directives', 'selectionSet'], Argument: ['value'], InlineFragment: ['directives', 'selectionSet'], FragmentSpread: ['directives'], FragmentDefinition: ['selectionSet'], ObjectValue: ['fields'], ObjectField: ['name', 'value'], Directive: ['arguments'], ListValue: ['values'], }; function finalizeSelectionSet(schema, type, validFragments, selectionSet) { const usedFragments = []; const usedVariables = []; const typeInfo = graphql_1.versionInfo.major < 16 ? new graphql_1.TypeInfo(schema, undefined, type) : new graphql_1.TypeInfo(schema, type); const filteredSelectionSet = (0, graphql_1.visit)(selectionSet, (0, graphql_1.visitWithTypeInfo)(typeInfo, { [graphql_1.Kind.FIELD]: { enter: node => { const parentType = typeInfo.getParentType(); if ((0, graphql_1.isObjectType)(parentType) || (0, graphql_1.isInterfaceType)(parentType)) { const fields = parentType.getFields(); const field = node.name.value === '__typename' ? graphql_1.TypeNameMetaFieldDef : fields[node.name.value]; if (!field) { return null; } const args = field.args != null ? field.args : []; const argsMap = Object.create(null); for (const arg of args) { argsMap[arg.name] = arg; } if (node.arguments != null) { const newArgs = []; for (const arg of node.arguments) { if (arg.name.value in argsMap) { newArgs.push(arg); } } if (newArgs.length !== node.arguments.length) { return { ...node, arguments: newArgs, }; } } } }, leave: node => { const type = typeInfo.getType(); if (type == null) { throw new Error(`No type was found for field node ${(0, utils_1.inspect)(node)}.`); } const namedType = (0, graphql_1.getNamedType)(type); if (!schema.getType(namedType.name) == null) { return null; } if ((0, graphql_1.isObjectType)(namedType) || (0, graphql_1.isInterfaceType)(namedType)) { const selections = node.selectionSet != null ? node.selectionSet.selections : null; if (selections == null || selections.length === 0) { return null; } } }, }, [graphql_1.Kind.FRAGMENT_SPREAD]: { enter: node => { if (!(node.name.value in validFragments)) { return null; } const parentType = typeInfo.getParentType(); const innerType = validFragments[node.name.value]; if (!(0, utils_1.implementsAbstractType)(schema, parentType, innerType)) { return null; } usedFragments.push(node.name.value); }, }, [graphql_1.Kind.INLINE_FRAGMENT]: { enter: node => { if (node.typeCondition != null) { const parentType = typeInfo.getParentType(); const innerType = schema.getType(node.typeCondition.name.value); if (!(0, utils_1.implementsAbstractType)(schema, parentType, innerType)) { return null; } } }, }, [graphql_1.Kind.SELECTION_SET]: { leave: node => { const parentType = typeInfo.getParentType(); if (parentType != null && (0, graphql_1.isAbstractType)(parentType)) { const selections = node.selections.concat([ { kind: graphql_1.Kind.FIELD, name: { kind: graphql_1.Kind.NAME, value: '__typename', }, }, ]); return { ...node, selections, }; } }, }, }), // visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js // empty keys cannot be removed only because of typescript errors // will hopefully be fixed in future version of graphql-js to be optional filteredSelectionSetVisitorKeys); (0, graphql_1.visit)(filteredSelectionSet, { [graphql_1.Kind.VARIABLE]: variableNode => { usedVariables.push(variableNode.name.value); }, }, // visitorKeys argument usage a la https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-graphql/src/batching/merge-queries.js // empty keys cannot be removed only because of typescript errors // will hopefully be fixed in future version of graphql-js to be optional variablesVisitorKeys); return { selectionSet: filteredSelectionSet, usedFragments, usedVariables, }; } function union(...arrays) { const cache = Object.create(null); const result = []; for (const array of arrays) { for (const item of array) { if (!(item in cache)) { cache[item] = true; result.push(item); } } } return result; }