@graphql-hive/gateway-plugin-console-sdk
Version:
GraphQL Hive + GraphQL Hive Gateway
132 lines (131 loc) • 5.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.addTypenames = addTypenames;
const graphql_1 = require("graphql");
const TYPENAME_FIELD = {
kind: 'Field',
name: { kind: 'Name', value: '__typename' },
};
/**
* Recursively adds __typename to every selection set whose parent type is
* abstract (union or interface), or whose parent type is a concrete object
* type that implements an abstract type (i.e. it appears as a possible type
* of some interface or union in the schema).
*
* Requires the schema so it can resolve field return types as it walks the
* document tree.
*/
function addTypenames(document, schema) {
let definitionsChanged = false;
let newDefinitions = null;
for (let i = 0; i < document.definitions.length; i++) {
const def = document.definitions[i];
let newDef = def;
if (def.kind === 'OperationDefinition') {
const rootType = def.operation === 'query'
? schema.getQueryType()
: def.operation === 'mutation'
? schema.getMutationType()
: schema.getSubscriptionType();
if (rootType) {
const newSelectionSet = walkSelectionSet(def.selectionSet, rootType, false, schema);
if (newSelectionSet !== def.selectionSet) {
newDef = Object.assign(Object.assign({}, def), { selectionSet: newSelectionSet });
}
}
}
else if (def.kind === 'FragmentDefinition') {
const onType = schema.getType(def.typeCondition.name.value);
if ((0, graphql_1.isCompositeType)(onType)) {
const newSelectionSet = walkSelectionSet(def.selectionSet, onType, (0, graphql_1.isAbstractType)(onType), schema);
if (newSelectionSet !== def.selectionSet) {
newDef = Object.assign(Object.assign({}, def), { selectionSet: newSelectionSet });
}
}
}
if (newDef !== def) {
if (!definitionsChanged) {
definitionsChanged = true;
newDefinitions = document.definitions.slice(0, i);
}
}
if (definitionsChanged && newDefinitions) {
newDefinitions.push(newDef);
}
}
if (!definitionsChanged) {
return document;
}
return Object.assign(Object.assign({}, document), { definitions: newDefinitions });
}
function walkSelectionSet(selectionSet, parentType, parentIsAbstract, schema) {
const thisTypeIsAbstract = (0, graphql_1.isAbstractType)(parentType);
const checkTypename = thisTypeIsAbstract ||
(parentIsAbstract && (0, graphql_1.isObjectType)(parentType) && parentType.getInterfaces().length > 0);
let selectionsChanged = false;
let newSelections = null;
let hasTypename = false;
const selections = selectionSet.selections;
for (let i = 0; i < selections.length; i++) {
const selection = selections[i];
let newSelection = selection;
if (selection.kind === 'Field') {
if (checkTypename && selection.name.value === '__typename') {
hasTypename = true;
}
if (selection.selectionSet) {
const fieldDef = getFieldDef(parentType, selection);
if (fieldDef) {
const fieldType = (0, graphql_1.getNamedType)(fieldDef.type);
if ((0, graphql_1.isCompositeType)(fieldType)) {
const newSelectionSet = walkSelectionSet(selection.selectionSet, fieldType, (0, graphql_1.isAbstractType)(fieldType), schema);
if (newSelectionSet !== selection.selectionSet) {
newSelection = Object.assign(Object.assign({}, selection), { selectionSet: newSelectionSet });
}
}
}
}
}
else if (selection.kind === 'InlineFragment') {
const branchType = selection.typeCondition
? schema.getType(selection.typeCondition.name.value)
: parentType;
if ((0, graphql_1.isCompositeType)(branchType)) {
const newSelectionSet = walkSelectionSet(selection.selectionSet, branchType, (0, graphql_1.isAbstractType)(branchType), schema);
if (newSelectionSet !== selection.selectionSet) {
newSelection = Object.assign(Object.assign({}, selection), { selectionSet: newSelectionSet });
}
}
}
if (newSelection !== selection) {
if (!selectionsChanged) {
selectionsChanged = true;
// This logic avoids copying the selection list before it's absolutely necessary.
// Therefore, no additional memory will be wasted copying when iterating over
// concrete, non-implementing object types.
newSelections = selections.slice(0, i);
}
}
if (selectionsChanged && newSelections) {
newSelections.push(newSelection);
}
}
const needsTypename = checkTypename && !hasTypename;
if (!needsTypename && !selectionsChanged) {
return selectionSet;
}
const finalSelections = selectionsChanged && newSelections ? newSelections : [...selections];
if (needsTypename) {
finalSelections.push(TYPENAME_FIELD);
}
return Object.assign(Object.assign({}, selectionSet), { selections: finalSelections });
}
function getFieldDef(parentType, field) {
var _a;
const name = field.name.value;
// ignore internal fields like __schema, __type, and __typename
if (name.startsWith('__')) {
return null;
}
return 'getFields' in parentType ? ((_a = parentType.getFields()[name]) !== null && _a !== void 0 ? _a : null) : null;
}