UNPKG

@graphql-tools/federation

Version:

Useful tools to create and manipulate GraphQL schemas.

887 lines • 53.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getStitchedSchemaFromSupergraphSdl = exports.getSubschemasFromSupergraphSdl = exports.getFieldMergerFromSupergraphSdl = exports.ensureSupergraphSDLAst = void 0; const graphql_1 = require("graphql"); const delegate_1 = require("@graphql-tools/delegate"); const executor_http_1 = require("@graphql-tools/executor-http"); const schema_1 = require("@graphql-tools/schema"); const stitch_1 = require("@graphql-tools/stitch"); const utils_1 = require("@graphql-tools/utils"); const utils_js_1 = require("./utils.js"); function ensureSupergraphSDLAst(supergraphSdl) { return typeof supergraphSdl === 'string' ? (0, graphql_1.parse)(supergraphSdl, { noLocation: true }) : supergraphSdl; } exports.ensureSupergraphSDLAst = ensureSupergraphSDLAst; function getTypeFieldMapFromSupergraphAST(supergraphAST) { const typeFieldASTMap = new Map(); for (const definition of supergraphAST.definitions) { if ('fields' in definition) { const fieldMap = new Map(); typeFieldASTMap.set(definition.name.value, fieldMap); for (const field of definition.fields || []) { fieldMap.set(field.name.value, field); } } } return typeFieldASTMap; } const rootTypeMap = new Map([ ['Query', 'query'], ['Mutation', 'mutation'], ['Subscription', 'subscription'], ]); function getFieldMergerFromSupergraphSdl(supergraphSdl) { const supergraphAST = ensureSupergraphSDLAst(supergraphSdl); const typeFieldASTMap = getTypeFieldMapFromSupergraphAST(supergraphAST); const defaultMerger = (0, stitch_1.getDefaultFieldConfigMerger)(true); const memoizedASTPrint = (0, utils_1.memoize1)(graphql_1.print); const memoizedTypePrint = (0, utils_1.memoize1)((type) => type.toString()); return function (candidates) { if (candidates.length === 1 || candidates.some(candidate => candidate.fieldName === '_entities')) { return candidates[0].fieldConfig; } if (candidates.some(candidate => rootTypeMap.has(candidate.type.name))) { const candidateNames = new Set(); const realCandidates = []; for (const candidate of candidates.toReversed ? candidates.toReversed() : [...candidates].reverse()) { if (candidate.transformedSubschema?.name && !candidateNames.has(candidate.transformedSubschema.name)) { candidateNames.add(candidate.transformedSubschema.name); realCandidates.push(candidate); } } const defaultMergedField = defaultMerger(candidates); return { ...defaultMergedField, resolve(_root, _args, context, info) { let currentSubschema; let currentScore = Infinity; let currentUnavailableSelectionSet; let currentFriendSubschemas; let currentAvailableSelectionSet; const originalSelectionSet = { kind: graphql_1.Kind.SELECTION_SET, selections: info.fieldNodes, }; // Find the best subschema to delegate this selection for (const candidate of realCandidates) { if (candidate.transformedSubschema) { const unavailableFields = (0, delegate_1.extractUnavailableFieldsFromSelectionSet)(candidate.transformedSubschema.transformedSchema, candidate.type, originalSelectionSet, () => true); const score = (0, stitch_1.calculateSelectionScore)(unavailableFields); if (score < currentScore) { currentScore = score; currentSubschema = candidate.transformedSubschema; currentFriendSubschemas = new Map(); currentUnavailableSelectionSet = { kind: graphql_1.Kind.SELECTION_SET, selections: unavailableFields, }; currentAvailableSelectionSet = (0, delegate_1.subtractSelectionSets)(originalSelectionSet, currentUnavailableSelectionSet); // Make parallel requests if there are other subschemas // that can resolve the remaining fields for this selection directly from the root field // instead of applying a type merging in advance for (const friendCandidate of realCandidates) { if (friendCandidate === candidate || !friendCandidate.transformedSubschema) { continue; } const unavailableFieldsInFriend = (0, delegate_1.extractUnavailableFieldsFromSelectionSet)(friendCandidate.transformedSubschema.transformedSchema, friendCandidate.type, currentUnavailableSelectionSet, () => true); const friendScore = (0, stitch_1.calculateSelectionScore)(unavailableFieldsInFriend); if (friendScore < score) { const unavailableInFriendSelectionSet = { kind: graphql_1.Kind.SELECTION_SET, selections: unavailableFieldsInFriend, }; const subschemaSelectionSet = (0, delegate_1.subtractSelectionSets)(currentUnavailableSelectionSet, unavailableInFriendSelectionSet); currentFriendSubschemas.set(friendCandidate.transformedSubschema, subschemaSelectionSet); currentUnavailableSelectionSet = unavailableInFriendSelectionSet; } } } } } if (!currentSubschema) { throw new Error('Could not determine subschema'); } const jobs = []; let hasPromise = false; const mainJob = (0, delegate_1.delegateToSchema)({ schema: currentSubschema, operation: rootTypeMap.get(info.parentType.name) || 'query', context, info: currentFriendSubschemas?.size ? { ...info, fieldNodes: [ ...(currentAvailableSelectionSet?.selections || []), ...(currentUnavailableSelectionSet?.selections || []), ], } : info, }); if ((0, utils_1.isPromise)(mainJob)) { hasPromise = true; } jobs.push(mainJob); if (currentFriendSubschemas?.size) { for (const [friendSubschema, friendSelectionSet] of currentFriendSubschemas) { const friendJob = (0, delegate_1.delegateToSchema)({ schema: friendSubschema, operation: rootTypeMap.get(info.parentType.name) || 'query', context, info: { ...info, fieldNodes: friendSelectionSet.selections, }, skipTypeMerging: true, }); if ((0, utils_1.isPromise)(friendJob)) { hasPromise = true; } jobs.push(friendJob); } } if (jobs.length === 1) { return jobs[0]; } if (hasPromise) { return Promise.all(jobs).then(results => (0, utils_1.mergeDeep)(results)); } return (0, utils_1.mergeDeep)(jobs); }, }; } const filteredCandidates = candidates.filter(candidate => { const fieldASTMap = typeFieldASTMap.get(candidate.type.name); if (fieldASTMap) { const fieldAST = fieldASTMap.get(candidate.fieldName); if (fieldAST) { const typeNodeInAST = memoizedASTPrint(fieldAST.type); const typeNodeInCandidate = memoizedTypePrint(candidate.fieldConfig.type); return typeNodeInAST === typeNodeInCandidate; } } return false; }); return defaultMerger(filteredCandidates.length ? filteredCandidates : candidates); }; } exports.getFieldMergerFromSupergraphSdl = getFieldMergerFromSupergraphSdl; function getSubschemasFromSupergraphSdl({ supergraphSdl, onExecutor = ({ endpoint }) => (0, executor_http_1.buildHTTPExecutor)({ endpoint }), batch = false, }) { const supergraphAst = ensureSupergraphSDLAst(supergraphSdl); const subgraphEndpointMap = new Map(); const subgraphTypesMap = new Map(); const typeNameKeysBySubgraphMap = new Map(); const typeNameFieldsKeyBySubgraphMap = new Map(); const typeNameCanonicalMap = new Map(); const subgraphTypeNameExtraFieldsMap = new Map(); const subgraphTypeNameProvidedMap = new Map(); const orphanTypeMap = new Map(); // To detect unresolvable interface fields const subgraphExternalFieldMap = new Map(); // TODO: Temporary fix to add missing join__type directives to Query const subgraphNames = []; (0, graphql_1.visit)(supergraphAst, { EnumTypeDefinition(node) { if (node.name.value === 'join__Graph') { node.values?.forEach(valueNode => { subgraphNames.push(valueNode.name.value); }); } }, }); // END TODO function TypeWithFieldsVisitor(typeNode) { // TODO: Temporary fix to add missing join__type directives to Query if (typeNode.name.value === 'Query' || (typeNode.name.value === 'Mutation' && !typeNode.directives?.some(directiveNode => directiveNode.name.value === 'join__type'))) { typeNode.directives = [ ...(typeNode.directives || []), ...subgraphNames.map(subgraphName => ({ kind: graphql_1.Kind.DIRECTIVE, name: { kind: graphql_1.Kind.NAME, value: 'join__type', }, arguments: [ { kind: graphql_1.Kind.ARGUMENT, name: { kind: graphql_1.Kind.NAME, value: 'graph', }, value: { kind: graphql_1.Kind.ENUM, value: subgraphName, }, }, ], })), ]; } let isOrphan = true; // END TODO const fieldDefinitionNodesByGraphName = new Map(); typeNode.directives?.forEach(directiveNode => { if (typeNode.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION) { if (directiveNode.name.value === 'join__owner') { directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph' && argumentNode.value?.kind === graphql_1.Kind.ENUM) { typeNameCanonicalMap.set(typeNode.name.value, argumentNode.value.value); } }); } } if (directiveNode.name.value === 'join__type') { isOrphan = false; const joinTypeGraphArgNode = directiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'graph'); if (joinTypeGraphArgNode?.value?.kind === graphql_1.Kind.ENUM) { const graphName = joinTypeGraphArgNode.value.value; if (typeNode.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION || typeNode.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION) { const keyArgumentNode = directiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'key'); if (keyArgumentNode?.value?.kind === graphql_1.Kind.STRING) { let typeNameKeysMap = typeNameKeysBySubgraphMap.get(graphName); if (!typeNameKeysMap) { typeNameKeysMap = new Map(); typeNameKeysBySubgraphMap.set(graphName, typeNameKeysMap); } let keys = typeNameKeysMap.get(typeNode.name.value); if (!keys) { keys = []; typeNameKeysMap.set(typeNode.name.value, keys); } keys.push(keyArgumentNode.value.value); } } const fieldDefinitionNodesOfSubgraph = []; typeNode.fields?.forEach(fieldNode => { const joinFieldDirectives = fieldNode.directives?.filter(directiveNode => directiveNode.name.value === 'join__field'); let notInSubgraph = true; joinFieldDirectives?.forEach(joinFieldDirectiveNode => { const joinFieldGraphArgNode = joinFieldDirectiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'graph'); if (joinFieldGraphArgNode?.value?.kind === graphql_1.Kind.ENUM && joinFieldGraphArgNode.value.value === graphName) { notInSubgraph = false; const isExternal = joinFieldDirectiveNode.arguments?.some(argumentNode => argumentNode.name.value === 'external' && argumentNode.value?.kind === graphql_1.Kind.BOOLEAN && argumentNode.value.value === true); const isOverridden = joinFieldDirectiveNode.arguments?.some(argumentNode => argumentNode.name.value === 'usedOverridden' && argumentNode.value?.kind === graphql_1.Kind.BOOLEAN && argumentNode.value.value === true); if (isExternal) { let externalFieldsByType = subgraphExternalFieldMap.get(graphName); if (!externalFieldsByType) { externalFieldsByType = new Map(); subgraphExternalFieldMap.set(graphName, externalFieldsByType); } let externalFields = externalFieldsByType.get(typeNode.name.value); if (!externalFields) { externalFields = new Set(); externalFieldsByType.set(typeNode.name.value, externalFields); } externalFields.add(fieldNode.name.value); } if (!isExternal && !isOverridden) { const typeArg = joinFieldDirectiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'type'); const typeNode = typeArg?.value.kind === graphql_1.Kind.STRING ? (0, graphql_1.parseType)(typeArg.value.value) : fieldNode.type; fieldDefinitionNodesOfSubgraph.push({ ...fieldNode, type: typeNode, directives: fieldNode.directives?.filter(directiveNode => directiveNode.name.value !== 'join__field'), }); } const providedExtraField = joinFieldDirectiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'provides'); if (providedExtraField?.value?.kind === graphql_1.Kind.STRING) { const providesSelectionSet = (0, utils_1.parseSelectionSet)( /* GraphQL */ `{ ${providedExtraField.value.value} }`); function handleSelection(fieldNodeTypeName, selection) { let typeNameExtraFieldsMap = subgraphTypeNameExtraFieldsMap.get(graphName); if (!typeNameExtraFieldsMap) { typeNameExtraFieldsMap = new Map(); subgraphTypeNameExtraFieldsMap.set(graphName, typeNameExtraFieldsMap); } switch (selection.kind) { case graphql_1.Kind.FIELD: { const extraFieldTypeNode = supergraphAst.definitions.find(def => 'name' in def && def.name?.value === fieldNodeTypeName); const extraFieldNodeInType = extraFieldTypeNode.fields?.find(fieldNode => fieldNode.name.value === selection.name.value); if (extraFieldNodeInType) { let extraFields = typeNameExtraFieldsMap.get(fieldNodeTypeName); if (!extraFields) { extraFields = []; typeNameExtraFieldsMap.set(fieldNodeTypeName, extraFields); } extraFields.push({ ...extraFieldNodeInType, directives: extraFieldNodeInType.directives?.filter(directiveNode => directiveNode.name.value !== 'join__field'), }); let typeNameProvidedMap = subgraphTypeNameProvidedMap.get(graphName); if (!typeNameProvidedMap) { typeNameProvidedMap = new Map(); subgraphTypeNameProvidedMap.set(graphName, typeNameProvidedMap); } let providedFields = typeNameProvidedMap.get(fieldNodeTypeName); if (!providedFields) { providedFields = new Set(); typeNameProvidedMap.set(fieldNodeTypeName, providedFields); } providedFields.add(selection.name.value); if (selection.selectionSet) { const extraFieldNodeNamedType = (0, utils_js_1.getNamedTypeNode)(extraFieldNodeInType.type); const extraFieldNodeTypeName = extraFieldNodeNamedType.name.value; for (const subSelection of selection.selectionSet.selections) { handleSelection(extraFieldNodeTypeName, subSelection); } } } } break; case graphql_1.Kind.INLINE_FRAGMENT: { const fragmentType = selection.typeCondition?.name?.value || fieldNodeType.name.value; if (selection.selectionSet) { for (const subSelection of selection.selectionSet.selections) { handleSelection(fragmentType, subSelection); } } } break; } } const fieldNodeType = (0, utils_js_1.getNamedTypeNode)(fieldNode.type); const fieldNodeTypeName = fieldNodeType.name.value; for (const selection of providesSelectionSet.selections) { handleSelection(fieldNodeTypeName, selection); } } const requiresArgumentNode = joinFieldDirectiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'requires'); if (requiresArgumentNode?.value?.kind === graphql_1.Kind.STRING) { let typeNameFieldsKeyMap = typeNameFieldsKeyBySubgraphMap.get(graphName); if (!typeNameFieldsKeyMap) { typeNameFieldsKeyMap = new Map(); typeNameFieldsKeyBySubgraphMap.set(graphName, typeNameFieldsKeyMap); } let fieldsKeyMap = typeNameFieldsKeyMap.get(typeNode.name.value); if (!fieldsKeyMap) { fieldsKeyMap = new Map(); typeNameFieldsKeyMap.set(typeNode.name.value, fieldsKeyMap); } fieldsKeyMap.set(fieldNode.name.value, requiresArgumentNode.value.value); } } }); // Add if no join__field directive if (!joinFieldDirectives?.length) { fieldDefinitionNodesOfSubgraph.push({ ...fieldNode, directives: fieldNode.directives?.filter(directiveNode => directiveNode.name.value !== 'join__field'), }); } else if (notInSubgraph && typeNameKeysBySubgraphMap .get(graphName) ?.get(typeNode.name.value) ?.some(key => key.split(' ').includes(fieldNode.name.value))) { fieldDefinitionNodesOfSubgraph.push({ ...fieldNode, directives: fieldNode.directives?.filter(directiveNode => directiveNode.name.value !== 'join__field'), }); } }); fieldDefinitionNodesByGraphName.set(graphName, fieldDefinitionNodesOfSubgraph); } } }); const joinImplementsDirectives = typeNode.directives?.filter(directiveNode => directiveNode.name.value === 'join__implements'); fieldDefinitionNodesByGraphName.forEach((fieldDefinitionNodesOfSubgraph, graphName) => { const interfaces = []; typeNode.interfaces?.forEach(interfaceNode => { const implementedSubgraphs = joinImplementsDirectives?.filter(directiveNode => { const argumentNode = directiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'interface'); return (argumentNode?.value?.kind === graphql_1.Kind.STRING && argumentNode.value.value === interfaceNode.name.value); }); if (!implementedSubgraphs?.length || implementedSubgraphs.some(directiveNode => { const argumentNode = directiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'graph'); return (argumentNode?.value?.kind === graphql_1.Kind.ENUM && argumentNode.value.value === graphName); })) { interfaces.push(interfaceNode); } }); if (typeNode.name.value === 'Query') { fieldDefinitionNodesOfSubgraph.push(entitiesFieldDefinitionNode); } const objectTypedDefNodeForSubgraph = { ...typeNode, interfaces, fields: fieldDefinitionNodesOfSubgraph, directives: typeNode.directives?.filter(directiveNode => directiveNode.name.value !== 'join__type' && directiveNode.name.value !== 'join__owner' && directiveNode.name.value !== 'join__implements'), }; let subgraphTypes = subgraphTypesMap.get(graphName); if (!subgraphTypes) { subgraphTypes = []; subgraphTypesMap.set(graphName, subgraphTypes); } subgraphTypes.push(objectTypedDefNodeForSubgraph); }); if (isOrphan) { orphanTypeMap.set(typeNode.name.value, typeNode); } } (0, graphql_1.visit)(supergraphAst, { ScalarTypeDefinition(node) { let isOrphan = !node.name.value.startsWith('link__') && !node.name.value.startsWith('join__'); node.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__type') { directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph' && argumentNode?.value?.kind === graphql_1.Kind.ENUM) { isOrphan = false; const graphName = argumentNode.value.value; let subgraphTypes = subgraphTypesMap.get(graphName); if (!subgraphTypes) { subgraphTypes = []; subgraphTypesMap.set(graphName, subgraphTypes); } subgraphTypes.push({ ...node, directives: node.directives?.filter(directiveNode => directiveNode.name.value !== 'join__type'), }); } }); } }); if (isOrphan) { orphanTypeMap.set(node.name.value, node); } }, InputObjectTypeDefinition(node) { let isOrphan = true; node.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__type') { directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph' && argumentNode?.value?.kind === graphql_1.Kind.ENUM) { isOrphan = false; const graphName = argumentNode.value.value; let subgraphTypes = subgraphTypesMap.get(graphName); if (!subgraphTypes) { subgraphTypes = []; subgraphTypesMap.set(graphName, subgraphTypes); } subgraphTypes.push({ ...node, directives: node.directives?.filter(directiveNode => directiveNode.name.value !== 'join__type'), }); } }); } }); if (isOrphan) { orphanTypeMap.set(node.name.value, node); } }, InterfaceTypeDefinition: TypeWithFieldsVisitor, UnionTypeDefinition(node) { let isOrphan = true; node.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__type') { directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph' && argumentNode?.value?.kind === graphql_1.Kind.ENUM) { isOrphan = false; const graphName = argumentNode.value.value; const unionMembers = []; node.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__unionMember') { const graphArgumentNode = directiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'graph'); const memberArgumentNode = directiveNode.arguments?.find(argumentNode => argumentNode.name.value === 'member'); if (graphArgumentNode?.value?.kind === graphql_1.Kind.ENUM && graphArgumentNode.value.value === graphName && memberArgumentNode?.value?.kind === graphql_1.Kind.STRING) { unionMembers.push({ kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: memberArgumentNode.value.value, }, }); } } }); if (unionMembers.length > 0) { let subgraphTypes = subgraphTypesMap.get(graphName); if (!subgraphTypes) { subgraphTypes = []; subgraphTypesMap.set(graphName, subgraphTypes); } subgraphTypes.push({ ...node, types: unionMembers, directives: node.directives?.filter(directiveNode => directiveNode.name.value !== 'join__type' && directiveNode.name.value !== 'join__unionMember'), }); } } }); } }); if (isOrphan && node.name.value !== '_Entity') { orphanTypeMap.set(node.name.value, node); } }, EnumTypeDefinition(node) { let isOrphan = true; if (node.name.value === 'join__Graph') { node.values?.forEach(valueNode => { isOrphan = false; valueNode.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__graph') { directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'url' && argumentNode.value?.kind === graphql_1.Kind.STRING) { subgraphEndpointMap.set(valueNode.name.value, argumentNode.value.value); } }); } }); }); } node.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__type') { isOrphan = false; directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph' && argumentNode.value?.kind === graphql_1.Kind.ENUM) { const graphName = argumentNode.value.value; const enumValueNodes = []; node.values?.forEach(valueNode => { const joinEnumValueDirectives = valueNode.directives?.filter(directiveNode => directiveNode.name.value === 'join__enumValue'); if (joinEnumValueDirectives?.length) { joinEnumValueDirectives.forEach(joinEnumValueDirectiveNode => { joinEnumValueDirectiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph' && argumentNode.value?.kind === graphql_1.Kind.ENUM && argumentNode.value.value === graphName) { enumValueNodes.push({ ...valueNode, directives: valueNode.directives?.filter(directiveNode => directiveNode.name.value !== 'join__enumValue'), }); } }); }); } else { enumValueNodes.push(valueNode); } }); const enumTypedDefNodeForSubgraph = { ...node, directives: node.directives?.filter(directiveNode => directiveNode.name.value !== 'join__type'), values: enumValueNodes, }; let subgraphTypes = subgraphTypesMap.get(graphName); if (!subgraphTypes) { subgraphTypes = []; subgraphTypesMap.set(graphName, subgraphTypes); } subgraphTypes.push(enumTypedDefNodeForSubgraph); } }); } }); if (isOrphan) { orphanTypeMap.set(node.name.value, node); } }, ObjectTypeDefinition: TypeWithFieldsVisitor, }); const subschemaMap = new Map(); for (const [subgraphName, endpoint] of subgraphEndpointMap) { const mergeConfig = {}; const typeNameKeyMap = typeNameKeysBySubgraphMap.get(subgraphName); const unionTypeNodes = []; if (typeNameKeyMap) { const typeNameFieldsKeyMap = typeNameFieldsKeyBySubgraphMap.get(subgraphName); for (const [typeName, keys] of typeNameKeyMap) { const mergedTypeConfig = (mergeConfig[typeName] = {}); const fieldsKeyMap = typeNameFieldsKeyMap?.get(typeName); const extraKeys = []; if (fieldsKeyMap) { const fieldsConfig = (mergedTypeConfig.fields = {}); for (const [fieldName, fieldNameKey] of fieldsKeyMap) { extraKeys.push(fieldNameKey); fieldsConfig[fieldName] = { selectionSet: `{ ${fieldNameKey} }`, computed: true, }; } } if (typeNameCanonicalMap.get(typeName) === subgraphName) { mergedTypeConfig.canonical = true; } mergedTypeConfig.entryPoints = keys.map(key => ({ selectionSet: `{ ${key} }`, argsFromKeys: utils_js_1.getArgsFromKeysForFederation, key: (0, utils_js_1.getKeyFnForFederation)(typeName, [key, ...extraKeys]), fieldName: `_entities`, dataLoaderOptions: { cacheKeyFn: (0, utils_js_1.getCacheKeyFnFromKey)(key), }, })); unionTypeNodes.push({ kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: typeName, }, }); } } const entitiesUnionTypeDefinitionNode = { name: { kind: graphql_1.Kind.NAME, value: '_Entity', }, kind: graphql_1.Kind.UNION_TYPE_DEFINITION, types: unionTypeNodes, }; const extraOrphanTypesForSubgraph = new Map(); function visitTypeDefinitionsForOrphanTypes(node) { function visitNamedTypeNode(namedTypeNode) { const typeName = namedTypeNode.name.value; if (specifiedTypeNames.includes(typeName)) { return node; } const orphanType = orphanTypeMap.get(typeName); if (orphanType) { if (!extraOrphanTypesForSubgraph.has(typeName)) { extraOrphanTypesForSubgraph.set(typeName, {}); const extraOrphanType = visitTypeDefinitionsForOrphanTypes(orphanType); extraOrphanTypesForSubgraph.set(typeName, extraOrphanType); } } else if (!subgraphTypes.some(typeNode => typeNode.name.value === typeName)) { return null; } return node; } function visitFieldDefs(nodeFields) { const fields = []; for (const field of nodeFields || []) { const isTypeNodeOk = visitNamedTypeNode((0, utils_js_1.getNamedTypeNode)(field.type)); if (!isTypeNodeOk) { continue; } if (field.kind === graphql_1.Kind.FIELD_DEFINITION) { const args = visitFieldDefs(field.arguments); fields.push({ ...field, arguments: args, }); } else { fields.push(field); } } return fields; } function visitObjectAndInterfaceDefs(node) { const fields = visitFieldDefs(node.fields); const interfaces = []; for (const iface of node.interfaces || []) { const isTypeNodeOk = visitNamedTypeNode(iface); if (!isTypeNodeOk) { continue; } interfaces.push(iface); } return { ...node, fields, interfaces, }; } return (0, graphql_1.visit)(node, { [graphql_1.Kind.OBJECT_TYPE_DEFINITION]: visitObjectAndInterfaceDefs, [graphql_1.Kind.OBJECT_TYPE_EXTENSION]: visitObjectAndInterfaceDefs, [graphql_1.Kind.INTERFACE_TYPE_DEFINITION]: visitObjectAndInterfaceDefs, [graphql_1.Kind.INTERFACE_TYPE_EXTENSION]: visitObjectAndInterfaceDefs, [graphql_1.Kind.UNION_TYPE_DEFINITION](node) { const types = []; for (const type of node.types || []) { const isTypeNodeOk = visitNamedTypeNode(type); if (!isTypeNodeOk) { continue; } types.push(type); } return { ...node, types, }; }, [graphql_1.Kind.UNION_TYPE_EXTENSION](node) { const types = []; for (const type of node.types || []) { const isTypeNodeOk = visitNamedTypeNode(type); if (!isTypeNodeOk) { continue; } types.push(type); } return { ...node, types, }; }, [graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION](node) { const fields = visitFieldDefs(node.fields); return { ...node, fields, }; }, [graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION](node) { const fields = visitFieldDefs(node.fields); return { ...node, fields, }; }, }); } const subgraphTypes = subgraphTypesMap.get(subgraphName) || []; const typeNameExtraFieldsMap = subgraphTypeNameExtraFieldsMap.get(subgraphName); subgraphTypes.forEach(typeNode => { if (typeNameExtraFieldsMap && 'fields' in typeNode) { const extraFields = typeNameExtraFieldsMap.get(typeNode.name.value); if (extraFields) { typeNode.fields.push(...extraFields); } } visitTypeDefinitionsForOrphanTypes(typeNode); }); const extendedSubgraphTypes = [...subgraphTypes, ...extraOrphanTypesForSubgraph.values()]; // We should add implemented objects from other subgraphs implemented by this interface for (const interfaceInSubgraph of extendedSubgraphTypes) { if (interfaceInSubgraph.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION) { let isOrphan = true; for (const definitionNode of supergraphAst.definitions) { if (definitionNode.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && definitionNode.interfaces?.some(interfaceNode => interfaceNode.name.value === interfaceInSubgraph.name.value)) { isOrphan = false; } } if (isOrphan) { // @ts-expect-error `kind` property is a readonly field in TS definitions but it is not actually interfaceInSubgraph.kind = graphql_1.Kind.OBJECT_TYPE_DEFINITION; } } } let schema; const schemaAst = { kind: graphql_1.Kind.DOCUMENT, definitions: [ ...extendedSubgraphTypes, entitiesUnionTypeDefinitionNode, anyTypeDefinitionNode, ], }; try { schema = (0, graphql_1.buildASTSchema)(schemaAst, { assumeValidSDL: true, assumeValid: true, }); } catch (e) { throw new Error(`Error building schema for subgraph ${subgraphName}: ${e?.stack || e?.message || e.toString()}`); } let executor = onExecutor({ subgraphName, endpoint, subgraphSchema: schema }); if (globalThis.process?.env?.['DEBUG']) { const origExecutor = executor; executor = async function debugExecutor(execReq) { console.log(`Executing ${subgraphName} with args:`, { document: (0, graphql_1.print)(execReq.document), variables: JSON.stringify(execReq.variables, null, 2), }); const res = await origExecutor(execReq); console.log(`Response from ${subgraphName}:`, JSON.stringify(res, null, 2)); return res; }; } const typeNameProvidedMap = subgraphTypeNameProvidedMap.get(subgraphName); const externalFieldMap = subgraphExternalFieldMap.get(subgraphName); subschemaMap.set(subgraphName, { name: subgraphName, schema, executor, merge: mergeConfig, batch, transforms: [ { transformRequest(request) { const typeInfo = new graphql_1.TypeInfo(schema); return { ...request, document: (0, graphql_1.visit)(request.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, { // To avoid resolving unresolvable interface fields [graphql_1.Kind.FIELD](node) { if (node.name.value !== '__typename') { const parentType = typeInfo.getParentType(); if ((0, graphql_1.isInterfaceType)(parentType)) { const providedInterfaceFields = typeNameProvidedMap?.get(parentType.name); const implementations = schema.getPossibleTypes(parentType); for (const implementation of implementations) { const externalFields = externalFieldMap?.get(implementation.name); const providedFields = typeNameProvidedMap?.get(implementation.name); if (!providedInterfaceFields?.has(node.name.value) && !providedFields?.has(node.name.value) && externalFields?.has(node.name.value)) { throw (0, utils_1.createGraphQLError)(`Was not able to find any options for ${node.name.value}: This shouldn't have happened.`); } } } } }, })), }; }, }, ], }); } return subschemaMap; } exports.getSubschemasFromSupergraphSdl = getSubschemasFromSupergraphSdl; function getStitchedSchemaFromSupergraphSdl(opts) { const subschemaMap = getSubschemasFromSupergraphSdl(opts); const supergraphSchema = (0, stitch_1.stitchSchemas)({ subschemas: [...subschemaMap.values()], assumeValid: true, assumeValidSDL: true, typeMergingOptions: { useNonNullableFieldOnConflict: true, validationSettings: { validationLevel: stitch_1.ValidationLevel.Off, }, fieldConfigMerger: getFieldMergerFromSupergraphSdl(opts.supergraphSdl), }, }); const extraDefinitions = []; for (const definition of ensureSupergraphSDLAst(opts.supergraphSdl).definitions) { if ('fields' in definition && definition.fields) { const typeInSchema = supergraphSchema.getType(definition.name.value); if (!typeInSchema || !('getFields' in typeInSchema)) { extraDefinitions.push(definition); } else { const fieldsInSchema = typeInSchema.getFields(); const extraFields = []; for (