UNPKG

@apollo/federation

Version:
380 lines 17.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.composeServices = exports.addFederationMetadataToSchemaNodes = exports.buildSchemaFromDefinitionsAndExtensions = exports.buildMapsFromServiceList = void 0; const graphql_1 = require("graphql"); const schema_helper_1 = require("@apollo/subgraph/dist/schema-helper"); const directives_1 = require("@apollo/subgraph/dist/directives"); const utils_1 = require("./utils"); const validate_1 = require("graphql/validation/validate"); const rules_1 = require("./rules"); const printSupergraphSdl_1 = require("../service/printSupergraphSdl"); const utilities_1 = require("../utilities"); const DirectiveMetadata_1 = require("./DirectiveMetadata"); const joinSpec_1 = require("../joinSpec"); const coreSpec_1 = require("../coreSpec"); const EmptyQueryDefinition = { kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION, name: { kind: graphql_1.Kind.NAME, value: utils_1.defaultRootOperationNameLookup.query }, fields: [], serviceName: null, }; const EmptyMutationDefinition = { kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION, name: { kind: graphql_1.Kind.NAME, value: utils_1.defaultRootOperationNameLookup.mutation }, fields: [], serviceName: null, }; function buildMapsFromServiceList(serviceList) { const typeDefinitionsMap = Object.create(null); const typeExtensionsMap = Object.create(null); const directiveDefinitionsMap = Object.create(null); const typeToServiceMap = Object.create(null); const externalFields = []; const keyDirectivesMap = Object.create(null); const valueTypes = new Set(); const directiveMetadata = new DirectiveMetadata_1.DirectiveMetadata(serviceList); for (const { typeDefs, name: serviceName } of serviceList) { const { typeDefsWithoutExternalFields, strippedFields, } = (0, utils_1.stripExternalFieldsFromTypeDefs)(typeDefs, serviceName); externalFields.push(...strippedFields); const typeDefsWithoutTypeSystemDirectives = (0, utils_1.stripTypeSystemDirectivesFromTypeDefs)(typeDefsWithoutExternalFields); for (const definition of typeDefsWithoutTypeSystemDirectives.definitions) { if (definition.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION || definition.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) { const typeName = definition.name.value; for (const keyDirective of (0, utils_1.findDirectivesOnNode)(definition, 'key')) { if (keyDirective.arguments && (0, utils_1.isStringValueNode)(keyDirective.arguments[0].value)) { keyDirectivesMap[typeName] = keyDirectivesMap[typeName] || {}; keyDirectivesMap[typeName][serviceName] = keyDirectivesMap[typeName][serviceName] || []; keyDirectivesMap[typeName][serviceName].push((0, utils_1.parseFieldSet)(keyDirective.arguments[0].value.value)); } } } if ((0, graphql_1.isTypeDefinitionNode)(definition)) { const typeName = definition.name.value; if (!typeToServiceMap[typeName]) { typeToServiceMap[typeName] = { extensionFieldsToOwningServiceMap: Object.create(null), }; } typeToServiceMap[typeName].owningService = serviceName; if (typeDefinitionsMap[typeName]) { const isValueType = (0, utils_1.typeNodesAreEquivalent)(typeDefinitionsMap[typeName][typeDefinitionsMap[typeName].length - 1], definition); if (isValueType) { valueTypes.add(typeName); } typeDefinitionsMap[typeName].push({ ...definition, serviceName }); } else { typeDefinitionsMap[typeName] = [{ ...definition, serviceName }]; } } else if ((0, graphql_1.isTypeExtensionNode)(definition)) { const typeName = definition.name.value; if (definition.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION || definition.kind === graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION) { if (!definition.fields) break; const fields = (0, utils_1.mapFieldNamesToServiceName)(definition.fields, serviceName); if (typeToServiceMap[typeName]) { typeToServiceMap[typeName].extensionFieldsToOwningServiceMap = { ...typeToServiceMap[typeName].extensionFieldsToOwningServiceMap, ...fields, }; } else { typeToServiceMap[typeName] = { extensionFieldsToOwningServiceMap: fields, }; } } if (definition.kind === graphql_1.Kind.ENUM_TYPE_EXTENSION) { if (!definition.values) break; const values = (0, utils_1.mapFieldNamesToServiceName)(definition.values, serviceName); if (typeToServiceMap[typeName]) { typeToServiceMap[typeName].extensionFieldsToOwningServiceMap = { ...typeToServiceMap[typeName].extensionFieldsToOwningServiceMap, ...values, }; } else { typeToServiceMap[typeName] = { extensionFieldsToOwningServiceMap: values, }; } } if (typeExtensionsMap[typeName]) { typeExtensionsMap[typeName].push({ ...definition, serviceName }); } else { typeExtensionsMap[typeName] = [{ ...definition, serviceName }]; } } else if ((0, utils_1.isDirectiveDefinitionNode)(definition)) { const directiveName = definition.name.value; const executableLocations = definition.locations.filter(location => utils_1.executableDirectiveLocations.includes(location.value)); if (executableLocations.length === 0) continue; const definitionWithExecutableLocations = { ...definition, locations: executableLocations, }; if (directiveDefinitionsMap[directiveName]) { directiveDefinitionsMap[directiveName][serviceName] = definitionWithExecutableLocations; } else { directiveDefinitionsMap[directiveName] = { [serviceName]: definitionWithExecutableLocations, }; } } } } if (!typeDefinitionsMap.Query) typeDefinitionsMap.Query = [EmptyQueryDefinition]; if (typeExtensionsMap.Mutation && !typeDefinitionsMap.Mutation) typeDefinitionsMap.Mutation = [EmptyMutationDefinition]; return { typeToServiceMap, typeDefinitionsMap, typeExtensionsMap, directiveDefinitionsMap, externalFields, keyDirectivesMap, valueTypes, directiveMetadata }; } exports.buildMapsFromServiceList = buildMapsFromServiceList; function buildSchemaFromDefinitionsAndExtensions({ typeDefinitionsMap, typeExtensionsMap, directiveDefinitionsMap, directiveMetadata, serviceList, }) { let errors = undefined; const autoIncludedDirectiveDefinitions = directives_1.directivesWithAutoIncludedDefinitions.filter((directive) => directiveMetadata.hasUsages(directive.name)); const { FieldSetScalar, JoinFieldDirective, JoinTypeDirective, JoinOwnerDirective, JoinGraphEnum, JoinGraphDirective, } = (0, joinSpec_1.getJoinDefinitions)(serviceList); let schema = new graphql_1.GraphQLSchema({ query: undefined, directives: [ coreSpec_1.CoreDirective, JoinFieldDirective, JoinTypeDirective, JoinOwnerDirective, JoinGraphDirective, ...graphql_1.specifiedDirectives, ...directives_1.directivesWithNoDefinitionNeeded, ...autoIncludedDirectiveDefinitions, ], types: [FieldSetScalar, JoinGraphEnum], }); function nodeHasInterfaces(node) { return 'interfaces' in node; } const definitionsDocument = { kind: graphql_1.Kind.DOCUMENT, definitions: [ ...Object.values(typeDefinitionsMap).flatMap((typeDefinitions) => { if (!typeDefinitions.some(nodeHasInterfaces)) return typeDefinitions; const uniqueInterfaces = typeDefinitions.reduce((map, objectTypeDef) => { var _a; (_a = objectTypeDef.interfaces) === null || _a === void 0 ? void 0 : _a.forEach((iface) => map.set(iface.name.value, iface)); return map; }, new Map()); if (uniqueInterfaces.size === 0) return typeDefinitions; const [first, ...rest] = typeDefinitions; return [ ...rest, { ...first, interfaces: Array.from(uniqueInterfaces.values()), }, ]; }), ...Object.values(directiveDefinitionsMap).map((definitions) => Object.values(definitions)[0]), ], }; errors = (0, validate_1.validateSDL)(definitionsDocument, schema, rules_1.compositionRules); try { schema = (0, graphql_1.extendSchema)(schema, definitionsDocument, { assumeValidSDL: true, }); } catch (e) { } const extensionsDocument = { kind: graphql_1.Kind.DOCUMENT, definitions: Object.values(typeExtensionsMap).flat(), }; errors.push(...(0, validate_1.validateSDL)(extensionsDocument, schema, rules_1.compositionRules)); try { schema = (0, graphql_1.extendSchema)(schema, extensionsDocument, { assumeValidSDL: true, }); } catch { } schema = new graphql_1.GraphQLSchema({ ...schema.toConfig(), directives: [ ...schema .getDirectives() .filter((x) => !(0, directives_1.isDirectiveWithNoDefinitionNeeded)(x)), ], }); return { schema, errors }; } exports.buildSchemaFromDefinitionsAndExtensions = buildSchemaFromDefinitionsAndExtensions; function addFederationMetadataToSchemaNodes({ schema, typeToServiceMap, externalFields, keyDirectivesMap, valueTypes, directiveDefinitionsMap, directiveMetadata, }) { var _a; for (const [typeName, { owningService, extensionFieldsToOwningServiceMap },] of Object.entries(typeToServiceMap)) { const namedType = schema.getType(typeName); if (!namedType) continue; const isValueType = valueTypes.has(typeName); const serviceName = isValueType ? null : owningService; const federationMetadata = { ...(0, utils_1.getFederationMetadata)(namedType), serviceName, isValueType, ...(keyDirectivesMap[typeName] && { keys: keyDirectivesMap[typeName], }), }; namedType.extensions = { ...namedType.extensions, federation: federationMetadata, }; if ((0, graphql_1.isObjectType)(namedType)) { for (const field of Object.values(namedType.getFields())) { const [providesDirective] = (0, utils_1.findDirectivesOnNode)(field.astNode, 'provides'); if (providesDirective && providesDirective.arguments && (0, utils_1.isStringValueNode)(providesDirective.arguments[0].value)) { const fieldFederationMetadata = { ...(0, utils_1.getFederationMetadata)(field), serviceName, provides: (0, utils_1.parseFieldSet)(providesDirective.arguments[0].value.value), belongsToValueType: isValueType, }; field.extensions = { ...field.extensions, federation: fieldFederationMetadata, }; } } } for (const [fieldName, extendingServiceName] of Object.entries(extensionFieldsToOwningServiceMap)) { if ((0, graphql_1.isObjectType)(namedType)) { const field = namedType.getFields()[fieldName]; if (!field) continue; const fieldFederationMetadata = { ...(0, utils_1.getFederationMetadata)(field), serviceName: extendingServiceName, }; field.extensions = { ...field.extensions, federation: fieldFederationMetadata, }; const [requiresDirective] = (0, utils_1.findDirectivesOnNode)(field.astNode, 'requires'); if (requiresDirective && requiresDirective.arguments && (0, utils_1.isStringValueNode)(requiresDirective.arguments[0].value)) { const fieldFederationMetadata = { ...(0, utils_1.getFederationMetadata)(field), requires: (0, utils_1.parseFieldSet)(requiresDirective.arguments[0].value.value), }; field.extensions = { ...field.extensions, federation: fieldFederationMetadata, }; } } } } for (const field of externalFields) { const namedType = schema.getType(field.parentTypeName); if (!namedType) continue; const existingMetadata = (0, utils_1.getFederationMetadata)(namedType); const typeFederationMetadata = { ...existingMetadata, externals: { ...existingMetadata === null || existingMetadata === void 0 ? void 0 : existingMetadata.externals, [field.serviceName]: [ ...(((_a = existingMetadata === null || existingMetadata === void 0 ? void 0 : existingMetadata.externals) === null || _a === void 0 ? void 0 : _a[field.serviceName]) || []), field, ], }, }; namedType.extensions = { ...namedType.extensions, federation: typeFederationMetadata, }; } for (const directiveName of Object.keys(directiveDefinitionsMap)) { const directive = schema.getDirective(directiveName); if (!directive) continue; const directiveFederationMetadata = { ...(0, utils_1.getFederationMetadata)(directive), directiveDefinitions: directiveDefinitionsMap[directiveName], }; directive.extensions = { ...directive.extensions, federation: directiveFederationMetadata, }; } directiveMetadata.applyMetadataToSupergraphSchema(schema); } exports.addFederationMetadataToSchemaNodes = addFederationMetadataToSchemaNodes; function composeServices(services) { const { typeToServiceMap, typeDefinitionsMap, typeExtensionsMap, directiveDefinitionsMap, externalFields, keyDirectivesMap, valueTypes, directiveMetadata, } = buildMapsFromServiceList(services); let { schema, errors } = buildSchemaFromDefinitionsAndExtensions({ typeDefinitionsMap, typeExtensionsMap, directiveDefinitionsMap, directiveMetadata, serviceList: services, }); schema = new graphql_1.GraphQLSchema({ ...schema.toConfig(), ...(0, utilities_1.mapValues)(utils_1.defaultRootOperationNameLookup, typeName => typeName ? schema.getType(typeName) : undefined), extensions: { serviceList: services } }); schema = (0, schema_helper_1.transformSchema)(schema, type => { if ((0, graphql_1.isObjectType)(type)) { const config = type.toConfig(); return new graphql_1.GraphQLObjectType({ ...config, interfaces: Array.from(new Set(config.interfaces)), }); } return undefined; }); schema = (0, graphql_1.lexicographicSortSchema)(schema); addFederationMetadataToSchemaNodes({ schema, typeToServiceMap, externalFields, keyDirectivesMap, valueTypes, directiveDefinitionsMap, directiveMetadata, }); const { graphNameToEnumValueName } = (0, joinSpec_1.getJoinDefinitions)(services); if (errors.length > 0) { return { schema, errors }; } else { return { schema, supergraphSdl: (0, printSupergraphSdl_1.printSupergraphSdl)(schema, graphNameToEnumValueName), }; } } exports.composeServices = composeServices; //# sourceMappingURL=compose.js.map