UNPKG

@graphql-tools/federation

Version:

Useful tools to create and manipulate GraphQL schemas.

487 lines (486 loc) • 27.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getStitchedSchemaFromSupergraphSdl = exports.getSubschemasFromSupergraphSdl = void 0; const graphql_1 = require("graphql"); const executor_http_1 = require("@graphql-tools/executor-http"); const stitch_1 = require("@graphql-tools/stitch"); const utils_js_1 = require("./utils.js"); function getSubschemasFromSupergraphSdl({ supergraphSdl, onExecutor = ({ endpoint }) => (0, executor_http_1.buildHTTPExecutor)({ endpoint }), batch = false, }) { const ast = typeof supergraphSdl === 'string' ? (0, graphql_1.parse)(supergraphSdl, { noLocation: true }) : supergraphSdl; const subgraphRootFieldDefinitionNodes = new Map(); const subgraphEndpointMap = new Map(); const subgraphTypesMap = new Map(); const typeNameKeysBySubgraphMap = new Map(); const typeNameFieldsKeyBySubgraphMap = new Map(); const typeNameCanonicalMap = new Map(); const rootTypeNames = new Map(); const subgraphTypeNameExtraFieldsMap = new Map(); function TypeWithFieldsVisitor(typeNode) { if ([...rootTypeNames.values()].includes(typeNode.name.value)) { const operationTypeName = [...rootTypeNames.entries()].find(([, rootTypeName]) => rootTypeName === typeNode.name.value)[0]; typeNode.fields?.forEach(fieldNode => { fieldNode.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__field') { directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph' && argumentNode.value?.kind === graphql_1.Kind.ENUM) { const graphName = argumentNode.value.value; let subgraphRootFieldDefinitionNodeMap = subgraphRootFieldDefinitionNodes.get(graphName); if (!subgraphRootFieldDefinitionNodeMap) { subgraphRootFieldDefinitionNodeMap = new Map(); subgraphRootFieldDefinitionNodes.set(graphName, subgraphRootFieldDefinitionNodeMap); } let fieldDefinitionNodesOfSubgraph = subgraphRootFieldDefinitionNodeMap.get(operationTypeName); if (!fieldDefinitionNodesOfSubgraph) { fieldDefinitionNodesOfSubgraph = []; subgraphRootFieldDefinitionNodeMap.set(operationTypeName, fieldDefinitionNodesOfSubgraph); } fieldDefinitionNodesOfSubgraph.push({ ...fieldNode, directives: fieldNode.directives?.filter(directiveNode => directiveNode.name.value !== 'join__field'), }); } }); } }); }); } 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') { 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) { 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'); 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) { const isExternal = joinFieldDirectiveNode.arguments?.some(argumentNode => argumentNode.name.value === 'external' && argumentNode.value?.kind === graphql_1.Kind.BOOLEAN && argumentNode.value.value === true); if (!isExternal) { fieldDefinitionNodesOfSubgraph.push({ ...fieldNode, 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) { let typeNameExtraFieldsMap = subgraphTypeNameExtraFieldsMap.get(graphName); if (!typeNameExtraFieldsMap) { typeNameExtraFieldsMap = new Map(); subgraphTypeNameExtraFieldsMap.set(graphName, typeNameExtraFieldsMap); } const fieldNodeType = (0, utils_js_1.getNamedTypeNode)(fieldNode.type); let extraFields = typeNameExtraFieldsMap.get(fieldNodeType.name.value); if (!extraFields) { extraFields = []; typeNameExtraFieldsMap.set(fieldNodeType.name.value, extraFields); } const extraFieldTypeNode = ast.definitions.find(def => 'name' in def && def.name?.value === fieldNodeType.name.value); providedExtraField.value.value.split(' ').forEach(extraField => { const extraFieldNodeInType = extraFieldTypeNode.fields?.find(fieldNode => fieldNode.name.value === extraField); if (extraFieldNodeInType) { extraFields.push({ ...extraFieldNodeInType, directives: extraFieldNodeInType.directives?.filter(directiveNode => directiveNode.name.value !== 'join__field'), }); } }); } 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'), }); } }); 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); } }); 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); }); } (0, graphql_1.visit)(ast, { SchemaDefinition(node) { node.operationTypes?.forEach(operationTypeNode => { rootTypeNames.set(operationTypeNode.operation, operationTypeNode.type.name.value); }); }, ScalarTypeDefinition(node) { 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) { 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'), }); } }); } }); }, InputObjectTypeDefinition(node) { 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) { 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'), }); } }); } }); }, InterfaceTypeDefinition: TypeWithFieldsVisitor, UnionTypeDefinition(node) { 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) { const graphName = argumentNode.value.value; const unionMembers = []; node.directives?.forEach(directiveNode => { if (directiveNode.name.value === 'join__unionMember') { directiveNode.arguments?.forEach(argumentNode => { if (argumentNode.name.value === 'graph') { if (argumentNode?.value?.kind === graphql_1.Kind.ENUM) { if (argumentNode.value.value === graphName) { unionMembers.push({ kind: graphql_1.Kind.NAMED_TYPE, name: node.name, }); } } } }); } }); 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'), }); } } }); } }); }, EnumTypeDefinition(node) { if (node.name.value === 'join__Graph') { node.values?.forEach(valueNode => { 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') { 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); } }); } }); }, ObjectTypeDefinition: TypeWithFieldsVisitor, }); const subschemaMap = new Map(); for (const [subgraphName, endpoint] of subgraphEndpointMap) { const executor = onExecutor({ subgraphName, endpoint }); 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, }; let subgraphRootFieldDefinitionNodeMap = subgraphRootFieldDefinitionNodes.get(subgraphName); if (!subgraphRootFieldDefinitionNodeMap) { subgraphRootFieldDefinitionNodeMap = new Map(); subgraphRootFieldDefinitionNodes.set(subgraphName, subgraphRootFieldDefinitionNodeMap); } let queryFields = subgraphRootFieldDefinitionNodeMap.get('query'); if (!queryFields) { queryFields = []; subgraphRootFieldDefinitionNodeMap.set('query', queryFields); } queryFields.push({ 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 rootTypes = []; for (const [operationType, fieldDefinitionNodes] of subgraphRootFieldDefinitionNodeMap) { rootTypes.push({ kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION, name: { kind: graphql_1.Kind.NAME, value: rootTypeNames.get(operationType), }, fields: fieldDefinitionNodes, }); } const subgraphTypes = subgraphTypesMap.get(subgraphName) || []; const typeNameExtraFieldsMap = subgraphTypeNameExtraFieldsMap.get(subgraphName); if (typeNameExtraFieldsMap) { subgraphTypes.forEach(typeNode => { if ('fields' in typeNode) { const extraFields = typeNameExtraFieldsMap.get(typeNode.name.value); if (extraFields) { typeNode.fields.push(...extraFields); } } }); } const schema = (0, graphql_1.buildASTSchema)({ kind: graphql_1.Kind.DOCUMENT, definitions: [ ...subgraphTypes, entitiesUnionTypeDefinitionNode, anyTypeDefinitionNode, ...rootTypes, ], }, { assumeValidSDL: true, assumeValid: true, }); subschemaMap.set(subgraphName, { schema, executor, merge: mergeConfig, batch, }); } 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, }); 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, };