@graphql-tools/utils
Version:
Common package containing utils and types for GraphQL tools
354 lines (353 loc) • 12.5 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildOperationNodeForField = buildOperationNodeForField;
const graphql_1 = require("graphql");
const rootTypes_js_1 = require("./rootTypes.js");
let operationVariables = [];
let fieldTypeMap = new Map();
function addOperationVariable(variable) {
operationVariables.push(variable);
}
function resetOperationVariables() {
operationVariables = [];
}
function resetFieldMap() {
fieldTypeMap = new Map();
}
function buildOperationNodeForField({ schema, kind, field, models, ignore = [], depthLimit, circularReferenceDepth, argNames, selectedFields = true, }) {
resetOperationVariables();
resetFieldMap();
const rootTypeNames = (0, rootTypes_js_1.getRootTypeNames)(schema);
const operationNode = buildOperationAndCollectVariables({
schema,
fieldName: field,
kind,
models: models || [],
ignore,
depthLimit: depthLimit || Infinity,
circularReferenceDepth: circularReferenceDepth || 1,
argNames,
selectedFields,
rootTypeNames,
});
// attach variables
operationNode.variableDefinitions = [...operationVariables];
resetOperationVariables();
resetFieldMap();
return operationNode;
}
function buildOperationAndCollectVariables({ schema, fieldName, kind, models, ignore, depthLimit, circularReferenceDepth, argNames, selectedFields, rootTypeNames, }) {
const type = (0, rootTypes_js_1.getDefinedRootType)(schema, kind);
const field = type.getFields()[fieldName];
const operationName = `${fieldName}_${kind}`;
if (field.args) {
for (const arg of field.args) {
const argName = arg.name;
if (!argNames || argNames.includes(argName)) {
addOperationVariable(resolveVariable(arg, argName));
}
}
}
return {
kind: graphql_1.Kind.OPERATION_DEFINITION,
operation: kind,
name: {
kind: graphql_1.Kind.NAME,
value: operationName,
},
variableDefinitions: [],
selectionSet: {
kind: graphql_1.Kind.SELECTION_SET,
selections: [
resolveField({
type,
field,
models,
firstCall: true,
path: [],
ancestors: [],
ignore,
depthLimit,
circularReferenceDepth,
schema,
depth: 0,
argNames,
selectedFields,
rootTypeNames,
}),
],
},
};
}
function resolveSelectionSet({ parent, type, models, firstCall, path, ancestors, ignore, depthLimit, circularReferenceDepth, schema, depth, argNames, selectedFields, rootTypeNames, }) {
if (typeof selectedFields === 'boolean' && depth > depthLimit) {
return;
}
if ((0, graphql_1.isUnionType)(type)) {
const types = type.getTypes();
return {
kind: graphql_1.Kind.SELECTION_SET,
selections: types
.filter(t => !hasCircularRef([...ancestors, t], {
depth: circularReferenceDepth,
}))
.map(t => {
return {
kind: graphql_1.Kind.INLINE_FRAGMENT,
typeCondition: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: t.name,
},
},
selectionSet: resolveSelectionSet({
parent: type,
type: t,
models,
path,
ancestors,
ignore,
depthLimit,
circularReferenceDepth,
schema,
depth,
argNames,
selectedFields,
rootTypeNames,
}),
};
})
.filter(fragmentNode => fragmentNode?.selectionSet?.selections?.length > 0),
};
}
if ((0, graphql_1.isInterfaceType)(type)) {
const types = Object.values(schema.getTypeMap()).filter((t) => (0, graphql_1.isObjectType)(t) && t.getInterfaces().includes(type));
return {
kind: graphql_1.Kind.SELECTION_SET,
selections: types
.filter(t => !hasCircularRef([...ancestors, t], {
depth: circularReferenceDepth,
}))
.map(t => {
return {
kind: graphql_1.Kind.INLINE_FRAGMENT,
typeCondition: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: t.name,
},
},
selectionSet: resolveSelectionSet({
parent: type,
type: t,
models,
path,
ancestors,
ignore,
depthLimit,
circularReferenceDepth,
schema,
depth,
argNames,
selectedFields,
rootTypeNames,
}),
};
})
.filter(fragmentNode => fragmentNode?.selectionSet?.selections?.length > 0),
};
}
if ((0, graphql_1.isObjectType)(type) && !rootTypeNames.has(type.name)) {
const isIgnored = ignore.includes(type.name) || ignore.includes(`${parent.name}.${path[path.length - 1]}`);
const isModel = models.includes(type.name);
if (!firstCall && isModel && !isIgnored) {
return {
kind: graphql_1.Kind.SELECTION_SET,
selections: [
{
kind: graphql_1.Kind.FIELD,
name: {
kind: graphql_1.Kind.NAME,
value: 'id',
},
},
],
};
}
const fields = type.getFields();
return {
kind: graphql_1.Kind.SELECTION_SET,
selections: Object.keys(fields)
.filter(fieldName => {
return !hasCircularRef([...ancestors, (0, graphql_1.getNamedType)(fields[fieldName].type)], {
depth: circularReferenceDepth,
});
})
.map(fieldName => {
const selectedSubFields = typeof selectedFields === 'object' ? selectedFields[fieldName] : true;
if (selectedSubFields) {
return resolveField({
type,
field: fields[fieldName],
models,
path: [...path, fieldName],
ancestors,
ignore,
depthLimit,
circularReferenceDepth,
schema,
depth,
argNames,
selectedFields: selectedSubFields,
rootTypeNames,
});
}
return null;
})
.filter((f) => {
if (f == null) {
return false;
}
else if ('selectionSet' in f) {
return !!f.selectionSet?.selections?.length;
}
return true;
}),
};
}
}
function resolveVariable(arg, name) {
function resolveVariableType(type) {
if ((0, graphql_1.isListType)(type)) {
return {
kind: graphql_1.Kind.LIST_TYPE,
type: resolveVariableType(type.ofType),
};
}
if ((0, graphql_1.isNonNullType)(type)) {
return {
kind: graphql_1.Kind.NON_NULL_TYPE,
// for v16 compatibility
type: resolveVariableType(type.ofType),
};
}
return {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: type.name,
},
};
}
return {
kind: graphql_1.Kind.VARIABLE_DEFINITION,
variable: {
kind: graphql_1.Kind.VARIABLE,
name: {
kind: graphql_1.Kind.NAME,
value: name || arg.name,
},
},
type: resolveVariableType(arg.type),
};
}
function getArgumentName(name, path) {
return [...path, name].join('_');
}
function resolveField({ type, field, models, firstCall, path, ancestors, ignore, depthLimit, circularReferenceDepth, schema, depth, argNames, selectedFields, rootTypeNames, }) {
const namedType = (0, graphql_1.getNamedType)(field.type);
let args = [];
let removeField = false;
if (field.args && field.args.length) {
args = field.args
.map(arg => {
const argumentName = getArgumentName(arg.name, path);
if (argNames && !argNames.includes(argumentName)) {
if ((0, graphql_1.isNonNullType)(arg.type)) {
removeField = true;
}
return null;
}
if (!firstCall) {
addOperationVariable(resolveVariable(arg, argumentName));
}
return {
kind: graphql_1.Kind.ARGUMENT,
name: {
kind: graphql_1.Kind.NAME,
value: arg.name,
},
value: {
kind: graphql_1.Kind.VARIABLE,
name: {
kind: graphql_1.Kind.NAME,
value: getArgumentName(arg.name, path),
},
},
};
})
.filter(Boolean);
}
if (removeField) {
return null;
}
const fieldPath = [...path, field.name];
const fieldPathStr = fieldPath.join('.');
let fieldName = field.name;
if (fieldTypeMap.has(fieldPathStr) && fieldTypeMap.get(fieldPathStr) !== field.type.toString()) {
fieldName += field.type
.toString()
.replace('!', 'NonNull')
.replace('[', 'List')
.replace(']', '');
}
fieldTypeMap.set(fieldPathStr, field.type.toString());
if (!(0, graphql_1.isScalarType)(namedType) && !(0, graphql_1.isEnumType)(namedType)) {
return {
kind: graphql_1.Kind.FIELD,
name: {
kind: graphql_1.Kind.NAME,
value: field.name,
},
...(fieldName !== field.name && { alias: { kind: graphql_1.Kind.NAME, value: fieldName } }),
selectionSet: resolveSelectionSet({
parent: type,
type: namedType,
models,
firstCall,
path: fieldPath,
ancestors: [...ancestors, type],
ignore,
depthLimit,
circularReferenceDepth,
schema,
depth: depth + 1,
argNames,
selectedFields,
rootTypeNames,
}) || undefined,
arguments: args,
};
}
return {
kind: graphql_1.Kind.FIELD,
name: {
kind: graphql_1.Kind.NAME,
value: field.name,
},
...(fieldName !== field.name && { alias: { kind: graphql_1.Kind.NAME, value: fieldName } }),
arguments: args,
};
}
function hasCircularRef(types, config = {
depth: 1,
}) {
const type = types[types.length - 1];
if ((0, graphql_1.isScalarType)(type)) {
return false;
}
const size = types.filter(t => t.name === type.name).length;
return size > config.depth;
}
;