@apollo/federation
Version:
Apollo Federation Utilities
380 lines • 17.5 kB
JavaScript
"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