@graphql-tools/stitch
Version: 
A set of utils for faster development of GraphQL tools
155 lines (154 loc) • 7.47 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createDelegationPlanBuilder = void 0;
const graphql_1 = require("graphql");
const getFieldsNotInSubschema_js_1 = require("./getFieldsNotInSubschema.js");
const utils_1 = require("@graphql-tools/utils");
function calculateDelegationStage(mergedTypeInfo, sourceSubschemas, targetSubschemas, fieldNodes) {
    var _a;
    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) {
        if (fieldNode.name.value === '__typename') {
            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 = (_a = delegationMap.get(uniqueSubschema)) === null || _a === void 0 ? void 0 : _a.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 {
            delegationMap.set(nonUniqueSubschemas[0], {
                kind: graphql_1.Kind.SELECTION_SET,
                selections: [fieldNode],
            });
        }
    }
    return {
        delegationMap,
        proxiableSubschemas,
        nonProxiableSubschemas,
        unproxiableFieldNodes,
    };
}
function getStitchingInfo(schema) {
    var _a;
    const stitchingInfo = (_a = schema.extensions) === null || _a === void 0 ? void 0 : _a['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) {
        var _a;
        const stitchingInfo = getStitchingInfo(schema);
        const targetSubschemas = mergedTypeInfo === null || mergedTypeInfo === void 0 ? void 0 : 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), (_a = mergedTypeInfo.typeMaps.get(sourceSubschema)) === null || _a === void 0 ? void 0 : _a[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) {
    var _a;
    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 && ((_a = selection.typeCondition) === null || _a === void 0 ? void 0 : _a.name.value) === types[0].name) {
            return typesContainSelectionSet(types, selection.selectionSet);
        }
    }
    return true;
}