All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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