node_modules.apollo-codegen.src.compiler.visitors.collectAndMergeFields.ts Maven / Gradle / Ivy
import { SelectionSet, Selection, Field, BooleanCondition } from '../';
import { GraphQLObjectType } from 'graphql';
// This is a temporary workaround to keep track of conditions on fields in the fields themselves.
// It is only added here because we want to expose it to the Android target, which relies on the legacy IR.
declare module '../' {
interface Field {
conditions?: BooleanCondition[];
}
}
export function collectAndMergeFields(
selectionSet: SelectionSet,
mergeInFragmentSpreads: Boolean = true
): Field[] {
const groupedFields: Map = new Map();
function visitSelectionSet(
selections: Selection[],
possibleTypes: GraphQLObjectType[],
conditions: BooleanCondition[] = []
) {
if (possibleTypes.length < 1) return;
for (const selection of selections) {
switch (selection.kind) {
case 'Field':
let groupForResponseKey = groupedFields.get(selection.responseKey);
if (!groupForResponseKey) {
groupForResponseKey = [];
groupedFields.set(selection.responseKey, groupForResponseKey);
}
// Make sure to deep clone selections to avoid modifying the original field
// TODO: Should we use an object freezing / immutability solution?
groupForResponseKey.push({
...selection,
isConditional: conditions.length > 0,
conditions,
selectionSet: selection.selectionSet
? {
possibleTypes: selection.selectionSet.possibleTypes,
selections: [...selection.selectionSet.selections]
}
: undefined
});
break;
case 'FragmentSpread':
case 'TypeCondition':
if (selection.kind === 'FragmentSpread' && !mergeInFragmentSpreads) continue;
// Only merge fragment spreads and type conditions if they match all possible types.
if (!possibleTypes.every(type => selection.selectionSet.possibleTypes.includes(type))) continue;
visitSelectionSet(selection.selectionSet.selections, possibleTypes, conditions);
break;
case 'BooleanCondition':
visitSelectionSet(selection.selectionSet.selections, possibleTypes, [...conditions, selection]);
break;
}
}
}
visitSelectionSet(selectionSet.selections, selectionSet.possibleTypes);
// Merge selection sets
const fields = Array.from(groupedFields.values()).map(fields => {
const isFieldIncludedUnconditionally = fields.some(field => !field.isConditional);
return fields
.map(field => {
if (isFieldIncludedUnconditionally && field.isConditional && field.selectionSet) {
field.selectionSet.selections = wrapInBooleanConditionsIfNeeded(
field.selectionSet.selections,
field.conditions
);
}
return field;
})
.reduce((field, otherField) => {
field.isConditional = field.isConditional && otherField.isConditional;
// FIXME: This is strictly taken incorrect, because the conditions should be ORed
// These conditions are only used in Android target however,
// and there is now way to express this in the legacy IR.
if (field.conditions && otherField.conditions) {
field.conditions = [...field.conditions, ...otherField.conditions];
} else {
field.conditions = undefined;
}
if (field.selectionSet && otherField.selectionSet) {
field.selectionSet.selections.push(...otherField.selectionSet.selections);
}
return field;
});
});
// Replace field descriptions with type-specific descriptions if possible
if (selectionSet.possibleTypes.length == 1) {
const type = selectionSet.possibleTypes[0];
const fieldDefMap = type.getFields();
for (const field of fields) {
const fieldDef = fieldDefMap[field.name];
if (fieldDef && fieldDef.description) {
field.description = fieldDef.description;
}
}
}
return fields;
}
export function wrapInBooleanConditionsIfNeeded(
selections: Selection[],
conditions?: BooleanCondition[]
): Selection[] {
if (!conditions || conditions.length == 0) return selections;
const [condition, ...rest] = conditions;
return [
{
...condition,
selectionSet: {
possibleTypes: condition.selectionSet.possibleTypes,
selections: wrapInBooleanConditionsIfNeeded(selections, rest)
}
}
];
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy