Spaces:
Running
Running
import { Kind } from '../language/kinds.mjs'; | |
import { isAbstractType } from '../type/definition.mjs'; | |
import { | |
GraphQLIncludeDirective, | |
GraphQLSkipDirective, | |
} from '../type/directives.mjs'; | |
import { typeFromAST } from '../utilities/typeFromAST.mjs'; | |
import { getDirectiveValues } from './values.mjs'; | |
/** | |
* Given a selectionSet, collects all of the fields and returns them. | |
* | |
* CollectFields requires the "runtime type" of an object. For a field that | |
* returns an Interface or Union type, the "runtime type" will be the actual | |
* object type returned by that field. | |
* | |
* @internal | |
*/ | |
export function collectFields( | |
schema, | |
fragments, | |
variableValues, | |
runtimeType, | |
selectionSet, | |
) { | |
const fields = new Map(); | |
collectFieldsImpl( | |
schema, | |
fragments, | |
variableValues, | |
runtimeType, | |
selectionSet, | |
fields, | |
new Set(), | |
); | |
return fields; | |
} | |
/** | |
* Given an array of field nodes, collects all of the subfields of the passed | |
* in fields, and returns them at the end. | |
* | |
* CollectSubFields requires the "return type" of an object. For a field that | |
* returns an Interface or Union type, the "return type" will be the actual | |
* object type returned by that field. | |
* | |
* @internal | |
*/ | |
export function collectSubfields( | |
schema, | |
fragments, | |
variableValues, | |
returnType, | |
fieldNodes, | |
) { | |
const subFieldNodes = new Map(); | |
const visitedFragmentNames = new Set(); | |
for (const node of fieldNodes) { | |
if (node.selectionSet) { | |
collectFieldsImpl( | |
schema, | |
fragments, | |
variableValues, | |
returnType, | |
node.selectionSet, | |
subFieldNodes, | |
visitedFragmentNames, | |
); | |
} | |
} | |
return subFieldNodes; | |
} | |
function collectFieldsImpl( | |
schema, | |
fragments, | |
variableValues, | |
runtimeType, | |
selectionSet, | |
fields, | |
visitedFragmentNames, | |
) { | |
for (const selection of selectionSet.selections) { | |
switch (selection.kind) { | |
case Kind.FIELD: { | |
if (!shouldIncludeNode(variableValues, selection)) { | |
continue; | |
} | |
const name = getFieldEntryKey(selection); | |
const fieldList = fields.get(name); | |
if (fieldList !== undefined) { | |
fieldList.push(selection); | |
} else { | |
fields.set(name, [selection]); | |
} | |
break; | |
} | |
case Kind.INLINE_FRAGMENT: { | |
if ( | |
!shouldIncludeNode(variableValues, selection) || | |
!doesFragmentConditionMatch(schema, selection, runtimeType) | |
) { | |
continue; | |
} | |
collectFieldsImpl( | |
schema, | |
fragments, | |
variableValues, | |
runtimeType, | |
selection.selectionSet, | |
fields, | |
visitedFragmentNames, | |
); | |
break; | |
} | |
case Kind.FRAGMENT_SPREAD: { | |
const fragName = selection.name.value; | |
if ( | |
visitedFragmentNames.has(fragName) || | |
!shouldIncludeNode(variableValues, selection) | |
) { | |
continue; | |
} | |
visitedFragmentNames.add(fragName); | |
const fragment = fragments[fragName]; | |
if ( | |
!fragment || | |
!doesFragmentConditionMatch(schema, fragment, runtimeType) | |
) { | |
continue; | |
} | |
collectFieldsImpl( | |
schema, | |
fragments, | |
variableValues, | |
runtimeType, | |
fragment.selectionSet, | |
fields, | |
visitedFragmentNames, | |
); | |
break; | |
} | |
} | |
} | |
} | |
/** | |
* Determines if a field should be included based on the `@include` and `@skip` | |
* directives, where `@skip` has higher precedence than `@include`. | |
*/ | |
function shouldIncludeNode(variableValues, node) { | |
const skip = getDirectiveValues(GraphQLSkipDirective, node, variableValues); | |
if ((skip === null || skip === void 0 ? void 0 : skip.if) === true) { | |
return false; | |
} | |
const include = getDirectiveValues( | |
GraphQLIncludeDirective, | |
node, | |
variableValues, | |
); | |
if ( | |
(include === null || include === void 0 ? void 0 : include.if) === false | |
) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Determines if a fragment is applicable to the given type. | |
*/ | |
function doesFragmentConditionMatch(schema, fragment, type) { | |
const typeConditionNode = fragment.typeCondition; | |
if (!typeConditionNode) { | |
return true; | |
} | |
const conditionalType = typeFromAST(schema, typeConditionNode); | |
if (conditionalType === type) { | |
return true; | |
} | |
if (isAbstractType(conditionalType)) { | |
return schema.isSubType(conditionalType, type); | |
} | |
return false; | |
} | |
/** | |
* Implements the logic to compute the key of a given field's entry | |
*/ | |
function getFieldEntryKey(node) { | |
return node.alias ? node.alias.value : node.name.value; | |
} | |