UNPKG

@graphql-tools/delegate

Version:

A set of utils for faster development of GraphQL tools

312 lines (311 loc) • 14.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.prepareGatewayDocument = void 0; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const getDocumentMetadata_js_1 = require("./getDocumentMetadata.js"); function prepareGatewayDocument(originalDocument, transformedSchema, returnType, infoSchema) { const wrappedConcreteTypesDocument = wrapConcreteTypes(returnType, transformedSchema, originalDocument); if (infoSchema == null) { return wrappedConcreteTypesDocument; } const { possibleTypesMap, reversePossibleTypesMap, interfaceExtensionsMap, fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField, } = getSchemaMetaData(infoSchema, transformedSchema); const { operations, fragments, fragmentNames } = (0, getDocumentMetadata_js_1.getDocumentMetadata)(wrappedConcreteTypesDocument); const { expandedFragments, fragmentReplacements } = getExpandedFragments(fragments, fragmentNames, possibleTypesMap); const typeInfo = new graphql_1.TypeInfo(transformedSchema); const expandedDocument = { kind: graphql_1.Kind.DOCUMENT, definitions: [...operations, ...fragments, ...expandedFragments], }; const visitorKeyMap = { Document: ['definitions'], OperationDefinition: ['selectionSet'], SelectionSet: ['selections'], Field: ['selectionSet'], InlineFragment: ['selectionSet'], FragmentDefinition: ['selectionSet'], }; return (0, graphql_1.visit)(expandedDocument, (0, graphql_1.visitWithTypeInfo)(typeInfo, { [graphql_1.Kind.SELECTION_SET]: node => visitSelectionSet(node, fragmentReplacements, transformedSchema, typeInfo, possibleTypesMap, reversePossibleTypesMap, interfaceExtensionsMap, fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField), }), // 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 visitorKeyMap); } exports.prepareGatewayDocument = prepareGatewayDocument; function visitSelectionSet(node, fragmentReplacements, schema, typeInfo, possibleTypesMap, reversePossibleTypesMap, interfaceExtensionsMap, fieldNodesByType, fieldNodesByField, dynamicSelectionSetsByField) { var _a, _b; const newSelections = new Set(); const maybeType = typeInfo.getParentType(); if (maybeType != null) { const parentType = (0, graphql_1.getNamedType)(maybeType); const parentTypeName = parentType.name; const fieldNodes = fieldNodesByType[parentTypeName]; if (fieldNodes) { for (const fieldNode of fieldNodes) { newSelections.add(fieldNode); } } const interfaceExtensions = interfaceExtensionsMap[parentType.name]; const interfaceExtensionFields = []; for (const selection of node.selections) { if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) { if (selection.typeCondition != null) { const possibleTypes = possibleTypesMap[selection.typeCondition.name.value]; if (possibleTypes == null) { newSelections.add(selection); continue; } for (const possibleTypeName of possibleTypes) { const maybePossibleType = schema.getType(possibleTypeName); if (maybePossibleType != null && (0, utils_1.implementsAbstractType)(schema, parentType, maybePossibleType)) { newSelections.add(generateInlineFragment(possibleTypeName, selection.selectionSet)); } } } } else if (selection.kind === graphql_1.Kind.FRAGMENT_SPREAD) { const fragmentName = selection.name.value; if (!fragmentReplacements[fragmentName]) { newSelections.add(selection); continue; } for (const replacement of fragmentReplacements[fragmentName]) { const typeName = replacement.typeName; const maybeReplacementType = schema.getType(typeName); if (maybeReplacementType != null && (0, utils_1.implementsAbstractType)(schema, parentType, maybeType)) { newSelections.add({ kind: graphql_1.Kind.FRAGMENT_SPREAD, name: { kind: graphql_1.Kind.NAME, value: replacement.fragmentName, }, }); } } } else { const fieldName = selection.name.value; const fieldNodes = (_a = fieldNodesByField[parentTypeName]) === null || _a === void 0 ? void 0 : _a[fieldName]; if (fieldNodes != null) { for (const fieldNode of fieldNodes) { newSelections.add(fieldNode); } } const dynamicSelectionSets = (_b = dynamicSelectionSetsByField[parentTypeName]) === null || _b === void 0 ? void 0 : _b[fieldName]; if (dynamicSelectionSets != null) { for (const selectionSetFn of dynamicSelectionSets) { const selectionSet = selectionSetFn(selection); if (selectionSet != null) { for (const selection of selectionSet.selections) { newSelections.add(selection); } } } } if (interfaceExtensions === null || interfaceExtensions === void 0 ? void 0 : interfaceExtensions[fieldName]) { interfaceExtensionFields.push(selection); } else { newSelections.add(selection); } } } if (reversePossibleTypesMap[parentType.name]) { newSelections.add({ kind: graphql_1.Kind.FIELD, name: { kind: graphql_1.Kind.NAME, value: '__typename', }, }); } if (interfaceExtensionFields.length) { const possibleTypes = possibleTypesMap[parentType.name]; if (possibleTypes != null) { for (const possibleType of possibleTypes) { newSelections.add(generateInlineFragment(possibleType, { kind: graphql_1.Kind.SELECTION_SET, selections: interfaceExtensionFields, })); } } } return { ...node, selections: Array.from(newSelections), }; } return node; } function generateInlineFragment(typeName, selectionSet) { return { kind: graphql_1.Kind.INLINE_FRAGMENT, typeCondition: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: typeName, }, }, selectionSet, }; } const getSchemaMetaData = (0, utils_1.memoize2)((sourceSchema, targetSchema) => { var _a, _b, _c, _d; const typeMap = sourceSchema.getTypeMap(); const targetTypeMap = targetSchema.getTypeMap(); const possibleTypesMap = Object.create(null); const interfaceExtensionsMap = Object.create(null); for (const typeName in typeMap) { const type = typeMap[typeName]; if ((0, graphql_1.isAbstractType)(type)) { const targetType = targetTypeMap[typeName]; if ((0, graphql_1.isInterfaceType)(type) && (0, graphql_1.isInterfaceType)(targetType)) { const targetTypeFields = targetType.getFields(); const sourceTypeFields = type.getFields(); const extensionFields = Object.create(null); let isExtensionFieldsEmpty = true; for (const fieldName in sourceTypeFields) { if (!targetTypeFields[fieldName]) { extensionFields[fieldName] = true; isExtensionFieldsEmpty = false; } } if (!isExtensionFieldsEmpty) { interfaceExtensionsMap[typeName] = extensionFields; } } if (interfaceExtensionsMap[typeName] || !(0, graphql_1.isAbstractType)(targetType)) { const implementations = sourceSchema.getPossibleTypes(type); possibleTypesMap[typeName] = []; for (const impl of implementations) { if (targetTypeMap[impl.name]) { possibleTypesMap[typeName].push(impl.name); } } } } } const stitchingInfo = (_a = sourceSchema.extensions) === null || _a === void 0 ? void 0 : _a['stitchingInfo']; return { possibleTypesMap, reversePossibleTypesMap: reversePossibleTypesMap(possibleTypesMap), interfaceExtensionsMap, fieldNodesByType: (_b = stitchingInfo === null || stitchingInfo === void 0 ? void 0 : stitchingInfo.fieldNodesByType) !== null && _b !== void 0 ? _b : {}, fieldNodesByField: (_c = stitchingInfo === null || stitchingInfo === void 0 ? void 0 : stitchingInfo.fieldNodesByField) !== null && _c !== void 0 ? _c : {}, dynamicSelectionSetsByField: (_d = stitchingInfo === null || stitchingInfo === void 0 ? void 0 : stitchingInfo.dynamicSelectionSetsByField) !== null && _d !== void 0 ? _d : {}, }; }); function reversePossibleTypesMap(possibleTypesMap) { const result = Object.create(null); for (const typeName in possibleTypesMap) { const toTypeNames = possibleTypesMap[typeName]; for (const toTypeName of toTypeNames) { if (!result[toTypeName]) { result[toTypeName] = []; } result[toTypeName].push(typeName); } } return result; } function getExpandedFragments(fragments, fragmentNames, possibleTypesMap) { let fragmentCounter = 0; function generateFragmentName(typeName) { let fragmentName; do { fragmentName = `_${typeName}_Fragment${fragmentCounter.toString()}`; fragmentCounter++; } while (fragmentNames.has(fragmentName)); return fragmentName; } const expandedFragments = []; const fragmentReplacements = Object.create(null); for (const fragment of fragments) { const possibleTypes = possibleTypesMap[fragment.typeCondition.name.value]; if (possibleTypes != null) { const fragmentName = fragment.name.value; fragmentReplacements[fragmentName] = []; for (const possibleTypeName of possibleTypes) { const name = generateFragmentName(possibleTypeName); fragmentNames.add(name); expandedFragments.push({ kind: graphql_1.Kind.FRAGMENT_DEFINITION, name: { kind: graphql_1.Kind.NAME, value: name, }, typeCondition: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: possibleTypeName, }, }, selectionSet: fragment.selectionSet, }); fragmentReplacements[fragmentName].push({ fragmentName: name, typeName: possibleTypeName, }); } } } return { expandedFragments, fragmentReplacements, }; } function wrapConcreteTypes(returnType, targetSchema, document) { const namedType = (0, graphql_1.getNamedType)(returnType); if (!(0, graphql_1.isObjectType)(namedType)) { return document; } const rootTypeNames = (0, utils_1.getRootTypeNames)(targetSchema); const typeInfo = new graphql_1.TypeInfo(targetSchema); const visitorKeys = { Document: ['definitions'], OperationDefinition: ['selectionSet'], SelectionSet: ['selections'], InlineFragment: ['selectionSet'], FragmentDefinition: ['selectionSet'], }; return (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, { [graphql_1.Kind.FRAGMENT_DEFINITION]: (node) => { const typeName = node.typeCondition.name.value; if (!rootTypeNames.has(typeName)) { return false; } }, [graphql_1.Kind.FIELD]: (node) => { const type = typeInfo.getType(); if (type != null && (0, graphql_1.isAbstractType)((0, graphql_1.getNamedType)(type))) { return { ...node, selectionSet: { kind: graphql_1.Kind.SELECTION_SET, selections: [ { kind: graphql_1.Kind.INLINE_FRAGMENT, typeCondition: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: namedType.name, }, }, selectionSet: node.selectionSet, }, ], }, }; } }, }), // 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 visitorKeys); }