@graphql-tools/federation
Version:
Useful tools to create and manipulate GraphQL schemas.
1,085 lines • 57.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStitchedSchemaFromSupergraphSdl = exports.getStitchingOptionsFromSupergraphSdl = 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 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;
const rootTypeMap = new Map([
['Query', 'query'],
['Mutation', 'mutation'],
['Subscription', 'subscription'],
]);
const memoizedASTPrint = (0, utils_1.memoize1)(graphql_1.print);
const memoizedTypePrint = (0, utils_1.memoize1)((type) => type.toString());
function getStitchingOptionsFromSupergraphSdl(opts) {
const supergraphAst = ensureSupergraphSDLAst(opts.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();
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);
}
}
}
// 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');
const isResolvable = !directiveNode.arguments?.some(argumentNode => argumentNode.name.value === 'resolvable' &&
argumentNode.value?.kind === graphql_1.Kind.BOOLEAN &&
argumentNode.value.value === false);
if (isResolvable && 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 subschemas = [];
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 = new Set();
if (fieldsKeyMap) {
const fieldsConfig = (mergedTypeConfig.fields = {});
for (const [fieldName, fieldNameKey] of fieldsKeyMap) {
const aliasedFieldNameKey = fieldNameKey.includes('(')
? `_${fieldNameKey.split('(')[0]}: ${fieldNameKey}`
: fieldNameKey;
extraKeys.add(aliasedFieldNameKey);
fieldsConfig[fieldName] = {
selectionSet: `{ ${aliasedFieldNameKey} }`,
computed: true,
};
}
}
if (typeNameCanonicalMap.get(typeName) === subgraphName) {
mergedTypeConfig.canonical = true;
}
function getMergedTypeConfigFromKey(key) {
return {
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),
...(opts.batchDelegateOptions || {}),
},
};
}
if (keys.length === 1) {
const key = keys[0];
Object.assign(mergedTypeConfig, getMergedTypeConfigFromKey(key));
}
if (keys.length > 1) {
const entryPoints = keys.map(key => getMergedTypeConfigFromKey(key));
mergedTypeConfig.entryPoints = entryPoints;
}
unionTypeNodes.push({
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: typeName,
},
});
opts.onMergedTypeConfig?.(typeName, mergedTypeConfig);
}
}
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;
let schemaAst = {
kind: graphql_1.Kind.DOCUMENT,
definitions: [
...extendedSubgraphTypes,
entitiesUnionTypeDefinitionNode,
anyTypeDefinitionNode,
],
};
if (opts.onSubgraphAST) {
schemaAst = opts.onSubgraphAST(subgraphName, schemaAst);
}
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 httpExecutorOpts;
if (typeof opts.httpExecutorOpts === 'function') {
httpExecutorOpts = opts.httpExecutorOpts({
name: subgraphName,
endpoint,
});
}
else {
httpExecutorOpts = opts.httpExecutorOpts || {};
}
let executor = (0, executor_http_1.buildHTTPExecutor)({
endpoint,
...httpExecutorOpts,
});
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);
const transforms = [];
if (externalFieldMap?.size &&
extendedSubgraphTypes.some(t => t.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION)) {
const typeInfo = new graphql_1.TypeInfo(schema);
const visitorKeys = {
Document: ['definitions'],
OperationDefinition: ['selectionSet'],
SelectionSet: ['selections'],
Field: ['selectionSet'],
InlineFragment: ['selectionSet'],
FragmentDefinition: ['selectionSet'],
};
function createUnresolvableError(fieldName, fieldNode) {
return (0, utils_1.createGraphQLError)(`Was not able to find any options for ${fieldName}: This shouldn't have happened.`, {
extensions: {
CRITICAL_ERROR: true,
},
nodes: [fieldNode],
});
}
const unresolvableIfaceFieldCheckerMap = new Map();
function createIfaceFieldChecker(parentType) {
const providedInterfaceFields = typeNameProvidedMap?.get(parentType.name);
const implementations = schema.getPossibleTypes(parentType);
const ifaceFieldCheckResult = new Map();
return function ifaceFieldChecker(fieldName) {
let result = ifaceFieldCheckResult.get(fieldName);
if (result == null) {
result = true;
for (const implementation of implementations) {
const externalFields = externalFieldMap?.get(implementation.name);
const providedFields = typeNameProvidedMap?.get(implementation.name);
if (!providedInterfaceFields?.has(fieldName) &&
!providedFields?.has(fieldName) &&
externalFields?.has(fieldName)) {
result = false;
break;
}
}
ifaceFieldCheckResult.set(fieldName, result);
}
return result;
};
}
transforms.push({
transformRequest(request) {
return {
...request,
document: (0, graphql_1.visit)(request.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
// To avoid resolving unresolvable interface fields
[graphql_1.Kind.FIELD](fieldNode) {
const fieldName = fieldNode.name.value;
if (fieldName !== '__typename') {
const parentType = typeInfo.getParentType();
if ((0, graphql_1.isInterfaceType)(parentType)) {
let unresolvableIfaceFieldChecker = unresolvableIfaceFieldCheckerMap.get(parentType.name);
if (unresolvableIfaceFieldChecker == null) {
unresolvableIfaceFieldChecker = createIfaceFieldChecker(parentType);
unresolvableIfaceFieldCheckerMap.set(parentType.name, unresolvableIfaceFieldChecker);
}
if (!unresolvableIfaceFieldChecker(fieldName)) {
throw createUnresolvableError(fieldName, fieldNode);
}
}
}
},
}), visitorKeys),
};
},
});
}
const subschemaConfig = {
name: subgraphName,
endpoint,
schema,
executor,
merge: mergeConfig,
transforms,
batch: opts.batch,
batchingOptions: opts.batchingOptions,
};
opts.onSubschemaConfig?.(subschemaConfig);
subschemas.push(subschemaConfig);
}
const defaultMerger = (0, stitch_1.getDefaultFieldConfigMerger)(true);
const fieldConfigMerger = 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 ||
!currentUnavailableSelectionSet.selections.length) {
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 => mergeResults(results));
}
return mergeResults(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);
};
function doesFieldExistInSupergraph(typeName, fieldName) {
for (const subschemaConfig of subschemas) {
const type = subschemaConfig.schema.getType(typeName);
if (type && 'getFields' in type) {
if (type.getFields()[fieldName]) {
return true;
}
}
}
return false;
}
function getTypeInSupergraph(typeName) {
for (const subschemaConfig of subschemas) {
const type = subschemaConfig.schema.getType(typeName);
if (type) {
return type;
}
}
return undefined;
}
const extraDefinitions = [];
for (const definition of supergraphAst.definitions) {
if ('name' in definition && definition.name && definition.kind !== graphql_1.Kind.DIRECTIVE_DEFINITION) {
const typeName = definition.name.value;
const typeInSchema = getTypeInSupergraph(typeName);
if (!typeInSchema) {
extraDefinitions.push(definition);
}
else if ('fields' in definition && definition.fields) {
const extraFields = [];
for (const field of definition.fields) {
if (!doesFieldExistInSupergraph(typeName, field.name.value)) {
extraFields.push(field);
}
}
if (extraFields.length) {
let definitionKind;
if ((0, graphql_1.isObjectType)(typeInSchema)) {
definitionKind = graphql_1.Kind.OBJECT_TYPE_DEFINITION;
}
else if ((0, graphql_1.isInterfaceType)(typeInSchema)) {
definitionKind = graphql_1.Kind.INTERFACE_TYPE_DEFINITION;
}
else if ((0, graphql_1.isInputObjectType)(typeInSchema)) {
definitionKind = graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION;
}
if (definitionKind) {
extraDefinitions.push({
kind: definitionKind,
name: definition.name,
fields: extraFields,
// `fields` are different in input object types and regular types
});
}
}
}
}
}
const additionalTypeDefs = {
kind: graphql_1.Kind.DOCUMENT,
definitions: extraDefinitions,
};
return {
subschemas,
typeDefs: additionalTypeDefs,
assumeValid: true,
assumeValidSDL: true,
typeMergingOptions: {
useNonNullableFieldOnConflict: true,
validationSettings: {
validationLevel: stitch_1.ValidationLevel.Off,
},
fieldConfigMerger,
},
};
}
exports.getStitchingOptionsFromSupergraphSdl = getStitchingOptionsFromSupergraphSdl;
function getStitchedSchemaFromSupergraphSdl(opts) {
const stitchSchemasOpts = getStitchingOptionsFromSupergraphSdl(opts);
opts.onStitchingOptions?.(stitchSchemasOpts);
const supergraphSchema = (0, stitch_1.stitchSchemas)(stitchSchemasOpts);
return (0, utils_js_1.filterInternalFieldsAndTypes)(supergraphSchema);
}
exports.getStitchedSchemaFromSupergraphSdl = getStitchedSchemaFromSupergraphSdl;
const anyTypeDefinitionNode = {
name: {
kind: graphql_1.Kind.NAME,
value: '_Any',
},
kind: graphql_1.Kind.SCALAR_TYPE_DEFINITION,
};
const entitiesFieldDefinitionNode = {
kind: graphql_1.Kind.FIELD_DEFINITION,
name: {
kind: graphql_1.Kind.NAME,
value: '_entities',
},
type: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: '_Entity',
},
},
arguments: [
{
kind: graphql_1.Kind.INPUT_VALUE_DEFINITION,
name: {
kind: graphql_1.Kind.NAME,
value: 'representations',
},
type: {
kind: graphql_1.Kind.NON_NULL_TYPE,
type: {
kind: graphql_1.Kind.LIST_TYPE,
type: {
kind: graphql_1.Kind.NON_NULL_TYPE,
type: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: '_Any',
},
},
},
},
},
},
],
};
const specifiedTypeNames = ['ID', 'String', 'Float', 'Int', 'Boolean', '_Any', '_Entity'];
function mergeResults(results) {
const errors = [];
const datas = [];
for (const result of results) {
if (result instanceof AggregateError) {
errors.push(...result.errors);
}
else if (result instanceof Error) {
errors.push(result);
}
else {
datas.push(result);
}
}
if (datas.length) {
return (0, utils_1.mergeDeep)(datas, false, true, true);
}
if (errors.length) {
return new AggregateError(errors, errors.map(error => error.message).join(', \n'));
}
return null;
}