@graphql-tools/federation
Version:
Useful tools to create and manipulate GraphQL schemas.
887 lines • 53.1 kB
JavaScript
"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 (