collectFields.js 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { memoize5 } from './memoize.js';
  2. import { Kind, getDirectiveValues, GraphQLSkipDirective, GraphQLIncludeDirective, isAbstractType, typeFromAST, } from 'graphql';
  3. // Taken from GraphQL-JS v16 for backwards compat
  4. export function collectFields(schema, fragments, variableValues, runtimeType, selectionSet, fields, visitedFragmentNames) {
  5. for (const selection of selectionSet.selections) {
  6. switch (selection.kind) {
  7. case Kind.FIELD: {
  8. if (!shouldIncludeNode(variableValues, selection)) {
  9. continue;
  10. }
  11. const name = getFieldEntryKey(selection);
  12. const fieldList = fields.get(name);
  13. if (fieldList !== undefined) {
  14. fieldList.push(selection);
  15. }
  16. else {
  17. fields.set(name, [selection]);
  18. }
  19. break;
  20. }
  21. case Kind.INLINE_FRAGMENT: {
  22. if (!shouldIncludeNode(variableValues, selection) ||
  23. !doesFragmentConditionMatch(schema, selection, runtimeType)) {
  24. continue;
  25. }
  26. collectFields(schema, fragments, variableValues, runtimeType, selection.selectionSet, fields, visitedFragmentNames);
  27. break;
  28. }
  29. case Kind.FRAGMENT_SPREAD: {
  30. const fragName = selection.name.value;
  31. if (visitedFragmentNames.has(fragName) || !shouldIncludeNode(variableValues, selection)) {
  32. continue;
  33. }
  34. visitedFragmentNames.add(fragName);
  35. const fragment = fragments[fragName];
  36. if (!fragment || !doesFragmentConditionMatch(schema, fragment, runtimeType)) {
  37. continue;
  38. }
  39. collectFields(schema, fragments, variableValues, runtimeType, fragment.selectionSet, fields, visitedFragmentNames);
  40. break;
  41. }
  42. }
  43. }
  44. return fields;
  45. }
  46. /**
  47. * Determines if a field should be included based on the `@include` and `@skip`
  48. * directives, where `@skip` has higher precedence than `@include`.
  49. */
  50. function shouldIncludeNode(variableValues, node) {
  51. const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues);
  52. if ((skip === null || skip === void 0 ? void 0 : skip['if']) === true) {
  53. return false;
  54. }
  55. const include = getDirectiveValues(GraphQLIncludeDirective, node, variableValues);
  56. if ((include === null || include === void 0 ? void 0 : include['if']) === false) {
  57. return false;
  58. }
  59. return true;
  60. }
  61. /**
  62. * Determines if a fragment is applicable to the given type.
  63. */
  64. function doesFragmentConditionMatch(schema, fragment, type) {
  65. const typeConditionNode = fragment.typeCondition;
  66. if (!typeConditionNode) {
  67. return true;
  68. }
  69. const conditionalType = typeFromAST(schema, typeConditionNode);
  70. if (conditionalType === type) {
  71. return true;
  72. }
  73. if (isAbstractType(conditionalType)) {
  74. const possibleTypes = schema.getPossibleTypes(conditionalType);
  75. return possibleTypes.includes(type);
  76. }
  77. return false;
  78. }
  79. /**
  80. * Implements the logic to compute the key of a given field's entry
  81. */
  82. function getFieldEntryKey(node) {
  83. return node.alias ? node.alias.value : node.name.value;
  84. }
  85. export const collectSubFields = memoize5(function collectSubFields(schema, fragments, variableValues, type, fieldNodes) {
  86. const subFieldNodes = new Map();
  87. const visitedFragmentNames = new Set();
  88. for (const fieldNode of fieldNodes) {
  89. if (fieldNode.selectionSet) {
  90. collectFields(schema, fragments, variableValues, type, fieldNode.selectionSet, subFieldNodes, visitedFragmentNames);
  91. }
  92. }
  93. return subFieldNodes;
  94. });