UNPKG

@graphql-tools/federation

Version:

Useful tools to create and manipulate GraphQL schemas.

1,346 lines (1,337 loc) • 72.4 kB
import { createDefaultExecutor, getTypeInfo, extractUnavailableFieldsFromSelectionSet, subtractSelectionSets, delegateToSchema, isExternalObject, UNPATHED_ERRORS_SYMBOL } from '@graphql-tools/delegate'; import { buildHTTPExecutor } from '@graphql-tools/executor-http'; import { mergeTypeDefs, mergeResolvers } from '@graphql-tools/merge'; import { stitchSchemas, getDefaultFieldConfigMerger, ValidationLevel, calculateSelectionScore } from '@graphql-tools/stitch'; import { printSchemaWithDirectives, mapMaybePromise, mergeDeep, parseSelectionSet, memoize1, mapSchema, MapperKind, getDirectiveExtensions, createGraphQLError, inspect, getDocumentNodeFromSchema, isPromise } from '@graphql-tools/utils'; import { visit, Kind, buildASTSchema, parse, print, isObjectType, isInterfaceType, isInputObjectType, parseType, visitWithTypeInfo } from 'graphql'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { fetch } from '@whatwg-node/fetch'; const SubgraphBaseSDL = ( /* GraphQL */ ` scalar _Any scalar _FieldSet scalar link__Import enum link__Purpose { SECURITY EXECUTION } type _Service { sdl: String! } type Query { _service: _Service! } directive @external on FIELD_DEFINITION | OBJECT directive @requires(fields: _FieldSet!) on FIELD_DEFINITION directive @provides(fields: _FieldSet!) on FIELD_DEFINITION directive @key( fields: _FieldSet! resolvable: Boolean = true ) repeatable on OBJECT | INTERFACE directive @link( url: String! as: String for: link__Purpose import: [link__Import] ) repeatable on SCHEMA directive @shareable repeatable on OBJECT | FIELD_DEFINITION directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @tag( name: String! ) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @override(from: String!) on FIELD_DEFINITION directive @composeDirective(name: String!) repeatable on SCHEMA directive @extends on OBJECT | INTERFACE ` ); function buildSubgraphSchema(optsOrModules) { const opts = Array.isArray(optsOrModules) ? { typeDefs: optsOrModules.map((opt) => opt.typeDefs), resolvers: optsOrModules.map((opt) => opt.resolvers).flat() } : optsOrModules; const entityTypeNames = []; function handleEntity(node) { if (node.directives?.some((directive) => directive.name.value === "key")) { entityTypeNames.push(node.name.value); } } const typeDefs = visit(mergeTypeDefs([SubgraphBaseSDL, opts.typeDefs]), { ObjectTypeDefinition: (node) => { handleEntity(node); }, ObjectTypeExtension: (node) => { handleEntity(node); return { ...node, kind: Kind.OBJECT_TYPE_DEFINITION, directives: [ ...node.directives || [], { kind: "Directive", name: { kind: "Name", value: "extends" } } ] }; } }); const givenResolvers = mergeResolvers(opts.resolvers); const allTypeDefs = [typeDefs]; const allResolvers = [sdlResolvers, givenResolvers]; if (entityTypeNames.length > 0) { allTypeDefs.push(`union _Entity = ${entityTypeNames.join(" | ")}`); allTypeDefs.push(`extend type Query { _entities(representations: [_Any!]!): [_Entity]! }`); allResolvers.push({ _Entity: { __resolveType: entityTypeResolver }, Query: { _entities: (_root, args, context, info) => args.representations.map( (representation) => mapMaybePromise( givenResolvers[representation.__typename]?.__resolveReference?.( representation, context, info ), (resolvedEntity) => { if (!resolvedEntity) { return representation; } if (!resolvedEntity.__typename) { resolvedEntity.__typename = representation.__typename; } return resolvedEntity; } ) ) } }); } return makeExecutableSchema({ assumeValid: true, assumeValidSDL: true, ...opts, typeDefs: allTypeDefs, resolvers: allResolvers }); } function entityTypeResolver(obj) { return obj.__typename; } const sdlResolvers = { Query: { _service: () => ({}) }, _Service: { sdl: (_root, _args, _context, info) => printSchemaWithDirectives(info.schema) } }; function getArgsFromKeysForFederation(representations) { return { representations }; } function getKeyForFederation(root) { return root; } function projectDataSelectionSet(data, selectionSet) { if (data == null || selectionSet == null || !selectionSet?.selections?.length) { return data; } if (data instanceof Error) { return null; } if (Array.isArray(data)) { return data.map((entry) => projectDataSelectionSet(entry, selectionSet)); } const projectedData = { __typename: data.__typename }; for (const selection of selectionSet.selections) { if (selection.kind === Kind.FIELD) { const fieldName = selection.name.value; const responseKey = selection.alias?.value || selection.name.value; if (Object.prototype.hasOwnProperty.call(data, responseKey)) { const projectedKeyData = projectDataSelectionSet( data[responseKey], selection.selectionSet ); if (projectedData[fieldName]) { if (projectedKeyData != null && !(projectedKeyData instanceof Error)) { projectedData[fieldName] = mergeDeep( [projectedData[fieldName], projectedKeyData], void 0, true, true ); } } else { projectedData[fieldName] = projectedKeyData; } } } else if (selection.kind === Kind.INLINE_FRAGMENT) { if (selection.typeCondition && projectedData["__typename"] != null && projectedData["__typename"] !== selection.typeCondition.name.value) { continue; } Object.assign( projectedData, mergeDeep( [ projectedData, projectDataSelectionSet(data, selection.selectionSet) ], void 0, true, true ) ); } } return projectedData; } function getKeyFnForFederation(typeName, keys) { if (keys.some((key) => key.includes("{") || key.includes("("))) { const parsedSelectionSet = parseSelectionSet(`{${keys.join(" ")}}`, { noLocation: true }); return function keyFn(root) { if (root == null) { return root; } return projectDataSelectionSet( { __typename: typeName, ...root }, parsedSelectionSet ); }; } const allKeyProps = keys.flatMap((key) => key.split(" ")).map((key) => key.trim()); if (allKeyProps.length > 1) { return function keyFn(root) { if (root == null) { return null; } return allKeyProps.reduce( (prev, key) => { if (key !== "__typename") { prev[key] = root[key]; } return prev; }, { __typename: typeName } ); }; } const keyProp = allKeyProps[0]; return memoize1(function keyFn(root) { if (root == null) { return null; } const keyPropVal = root[keyProp]; if (keyPropVal == null) { return null; } return { __typename: typeName, [keyProp]: keyPropVal }; }); } function getCacheKeyFnFromKey(key) { if (key.includes("{") || key.includes("(")) { const parsedSelectionSet = parseSelectionSet(`{${key}}`, { noLocation: true }); return function cacheKeyFn(root) { return JSON.stringify(projectDataSelectionSet(root, parsedSelectionSet)); }; } const keyTrimmed = key.trim(); const keys = keyTrimmed.split(" ").map((key2) => key2.trim()); if (keys.length > 1) { return function cacheKeyFn(root) { return keys.map((key2) => { const keyVal = root[key2]; if (keyVal == null) { return ""; } if (typeof keyVal === "object") { return JSON.stringify(keyVal); } return keyVal; }).join(" "); }; } return memoize1(function cacheKeyFn(root) { const keyVal = root[keyTrimmed]; if (keyVal == null) { return ""; } if (typeof keyVal === "object") { return JSON.stringify(keyVal); } return keyVal; }); } function hasInaccessible(obj) { return getDirectiveExtensions(obj)?.inaccessible?.length; } function filterInternalFieldsAndTypes(finalSchema) { const internalTypeNameRegexp = /^(?:_Entity|_Any|_FieldSet|_Service|link|inaccessible|(?:link__|join__|core__)[\w]*)$/; return mapSchema(finalSchema, { [MapperKind.DIRECTIVE]: (directive) => { if (internalTypeNameRegexp.test(directive.name)) { return null; } return directive; }, [MapperKind.TYPE]: (type) => { if (internalTypeNameRegexp.test(type.name) || hasInaccessible(type)) { return null; } return type; }, [MapperKind.FIELD]: (fieldConfig) => { if (hasInaccessible(fieldConfig)) { return null; } return fieldConfig; }, [MapperKind.QUERY_ROOT_FIELD]: (fieldConfig, fieldName) => { if (fieldName === "_entities" || hasInaccessible(fieldConfig)) { return null; } return fieldConfig; }, [MapperKind.ENUM_VALUE]: (valueConfig) => { if (hasInaccessible(valueConfig)) { return null; } return valueConfig; }, [MapperKind.ARGUMENT]: (argConfig) => { if (hasInaccessible(argConfig)) { return null; } return argConfig; } }); } function getNamedTypeNode(typeNode) { if (typeNode.kind !== Kind.NAMED_TYPE) { return getNamedTypeNode(typeNode.type); } return typeNode; } class EventEmitter { #listeners = /* @__PURE__ */ new Map(); on(eventName, listener) { const listeners = this.#listeners.get(eventName); if (!listeners) { this.#listeners.set(eventName, [listener]); } else { listeners.push(listener); } return this; } once(eventName, listener) { const selfRemovingListener = (...args) => { this.off(eventName, selfRemovingListener); listener(...args); }; this.on(eventName, selfRemovingListener); return this; } off(eventName, listener) { const listeners = this.#listeners.get(eventName); if (listeners) { const index = listeners.indexOf(listener); listeners.splice(index, 1); } return this; } emit(eventName, ...args) { const listeners = this.#listeners.get(eventName); if (!listeners) { return false; } for (let i = listeners.length - 1; i >= 0; i--) { listeners[i]?.(...args); } return true; } } const SubgraphSDLQuery = ( /* GraphQL */ ` query SubgraphSDL { _service { sdl } } ` ); async function getSubschemaForFederationWithURL(config) { const executor = buildHTTPExecutor(config); const subschemaConfig = await getSubschemaForFederationWithExecutor(executor); return { batch: true, ...subschemaConfig }; } function getSubschemaForFederationWithTypeDefs(typeDefs) { const subschemaConfig = {}; const typeMergingConfig = subschemaConfig.merge = subschemaConfig.merge || {}; const entityTypes = []; const visitor = (node) => { if (node.directives) { const typeName = node.name.value; const selections = []; for (const directive of node.directives) { const directiveArgs = directive.arguments || []; switch (directive.name.value) { case "key": { if (directiveArgs.some( (arg) => arg.name.value === "resolvable" && arg.value.kind === Kind.BOOLEAN && arg.value.value === false )) { continue; } const selectionValueNode = directiveArgs.find( (arg) => arg.name.value === "fields" )?.value; if (selectionValueNode?.kind === Kind.STRING) { selections.push(selectionValueNode.value); } break; } case "inaccessible": return null; } } if (selections.length === 0) { return node; } const typeMergingTypeConfig = typeMergingConfig[typeName] = typeMergingConfig[typeName] || {}; if (node.kind === Kind.OBJECT_TYPE_DEFINITION && !node.directives?.some((d) => d.name.value === "extends")) { typeMergingTypeConfig.canonical = true; } entityTypes.push(typeName); const selectionsStr = selections.join(" "); typeMergingTypeConfig.selectionSet = `{ ${selectionsStr} }`; typeMergingTypeConfig.dataLoaderOptions = { cacheKeyFn: getCacheKeyFnFromKey(selectionsStr) }; typeMergingTypeConfig.argsFromKeys = getArgsFromKeysForFederation; typeMergingTypeConfig.fieldName = `_entities`; typeMergingTypeConfig.key = getKeyForFederation; const fields = []; if (node.fields) { for (const fieldNode of node.fields) { let removed = false; if (fieldNode.directives) { const fieldName = fieldNode.name.value; for (const directive of fieldNode.directives) { const directiveArgs = directive.arguments || []; switch (directive.name.value) { case "requires": { const typeMergingFieldsConfig = typeMergingTypeConfig.fields = typeMergingTypeConfig.fields || {}; typeMergingFieldsConfig[fieldName] = typeMergingFieldsConfig[fieldName] || {}; if (directiveArgs.some( (arg) => arg.name.value === "resolvable" && arg.value.kind === Kind.BOOLEAN && arg.value.value === false )) { continue; } const selectionValueNode = directiveArgs.find( (arg) => arg.name.value === "fields" )?.value; if (selectionValueNode?.kind === Kind.STRING) { typeMergingFieldsConfig[fieldName].selectionSet = `{ ${selectionValueNode.value} }`; typeMergingFieldsConfig[fieldName].computed = true; } break; } case "external": case "inaccessible": { removed = !typeMergingTypeConfig.selectionSet?.includes( ` ${fieldName} ` ); break; } case "override": { const typeMergingFieldsConfig = typeMergingTypeConfig.fields = typeMergingTypeConfig.fields || {}; typeMergingFieldsConfig[fieldName] = typeMergingFieldsConfig[fieldName] || {}; typeMergingFieldsConfig[fieldName].canonical = true; break; } } } } if (!removed) { fields.push(fieldNode); } } node.fields = fields; } } return { ...node, kind: Kind.OBJECT_TYPE_DEFINITION }; }; const parsedSDL = visit(typeDefs, { ObjectTypeExtension: visitor, ObjectTypeDefinition: visitor }); let extraSdl = SubgraphBaseSDL; if (entityTypes.length > 0) { extraSdl += ` union _Entity = ${entityTypes.join(" | ")}`; extraSdl += ` extend type Query { _entities(representations: [_Any!]!): [_Entity]! }`; } subschemaConfig.schema = buildASTSchema( mergeTypeDefs([extraSdl, parsedSDL]), { assumeValidSDL: true, assumeValid: true } ); return subschemaConfig; } async function getSubschemaForFederationWithExecutor(executor) { const sdlQueryResult = await executor({ document: parse(SubgraphSDLQuery) }); if (sdlQueryResult.errors?.length && sdlQueryResult.errors[0]) { const error = sdlQueryResult.errors[0]; throw createGraphQLError(error.message, error); } if (!sdlQueryResult.data?._service?.sdl) { throw new Error(`Unexpected result: ${inspect(sdlQueryResult)}`); } const typeDefs = parse(sdlQueryResult.data._service.sdl); const subschemaConfig = getSubschemaForFederationWithTypeDefs(typeDefs); return { ...subschemaConfig, executor }; } async function getSubschemaForFederationWithSchema(schema) { const executor = createDefaultExecutor(schema); return getSubschemaForFederationWithExecutor(executor); } async function getStitchedSchemaWithUrls(configs) { const subschemas = await Promise.all( configs.map((config) => getSubschemaForFederationWithURL(config)) ); const schema = stitchSchemas({ subschemas }); return filterInternalFieldsAndTypes(schema); } const federationSubschemaTransformer = function federationSubschemaTransformer2(subschemaConfig) { const typeDefs = getDocumentNodeFromSchema(subschemaConfig.schema); const newSubschema = getSubschemaForFederationWithTypeDefs(typeDefs); return { ...subschemaConfig, ...newSubschema, merge: { ...newSubschema.merge, ...subschemaConfig.merge } }; }; function ensureSupergraphSDLAst(supergraphSdl) { return typeof supergraphSdl === "string" ? parse(supergraphSdl, { noLocation: true }) : supergraphSdl; } const rootTypeMap = /* @__PURE__ */ new Map([ ["Query", "query"], ["Mutation", "mutation"], ["Subscription", "subscription"] ]); const memoizedASTPrint = memoize1(print); const memoizedTypePrint = memoize1( (type) => type.toString() ); function getStitchingOptionsFromSupergraphSdl(opts) { const supergraphAst = ensureSupergraphSDLAst(opts.supergraphSdl); const subgraphEndpointMap = /* @__PURE__ */ new Map(); const subgraphTypesMap = /* @__PURE__ */ new Map(); const typeNameKeysBySubgraphMap = /* @__PURE__ */ new Map(); const typeNameFieldsKeyBySubgraphMap = /* @__PURE__ */ new Map(); const typeNameCanonicalMap = /* @__PURE__ */ new Map(); const subgraphTypeNameProvidedMap = /* @__PURE__ */ new Map(); const subgraphTypeNameFieldProvidedSelectionMap = /* @__PURE__ */ new Map(); const orphanTypeMap = /* @__PURE__ */ new Map(); const typeFieldASTMap = /* @__PURE__ */ new Map(); for (const definition of supergraphAst.definitions) { if ("fields" in definition) { const fieldMap = /* @__PURE__ */ new Map(); typeFieldASTMap.set(definition.name.value, fieldMap); for (const field of definition.fields || []) { fieldMap.set(field.name.value, field); } } } const subgraphExternalFieldMap = /* @__PURE__ */ new Map(); const subgraphNames = []; visit(supergraphAst, { EnumTypeDefinition(node) { if (node.name.value === "join__Graph") { node.values?.forEach((valueNode) => { subgraphNames.push(valueNode.name.value); }); } } }); function TypeWithFieldsVisitor(typeNode) { 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: Kind.DIRECTIVE, name: { kind: Kind.NAME, value: "join__type" }, arguments: [ { kind: Kind.ARGUMENT, name: { kind: Kind.NAME, value: "graph" }, value: { kind: Kind.ENUM, value: subgraphName } } ] })) ]; } let isOrphan = true; const fieldDefinitionNodesByGraphName = /* @__PURE__ */ new Map(); typeNode.directives?.forEach((directiveNode) => { if (typeNode.kind === Kind.OBJECT_TYPE_DEFINITION) { if (directiveNode.name.value === "join__owner") { directiveNode.arguments?.forEach((argumentNode) => { if (argumentNode.name.value === "graph" && argumentNode.value?.kind === 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 === Kind.ENUM) { const graphName = joinTypeGraphArgNode.value.value; if (typeNode.kind === Kind.OBJECT_TYPE_DEFINITION || typeNode.kind === 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 === Kind.BOOLEAN && argumentNode.value.value === false ); if (isResolvable && keyArgumentNode?.value?.kind === Kind.STRING) { let typeNameKeysMap = typeNameKeysBySubgraphMap.get(graphName); if (!typeNameKeysMap) { typeNameKeysMap = /* @__PURE__ */ 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( (directiveNode2) => directiveNode2.name.value === "join__field" ); let notInSubgraph = true; joinFieldDirectives?.forEach((joinFieldDirectiveNode) => { const joinFieldGraphArgNode = joinFieldDirectiveNode.arguments?.find( (argumentNode) => argumentNode.name.value === "graph" ); if (joinFieldGraphArgNode?.value?.kind === Kind.ENUM && joinFieldGraphArgNode.value.value === graphName) { notInSubgraph = false; const isExternal = joinFieldDirectiveNode.arguments?.some( (argumentNode) => argumentNode.name.value === "external" && argumentNode.value?.kind === Kind.BOOLEAN && argumentNode.value.value === true ); const isOverridden = joinFieldDirectiveNode.arguments?.some( (argumentNode) => argumentNode.name.value === "usedOverridden" && argumentNode.value?.kind === Kind.BOOLEAN && argumentNode.value.value === true ); if (isExternal) { let externalFieldsByType = subgraphExternalFieldMap.get(graphName); if (!externalFieldsByType) { externalFieldsByType = /* @__PURE__ */ new Map(); subgraphExternalFieldMap.set( graphName, externalFieldsByType ); } let externalFields = externalFieldsByType.get( typeNode.name.value ); if (!externalFields) { externalFields = /* @__PURE__ */ 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 typeNode2 = typeArg?.value.kind === Kind.STRING ? parseType(typeArg.value.value) : fieldNode.type; fieldDefinitionNodesOfSubgraph.push({ ...fieldNode, type: typeNode2, directives: fieldNode.directives?.filter( (directiveNode2) => directiveNode2.name.value !== "join__field" ) }); } const providedExtraField = joinFieldDirectiveNode.arguments?.find( (argumentNode) => argumentNode.name.value === "provides" ); if (providedExtraField?.value?.kind === Kind.STRING) { let handleSelection2 = function(fieldNodeTypeName2, selection) { switch (selection.kind) { case Kind.FIELD: { const extraFieldTypeNode = supergraphAst.definitions.find( (def) => "name" in def && def.name?.value === fieldNodeTypeName2 ); const extraFieldNodeInType = extraFieldTypeNode.fields?.find( (fieldNode2) => fieldNode2.name.value === selection.name.value ); if (extraFieldNodeInType) { let typeNameProvidedMap = subgraphTypeNameProvidedMap.get(graphName); if (!typeNameProvidedMap) { typeNameProvidedMap = /* @__PURE__ */ new Map(); subgraphTypeNameProvidedMap.set( graphName, typeNameProvidedMap ); } let providedFields = typeNameProvidedMap.get(fieldNodeTypeName2); if (!providedFields) { providedFields = /* @__PURE__ */ new Set(); typeNameProvidedMap.set( fieldNodeTypeName2, providedFields ); } providedFields.add(selection.name.value); if (selection.selectionSet) { const extraFieldNodeNamedType = getNamedTypeNode( extraFieldNodeInType.type ); const extraFieldNodeTypeName = extraFieldNodeNamedType.name.value; for (const subSelection of selection.selectionSet.selections) { handleSelection2( extraFieldNodeTypeName, subSelection ); } } } } break; case Kind.INLINE_FRAGMENT: { const fragmentType = selection.typeCondition?.name?.value || fieldNodeType.name.value; if (selection.selectionSet) { for (const subSelection of selection.selectionSet.selections) { handleSelection2(fragmentType, subSelection); } } } break; } }; const providesSelectionSet = parseSelectionSet( /* GraphQL */ `{ ${providedExtraField.value.value} }` ); let typeNameFieldProvidedSelectionMap = subgraphTypeNameFieldProvidedSelectionMap.get(graphName); if (!typeNameFieldProvidedSelectionMap) { typeNameFieldProvidedSelectionMap = /* @__PURE__ */ new Map(); subgraphTypeNameFieldProvidedSelectionMap.set( graphName, typeNameFieldProvidedSelectionMap ); } let fieldProvidedSelectionMap = typeNameFieldProvidedSelectionMap.get(typeNode.name.value); if (!fieldProvidedSelectionMap) { fieldProvidedSelectionMap = /* @__PURE__ */ new Map(); typeNameFieldProvidedSelectionMap.set( typeNode.name.value, fieldProvidedSelectionMap ); } fieldProvidedSelectionMap.set( fieldNode.name.value, providesSelectionSet ); const fieldNodeType = getNamedTypeNode(fieldNode.type); const fieldNodeTypeName = fieldNodeType.name.value; for (const selection of providesSelectionSet.selections) { handleSelection2(fieldNodeTypeName, selection); } } const requiresArgumentNode = joinFieldDirectiveNode.arguments?.find( (argumentNode) => argumentNode.name.value === "requires" ); if (requiresArgumentNode?.value?.kind === Kind.STRING) { let typeNameFieldsKeyMap = typeNameFieldsKeyBySubgraphMap.get(graphName); if (!typeNameFieldsKeyMap) { typeNameFieldsKeyMap = /* @__PURE__ */ new Map(); typeNameFieldsKeyBySubgraphMap.set( graphName, typeNameFieldsKeyMap ); } let fieldsKeyMap = typeNameFieldsKeyMap.get( typeNode.name.value ); if (!fieldsKeyMap) { fieldsKeyMap = /* @__PURE__ */ new Map(); typeNameFieldsKeyMap.set(typeNode.name.value, fieldsKeyMap); } fieldsKeyMap.set( fieldNode.name.value, requiresArgumentNode.value.value ); } } }); if (!joinFieldDirectives?.length) { fieldDefinitionNodesOfSubgraph.push({ ...fieldNode, directives: fieldNode.directives?.filter( (directiveNode2) => directiveNode2.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( (directiveNode2) => directiveNode2.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( (argumentNode2) => argumentNode2.name.value === "interface" ); return argumentNode?.value?.kind === Kind.STRING && argumentNode.value.value === interfaceNode.name.value; } ); if (!implementedSubgraphs?.length || implementedSubgraphs.some((directiveNode) => { const argumentNode = directiveNode.arguments?.find( (argumentNode2) => argumentNode2.name.value === "graph" ); return argumentNode?.value?.kind === 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); } } 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 === 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( (directiveNode2) => directiveNode2.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 === 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( (directiveNode2) => directiveNode2.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 === Kind.ENUM) { isOrphan = false; const graphName = argumentNode.value.value; const unionMembers = []; node.directives?.forEach((directiveNode2) => { if (directiveNode2.name.value === "join__unionMember") { const graphArgumentNode = directiveNode2.arguments?.find( (argumentNode2) => argumentNode2.name.value === "graph" ); const memberArgumentNode = directiveNode2.arguments?.find( (argumentNode2) => argumentNode2.name.value === "member" ); if (graphArgumentNode?.value?.kind === Kind.ENUM && graphArgumentNode.value.value === graphName && memberArgumentNode?.value?.kind === Kind.STRING) { unionMembers.push({ kind: Kind.NAMED_TYPE, name: { kind: 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( (directiveNode2) => directiveNode2.name.value !== "join__type" && directiveNode2.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 === 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 === Kind.ENUM) { const graphName = argumentNode.value.value; const enumValueNodes = []; node.values?.forEach((valueNode) => { const joinEnumValueDirectives = valueNode.directives?.filter( (directiveNode2) => directiveNode2.name.value === "join__enumValue" ); if (joinEnumValueDirectives?.length) { joinEnumValueDirectives.forEach( (joinEnumValueDirectiveNode) => { joinEnumValueDirectiveNode.arguments?.forEach( (argumentNode2) => { if (argumentNode2.name.value === "graph" && argumentNode2.value?.kind === Kind.ENUM && argumentNode2.value.value === graphName) { enumValueNodes.push({ ...valueNode, directives: valueNode.directives?.filter( (directiveNode2) => directiveNode2.name.value !== "join__enumValue" ) }); } } ); } ); } else { enumValueNodes.push(valueNode); } }); const enumTypedDefNodeForSubgraph = { ...node, directives: node.directives?.filter( (directiveNode2) => directiveNode2.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) { let visitTypeDefinitionsForOrphanTypes2 = function(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 = visitTypeDefinitionsForOrphanTypes2(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( getNamedTypeNode(field.type) ); if (!isTypeNodeOk) { continue; } if (field.kind === Kind.FIELD_DEFINITION) { const args = visitFieldDefs( field.arguments ); fields.push({ ...field, arguments: args }); } else { fields.push(field); } } return fields; } function visitObjectAndInterfaceDefs(node2) { const fields = visitFieldDefs(node2.fields); const interfaces = []; for (const iface of node2.interfaces || []) { const isTypeNodeOk = visitNamedTypeNode(iface); if (!isTypeNodeOk) { continue; } interfaces.push(iface); } return { ...node2, fields, interfaces }; } return visit(node, { [Kind.OBJECT_TYPE_DEFINITION]: visitObjectAndInterfaceDefs, [Kind.OBJECT_TYPE_EXTENSION]: visitObjectAndInterfaceDefs, [Kind.INTERFACE_TYPE_DEFINITION]: visitObjectAndInterfaceDefs, [Kind.INTERFACE_TYPE_EXTENSION]: visitObjectAndInterfaceDefs, [Kind.UNION_TYPE_DEFINITION](node2) { const types = []; for (const type of node2.types || []) { const isTypeNodeOk = visitNamedTypeNode(type); if (!isTypeNodeOk) { continue; } types.push(type); } return { ...node2, types }; }, [Kind.UNION_TYPE_EXTENSION](node2) { const types = []; for (const type of node2.types || []) { const isTypeNodeOk = visitNamedTypeNode(type); if (!isTypeNodeOk) { continue; } types.push(type); } return { ...node2, types }; }, [Kind.INPUT_OBJECT_TYPE_DEFINITION](node2) { const fields = visitFieldDefs(node2.fields); return { ...node2, fields }; }, [Kind.INPUT_OBJECT_TYPE_EXTENSION](node2) { const fields = visitFieldDefs(node2.fields); return { ...node2, fields }; } }); }; const mergeConfig = {}; const typeNameKeyMap = typeNameKeysBySubgraphMap.get(subgraphName); const unionTypeNodes = []; if (typeNameKeyMap) { const typeNameFieldsKeyMap = typeNameFieldsKeyBySubgraphMap.get(subgraphName); for (const [typeName, keys] of typeNameKeyMap) { let getMergedTypeConfigFromKey2 = function(key) { return { selectionSet: `{ ${key} }`, argsFromKeys: getArgsFromKeysForFederation, key: getKeyFnForFederation(typeName, [key, ...extraKeys]), fieldName: `_entities`, dataLoaderOptions: { cacheKeyFn: getCacheKeyFnFromKey(key), ...opts.batchDelegateOptions || {} } }; }; const mergedTypeConfig = mergeConfig[typeName] = {}; const fieldsKeyMap = typeNameFieldsKeyMap?.get(typeName); const extraKeys = /* @__PURE__ */ 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; } if (keys.length === 1 && keys[0]) { Object.assign(mergedTypeConfig, getMergedTypeConfigFromKey2(keys[0])); } if (keys.length > 1) { const entryPoints = keys.map( (key) => getMergedTypeConfigFromKey2(key) ); mergedTypeConfig.entryPoints = entryPoints; } unionTypeNodes.push({ kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: typeName } }); opts.onMergedTypeConfig?.(typeName, mergedTypeConfig); } } const typeNameProvidedSelectionMap = subgraphTypeNameFieldProvidedSelectionMap.get(subgraphName); if (typeNameProvidedSelectionMap) { for (const [ typeName, fieldSelectionMap ] of typeNameProvidedSelectionMap) { const mergedTypeConfig = mergeConfig[typeName] ||= {}; const fieldsConfig = mergedTypeConfig.fields ||= {}; for (const [fieldName, selectionSet] of fieldSelectionMap) { fieldsConfig[fieldName] = { provides: selectionSet }; } } } const entitiesUnionTypeDefinitionNode = { name: { kind: Kind.NAME, value: "_Entity" }, kind: Kind.UNION_TYPE_DEFINITION, types: unionTypeNodes }; const extraOrphanTypesForSubgraph = /* @__PURE__ */ new Map(); const subgraphTypes = subgraphTypesMap.get(subgraphName) || []; subgraphTypes.forEach((typeNode) => { visitTypeDefinitionsForOrphanTypes2(typeNode); }); const extendedSubgraphTypes = [ ...subgraphTypes, ...extraOrphanTypesForSubgraph.values() ]; for (const interfaceInSubgraph of extendedSubgraphTypes) { if (interfaceInSubgraph.kind === Kind.INTERFACE_TYPE_DEFINITION) { let isOrphan = true; for (const definitionNode of supergraphAst.definitions) { if (definitionNode.kind === Kind.OBJECT_TYPE_DEFINITION && definitionNode.interfaces?.some( (interfaceNode) => interfaceNode.name.value === interfaceInSubgraph.name.value )) { isOrphan = false; } } if (isOrphan) { interfaceInSubgraph.kind = Kind.OBJECT_TYPE_DEFINITION; } } } let schema; let schemaAst = { kind: Kind.DOCUMENT, definitions: [ ...extendedSubgraphTypes, entitiesUnionTypeDefinitionNode, anyTypeDefinitionNode ] }; if (opts.onSubgraphAST) { schemaAst = opts.onSubgraphAST(subgraphName, schemaAst); } try { schema = 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 = buildHTTPExecutor({ endpoint, ...httpExecutorOpts }); if (globalThis.process?.env?.["DEBUG"]) { const origExecutor = executor; executor = async function debugExecutor(execReq) { console.log(`Executing ${subgraphName} with args:`, { document: print(execReq.document), variables: JSON.stringify(execReq.variables) }); const res = await origExecutor(execReq); console.log(`Response from ${subgraphName}:`, JSON.stringify(res)); return res; }; } const typeNameProvidedMap = subgraphTypeNameProvidedMap.get(subgraphName); const externalFieldMap = subgraphExternalFieldMap.get(subgra