@apollo/federation
Version:
Apollo Federation Utilities
422 lines • 18.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFederationMetadata = exports.assertCompositionFailure = exports.assertCompositionSuccess = exports.compositionHasErrors = exports.defaultRootOperationNameLookup = exports.reservedRootFields = exports.executableDirectiveLocations = exports.defKindToExtKind = exports.findTypeNodeInServiceList = exports.typeNodesAreEquivalent = exports.diffTypeNodes = exports.isTypeNodeAnEntity = exports.selectionIncludesField = exports.findFieldsThatReturnType = exports.findTypesContainingFieldWithReturnType = exports.errorWithCode = exports.logDirective = exports.logServiceAndType = exports.hasMatchingFieldInDirectives = exports.parseFieldSet = exports.stripTypeSystemDirectivesFromTypeDefs = exports.stripExternalFieldsFromTypeDefs = exports.findSelectionSetOnNode = exports.printFieldSet = exports.findDirectivesOnNode = exports.mapFieldNamesToServiceName = exports.isNamedTypeNode = exports.isNonNullTypeNode = exports.isDirectiveDefinitionNode = exports.isStringValueNode = void 0;
const graphql_1 = require("graphql");
const directives_1 = require("@apollo/subgraph/dist/directives");
const utilities_1 = require("../utilities");
const parser_1 = require("graphql/language/parser");
function isStringValueNode(node) {
return node.kind === graphql_1.Kind.STRING;
}
exports.isStringValueNode = isStringValueNode;
function isDirectiveDefinitionNode(node) {
return node.kind === graphql_1.Kind.DIRECTIVE_DEFINITION;
}
exports.isDirectiveDefinitionNode = isDirectiveDefinitionNode;
function isNonNullTypeNode(node) {
return node.kind === graphql_1.Kind.NON_NULL_TYPE;
}
exports.isNonNullTypeNode = isNonNullTypeNode;
function isNamedTypeNode(node) {
return node.kind === graphql_1.Kind.NAMED_TYPE;
}
exports.isNamedTypeNode = isNamedTypeNode;
function mapFieldNamesToServiceName(fields, serviceName) {
return fields.reduce((prev, next) => {
prev[next.name.value] = serviceName;
return prev;
}, Object.create(null));
}
exports.mapFieldNamesToServiceName = mapFieldNamesToServiceName;
function findDirectivesOnNode(node, directiveName) {
var _a, _b;
return ((_b = (_a = node === null || node === void 0 ? void 0 : node.directives) === null || _a === void 0 ? void 0 : _a.filter((directive) => directive.name.value === directiveName)) !== null && _b !== void 0 ? _b : []);
}
exports.findDirectivesOnNode = findDirectivesOnNode;
function printFieldSet(selections) {
return selections
.map((selection) => (0, graphql_1.stripIgnoredCharacters)((0, graphql_1.print)(selection)))
.join(' ');
}
exports.printFieldSet = printFieldSet;
function findSelectionSetOnNode(node, directiveName, printedSelectionSet) {
var _a, _b, _c, _d;
return (_d = (_c = (_b = (_a = node === null || node === void 0 ? void 0 : node.directives) === null || _a === void 0 ? void 0 : _a.find(directive => {
var _a;
return directive.name.value === directiveName && ((_a = directive.arguments) === null || _a === void 0 ? void 0 : _a.some(argument => isStringValueNode(argument.value) &&
argument.value.value === printedSelectionSet));
})) === null || _b === void 0 ? void 0 : _b.arguments) === null || _c === void 0 ? void 0 : _c.find(argument => argument.name.value === 'fields')) === null || _d === void 0 ? void 0 : _d.value;
}
exports.findSelectionSetOnNode = findSelectionSetOnNode;
function stripExternalFieldsFromTypeDefs(typeDefs, serviceName) {
const strippedFields = [];
const typeDefsWithoutExternalFields = (0, graphql_1.visit)(typeDefs, {
ObjectTypeExtension: removeExternalFieldsFromExtensionVisitor(strippedFields, serviceName),
InterfaceTypeExtension: removeExternalFieldsFromExtensionVisitor(strippedFields, serviceName),
});
return { typeDefsWithoutExternalFields, strippedFields };
}
exports.stripExternalFieldsFromTypeDefs = stripExternalFieldsFromTypeDefs;
function stripTypeSystemDirectivesFromTypeDefs(typeDefs) {
const typeDefsWithoutTypeSystemDirectives = (0, graphql_1.visit)(typeDefs, {
Directive(node) {
if (node.name.value === 'deprecated' || node.name.value === 'specifiedBy')
return;
const isKnownSubgraphDirective = directives_1.knownSubgraphDirectives.some(({ name }) => name === node.name.value);
return isKnownSubgraphDirective ? undefined : null;
},
});
return typeDefsWithoutTypeSystemDirectives;
}
exports.stripTypeSystemDirectivesFromTypeDefs = stripTypeSystemDirectivesFromTypeDefs;
function removeExternalFieldsFromExtensionVisitor(collector, serviceName) {
return (node) => {
let fields = node.fields;
if (fields) {
fields = fields.filter(field => {
const externalDirectives = findDirectivesOnNode(field, 'external');
if (externalDirectives.length > 0) {
collector.push({
field,
parentTypeName: node.name.value,
serviceName,
});
return false;
}
return true;
});
}
return {
...node,
fields,
};
};
}
function parseFieldSet(source) {
const parser = new parser_1.Parser(`{${source}}`);
parser.expectToken(graphql_1.TokenKind.SOF);
const selectionSet = parser.parseSelectionSet();
try {
parser.expectToken(graphql_1.TokenKind.EOF);
}
catch {
throw new Error(`Invalid FieldSet provided: '${source}'. FieldSets may not contain operations within them.`);
}
const selections = selectionSet.selections;
(0, utilities_1.assert)(selections.length > 0, `Field sets may not be empty`);
(0, graphql_1.visit)(selectionSet, {
FragmentSpread() {
throw Error(`Field sets may not contain fragment spreads, but found: "${source}"`);
},
});
return selections;
}
exports.parseFieldSet = parseFieldSet;
function hasMatchingFieldInDirectives({ directives, fieldNameToMatch, namedType, }) {
return Boolean(namedType.astNode &&
directives
.map(keyDirective => keyDirective.arguments &&
isStringValueNode(keyDirective.arguments[0].value)
? {
typeName: namedType.astNode.name.value,
keyArgument: keyDirective.arguments[0].value.value,
}
: null)
.filter(utilities_1.isNotNullOrUndefined)
.flatMap(selection => parseFieldSet(selection.keyArgument))
.some(field => field.kind === graphql_1.Kind.FIELD && field.name.value === fieldNameToMatch));
}
exports.hasMatchingFieldInDirectives = hasMatchingFieldInDirectives;
const logServiceAndType = (serviceName, typeName, fieldName) => `[${serviceName}] ${typeName}${fieldName ? `.${fieldName} -> ` : ' -> '}`;
exports.logServiceAndType = logServiceAndType;
function logDirective(directiveName) {
return `[@${directiveName}] -> `;
}
exports.logDirective = logDirective;
function errorWithCode(code, message, nodes) {
return new graphql_1.GraphQLError(message, nodes, undefined, undefined, undefined, undefined, {
code,
});
}
exports.errorWithCode = errorWithCode;
function findTypesContainingFieldWithReturnType(schema, node) {
const returnType = (0, graphql_1.getNamedType)(node.type);
if (!(0, graphql_1.isObjectType)(returnType))
return [];
const containingTypes = [];
const types = schema.getTypeMap();
for (const selectionSetType of Object.values(types)) {
if (!(0, graphql_1.isObjectType)(selectionSetType))
continue;
const allFields = selectionSetType.getFields();
Object.values(allFields).forEach(field => {
const fieldReturnType = (0, graphql_1.getNamedType)(field.type);
if (fieldReturnType === returnType) {
containingTypes.push(fieldReturnType);
}
});
}
return containingTypes;
}
exports.findTypesContainingFieldWithReturnType = findTypesContainingFieldWithReturnType;
function findFieldsThatReturnType({ schema, typeToFind, }) {
if (!(0, graphql_1.isObjectType)(typeToFind))
return [];
const fieldsThatReturnType = [];
const types = schema.getTypeMap();
for (const selectionSetType of Object.values(types)) {
if (!(0, graphql_1.isObjectType)(selectionSetType))
continue;
const fieldsOnNamedType = selectionSetType.getFields();
Object.values(fieldsOnNamedType).forEach(field => {
const fieldReturnType = (0, graphql_1.getNamedType)(field.type);
if (fieldReturnType === typeToFind) {
fieldsThatReturnType.push(field);
}
});
}
return fieldsThatReturnType;
}
exports.findFieldsThatReturnType = findFieldsThatReturnType;
function selectionIncludesField({ selections, selectionSetType, typeToFind, fieldToFind, }) {
for (const selection of selections) {
const selectionName = selection.name.value;
if (selectionName === fieldToFind &&
(0, graphql_1.isEqualType)(selectionSetType, typeToFind))
return true;
const typeIncludesField = selectionName &&
Object.keys(selectionSetType.getFields()).includes(selectionName);
if (!selectionName || !typeIncludesField)
continue;
const returnType = (0, graphql_1.getNamedType)(selectionSetType.getFields()[selectionName].type);
if (!returnType || !(0, graphql_1.isObjectType)(returnType))
continue;
const subselections = selection.selectionSet && selection.selectionSet.selections;
if (subselections) {
const selectionDoesIncludeField = selectionIncludesField({
selectionSetType: returnType,
selections: subselections,
typeToFind,
fieldToFind,
});
if (selectionDoesIncludeField)
return true;
}
}
return false;
}
exports.selectionIncludesField = selectionIncludesField;
function isTypeNodeAnEntity(node) {
let isEntity = false;
(0, graphql_1.visit)(node, {
Directive(directive) {
if (directive.name.value === 'key') {
isEntity = true;
return graphql_1.BREAK;
}
},
});
return isEntity;
}
exports.isTypeNodeAnEntity = isTypeNodeAnEntity;
function diffTypeNodes(firstNode, secondNode) {
const fieldsDiff = Object.create(null);
const unionTypesDiff = Object.create(null);
const locationsDiff = new Set();
const fieldArgsDiff = Object.create(null);
const directiveArgsDiff = Object.create(null);
const document = {
kind: graphql_1.Kind.DOCUMENT,
definitions: [firstNode, secondNode],
};
function inputFieldVisitor(node) {
const fieldName = node.name.value;
const type = (0, graphql_1.print)(node.type);
if (!fieldsDiff[fieldName]) {
fieldsDiff[fieldName] = [type];
return;
}
const fieldTypes = fieldsDiff[fieldName];
if (fieldTypes[0] === type) {
delete fieldsDiff[fieldName];
}
else {
fieldTypes.push(type);
}
}
function fieldVisitor(node) {
const fieldName = node.name.value;
const type = (0, graphql_1.print)(node.type);
if (!fieldsDiff[fieldName]) {
fieldsDiff[fieldName] = [type];
}
else {
const fieldTypes = fieldsDiff[fieldName];
if (fieldTypes[0] === type) {
delete fieldsDiff[fieldName];
}
else {
fieldTypes.push(type);
}
}
if (!fieldArgsDiff[fieldName]) {
fieldArgsDiff[fieldName] = Object.create(null);
}
const argumentsDiff = fieldArgsDiff[fieldName];
const nodeArgs = Array.isArray(node.arguments) ? node.arguments : [];
nodeArgs.forEach(argument => {
const argumentName = argument.name.value;
const printedType = (0, graphql_1.print)(argument.type);
if (!argumentsDiff[argumentName]) {
argumentsDiff[argumentName] = [printedType];
}
else {
if (printedType === argumentsDiff[argumentName][0]) {
delete argumentsDiff[argumentName];
}
else {
argumentsDiff[argumentName].push(printedType);
}
}
});
if (Object.keys(argumentsDiff).length === 0) {
delete fieldArgsDiff[fieldName];
}
}
(0, graphql_1.visit)(document, {
FieldDefinition: fieldVisitor,
InputObjectTypeDefinition(node) {
if (Array.isArray(node.fields)) {
node.fields.forEach(inputFieldVisitor);
}
},
UnionTypeDefinition(node) {
if (!node.types)
return graphql_1.BREAK;
for (const namedTypeNode of node.types) {
const name = namedTypeNode.name.value;
if (unionTypesDiff[name]) {
delete unionTypesDiff[name];
}
else {
unionTypesDiff[name] = true;
}
}
},
DirectiveDefinition(node) {
node.locations.forEach(location => {
const locationName = location.value;
if (locationsDiff.has(locationName)) {
locationsDiff.delete(locationName);
}
else {
locationsDiff.add(locationName);
}
});
if (!node.arguments)
return;
node.arguments.forEach(argument => {
const argumentName = argument.name.value;
const printedType = (0, graphql_1.print)(argument.type);
if (directiveArgsDiff[argumentName]) {
if (printedType === directiveArgsDiff[argumentName][0]) {
delete directiveArgsDiff[argumentName];
}
else {
directiveArgsDiff[argumentName].push(printedType);
}
}
else {
directiveArgsDiff[argumentName] = [printedType];
}
});
},
});
const typeNameDiff = firstNode.name.value === secondNode.name.value
? []
: [firstNode.name.value, secondNode.name.value];
const kindDiff = firstNode.kind === secondNode.kind ? [] : [firstNode.kind, secondNode.kind];
return {
name: typeNameDiff,
kind: kindDiff,
fields: fieldsDiff,
fieldArgs: fieldArgsDiff,
unionTypes: unionTypesDiff,
locations: Array.from(locationsDiff),
directiveArgs: directiveArgsDiff
};
}
exports.diffTypeNodes = diffTypeNodes;
function typeNodesAreEquivalent(firstNode, secondNode) {
const { name, kind, fields, fieldArgs, unionTypes, locations, directiveArgs } = diffTypeNodes(firstNode, secondNode);
return (name.length === 0 &&
kind.length === 0 &&
Object.keys(fields).length === 0 &&
Object.keys(fieldArgs).length === 0 &&
Object.keys(unionTypes).length === 0 &&
locations.length === 0 &&
Object.keys(directiveArgs).length === 0);
}
exports.typeNodesAreEquivalent = typeNodesAreEquivalent;
function findTypeNodeInServiceList(typeName, serviceName, serviceList) {
var _a;
return (_a = serviceList.find(service => service.name === serviceName)) === null || _a === void 0 ? void 0 : _a.typeDefs.definitions.find(definition => {
var _a;
return 'name' in definition
&& ((_a = definition.name) === null || _a === void 0 ? void 0 : _a.value) === typeName;
});
}
exports.findTypeNodeInServiceList = findTypeNodeInServiceList;
exports.defKindToExtKind = {
[graphql_1.Kind.SCALAR_TYPE_DEFINITION]: graphql_1.Kind.SCALAR_TYPE_EXTENSION,
[graphql_1.Kind.OBJECT_TYPE_DEFINITION]: graphql_1.Kind.OBJECT_TYPE_EXTENSION,
[graphql_1.Kind.INTERFACE_TYPE_DEFINITION]: graphql_1.Kind.INTERFACE_TYPE_EXTENSION,
[graphql_1.Kind.UNION_TYPE_DEFINITION]: graphql_1.Kind.UNION_TYPE_EXTENSION,
[graphql_1.Kind.ENUM_TYPE_DEFINITION]: graphql_1.Kind.ENUM_TYPE_EXTENSION,
[graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION]: graphql_1.Kind.INPUT_OBJECT_TYPE_EXTENSION,
};
exports.executableDirectiveLocations = [
'QUERY',
'MUTATION',
'SUBSCRIPTION',
'FIELD',
'FRAGMENT_DEFINITION',
'FRAGMENT_SPREAD',
'INLINE_FRAGMENT',
'VARIABLE_DEFINITION',
];
exports.reservedRootFields = ['_service', '_entities'];
exports.defaultRootOperationNameLookup = {
query: 'Query',
mutation: 'Mutation',
subscription: 'Subscription',
};
function compositionHasErrors(compositionResult) {
return 'errors' in compositionResult && !!compositionResult.errors;
}
exports.compositionHasErrors = compositionHasErrors;
function assertCompositionSuccess(compositionResult, message) {
if (compositionHasErrors(compositionResult)) {
throw new Error(message || 'Unexpected test failure');
}
}
exports.assertCompositionSuccess = assertCompositionSuccess;
function assertCompositionFailure(compositionResult, message) {
if (!compositionHasErrors(compositionResult)) {
throw new Error(message || 'Unexpected test failure');
}
}
exports.assertCompositionFailure = assertCompositionFailure;
function getFederationMetadata(obj) {
var _a, _b, _c;
if (typeof obj === "undefined")
return undefined;
else if ((0, graphql_1.isNamedType)(obj))
return (_a = obj.extensions) === null || _a === void 0 ? void 0 : _a.federation;
else if ((0, graphql_1.isDirective)(obj))
return (_b = obj.extensions) === null || _b === void 0 ? void 0 : _b.federation;
else
return (_c = obj.extensions) === null || _c === void 0 ? void 0 : _c.federation;
}
exports.getFederationMetadata = getFederationMetadata;
//# sourceMappingURL=utils.js.map
;