UNPKG

@apollo/federation

Version:
422 lines 18.3 kB
"use strict"; 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