@graphql-tools/executor
Version:
Fork of GraphQL.js' execute function
182 lines (181 loc) • 7.38 kB
JavaScript
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;
}