UNPKG

@graphql-tools/executor

Version:

Fork of GraphQL.js' execute function

182 lines (181 loc) • 7.38 kB
import { Kind, } from 'graphql'; import { doesFragmentConditionMatch, getDirectiveValues, getFieldEntryKey, GraphQLDeferDirective, shouldIncludeNode, } from '@graphql-tools/utils'; import { getBySet } from './getBySet.js'; import { isSameSet } from './isSameSet.js'; class FieldDetailsMap extends Map { add(key, item) { const group = this.get(key); if (group === undefined) { this.set(key, [item]); } else { group.push(item); } } } export function collectIncrementalFields(schema, fragments, variableValues, runtimeType, selectionSet, parentDeferUsage) { const groupedFieldSet = new FieldDetailsMap(); const newDeferUsages = []; const context = { schema, fragments, variableValues, runtimeType, visitedFragmentNames: new Map(), }; collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsages, parentDeferUsage); return { groupedFieldSet, newDeferUsages }; } export function collectIncrementalSubfields(schema, fragments, variableValues, returnType, fieldNodes, parentDeferUsage, deferUsageByFieldNode) { const groupedFieldSet = new FieldDetailsMap(); const newDeferUsages = []; const context = { schema, fragments, variableValues, runtimeType: returnType, visitedFragmentNames: new Map(), }; for (const node of fieldNodes) { if (node.selectionSet) { const deferUsage = deferUsageByFieldNode?.has(node) ? deferUsageByFieldNode.get(node) : parentDeferUsage; collectFieldsImpl(context, node.selectionSet, groupedFieldSet, newDeferUsages, deferUsage); } } return { groupedFieldSet, newDeferUsages }; } function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsages, deferUsage) { const { schema, fragments, variableValues, runtimeType, visitedFragmentNames } = context; for (const selection of selectionSet.selections) { switch (selection.kind) { case Kind.FIELD: { if (!shouldIncludeNode(variableValues, selection)) { continue; } groupedFieldSet.add(getFieldEntryKey(selection), { node: selection, deferUsage, }); break; } case Kind.INLINE_FRAGMENT: { if (!shouldIncludeNode(variableValues, selection) || !doesFragmentConditionMatch(schema, selection, runtimeType)) { continue; } const newDeferUsage = getDeferUsage(variableValues, selection, deferUsage); if (!newDeferUsage) { collectFieldsImpl(context, selection.selectionSet, groupedFieldSet, newDeferUsages, deferUsage); } else { newDeferUsages.push(newDeferUsage); collectFieldsImpl(context, selection.selectionSet, groupedFieldSet, newDeferUsages, newDeferUsage); } break; } case Kind.FRAGMENT_SPREAD: { const fragName = selection.name.value; if (!shouldIncludeNode(variableValues, selection)) { continue; } const fragment = fragments[fragName]; if (!fragment || !doesFragmentConditionMatch(schema, fragment, runtimeType)) { continue; } const newDeferUsage = getDeferUsage(variableValues, selection, deferUsage); const visitedAsDeferred = visitedFragmentNames.get(fragName); let maybeNewDeferUsage; if (!newDeferUsage) { if (visitedAsDeferred === false) { continue; } visitedFragmentNames.set(fragName, false); maybeNewDeferUsage = deferUsage; } else { if (visitedAsDeferred !== undefined) { continue; } visitedFragmentNames.set(fragName, true); newDeferUsages.push(newDeferUsage); maybeNewDeferUsage = newDeferUsage; } collectFieldsImpl(context, fragment.selectionSet, groupedFieldSet, newDeferUsages, maybeNewDeferUsage); break; } } } } function getDeferUsage(variableValues, node, parentDeferUsage) { const defer = getDirectiveValues(GraphQLDeferDirective, node, variableValues); if (!defer || defer['if'] === false) { return; } return { label: typeof defer['label'] === 'string' ? defer['label'] : undefined, parentDeferUsage, }; } export function buildExecutionPlan(originalGroupedFieldSet, parentDeferUsages = new Set()) { const groupedFieldSet = new Map(); const newGroupedFieldSets = new Map(); for (const [responseKey, fieldDetailsList] of originalGroupedFieldSet) { const filteredDeferUsageSet = getFilteredDeferUsageSet(fieldDetailsList); if (isSameSet(filteredDeferUsageSet, parentDeferUsages)) { groupedFieldSet.set(responseKey, fieldDetailsList); continue; } let newGroupedFieldSet = getBySet(newGroupedFieldSets, filteredDeferUsageSet); if (newGroupedFieldSet === undefined) { newGroupedFieldSet = new Map(); newGroupedFieldSets.set(filteredDeferUsageSet, newGroupedFieldSet); } newGroupedFieldSet.set(responseKey, fieldDetailsList); } return { groupedFieldSet, newGroupedFieldSets }; } function getFilteredDeferUsageSet(fieldDetailsList) { const filteredDeferUsageSet = new Set(); for (const fieldDetails of fieldDetailsList) { const deferUsage = fieldDetails.deferUsage; if (deferUsage === undefined) { filteredDeferUsageSet.clear(); return filteredDeferUsageSet; } filteredDeferUsageSet.add(deferUsage); } for (const deferUsage of filteredDeferUsageSet) { let parentDeferUsage = deferUsage.parentDeferUsage; while (parentDeferUsage !== undefined) { if (filteredDeferUsageSet.has(parentDeferUsage)) { filteredDeferUsageSet.delete(deferUsage); break; } parentDeferUsage = parentDeferUsage.parentDeferUsage; } } return filteredDeferUsageSet; } export function groupedFieldSetToFieldMap(groupedFieldSet, deferUsageByFieldNode) { const fields = new Map(); for (const [responseKey, fieldDetailsList] of groupedFieldSet) { fields.set(responseKey, fieldDetailsList.map(details => { deferUsageByFieldNode?.set(details.node, details.deferUsage); return details.node; })); } return fields; } export function rootDeferUsage(deferUsageSet) { for (const usage of deferUsageSet) { if (usage.parentDeferUsage == null || !deferUsageSet.has(usage.parentDeferUsage)) { return usage; } } return undefined; } export function labelForDeferUsageSet(deferUsageSet) { return rootDeferUsage(deferUsageSet)?.label; }