UNPKG

@graphql-tools/stitch

Version:

A set of utils for faster development of GraphQL tools

426 lines (425 loc) • 20.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeCandidates = void 0; const graphql_1 = require("graphql"); const merge_1 = require("@graphql-tools/merge"); const mergeValidations_js_1 = require("./mergeValidations.js"); const delegate_1 = require("@graphql-tools/delegate"); function mergeCandidates(typeName, candidates, typeMergingOptions) { const initialCandidateType = candidates[0].type; if (candidates.some(candidate => candidate.type.constructor !== initialCandidateType.constructor)) { throw new Error(`Cannot merge different type categories into common type ${typeName}.`); } if ((0, graphql_1.isObjectType)(initialCandidateType)) { return mergeObjectTypeCandidates(typeName, candidates, typeMergingOptions); } else if ((0, graphql_1.isInputObjectType)(initialCandidateType)) { return mergeInputObjectTypeCandidates(typeName, candidates, typeMergingOptions); } else if ((0, graphql_1.isInterfaceType)(initialCandidateType)) { return mergeInterfaceTypeCandidates(typeName, candidates, typeMergingOptions); } else if ((0, graphql_1.isUnionType)(initialCandidateType)) { return mergeUnionTypeCandidates(typeName, candidates, typeMergingOptions); } else if ((0, graphql_1.isEnumType)(initialCandidateType)) { return mergeEnumTypeCandidates(typeName, candidates, typeMergingOptions); } else if ((0, graphql_1.isScalarType)(initialCandidateType)) { return mergeScalarTypeCandidates(typeName, candidates, typeMergingOptions); } else { // not reachable. throw new Error(`Type ${typeName} has unknown GraphQL type.`); } } exports.mergeCandidates = mergeCandidates; function mergeObjectTypeCandidates(typeName, candidates, typeMergingOptions) { candidates = orderedTypeCandidates(candidates, typeMergingOptions); const description = mergeTypeDescriptions(candidates, typeMergingOptions); const fields = fieldConfigMapFromTypeCandidates(candidates, typeMergingOptions); const typeConfigs = candidates.map(candidate => candidate.type.toConfig()); const interfaceMap = typeConfigs .map(typeConfig => typeConfig.interfaces) .reduce((acc, interfaces) => { if (interfaces != null) { for (const iface of interfaces) { acc[iface.name] = iface; } } return acc; }, Object.create(null)); const interfaces = Object.values(interfaceMap); const astNodes = pluck('astNode', candidates); const fieldAstNodes = canonicalFieldNamesForType(candidates) .map(fieldName => { var _a; return (_a = fields[fieldName]) === null || _a === void 0 ? void 0 : _a.astNode; }) .filter(n => n != null); if (astNodes.length > 1 && fieldAstNodes.length) { astNodes.push({ ...astNodes[astNodes.length - 1], fields: JSON.parse(JSON.stringify(fieldAstNodes)), }); } const astNode = astNodes .slice(1) .reduce((acc, astNode) => (0, merge_1.mergeType)(astNode, acc, { ignoreFieldConflicts: true }), astNodes[0]); const extensionASTNodes = pluck('extensionASTNodes', candidates); const extensions = Object.assign({}, ...pluck('extensions', candidates)); const typeConfig = { name: typeName, description, fields, interfaces, astNode, extensionASTNodes, extensions, }; return new graphql_1.GraphQLObjectType(typeConfig); } function mergeInputObjectTypeCandidates(typeName, candidates, typeMergingOptions) { candidates = orderedTypeCandidates(candidates, typeMergingOptions); const description = mergeTypeDescriptions(candidates, typeMergingOptions); const fields = inputFieldConfigMapFromTypeCandidates(candidates, typeMergingOptions); const astNodes = pluck('astNode', candidates); const fieldAstNodes = canonicalFieldNamesForType(candidates) .map(fieldName => { var _a; return (_a = fields[fieldName]) === null || _a === void 0 ? void 0 : _a.astNode; }) .filter(n => n != null); if (astNodes.length > 1 && fieldAstNodes.length) { astNodes.push({ ...astNodes[astNodes.length - 1], fields: JSON.parse(JSON.stringify(fieldAstNodes)), }); } const astNode = astNodes.slice(1).reduce((acc, astNode) => (0, merge_1.mergeInputType)(astNode, acc, { ignoreFieldConflicts: true, }), astNodes[0]); const extensionASTNodes = pluck('extensionASTNodes', candidates); const extensions = Object.assign({}, ...pluck('extensions', candidates)); const typeConfig = { name: typeName, description, fields, astNode, extensionASTNodes, extensions, }; return new graphql_1.GraphQLInputObjectType(typeConfig); } function pluck(typeProperty, candidates) { return candidates.map(candidate => candidate.type[typeProperty]).filter(value => value != null); } function mergeInterfaceTypeCandidates(typeName, candidates, typeMergingOptions) { candidates = orderedTypeCandidates(candidates, typeMergingOptions); const description = mergeTypeDescriptions(candidates, typeMergingOptions); const fields = fieldConfigMapFromTypeCandidates(candidates, typeMergingOptions); const typeConfigs = candidates.map(candidate => candidate.type.toConfig()); const interfaceMap = typeConfigs .map(typeConfig => ('interfaces' in typeConfig ? typeConfig.interfaces : [])) .reduce((acc, interfaces) => { if (interfaces != null) { for (const iface of interfaces) { acc[iface.name] = iface; } } return acc; }, Object.create(null)); const interfaces = Object.values(interfaceMap); const astNodes = pluck('astNode', candidates); const fieldAstNodes = canonicalFieldNamesForType(candidates) .map(fieldName => { var _a; return (_a = fields[fieldName]) === null || _a === void 0 ? void 0 : _a.astNode; }) .filter(n => n != null); if (astNodes.length > 1 && fieldAstNodes.length) { astNodes.push({ ...astNodes[astNodes.length - 1], fields: JSON.parse(JSON.stringify(fieldAstNodes)), }); } const astNode = astNodes.slice(1).reduce((acc, astNode) => (0, merge_1.mergeInterface)(astNode, acc, { ignoreFieldConflicts: true, }), astNodes[0]); const extensionASTNodes = pluck('extensionASTNodes', candidates); const extensions = Object.assign({}, ...pluck('extensions', candidates)); const typeConfig = { name: typeName, description, fields, interfaces, astNode, extensionASTNodes, extensions, }; return new graphql_1.GraphQLInterfaceType(typeConfig); } function mergeUnionTypeCandidates(typeName, candidates, typeMergingOptions) { candidates = orderedTypeCandidates(candidates, typeMergingOptions); const description = mergeTypeDescriptions(candidates, typeMergingOptions); const typeConfigs = candidates.map(candidate => { if (!(0, graphql_1.isUnionType)(candidate.type)) { throw new Error(`Expected ${candidate.type} to be a union type!`); } return candidate.type.toConfig(); }); const typeMap = typeConfigs.reduce((acc, typeConfig) => { for (const type of typeConfig.types) { acc[type.name] = type; } return acc; }, Object.create(null)); const types = Object.values(typeMap); const astNodes = pluck('astNode', candidates); const astNode = astNodes .slice(1) .reduce((acc, astNode) => (0, merge_1.mergeUnion)(astNode, acc), astNodes[0]); const extensionASTNodes = pluck('extensionASTNodes', candidates); const extensions = Object.assign({}, ...pluck('extensions', candidates)); const typeConfig = { name: typeName, description, types, astNode, extensionASTNodes, extensions, }; return new graphql_1.GraphQLUnionType(typeConfig); } function mergeEnumTypeCandidates(typeName, candidates, typeMergingOptions) { candidates = orderedTypeCandidates(candidates, typeMergingOptions); const description = mergeTypeDescriptions(candidates, typeMergingOptions); const values = enumValueConfigMapFromTypeCandidates(candidates, typeMergingOptions); const astNodes = pluck('astNode', candidates); const astNode = astNodes .slice(1) .reduce((acc, astNode) => (0, merge_1.mergeEnum)(astNode, acc, { consistentEnumMerge: true }), astNodes[0]); const extensionASTNodes = pluck('extensionASTNodes', candidates); const extensions = Object.assign({}, ...pluck('extensions', candidates)); const typeConfig = { name: typeName, description, values, astNode, extensionASTNodes, extensions, }; return new graphql_1.GraphQLEnumType(typeConfig); } function enumValueConfigMapFromTypeCandidates(candidates, typeMergingOptions) { var _a; const enumValueConfigCandidatesMap = Object.create(null); for (const candidate of candidates) { const valueMap = candidate.type.toConfig().values; for (const enumValue in valueMap) { const enumValueConfigCandidate = { enumValueConfig: valueMap[enumValue], enumValue, type: candidate.type, subschema: candidate.subschema, transformedSubschema: candidate.transformedSubschema, }; if (enumValue in enumValueConfigCandidatesMap) { enumValueConfigCandidatesMap[enumValue].push(enumValueConfigCandidate); } else { enumValueConfigCandidatesMap[enumValue] = [enumValueConfigCandidate]; } } } const enumValueConfigMap = Object.create(null); for (const enumValue in enumValueConfigCandidatesMap) { const enumValueConfigMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.enumValueConfigMerger) !== null && _a !== void 0 ? _a : defaultEnumValueConfigMerger; enumValueConfigMap[enumValue] = enumValueConfigMerger(enumValueConfigCandidatesMap[enumValue]); } return enumValueConfigMap; } function defaultEnumValueConfigMerger(candidates) { const preferred = candidates.find(({ type, transformedSubschema }) => { var _a, _b; return (0, delegate_1.isSubschemaConfig)(transformedSubschema) && ((_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.canonical); }); return (preferred || candidates[candidates.length - 1]).enumValueConfig; } function mergeScalarTypeCandidates(typeName, candidates, typeMergingOptions) { candidates = orderedTypeCandidates(candidates, typeMergingOptions); const description = mergeTypeDescriptions(candidates, typeMergingOptions); const serializeFns = pluck('serialize', candidates); const serialize = serializeFns[serializeFns.length - 1]; const parseValueFns = pluck('parseValue', candidates); const parseValue = parseValueFns[parseValueFns.length - 1]; const parseLiteralFns = pluck('parseLiteral', candidates); const parseLiteral = parseLiteralFns[parseLiteralFns.length - 1]; const astNodes = pluck('astNode', candidates); const astNode = astNodes .slice(1) .reduce((acc, astNode) => (0, merge_1.mergeScalar)(astNode, acc), astNodes[0]); const extensionASTNodes = pluck('extensionASTNodes', candidates); const extensions = Object.assign({}, ...pluck('extensions', candidates)); const typeConfig = { name: typeName, description, serialize, parseValue, parseLiteral, astNode, extensionASTNodes, extensions, }; return new graphql_1.GraphQLScalarType(typeConfig); } function orderedTypeCandidates(candidates, typeMergingOptions) { var _a; const typeCandidateMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.typeCandidateMerger) !== null && _a !== void 0 ? _a : defaultTypeCandidateMerger; const candidate = typeCandidateMerger(candidates); return candidates.filter(c => c !== candidate).concat([candidate]); } function defaultTypeCandidateMerger(candidates) { const canonical = candidates.filter(({ type, transformedSubschema }) => { var _a, _b; return (0, delegate_1.isSubschemaConfig)(transformedSubschema) ? (_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.canonical : false; }); if (canonical.length > 1) { throw new Error(`Multiple canonical definitions for "${canonical[0].type.name}"`); } else if (canonical.length) { return canonical[0]; } return candidates[candidates.length - 1]; } function mergeTypeDescriptions(candidates, typeMergingOptions) { var _a; const typeDescriptionsMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.typeDescriptionsMerger) !== null && _a !== void 0 ? _a : defaultTypeDescriptionMerger; return typeDescriptionsMerger(candidates); } function defaultTypeDescriptionMerger(candidates) { return candidates[candidates.length - 1].type.description; } function fieldConfigMapFromTypeCandidates(candidates, typeMergingOptions) { const fieldConfigCandidatesMap = Object.create(null); for (const candidate of candidates) { const typeConfig = candidate.type.toConfig(); const fieldConfigMap = typeConfig.fields; for (const fieldName in fieldConfigMap) { const fieldConfig = fieldConfigMap[fieldName]; const fieldConfigCandidate = { fieldConfig, fieldName, type: candidate.type, subschema: candidate.subschema, transformedSubschema: candidate.transformedSubschema, }; if (fieldName in fieldConfigCandidatesMap) { fieldConfigCandidatesMap[fieldName].push(fieldConfigCandidate); } else { fieldConfigCandidatesMap[fieldName] = [fieldConfigCandidate]; } } } const fieldConfigMap = Object.create(null); for (const fieldName in fieldConfigCandidatesMap) { fieldConfigMap[fieldName] = mergeFieldConfigs(fieldConfigCandidatesMap[fieldName], typeMergingOptions); } return fieldConfigMap; } function mergeFieldConfigs(candidates, typeMergingOptions) { var _a; const fieldConfigMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.fieldConfigMerger) !== null && _a !== void 0 ? _a : defaultFieldConfigMerger; const finalFieldConfig = fieldConfigMerger(candidates); (0, mergeValidations_js_1.validateFieldConsistency)(finalFieldConfig, candidates, typeMergingOptions); return finalFieldConfig; } function defaultFieldConfigMerger(candidates) { var _a, _b, _c, _d, _e, _f; const canonicalByField = []; const canonicalByType = []; for (const { type, fieldName, fieldConfig, transformedSubschema } of candidates) { if (!(0, delegate_1.isSubschemaConfig)(transformedSubschema)) continue; if ((_d = (_c = (_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[fieldName]) === null || _d === void 0 ? void 0 : _d.canonical) { canonicalByField.push(fieldConfig); } else if ((_f = (_e = transformedSubschema.merge) === null || _e === void 0 ? void 0 : _e[type.name]) === null || _f === void 0 ? void 0 : _f.canonical) { canonicalByType.push(fieldConfig); } } if (canonicalByField.length > 1) { throw new Error(`Multiple canonical definitions for "${candidates[0].type.name}.${candidates[0].fieldName}"`); } else if (canonicalByField.length) { return canonicalByField[0]; } else if (canonicalByType.length) { return canonicalByType[0]; } return candidates[candidates.length - 1].fieldConfig; } function inputFieldConfigMapFromTypeCandidates(candidates, typeMergingOptions) { var _a; const inputFieldConfigCandidatesMap = Object.create(null); const fieldInclusionMap = Object.create(null); for (const candidate of candidates) { const typeConfig = candidate.type.toConfig(); const inputFieldConfigMap = typeConfig.fields; for (const fieldName in inputFieldConfigMap) { const inputFieldConfig = inputFieldConfigMap[fieldName]; fieldInclusionMap[fieldName] = fieldInclusionMap[fieldName] || 0; fieldInclusionMap[fieldName] += 1; const inputFieldConfigCandidate = { inputFieldConfig, fieldName, type: candidate.type, subschema: candidate.subschema, transformedSubschema: candidate.transformedSubschema, }; if (fieldName in inputFieldConfigCandidatesMap) { inputFieldConfigCandidatesMap[fieldName].push(inputFieldConfigCandidate); } else { inputFieldConfigCandidatesMap[fieldName] = [inputFieldConfigCandidate]; } } } (0, mergeValidations_js_1.validateInputObjectConsistency)(fieldInclusionMap, candidates, typeMergingOptions); const inputFieldConfigMap = Object.create(null); for (const fieldName in inputFieldConfigCandidatesMap) { const inputFieldConfigMerger = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.inputFieldConfigMerger) !== null && _a !== void 0 ? _a : defaultInputFieldConfigMerger; inputFieldConfigMap[fieldName] = inputFieldConfigMerger(inputFieldConfigCandidatesMap[fieldName]); (0, mergeValidations_js_1.validateInputFieldConsistency)(inputFieldConfigMap[fieldName], inputFieldConfigCandidatesMap[fieldName], typeMergingOptions); } return inputFieldConfigMap; } function defaultInputFieldConfigMerger(candidates) { var _a, _b, _c, _d, _e, _f; const canonicalByField = []; const canonicalByType = []; for (const { type, fieldName, inputFieldConfig, transformedSubschema } of candidates) { if (!(0, delegate_1.isSubschemaConfig)(transformedSubschema)) continue; if ((_d = (_c = (_b = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[fieldName]) === null || _d === void 0 ? void 0 : _d.canonical) { canonicalByField.push(inputFieldConfig); } else if ((_f = (_e = transformedSubschema.merge) === null || _e === void 0 ? void 0 : _e[type.name]) === null || _f === void 0 ? void 0 : _f.canonical) { canonicalByType.push(inputFieldConfig); } } if (canonicalByField.length > 1) { throw new Error(`Multiple canonical definitions for "${candidates[0].type.name}.${candidates[0].fieldName}"`); } else if (canonicalByField.length) { return canonicalByField[0]; } else if (canonicalByType.length) { return canonicalByType[0]; } return candidates[candidates.length - 1].inputFieldConfig; } function canonicalFieldNamesForType(candidates) { var _a; const canonicalFieldNames = Object.create(null); for (const { type, transformedSubschema } of candidates) { if (!(0, delegate_1.isSubschemaConfig)(transformedSubschema)) continue; const mergeConfig = (_a = transformedSubschema.merge) === null || _a === void 0 ? void 0 : _a[type.name]; if (mergeConfig != null && mergeConfig.fields != null && !mergeConfig.canonical) { for (const fieldName in mergeConfig.fields) { const mergedFieldConfig = mergeConfig.fields[fieldName]; if (mergedFieldConfig.canonical) { canonicalFieldNames[fieldName] = true; } } } } return Object.keys(canonicalFieldNames); }