@graphql-tools/merge
Version:
A set of utils for faster development of GraphQL tools
82 lines (81 loc) • 3.72 kB
JavaScript
import { extractType, isWrappingTypeNode, isListTypeNode, isNonNullTypeNode, printTypeNode } from './utils.js';
import { mergeDirectives } from './directives.js';
import { compareNodes } from '@graphql-tools/utils';
import { mergeArguments } from './arguments.js';
function fieldAlreadyExists(fieldsArr, otherField, config) {
const result = fieldsArr.find(field => field.name.value === otherField.name.value);
if (result && !(config === null || config === void 0 ? void 0 : config.ignoreFieldConflicts)) {
const t1 = extractType(result.type);
const t2 = extractType(otherField.type);
if (t1.name.value !== t2.name.value) {
throw new Error(`Field "${otherField.name.value}" already defined with a different type. Declared as "${t1.name.value}", but you tried to override with "${t2.name.value}"`);
}
}
return !!result;
}
export function mergeFields(type, f1, f2, config) {
const result = [];
if (f2 != null) {
result.push(...f2);
}
if (f1 != null) {
for (const field of f1) {
if (fieldAlreadyExists(result, field, config)) {
const existing = result.find((f) => f.name.value === field.name.value);
if (!(config === null || config === void 0 ? void 0 : config.ignoreFieldConflicts)) {
if (config === null || config === void 0 ? void 0 : config.throwOnConflict) {
preventConflicts(type, existing, field, false);
}
else {
preventConflicts(type, existing, field, true);
}
if (isNonNullTypeNode(field.type) && !isNonNullTypeNode(existing.type)) {
existing.type = field.type;
}
}
existing.arguments = mergeArguments(field['arguments'] || [], existing.arguments || [], config);
existing.directives = mergeDirectives(field.directives, existing.directives, config);
existing.description = field.description || existing.description;
}
else {
result.push(field);
}
}
}
if (config && config.sort) {
result.sort(compareNodes);
}
if (config && config.exclusions) {
const exclusions = config.exclusions;
return result.filter(field => !exclusions.includes(`${type.name.value}.${field.name.value}`));
}
return result;
}
function preventConflicts(type, a, b, ignoreNullability = false) {
const aType = printTypeNode(a.type);
const bType = printTypeNode(b.type);
if (aType !== bType && !safeChangeForFieldType(a.type, b.type, ignoreNullability)) {
throw new Error(`Field '${type.name.value}.${a.name.value}' changed type from '${aType}' to '${bType}'`);
}
}
function safeChangeForFieldType(oldType, newType, ignoreNullability = false) {
// both are named
if (!isWrappingTypeNode(oldType) && !isWrappingTypeNode(newType)) {
return oldType.toString() === newType.toString();
}
// new is non-null
if (isNonNullTypeNode(newType)) {
const ofType = isNonNullTypeNode(oldType) ? oldType.type : oldType;
return safeChangeForFieldType(ofType, newType.type);
}
// old is non-null
if (isNonNullTypeNode(oldType)) {
return safeChangeForFieldType(newType, oldType, ignoreNullability);
}
// old is list
if (isListTypeNode(oldType)) {
return ((isListTypeNode(newType) && safeChangeForFieldType(oldType.type, newType.type)) ||
(isNonNullTypeNode(newType) && safeChangeForFieldType(oldType, newType['type'])));
}
return false;
}