UNPKG

6.39 kBJavaScriptView Raw
1import { getDirectiveValues, GraphQLIncludeDirective, GraphQLSkipDirective, isAbstractType, Kind, typeFromAST, } from 'graphql';
2import { AccumulatorMap } from './AccumulatorMap.js';
3import { GraphQLDeferDirective } from './directives.js';
4import { memoize5 } from './memoize.js';
5function collectFieldsImpl(schema, fragments, variableValues, runtimeType, selectionSet, fields, patches, visitedFragmentNames) {
6 for (const selection of selectionSet.selections) {
7 switch (selection.kind) {
8 case Kind.FIELD: {
9 if (!shouldIncludeNode(variableValues, selection)) {
10 continue;
11 }
12 fields.add(getFieldEntryKey(selection), selection);
13 break;
14 }
15 case Kind.INLINE_FRAGMENT: {
16 if (!shouldIncludeNode(variableValues, selection) ||
17 !doesFragmentConditionMatch(schema, selection, runtimeType)) {
18 continue;
19 }
20 const defer = getDeferValues(variableValues, selection);
21 if (defer) {
22 const patchFields = new AccumulatorMap();
23 collectFieldsImpl(schema, fragments, variableValues, runtimeType, selection.selectionSet, patchFields, patches, visitedFragmentNames);
24 patches.push({
25 label: defer.label,
26 fields: patchFields,
27 });
28 }
29 else {
30 collectFieldsImpl(schema, fragments, variableValues, runtimeType, selection.selectionSet, fields, patches, visitedFragmentNames);
31 }
32 break;
33 }
34 case Kind.FRAGMENT_SPREAD: {
35 const fragName = selection.name.value;
36 if (!shouldIncludeNode(variableValues, selection)) {
37 continue;
38 }
39 const defer = getDeferValues(variableValues, selection);
40 if (visitedFragmentNames.has(fragName) && !defer) {
41 continue;
42 }
43 const fragment = fragments[fragName];
44 if (!fragment || !doesFragmentConditionMatch(schema, fragment, runtimeType)) {
45 continue;
46 }
47 if (!defer) {
48 visitedFragmentNames.add(fragName);
49 }
50 if (defer) {
51 const patchFields = new AccumulatorMap();
52 collectFieldsImpl(schema, fragments, variableValues, runtimeType, fragment.selectionSet, patchFields, patches, visitedFragmentNames);
53 patches.push({
54 label: defer.label,
55 fields: patchFields,
56 });
57 }
58 else {
59 collectFieldsImpl(schema, fragments, variableValues, runtimeType, fragment.selectionSet, fields, patches, visitedFragmentNames);
60 }
61 break;
62 }
63 }
64 }
65}
66/**
67 * Given a selectionSet, collects all of the fields and returns them.
68 *
69 * CollectFields requires the "runtime type" of an object. For a field that
70 * returns an Interface or Union type, the "runtime type" will be the actual
71 * object type returned by that field.
72 *
73 */
74export function collectFields(schema, fragments, variableValues, runtimeType, selectionSet) {
75 const fields = new AccumulatorMap();
76 const patches = [];
77 collectFieldsImpl(schema, fragments, variableValues, runtimeType, selectionSet, fields, patches, new Set());
78 return { fields, patches };
79}
80/**
81 * Determines if a field should be included based on the `@include` and `@skip`
82 * directives, where `@skip` has higher precedence than `@include`.
83 */
84export function shouldIncludeNode(variableValues, node) {
85 const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues);
86 if (skip?.['if'] === true) {
87 return false;
88 }
89 const include = getDirectiveValues(GraphQLIncludeDirective, node, variableValues);
90 if (include?.['if'] === false) {
91 return false;
92 }
93 return true;
94}
95/**
96 * Determines if a fragment is applicable to the given type.
97 */
98export function doesFragmentConditionMatch(schema, fragment, type) {
99 const typeConditionNode = fragment.typeCondition;
100 if (!typeConditionNode) {
101 return true;
102 }
103 const conditionalType = typeFromAST(schema, typeConditionNode);
104 if (conditionalType === type) {
105 return true;
106 }
107 if (isAbstractType(conditionalType)) {
108 const possibleTypes = schema.getPossibleTypes(conditionalType);
109 return possibleTypes.includes(type);
110 }
111 return false;
112}
113/**
114 * Implements the logic to compute the key of a given field's entry
115 */
116export function getFieldEntryKey(node) {
117 return node.alias ? node.alias.value : node.name.value;
118}
119/**
120 * Returns an object containing the `@defer` arguments if a field should be
121 * deferred based on the experimental flag, defer directive present and
122 * not disabled by the "if" argument.
123 */
124export function getDeferValues(variableValues, node) {
125 const defer = getDirectiveValues(GraphQLDeferDirective, node, variableValues);
126 if (!defer) {
127 return;
128 }
129 if (defer['if'] === false) {
130 return;
131 }
132 return {
133 label: typeof defer['label'] === 'string' ? defer['label'] : undefined,
134 };
135}
136/**
137 * Given an array of field nodes, collects all of the subfields of the passed
138 * in fields, and returns them at the end.
139 *
140 * CollectSubFields requires the "return type" of an object. For a field that
141 * returns an Interface or Union type, the "return type" will be the actual
142 * object type returned by that field.
143 *
144 */
145export const collectSubFields = memoize5(function collectSubfields(schema, fragments, variableValues, returnType, fieldNodes) {
146 const subFieldNodes = new AccumulatorMap();
147 const visitedFragmentNames = new Set();
148 const subPatches = [];
149 const subFieldsAndPatches = {
150 fields: subFieldNodes,
151 patches: subPatches,
152 };
153 for (const node of fieldNodes) {
154 if (node.selectionSet) {
155 collectFieldsImpl(schema, fragments, variableValues, returnType, node.selectionSet, subFieldNodes, subPatches, visitedFragmentNames);
156 }
157 }
158 return subFieldsAndPatches;
159});