UNPKG

@graphql-tools/utils

Version:

Common package containing utils and types for GraphQL tools

237 lines (236 loc) • 8.66 kB
import { getNamedType, GraphQLDirective, GraphQLEnumType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType, isEnumType, isInputObjectType, isInterfaceType, isListType, isNamedType, isNonNullType, isObjectType, isScalarType, isSpecifiedDirective, isSpecifiedScalarType, isUnionType, } from 'graphql'; import { getBuiltInForStub, isNamedStub } from './stub.js'; export function rewireTypes(originalTypeMap, directives) { const referenceTypeMap = Object.create(null); for (const typeName in originalTypeMap) { referenceTypeMap[typeName] = originalTypeMap[typeName]; } const newTypeMap = Object.create(null); for (const typeName in referenceTypeMap) { const namedType = referenceTypeMap[typeName]; if (namedType == null || typeName.startsWith('__')) { continue; } const newName = namedType.name; if (newName.startsWith('__')) { continue; } if (newTypeMap[newName] != null) { console.warn(`Duplicate schema type name ${newName} found; keeping the existing one found in the schema`); continue; } newTypeMap[newName] = namedType; } const typesToRewire = typesThatNeedRewire(newTypeMap, referenceTypeMap); for (const typeName of typesToRewire) { newTypeMap[typeName] = rewireNamedType(newTypeMap[typeName]); } const newDirectives = directives.map(directive => directiveNeedsRewire(directive, referenceTypeMap, typesToRewire) ? rewireDirective(directive) : directive); return { typeMap: newTypeMap, directives: newDirectives, }; function rewireDirective(directive) { if (isSpecifiedDirective(directive)) { return directive; } const directiveConfig = directive.toConfig(); directiveConfig.args = rewireArgs(directiveConfig.args); return new GraphQLDirective(directiveConfig); } function rewireArgs(args) { const rewiredArgs = {}; for (const argName in args) { const arg = args[argName]; const rewiredArgType = rewireType(arg.type); if (rewiredArgType != null) { arg.type = rewiredArgType; rewiredArgs[argName] = arg; } } return rewiredArgs; } function rewireNamedType(type) { if (isObjectType(type)) { const config = type.toConfig(); const newConfig = { ...config, fields: () => rewireFields(config.fields), interfaces: () => rewireNamedTypes(config.interfaces), }; return new GraphQLObjectType(newConfig); } else if (isInterfaceType(type)) { const config = type.toConfig(); const newConfig = { ...config, fields: () => rewireFields(config.fields), }; if ('interfaces' in newConfig) { newConfig.interfaces = () => rewireNamedTypes(config.interfaces); } return new GraphQLInterfaceType(newConfig); } else if (isUnionType(type)) { const config = type.toConfig(); const newConfig = { ...config, types: () => rewireNamedTypes(config.types), }; return new GraphQLUnionType(newConfig); } else if (isInputObjectType(type)) { const config = type.toConfig(); const newConfig = { ...config, fields: () => rewireInputFields(config.fields), }; return new GraphQLInputObjectType(newConfig); } else if (isEnumType(type)) { const enumConfig = type.toConfig(); return new GraphQLEnumType(enumConfig); } else if (isScalarType(type)) { if (isSpecifiedScalarType(type)) { return type; } const scalarConfig = type.toConfig(); return new GraphQLScalarType(scalarConfig); } throw new Error(`Unexpected schema type: ${type}`); } function rewireFields(fields) { const rewiredFields = {}; for (const fieldName in fields) { const field = fields[fieldName]; const rewiredFieldType = rewireType(field.type); if (rewiredFieldType != null && field.args) { field.type = rewiredFieldType; field.args = rewireArgs(field.args); rewiredFields[fieldName] = field; } } return rewiredFields; } function rewireInputFields(fields) { const rewiredFields = {}; for (const fieldName in fields) { const field = fields[fieldName]; const rewiredFieldType = rewireType(field.type); if (rewiredFieldType != null) { field.type = rewiredFieldType; rewiredFields[fieldName] = field; } } return rewiredFields; } function rewireNamedTypes(namedTypes) { const rewiredTypes = []; for (const namedType of namedTypes) { const rewiredType = rewireType(namedType); if (rewiredType != null) { rewiredTypes.push(rewiredType); } } return rewiredTypes; } function rewireType(type) { if (isListType(type)) { const rewiredType = rewireType(type.ofType); return rewiredType != null ? new GraphQLList(rewiredType) : null; } else if (isNonNullType(type)) { const rewiredType = rewireType(type.ofType); return rewiredType != null ? new GraphQLNonNull(rewiredType) : null; } else if (isNamedType(type)) { let rewiredType = referenceTypeMap[type.name]; if (rewiredType === undefined) { rewiredType = isNamedStub(type) ? getBuiltInForStub(type) : rewireNamedType(type); newTypeMap[rewiredType.name] = referenceTypeMap[type.name] = rewiredType; } return rewiredType != null ? newTypeMap[rewiredType.name] : null; } return null; } } function typesThatNeedRewire(typeMap, referenceTypeMap) { const needsRewire = new Set(); let added = true; while (added) { added = false; for (const typeName in typeMap) { if (needsRewire.has(typeName)) { continue; } if (typeNeedsRewire(typeMap[typeName], referenceTypeMap, needsRewire)) { needsRewire.add(typeName); added = true; } } } return needsRewire; } function typeNeedsRewire(type, referenceTypeMap, needsRewire, visiting = new Set()) { if (visiting.has(type)) { return false; } visiting.add(type); for (const ref of namedTypeReferences(type)) { if (needsRewire.has(ref.name)) { return true; } if (replacementFor(ref, referenceTypeMap) !== ref) { return true; } if (!(ref.name in referenceTypeMap) && typeNeedsRewire(ref, referenceTypeMap, needsRewire, visiting)) { return true; } } return false; } function directiveNeedsRewire(directive, referenceTypeMap, needsRewire) { if (isSpecifiedDirective(directive)) { return false; } for (const arg of directive.args) { const ref = getNamedType(arg.type); if (needsRewire.has(ref.name) || replacementFor(ref, referenceTypeMap) !== ref) { return true; } } return false; } function replacementFor(ref, referenceTypeMap) { if (ref.name in referenceTypeMap) { return referenceTypeMap[ref.name]; } return ref; } function namedTypeReferences(type) { const refs = []; if (isObjectType(type) || isInterfaceType(type)) { for (const iface of type.getInterfaces()) { refs.push(iface); } for (const field of Object.values(type.getFields())) { refs.push(getNamedType(field.type)); for (const arg of field.args) { refs.push(getNamedType(arg.type)); } } } else if (isUnionType(type)) { refs.push(...type.getTypes()); } else if (isInputObjectType(type)) { for (const field of Object.values(type.getFields())) { refs.push(getNamedType(field.type)); } } return refs; }