collectFields.mjs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { Kind } from '../language/kinds.mjs';
  2. import { isAbstractType } from '../type/definition.mjs';
  3. import {
  4. GraphQLIncludeDirective,
  5. GraphQLSkipDirective,
  6. } from '../type/directives.mjs';
  7. import { typeFromAST } from '../utilities/typeFromAST.mjs';
  8. import { getDirectiveValues } from './values.mjs';
  9. /**
  10. * Given a selectionSet, collects all of the fields and returns them.
  11. *
  12. * CollectFields requires the "runtime type" of an object. For a field that
  13. * returns an Interface or Union type, the "runtime type" will be the actual
  14. * object type returned by that field.
  15. *
  16. * @internal
  17. */
  18. export function collectFields(
  19. schema,
  20. fragments,
  21. variableValues,
  22. runtimeType,
  23. selectionSet,
  24. ) {
  25. const fields = new Map();
  26. collectFieldsImpl(
  27. schema,
  28. fragments,
  29. variableValues,
  30. runtimeType,
  31. selectionSet,
  32. fields,
  33. new Set(),
  34. );
  35. return fields;
  36. }
  37. /**
  38. * Given an array of field nodes, collects all of the subfields of the passed
  39. * in fields, and returns them at the end.
  40. *
  41. * CollectSubFields requires the "return type" of an object. For a field that
  42. * returns an Interface or Union type, the "return type" will be the actual
  43. * object type returned by that field.
  44. *
  45. * @internal
  46. */
  47. export function collectSubfields(
  48. schema,
  49. fragments,
  50. variableValues,
  51. returnType,
  52. fieldNodes,
  53. ) {
  54. const subFieldNodes = new Map();
  55. const visitedFragmentNames = new Set();
  56. for (const node of fieldNodes) {
  57. if (node.selectionSet) {
  58. collectFieldsImpl(
  59. schema,
  60. fragments,
  61. variableValues,
  62. returnType,
  63. node.selectionSet,
  64. subFieldNodes,
  65. visitedFragmentNames,
  66. );
  67. }
  68. }
  69. return subFieldNodes;
  70. }
  71. function collectFieldsImpl(
  72. schema,
  73. fragments,
  74. variableValues,
  75. runtimeType,
  76. selectionSet,
  77. fields,
  78. visitedFragmentNames,
  79. ) {
  80. for (const selection of selectionSet.selections) {
  81. switch (selection.kind) {
  82. case Kind.FIELD: {
  83. if (!shouldIncludeNode(variableValues, selection)) {
  84. continue;
  85. }
  86. const name = getFieldEntryKey(selection);
  87. const fieldList = fields.get(name);
  88. if (fieldList !== undefined) {
  89. fieldList.push(selection);
  90. } else {
  91. fields.set(name, [selection]);
  92. }
  93. break;
  94. }
  95. case Kind.INLINE_FRAGMENT: {
  96. if (
  97. !shouldIncludeNode(variableValues, selection) ||
  98. !doesFragmentConditionMatch(schema, selection, runtimeType)
  99. ) {
  100. continue;
  101. }
  102. collectFieldsImpl(
  103. schema,
  104. fragments,
  105. variableValues,
  106. runtimeType,
  107. selection.selectionSet,
  108. fields,
  109. visitedFragmentNames,
  110. );
  111. break;
  112. }
  113. case Kind.FRAGMENT_SPREAD: {
  114. const fragName = selection.name.value;
  115. if (
  116. visitedFragmentNames.has(fragName) ||
  117. !shouldIncludeNode(variableValues, selection)
  118. ) {
  119. continue;
  120. }
  121. visitedFragmentNames.add(fragName);
  122. const fragment = fragments[fragName];
  123. if (
  124. !fragment ||
  125. !doesFragmentConditionMatch(schema, fragment, runtimeType)
  126. ) {
  127. continue;
  128. }
  129. collectFieldsImpl(
  130. schema,
  131. fragments,
  132. variableValues,
  133. runtimeType,
  134. fragment.selectionSet,
  135. fields,
  136. visitedFragmentNames,
  137. );
  138. break;
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Determines if a field should be included based on the `@include` and `@skip`
  145. * directives, where `@skip` has higher precedence than `@include`.
  146. */
  147. function shouldIncludeNode(variableValues, node) {
  148. const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues);
  149. if ((skip === null || skip === void 0 ? void 0 : skip.if) === true) {
  150. return false;
  151. }
  152. const include = getDirectiveValues(
  153. GraphQLIncludeDirective,
  154. node,
  155. variableValues,
  156. );
  157. if (
  158. (include === null || include === void 0 ? void 0 : include.if) === false
  159. ) {
  160. return false;
  161. }
  162. return true;
  163. }
  164. /**
  165. * Determines if a fragment is applicable to the given type.
  166. */
  167. function doesFragmentConditionMatch(schema, fragment, type) {
  168. const typeConditionNode = fragment.typeCondition;
  169. if (!typeConditionNode) {
  170. return true;
  171. }
  172. const conditionalType = typeFromAST(schema, typeConditionNode);
  173. if (conditionalType === type) {
  174. return true;
  175. }
  176. if (isAbstractType(conditionalType)) {
  177. return schema.isSubType(conditionalType, type);
  178. }
  179. return false;
  180. }
  181. /**
  182. * Implements the logic to compute the key of a given field's entry
  183. */
  184. function getFieldEntryKey(node) {
  185. return node.alias ? node.alias.value : node.name.value;
  186. }