UNPKG

@graphql-tools/stitch

Version:

A set of utils for faster development of GraphQL tools

208 lines (207 loc) • 9.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createDelegationPlanBuilder = void 0; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const getFieldsNotInSubschema_js_1 = require("./getFieldsNotInSubschema.js"); function calculateDelegationStage(mergedTypeInfo, sourceSubschemas, targetSubschemas, fieldNodes) { const { selectionSets, fieldSelectionSets, uniqueFields, nonUniqueFields } = mergedTypeInfo; // 1. calculate if possible to delegate to given subschema const proxiableSubschemas = []; const nonProxiableSubschemas = []; for (const t of targetSubschemas) { const selectionSet = selectionSets.get(t); const fieldSelectionSetsMap = fieldSelectionSets.get(t); if (selectionSet != null && !subschemaTypesContainSelectionSet(mergedTypeInfo, sourceSubschemas, selectionSet)) { nonProxiableSubschemas.push(t); } else { if (fieldSelectionSetsMap == null || fieldNodes.every(fieldNode => { const fieldName = fieldNode.name.value; const fieldSelectionSet = fieldSelectionSetsMap[fieldName]; return (fieldSelectionSet == null || subschemaTypesContainSelectionSet(mergedTypeInfo, sourceSubschemas, fieldSelectionSet)); })) { proxiableSubschemas.push(t); } else { nonProxiableSubschemas.push(t); } } } const unproxiableFieldNodes = []; // 2. for each selection: const delegationMap = new Map(); for (const fieldNode of fieldNodes) { const fieldName = fieldNode.name.value; if (fieldName === '__typename') { continue; } // check dependencies for computed fields are available in the source schemas const sourcesWithUnsatisfiedDependencies = sourceSubschemas.filter(s => fieldSelectionSets.get(s) != null && fieldSelectionSets.get(s)[fieldName] != null && !subschemaTypesContainSelectionSet(mergedTypeInfo, sourceSubschemas, fieldSelectionSets.get(s)[fieldName])); if (sourcesWithUnsatisfiedDependencies.length === sourceSubschemas.length) { unproxiableFieldNodes.push(fieldNode); for (const source of sourcesWithUnsatisfiedDependencies) { if (!nonProxiableSubschemas.includes(source)) { nonProxiableSubschemas.push(source); } } continue; } // 2a. use uniqueFields map to assign fields to subschema if one of possible subschemas const uniqueSubschema = uniqueFields[fieldNode.name.value]; if (uniqueSubschema != null) { if (!proxiableSubschemas.includes(uniqueSubschema)) { unproxiableFieldNodes.push(fieldNode); continue; } const existingSubschema = delegationMap.get(uniqueSubschema)?.selections; if (existingSubschema != null) { existingSubschema.push(fieldNode); } else { delegationMap.set(uniqueSubschema, { kind: graphql_1.Kind.SELECTION_SET, selections: [fieldNode], }); } continue; } // 2b. use nonUniqueFields to assign to a possible subschema, // preferring one of the subschemas already targets of delegation let nonUniqueSubschemas = nonUniqueFields[fieldNode.name.value]; if (nonUniqueSubschemas == null) { unproxiableFieldNodes.push(fieldNode); continue; } nonUniqueSubschemas = nonUniqueSubschemas.filter(s => proxiableSubschemas.includes(s)); if (!nonUniqueSubschemas.length) { unproxiableFieldNodes.push(fieldNode); continue; } const existingSubschema = nonUniqueSubschemas.find(s => delegationMap.has(s)); if (existingSubschema != null) { // It is okay we previously explicitly check whether the map has the element. delegationMap.get(existingSubschema).selections.push(fieldNode); } else { let bestUniqueSubschema = nonUniqueSubschemas[0]; let bestScore = Infinity; for (const nonUniqueSubschema of nonUniqueSubschemas) { const typeInSubschema = nonUniqueSubschema.transformedSchema.getType(mergedTypeInfo.typeName); const fields = typeInSubschema.getFields(); const field = fields[fieldNode.name.value]; if (field != null) { const unavailableFields = (0, getFieldsNotInSubschema_js_1.extractUnavailableFields)(nonUniqueSubschema.transformedSchema, field, fieldNode, fieldType => { if (!nonUniqueSubschema.merge?.[fieldType.name]) { delegationMap.set(nonUniqueSubschema, { kind: graphql_1.Kind.SELECTION_SET, selections: [fieldNode], }); // Ignore unresolvable fields return false; } return true; }); const currentScore = calculateScore(unavailableFields); if (currentScore < bestScore) { bestScore = currentScore; bestUniqueSubschema = nonUniqueSubschema; } } } delegationMap.set(bestUniqueSubschema, { kind: graphql_1.Kind.SELECTION_SET, selections: [fieldNode], }); } } return { delegationMap, proxiableSubschemas, nonProxiableSubschemas, unproxiableFieldNodes, }; } function calculateScore(selections) { let score = 0; for (const selectionNode of selections) { switch (selectionNode.kind) { case graphql_1.Kind.FIELD: score += 1; break; case graphql_1.Kind.INLINE_FRAGMENT: score += calculateScore(selectionNode.selectionSet.selections); break; } } return score; } function getStitchingInfo(schema) { const stitchingInfo = schema.extensions?.['stitchingInfo']; if (!stitchingInfo) { throw new Error(`Schema is not a stitched schema.`); } return stitchingInfo; } function createDelegationPlanBuilder(mergedTypeInfo) { return (0, utils_1.memoize5)(function delegationPlanBuilder(schema, sourceSubschema, variableValues, fragments, fieldNodes) { const stitchingInfo = getStitchingInfo(schema); const targetSubschemas = mergedTypeInfo?.targetSubschemas.get(sourceSubschema); if (!targetSubschemas || !targetSubschemas.length) { return []; } const typeName = mergedTypeInfo.typeName; const fieldsNotInSubschema = (0, getFieldsNotInSubschema_js_1.getFieldsNotInSubschema)(schema, stitchingInfo, schema.getType(typeName), mergedTypeInfo.typeMaps.get(sourceSubschema)?.[typeName], fieldNodes, fragments, variableValues); if (!fieldsNotInSubschema.length) { return []; } const delegationMaps = []; let sourceSubschemas = createSubschemas(sourceSubschema); let delegationStage = calculateDelegationStage(mergedTypeInfo, sourceSubschemas, targetSubschemas, fieldsNotInSubschema); let { delegationMap } = delegationStage; while (delegationMap.size) { delegationMaps.push(delegationMap); const { proxiableSubschemas, nonProxiableSubschemas, unproxiableFieldNodes } = delegationStage; sourceSubschemas = combineSubschemas(sourceSubschemas, proxiableSubschemas); delegationStage = calculateDelegationStage(mergedTypeInfo, sourceSubschemas, nonProxiableSubschemas, unproxiableFieldNodes); delegationMap = delegationStage.delegationMap; } return delegationMaps; }); } exports.createDelegationPlanBuilder = createDelegationPlanBuilder; const createSubschemas = (0, utils_1.memoize1)(function createSubschemas(sourceSubschema) { return [sourceSubschema]; }); const combineSubschemas = (0, utils_1.memoize2)(function combineSubschemas(sourceSubschemas, additionalSubschemas) { return sourceSubschemas.concat(additionalSubschemas); }); const subschemaTypesContainSelectionSet = (0, utils_1.memoize3)(function subschemaTypesContainSelectionSet(mergedTypeInfo, sourceSubchemas, selectionSet) { return typesContainSelectionSet(sourceSubchemas.map(sourceSubschema => sourceSubschema.transformedSchema.getType(mergedTypeInfo.typeName)), selectionSet); }); function typesContainSelectionSet(types, selectionSet) { const fieldMaps = types.map(type => type.getFields()); for (const selection of selectionSet.selections) { if (selection.kind === graphql_1.Kind.FIELD) { const fields = fieldMaps .map(fieldMap => fieldMap[selection.name.value]) .filter(field => field != null); if (!fields.length) { return false; } if (selection.selectionSet != null) { return typesContainSelectionSet(fields.map(field => (0, graphql_1.getNamedType)(field.type)), selection.selectionSet); } } else if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT && selection.typeCondition?.name.value === types[0].name) { return typesContainSelectionSet(types, selection.selectionSet); } } return true; }