@apollo/federation-internals
Version:
Apollo Federation internal utilities
1,953 lines • 97.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeInactiveProvidesAndRequires = exports.addSubgraphToError = exports.addSubgraphToASTNode = exports.Subgraph = exports.FEDERATION_OPERATION_FIELDS = exports.entitiesFieldName = exports.serviceFieldName = exports.FEDERATION_OPERATION_TYPES = exports.entityTypeSpec = exports.serviceTypeSpec = exports.anyTypeSpec = exports.Subgraphs = exports.subgraphsFromServiceList = exports.collectTargetFields = exports.parseFieldSetArgument = exports.newEmptyFederation2Schema = exports.buildSubgraph = exports.isInterfaceObjectType = exports.isEntityType = exports.isFederationField = exports.isFederationSubgraphSchema = exports.federationMetadata = exports.printSubgraphNames = exports.asFed2SubgraphDocument = exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS_UPGRADED = exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = exports.setSchemaAsFed2Subgraph = exports.FederationBlueprint = exports.hasAppliedDirective = exports.isFederationDirectiveDefinedInSchema = exports.FederationMetadata = exports.collectUsedFields = exports.parseContext = exports.FEDERATION_UNNAMED_SUBGRAPH_NAME = exports.FEDERATION_RESERVED_SUBGRAPH_NAME = void 0;
const definitions_1 = require("./definitions");
const utils_1 = require("./utils");
const specifiedRules_1 = require("graphql/validation/specifiedRules");
const graphql_1 = require("graphql");
const KnownTypeNamesInFederationRule_1 = require("./validation/KnownTypeNamesInFederationRule");
const buildSchema_1 = require("./buildSchema");
const operations_1 = require("./operations");
const tagSpec_1 = require("./specs/tagSpec");
const error_1 = require("./error");
const precompute_1 = require("./precompute");
const coreSpec_1 = require("./specs/coreSpec");
const federationSpec_1 = require("./specs/federationSpec");
const print_1 = require("./print");
const directiveAndTypeSpecification_1 = require("./directiveAndTypeSpecification");
const suggestions_1 = require("./suggestions");
const knownCoreFeatures_1 = require("./knownCoreFeatures");
const joinSpec_1 = require("./specs/joinSpec");
const costSpec_1 = require("./specs/costSpec");
const linkSpec = coreSpec_1.LINK_VERSIONS.latest();
const tagSpec = tagSpec_1.TAG_VERSIONS.latest();
const federationSpec = (version) => {
if (!version)
return federationSpec_1.FEDERATION_VERSIONS.latest();
const spec = federationSpec_1.FEDERATION_VERSIONS.find(version);
(0, utils_1.assert)(spec, `Federation spec version ${version} is not known`);
return spec;
};
const autoExpandedFederationSpec = federationSpec(new coreSpec_1.FeatureVersion(2, 4));
const latestFederationSpec = federationSpec();
exports.FEDERATION_RESERVED_SUBGRAPH_NAME = '_';
exports.FEDERATION_UNNAMED_SUBGRAPH_NAME = '<unnamed>';
const FEDERATION_OMITTED_VALIDATION_RULES = [
graphql_1.PossibleTypeExtensionsRule,
graphql_1.KnownTypeNamesRule
];
const FEDERATION_SPECIFIC_VALIDATION_RULES = [
KnownTypeNamesInFederationRule_1.KnownTypeNamesInFederationRule
];
const FEDERATION_VALIDATION_RULES = specifiedRules_1.specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES);
const ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES = Object.values(federationSpec_1.FederationDirectiveName);
const FAKE_FED1_CORE_FEATURE_TO_RENAME_TYPES = new definitions_1.CoreFeature(new coreSpec_1.FeatureUrl('<fed1>', 'fed1', new coreSpec_1.FeatureVersion(0, 1)), 'fed1', new definitions_1.Directive('fed1'), federationSpec_1.FEDERATION1_TYPES.map((spec) => ({ name: spec.name, as: '_' + spec.name })));
function validateFieldSetSelections({ directiveName, selectionSet, hasExternalInParents, metadata, onError, allowOnNonExternalLeafFields, allowFieldsWithArguments, }) {
for (const selection of selectionSet.selections()) {
const appliedDirectives = selection.element.appliedDirectives;
if (appliedDirectives.length > 0) {
onError(error_1.ERROR_CATEGORIES.DIRECTIVE_IN_FIELDS_ARG.get(directiveName).err(`cannot have directive applications in the @${directiveName}(fields:) argument but found ${appliedDirectives.join(', ')}.`));
}
if (selection.kind === 'FieldSelection') {
const field = selection.element.definition;
const isExternal = metadata.isFieldExternal(field);
if (!allowFieldsWithArguments && field.hasArguments()) {
onError(error_1.ERROR_CATEGORIES.FIELDS_HAS_ARGS.get(directiveName).err(`field ${field.coordinate} cannot be included because it has arguments (fields with argument are not allowed in @${directiveName})`, { nodes: field.sourceAST }));
}
const mustBeExternal = !selection.selectionSet && !allowOnNonExternalLeafFields && !hasExternalInParents;
if (!isExternal && mustBeExternal) {
const errorCode = error_1.ERROR_CATEGORIES.DIRECTIVE_FIELDS_MISSING_EXTERNAL.get(directiveName);
if (metadata.isFieldFakeExternal(field)) {
onError(errorCode.err(`field "${field.coordinate}" should not be part of a @${directiveName} since it is already "effectively" provided by this subgraph `
+ `(while it is marked @${federationSpec_1.FederationDirectiveName.EXTERNAL}, it is a @${federationSpec_1.FederationDirectiveName.KEY} field of an extension type, which are not internally considered external for historical/backward compatibility reasons)`, { nodes: field.sourceAST }));
}
else {
onError(errorCode.err(`field "${field.coordinate}" should not be part of a @${directiveName} since it is already provided by this subgraph (it is not marked @${federationSpec_1.FederationDirectiveName.EXTERNAL})`, { nodes: field.sourceAST }));
}
}
if (selection.selectionSet) {
let newHasExternalInParents = hasExternalInParents || isExternal;
const parentType = field.parent;
if (!newHasExternalInParents && (0, definitions_1.isInterfaceType)(parentType)) {
for (const implem of parentType.possibleRuntimeTypes()) {
const fieldInImplem = implem.field(field.name);
if (fieldInImplem && metadata.isFieldExternal(fieldInImplem)) {
newHasExternalInParents = true;
break;
}
}
}
validateFieldSetSelections({
directiveName,
selectionSet: selection.selectionSet,
hasExternalInParents: newHasExternalInParents,
metadata,
onError,
allowOnNonExternalLeafFields,
allowFieldsWithArguments,
});
}
}
else {
validateFieldSetSelections({
directiveName,
selectionSet: selection.selectionSet,
hasExternalInParents,
metadata,
onError,
allowOnNonExternalLeafFields,
allowFieldsWithArguments,
});
}
}
}
function validateFieldSet({ type, directive, metadata, errorCollector, allowOnNonExternalLeafFields, allowFieldsWithArguments, onFields, }) {
try {
const fieldAccessor = onFields
? (type, fieldName) => {
const field = type.field(fieldName);
if (field) {
onFields(field);
}
return field;
}
: undefined;
const selectionSet = parseFieldSetArgument({ parentType: type, directive, fieldAccessor });
validateFieldSetSelections({
directiveName: directive.name,
selectionSet,
hasExternalInParents: false,
metadata,
onError: (error) => errorCollector.push(handleFieldSetValidationError(directive, error)),
allowOnNonExternalLeafFields,
allowFieldsWithArguments,
});
}
catch (e) {
if (e instanceof graphql_1.GraphQLError) {
errorCollector.push(e);
}
else {
throw e;
}
}
}
function handleFieldSetValidationError(directive, originalError, messageUpdater) {
const nodes = (0, definitions_1.sourceASTs)(directive);
if (originalError.nodes) {
nodes.push(...originalError.nodes);
}
let codeDef = (0, error_1.errorCodeDef)(originalError);
if (!codeDef || codeDef === error_1.ERRORS.INVALID_GRAPHQL) {
codeDef = error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS.get(directive.name);
}
let msg = originalError.message.trim();
if (messageUpdater) {
msg = messageUpdater(msg);
}
return codeDef.err(`${fieldSetErrorDescriptor(directive)}: ${msg}`, {
nodes,
originalError,
});
}
function fieldSetErrorDescriptor(directive) {
return `On ${fieldSetTargetDescription(directive)}, for ${directiveStrUsingASTIfPossible(directive)}`;
}
function directiveStrUsingASTIfPossible(directive) {
return directive.sourceAST ? (0, graphql_1.print)(directive.sourceAST) : directive.toString();
}
function fieldSetTargetDescription(directive) {
var _a;
const targetKind = directive.parent instanceof definitions_1.FieldDefinition ? "field" : "type";
return `${targetKind} "${(_a = directive.parent) === null || _a === void 0 ? void 0 : _a.coordinate}"`;
}
function parseContext(input) {
const regex = /^(?:[\n\r\t ,]|#[^\n\r]*(?![^\n\r]))*\$(?:[\n\r\t ,]|#[^\n\r]*(?![^\n\r]))*([A-Za-z_]\w*(?!\w))([\s\S]*)$/;
const match = input.match(regex);
if (!match) {
return { context: undefined, selection: undefined };
}
const [, context, selection] = match;
return {
context,
selection,
};
}
exports.parseContext = parseContext;
const wrapResolvedType = ({ originalType, resolvedType, }) => {
const stack = [];
let unwrappedType = originalType;
while (unwrappedType.kind === 'NonNullType' || unwrappedType.kind === 'ListType') {
stack.push(unwrappedType.kind);
unwrappedType = unwrappedType.ofType;
}
let type = resolvedType;
while (stack.length > 0) {
const kind = stack.pop();
if (kind === 'ListType') {
type = new definitions_1.ListType(type);
}
}
return type;
};
const validateFieldValueType = ({ currentType, selectionSet, errorCollector, metadata, fromContextParent, }) => {
const selections = selectionSet.selections();
const interfaceObjectDirective = metadata.interfaceObjectDirective();
if (currentType.kind === 'ObjectType' && isFederationDirectiveDefinedInSchema(interfaceObjectDirective) && (currentType.appliedDirectivesOf(interfaceObjectDirective).length > 0)) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "is used in "${fromContextParent.coordinate}" but the selection is invalid: One of the types in the selection is an interfaceObject: "${currentType.name}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
}
const typesArray = selections.map((selection) => {
if (selection.kind !== 'FieldSelection') {
return { resolvedType: undefined };
}
const { element, selectionSet: childSelectionSet } = selection;
(0, utils_1.assert)(element.definition.type, 'Element type definition should exist');
let type = element.definition.type;
if (childSelectionSet) {
(0, utils_1.assert)((0, definitions_1.isCompositeType)((0, definitions_1.baseType)(type)), 'Child selection sets should only exist on composite types');
const { resolvedType } = validateFieldValueType({
currentType: (0, definitions_1.baseType)(type),
selectionSet: childSelectionSet,
errorCollector,
metadata,
fromContextParent,
});
if (!resolvedType) {
return { resolvedType: undefined };
}
return { resolvedType: wrapResolvedType({ originalType: type, resolvedType }) };
}
(0, utils_1.assert)((0, definitions_1.isLeafType)((0, definitions_1.baseType)(type)), 'Expected a leaf type');
return {
resolvedType: wrapResolvedType({
originalType: type,
resolvedType: (0, definitions_1.baseType)(type)
})
};
});
return typesArray.reduce((acc, { resolvedType }) => {
var _a;
if (((_a = acc.resolvedType) === null || _a === void 0 ? void 0 : _a.toString()) === (resolvedType === null || resolvedType === void 0 ? void 0 : resolvedType.toString())) {
return { resolvedType };
}
return { resolvedType: undefined };
});
};
const validateSelectionFormat = ({ context, selection, fromContextParent, errorCollector, }) => {
try {
const node = (0, operations_1.parseOperationAST)(selection.trim().startsWith('{') ? selection : `{${selection}}`);
const selections = node.selectionSet.selections;
if (selections.length === 0) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: no selection is made`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return { selectionType: 'error' };
}
const firstSelectionKind = selections[0].kind;
if (firstSelectionKind === 'Field') {
if (selections.length !== 1) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: multiple selections are made`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return { selectionType: 'error' };
}
return { selectionType: 'field' };
}
else if (firstSelectionKind === 'InlineFragment') {
const inlineFragmentTypeConditions = new Set();
if (!selections.every((s) => s.kind === 'InlineFragment')) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: multiple fields could be selected`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return { selectionType: 'error' };
}
selections.forEach((s) => {
(0, utils_1.assert)(s.kind === 'InlineFragment', 'Expected an inline fragment');
const { typeCondition } = s;
if (typeCondition) {
inlineFragmentTypeConditions.add(typeCondition.name.value);
}
});
if (inlineFragmentTypeConditions.size !== selections.length) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: type conditions have same name`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return { selectionType: 'error' };
}
return {
selectionType: 'inlineFragment',
typeConditions: inlineFragmentTypeConditions,
};
}
else if (firstSelectionKind === 'FragmentSpread') {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: fragment spread is not allowed`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return { selectionType: 'error' };
}
else {
(0, utils_1.assertUnreachable)(firstSelectionKind);
}
}
catch (err) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: ${err.message}`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return { selectionType: 'error' };
}
};
function isValidImplementationFieldType(fieldType, implementedFieldType) {
if ((0, definitions_1.isNonNullType)(fieldType)) {
if ((0, definitions_1.isNonNullType)(implementedFieldType)) {
return isValidImplementationFieldType(fieldType.ofType, implementedFieldType.ofType);
}
else {
return isValidImplementationFieldType(fieldType.ofType, implementedFieldType);
}
}
if ((0, definitions_1.isListType)(fieldType) && (0, definitions_1.isListType)(implementedFieldType)) {
return isValidImplementationFieldType(fieldType.ofType, implementedFieldType.ofType);
}
return !(0, definitions_1.isWrapperType)(fieldType) &&
!(0, definitions_1.isWrapperType)(implementedFieldType) &&
fieldType.name === implementedFieldType.name;
}
function selectionSetHasDirectives(selectionSet) {
return (0, operations_1.hasSelectionWithPredicate)(selectionSet, (s) => {
if (s.kind === 'FieldSelection') {
return s.element.appliedDirectives.length > 0;
}
else if (s.kind === 'FragmentSelection') {
return s.element.appliedDirectives.length > 0;
}
else {
(0, utils_1.assertUnreachable)(s);
}
});
}
function selectionSetHasAlias(selectionSet) {
return (0, operations_1.hasSelectionWithPredicate)(selectionSet, (s) => {
if (s.kind === 'FieldSelection') {
return s.element.alias !== undefined;
}
return false;
});
}
function validateFieldValue({ context, selection, fromContextParent, setContextLocations, errorCollector, metadata, }) {
const expectedType = fromContextParent.type;
(0, utils_1.assert)(expectedType, 'Expected a type');
const validateSelectionFormatResults = validateSelectionFormat({ context, selection, fromContextParent, errorCollector });
const selectionType = validateSelectionFormatResults.selectionType;
if (selectionType === 'error') {
return;
}
const usedTypeConditions = new Set;
for (const location of setContextLocations) {
let selectionSet;
try {
selectionSet = (0, operations_1.parseSelectionSet)({ parentType: location, source: selection });
}
catch (e) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid for type ${location.name}. Error: ${e.message}`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return;
}
if (selectionSetHasDirectives(selectionSet)) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: directives are not allowed in the selection`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
}
if (selectionSetHasAlias(selectionSet)) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: aliases are not allowed in the selection`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
}
if (selectionType === 'field') {
const { resolvedType } = validateFieldValueType({
currentType: location,
selectionSet,
errorCollector,
metadata,
fromContextParent,
});
if (resolvedType === undefined || !isValidImplementationFieldType(resolvedType, expectedType)) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: the type of the selection "${resolvedType}" does not match the expected type "${expectedType === null || expectedType === void 0 ? void 0 : expectedType.toString()}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return;
}
}
else if (selectionType === 'inlineFragment') {
const selections = [];
for (const selection of selectionSet.selections()) {
if (selection.kind !== 'FragmentSelection') {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: selection should only contain a single field or at least one inline fragment}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
continue;
}
const { typeCondition } = selection.element;
if (!typeCondition) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: inline fragments must have type conditions"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
continue;
}
if (typeCondition.kind === 'ObjectType') {
if ((0, definitions_1.possibleRuntimeTypes)(location).includes(typeCondition)) {
selections.push(selection);
usedTypeConditions.add(typeCondition.name);
}
}
else {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: type conditions must be an object type"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
}
}
if (selections.length === 0) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: no type condition matches the location "${location.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return;
}
else {
for (const selection of selections) {
let { resolvedType } = validateFieldValueType({
currentType: selection.element.typeCondition,
selectionSet: selection.selectionSet,
errorCollector,
metadata,
fromContextParent,
});
if (resolvedType === undefined) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: the type of the selection does not match the expected type "${expectedType === null || expectedType === void 0 ? void 0 : expectedType.toString()}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return;
}
if ((0, definitions_1.isNonNullType)(resolvedType)) {
resolvedType = resolvedType.ofType;
}
if (!isValidImplementationFieldType(resolvedType, expectedType)) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: the type of the selection "${resolvedType === null || resolvedType === void 0 ? void 0 : resolvedType.toString()}" does not match the expected type "${expectedType === null || expectedType === void 0 ? void 0 : expectedType.toString()}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
return;
}
}
}
}
}
if (validateSelectionFormatResults.selectionType === 'inlineFragment') {
for (const typeCondition of validateSelectionFormatResults.typeConditions) {
if (!usedTypeConditions.has(typeCondition)) {
errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: type condition "${typeCondition}" is never used.`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) }));
}
}
}
}
function validateAllFieldSet({ definition, targetTypeExtractor, errorCollector, metadata, isOnParentType = false, allowOnNonExternalLeafFields = false, allowFieldsWithArguments = false, allowOnInterface = false, onFields, }) {
for (const application of definition.applications()) {
const elt = application.parent;
const type = targetTypeExtractor(elt);
const parentType = isOnParentType ? type : elt.parent;
if ((0, definitions_1.isInterfaceType)(parentType) && !allowOnInterface) {
const code = error_1.ERROR_CATEGORIES.DIRECTIVE_UNSUPPORTED_ON_INTERFACE.get(definition.name);
errorCollector.push(code.err(isOnParentType
? `Cannot use ${definition.coordinate} on interface "${parentType.coordinate}": ${definition.coordinate} is not yet supported on interfaces`
: `Cannot use ${definition.coordinate} on ${fieldSetTargetDescription(application)} of parent type "${parentType}": ${definition.coordinate} is not yet supported within interfaces`, { nodes: (0, definitions_1.sourceASTs)(application).concat(isOnParentType ? [] : (0, definitions_1.sourceASTs)(type)) }));
}
validateFieldSet({
type,
directive: application,
metadata,
errorCollector,
allowOnNonExternalLeafFields,
allowFieldsWithArguments,
onFields,
});
}
}
function collectUsedFields(metadata) {
const usedFields = new Set();
collectUsedFieldsForDirective(metadata.keyDirective(), type => type, usedFields);
collectUsedFieldsForDirective(metadata.requiresDirective(), field => field.parent, usedFields);
collectUsedFieldsForDirective(metadata.providesDirective(), field => {
const type = (0, definitions_1.baseType)(field.type);
return (0, definitions_1.isCompositeType)(type) ? type : undefined;
}, usedFields);
collectUsedFieldsForFromContext(metadata, usedFields);
for (const itfType of metadata.schema.interfaceTypes()) {
const runtimeTypes = itfType.possibleRuntimeTypes();
for (const field of itfType.fields()) {
for (const runtimeType of runtimeTypes) {
const implemField = runtimeType.field(field.name);
if (implemField) {
usedFields.add(implemField);
}
}
}
}
return usedFields;
}
exports.collectUsedFields = collectUsedFields;
function collectUsedFieldsForFromContext(metadata, usedFieldDefs) {
const fromContextDirective = metadata.fromContextDirective();
const contextDirective = metadata.contextDirective();
if (!isFederationDirectiveDefinedInSchema(fromContextDirective) || !isFederationDirectiveDefinedInSchema(contextDirective)) {
return;
}
const entryPoints = new Map();
for (const application of contextDirective.applications()) {
const type = application.parent;
if (!type) {
continue;
}
const context = application.arguments().name;
if (!entryPoints.has(context)) {
entryPoints.set(context, new Set());
}
entryPoints.get(context).add(type);
}
for (const application of fromContextDirective.applications()) {
const type = application.parent;
if (!type) {
continue;
}
const fieldValue = application.arguments().field;
const { context, selection } = parseContext(fieldValue);
if (!context) {
continue;
}
const contextTypes = entryPoints.get(context);
if (!contextTypes) {
continue;
}
for (const contextType of contextTypes) {
try {
const fieldAccessor = (t, f) => {
const field = t.field(f);
if (field) {
usedFieldDefs.add(field);
if ((0, definitions_1.isInterfaceType)(t)) {
for (const implType of t.possibleRuntimeTypes()) {
const implField = implType.field(f);
if (implField) {
usedFieldDefs.add(implField);
}
}
}
}
return field;
};
(0, operations_1.parseSelectionSet)({ parentType: contextType, source: selection, fieldAccessor });
}
catch (e) {
}
}
}
}
function collectUsedFieldsForDirective(definition, targetTypeExtractor, usedFieldDefs) {
for (const application of definition.applications()) {
const type = targetTypeExtractor(application.parent);
if (!type) {
continue;
}
collectTargetFields({
parentType: type,
directive: application,
includeInterfaceFieldsImplementations: true,
validate: false,
}).forEach((field) => usedFieldDefs.add(field));
}
}
function validateAllExternalFieldsUsed(metadata, errorCollector) {
for (const type of metadata.schema.types()) {
if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
continue;
}
for (const field of type.fields()) {
if (!metadata.isFieldExternal(field) || metadata.isFieldUsed(field)) {
continue;
}
errorCollector.push(error_1.ERRORS.EXTERNAL_UNUSED.err(`Field "${field.coordinate}" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface;`
+ ' the field declaration has no use and should be removed (or the field should not be @external).', { nodes: field.sourceAST }));
}
}
}
function validateNoExternalOnInterfaceFields(metadata, errorCollector) {
for (const itf of metadata.schema.interfaceTypes()) {
for (const field of itf.fields()) {
if (metadata.isFieldExternal(field)) {
errorCollector.push(error_1.ERRORS.EXTERNAL_ON_INTERFACE.err(`Interface type field "${field.coordinate}" is marked @external but @external is not allowed on interface fields (it is nonsensical).`, { nodes: field.sourceAST }));
}
}
}
}
function validateKeyOnInterfacesAreAlsoOnAllImplementations(metadata, errorCollector) {
for (const itfType of metadata.schema.interfaceTypes()) {
const implementations = itfType.possibleRuntimeTypes();
for (const keyApplication of itfType.appliedDirectivesOf(metadata.keyDirective())) {
const fields = parseFieldSetArgument({ parentType: itfType, directive: keyApplication, validate: false });
const isResolvable = !(keyApplication.arguments().resolvable === false);
const implementationsWithKeyButNotResolvable = new Array();
const implementationsMissingKey = new Array();
for (const type of implementations) {
const matchingApp = type.appliedDirectivesOf(metadata.keyDirective()).find((app) => {
const appFields = parseFieldSetArgument({ parentType: type, directive: app, validate: false });
return fields.equals(appFields);
});
if (matchingApp) {
if (isResolvable && matchingApp.arguments().resolvable === false) {
implementationsWithKeyButNotResolvable.push(type);
}
}
else {
implementationsMissingKey.push(type);
}
}
if (implementationsMissingKey.length > 0) {
const typesString = (0, utils_1.printHumanReadableList)(implementationsMissingKey.map((i) => `"${i.coordinate}"`), {
prefix: 'type',
prefixPlural: 'types',
});
errorCollector.push(error_1.ERRORS.INTERFACE_KEY_NOT_ON_IMPLEMENTATION.err(`Key ${keyApplication} on interface type "${itfType.coordinate}" is missing on implementation ${typesString}.`, { nodes: (0, definitions_1.sourceASTs)(...implementationsMissingKey) }));
}
else if (implementationsWithKeyButNotResolvable.length > 0) {
const typesString = (0, utils_1.printHumanReadableList)(implementationsWithKeyButNotResolvable.map((i) => `"${i.coordinate}"`), {
prefix: 'type',
prefixPlural: 'types',
});
errorCollector.push(error_1.ERRORS.INTERFACE_KEY_NOT_ON_IMPLEMENTATION.err(`Key ${keyApplication} on interface type "${itfType.coordinate}" should be resolvable on all implementation types, but is declared with argument "@key(resolvable:)" set to false in ${typesString}.`, { nodes: (0, definitions_1.sourceASTs)(...implementationsWithKeyButNotResolvable) }));
}
}
}
}
function validateInterfaceObjectsAreOnEntities(metadata, errorCollector) {
for (const application of metadata.interfaceObjectDirective().applications()) {
if (!isEntityType(application.parent)) {
errorCollector.push(error_1.ERRORS.INTERFACE_OBJECT_USAGE_ERROR.err(`The @interfaceObject directive can only be applied to entity types but type "${application.parent.coordinate}" has no @key in this subgraph.`, { nodes: application.parent.sourceAST }));
}
}
}
function validateShareableNotRepeatedOnSameDeclaration(element, metadata, errorCollector) {
const shareableApplications = element.appliedDirectivesOf(metadata.shareableDirective());
if (shareableApplications.length <= 1) {
return;
}
const byExtensions = shareableApplications.reduce((acc, v) => {
const ext = v.ofExtension();
if (ext) {
acc.with.add(ext, v);
}
else {
acc.without.push(v);
}
return acc;
}, { without: [], with: new utils_1.MultiMap() });
const groups = [byExtensions.without].concat((0, utils_1.mapValues)(byExtensions.with));
for (const group of groups) {
if (group.length > 1) {
const eltStr = element.kind === 'ObjectType'
? `the same type declaration of "${element.coordinate}"`
: `field "${element.coordinate}"`;
errorCollector.push(error_1.ERRORS.INVALID_SHAREABLE_USAGE.err(`Invalid duplicate application of @shareable on ${eltStr}: `
+ '@shareable is only repeatable on types so it can be used simultaneously on a type definition and its extensions, but it should not be duplicated on the same definition/extension declaration', { nodes: (0, definitions_1.sourceASTs)(...group) }));
}
}
}
function validateCostNotAppliedToInterface(application, errorCollector) {
const parent = application.parent;
if (parent instanceof definitions_1.FieldDefinition && parent.parent instanceof definitions_1.InterfaceType) {
errorCollector.push(error_1.ERRORS.COST_APPLIED_TO_INTERFACE_FIELD.err(`@cost cannot be applied to interface "${parent.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
}
function validateListSizeAppliedToList(application, parent, errorCollector) {
const { sizedFields = [] } = application.arguments();
if (!sizedFields.length && parent.type && !(0, definitions_1.isListType)(parent.type)) {
errorCollector.push(error_1.ERRORS.LIST_SIZE_APPLIED_TO_NON_LIST.err(`"${parent.coordinate}" is not a list`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
}
function validateAssumedSizeNotNegative(application, parent, errorCollector) {
const { assumedSize } = application.arguments();
if (assumedSize !== undefined && assumedSize !== null && assumedSize < 0) {
errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_ASSUMED_SIZE.err(`Assumed size of "${parent.coordinate}" cannot be negative`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
}
function isNonNullIntType(ty) {
return (0, definitions_1.isNonNullType)(ty) && (0, definitions_1.isIntType)(ty.ofType);
}
function validateSlicingArgumentsAreValidIntegers(application, parent, errorCollector) {
const { slicingArguments = [] } = application.arguments();
for (const slicingArgumentName of slicingArguments) {
const slicingArgument = parent.argument(slicingArgumentName);
if (!(slicingArgument === null || slicingArgument === void 0 ? void 0 : slicingArgument.type)) {
errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SLICING_ARGUMENT.err(`Slicing argument "${slicingArgumentName}" is not an argument of "${parent.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
else if (!(0, definitions_1.isIntType)(slicingArgument.type) && !isNonNullIntType(slicingArgument.type)) {
errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SLICING_ARGUMENT.err(`Slicing argument "${slicingArgument.coordinate}" must be Int or Int!`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
}
}
function isNonNullListType(ty) {
return (0, definitions_1.isNonNullType)(ty) && (0, definitions_1.isListType)(ty.ofType);
}
function validateSizedFieldsAreValidLists(application, parent, errorCollector) {
const { sizedFields = [] } = application.arguments();
if (sizedFields.length) {
if (!parent.type || !(0, definitions_1.isCompositeType)(parent.type)) {
errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(`Sized fields cannot be used because "${parent.type}" is not a composite type`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
else {
for (const sizedFieldName of sizedFields) {
const sizedField = parent.type.field(sizedFieldName);
if (!sizedField) {
errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(`Sized field "${sizedFieldName}" is not a field on type "${parent.type.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
else if (!sizedField.type || !((0, definitions_1.isListType)(sizedField.type) || isNonNullListType(sizedField.type))) {
errorCollector.push(error_1.ERRORS.LIST_SIZE_APPLIED_TO_NON_LIST.err(`Sized field "${sizedField.coordinate}" is not a list`, { nodes: (0, definitions_1.sourceASTs)(application, parent) }));
}
}
}
}
}
class FederationMetadata {
constructor(schema) {
this.schema = schema;
}
onInvalidate() {
this._externalTester = undefined;
this._sharingPredicate = undefined;
this._isFed2Schema = undefined;
this._fieldUsedPredicate = undefined;
}
isFed2Schema() {
if (!this._isFed2Schema) {
const feature = this.federationFeature();
this._isFed2Schema = !!feature && feature.url.version.satisfies(new coreSpec_1.FeatureVersion(2, 0));
}
return this._isFed2Schema;
}
federationFeature() {
var _a;
return (_a = this.schema.coreFeatures) === null || _a === void 0 ? void 0 : _a.getByIdentity(latestFederationSpec.identity);
}
externalTester() {
if (!this._externalTester) {
this._externalTester = new ExternalTester(this.schema, this.isFed2Schema());
}
return this._externalTester;
}
sharingPredicate() {
if (!this._sharingPredicate) {
this._sharingPredicate = (0, precompute_1.computeShareables)(this.schema);
}
return this._sharingPredicate;
}
fieldUsedPredicate() {
if (!this._fieldUsedPredicate) {
const usedFields = collectUsedFields(this);
this._fieldUsedPredicate = (field) => !!usedFields.has(field);
}
return this._fieldUsedPredicate;
}
isFieldUsed(field) {
return this.fieldUsedPredicate()(field);
}
isFieldExternal(field) {
return this.externalTester().isExternal(field);
}
isFieldPartiallyExternal(field) {
return this.externalTester().isPartiallyExternal(field);
}
isFieldFullyExternal(field) {
return this.externalTester().isFullyExternal(field);
}
isFieldFakeExternal(field) {
return this.externalTester().isFakeExternal(field);
}
selectionSelectsAnyExternalField(selectionSet) {
return this.externalTester().selectsAnyExternalField(selectionSet);
}
isFieldShareable(field) {
return this.sharingPredicate()(field);
}
isInterfaceObjectType(type) {
return (0, definitions_1.isObjectType)(type)
&& hasAppliedDirective(type, this.interfaceObjectDirective());
}
federationDirectiveNameInSchema(name) {
if (this.isFed2Schema()) {
const coreFeatures = this.schema.coreFeatures;
(0, utils_1.assert)(coreFeatures, 'Schema should be a core schema');
const federationFeature = coreFeatures.getByIdentity(latestFederationSpec.identity);
(0, utils_1.assert)(federationFeature, 'Schema should have the federation feature');
return federationFeature.directiveNameInSchema(name);
}
else {
return name;
}
}
federationTypeNameInSchema(name) {
if (name.charAt(0) === '_') {
return name;
}
if (this.isFed2Schema()) {
const coreFeatures = this.schema.coreFeatures;
(0, utils_1.assert)(coreFeatures, 'Schema should be a core schema');
const federationFeature = coreFeatures.getByIdentity(latestFederationSpec.identity);
(0, utils_1.assert)(federationFeature, 'Schema should have the federation feature');
return federationFeature.typeNameInSchema(name);
}
else {
return '_' + name;
}
}
getLegacyFederationDirective(name) {
const directive = this.getFederationDirective(name);
(0, utils_1.assert)(directive, `The provided schema does not have federation directive @${name}`);
return directive;
}
getFederationDirective(name) {
return this.schema.directive(this.federationDirectiveNameInSchema(name));
}
getPost20FederationDirective(name) {
var _a;
return (_a = this.getFederationDirective(name)) !== null && _a !== void 0 ? _a : {
name,
applications: () => new Set(),
};
}
keyDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.KEY);
}
overrideDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.OVERRIDE);
}
extendsDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.EXTENDS);
}
externalDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.EXTERNAL);
}
requiresDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.REQUIRES);
}
providesDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.PROVIDES);
}
shareableDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.SHAREABLE);
}
tagDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.TAG);
}
composeDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.COMPOSE_DIRECTIVE);
}
inaccessibleDirective() {
return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.INACCESSIBLE);
}
interfaceObjectDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.INTERFACE_OBJECT);
}
authenticatedDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.AUTHENTICATED);
}
requiresScopesDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.REQUIRES_SCOPES);
}
policyDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.POLICY);
}
fromContextDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.FROM_CONTEXT);
}
contextDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.CONTEXT);
}
costDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.COST);
}
listSizeDirective() {
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.LIST_SIZE);
}
allFederationDirectives() {
const baseDirectives = [
this.keyDirective(),
this.externalDirective(),
this.requiresDirective(),
this.providesDirective(),
this.tagDirective(),
this.extendsDirective(),
];
if (!this.isFed2Schema()) {
return baseDirectives;
}
baseDirectives.push(this.shareableDirective());
baseDirectives.push(this.inaccessibleDirective());
baseDirectives.push(this.overrideDirective());
const composeDirective = this.composeDirective();
if (isFederationDirectiveDefinedInSchema(composeDirective)) {
baseDirectives.push(composeDirective);
}
const interfaceObjectDirective = this.interfaceObjectDirective();
if (isFederationDirectiveDefinedInSchema(interfaceObjectDirective)) {
baseDirectives.push(interfaceObjectDirective);
}
const authenticatedDirective = this.authenticatedDirective();
if (isFederationDirectiveDefinedInSchema(authenticatedDirective)) {
baseDirectives.push(authenticatedDirective);
}
const requiresScopesDirective = this.requiresScopesDirective();
if (isFederationDirectiveDefinedInSchema(requiresScopesDirective)) {
baseDirectives.push(requiresScopesDirective);
}
const policyDirective = this.policyDirective();
if (isFederationDirectiveDefinedInSchema(policyDirective)) {
baseDirectives.push(policyDirective);
}
const contextDirective = this.contextDirective();
if (isFederationDirectiveDefinedInSchema(contextDirective)) {
baseDirectives.push(contextDirective);
}
const fromContextDirective = this.fromContextDirective();
if (isFederationDirectiveDefinedInSchema(fromContextDirective)) {
baseDirectives.push(fromContextDirective);
}
const costDirective = this.costDirective();
if (isFederationDirectiveDefinedInSchema(costDirective)) {
baseDirectives.push(costDirective);
}
const listSizeDirective = this.listSizeDirective();
if (isFederationDirectiveDefinedInSchema(listSizeDirective)) {
baseDirectives.push(listSizeDirective);
}
return baseDirectives;
}
entityType() {
return this.schema.type(this.federationTypeNameInSchema(exports.entityTypeSpec.name));
}
anyType() {
return this.schema.type(this.federationTypeNameInSchema(exports.anyTypeSpec.name));
}
serviceType() {
return this.schema.type(this.federationTypeNameInSchema(exports.serviceTypeSpec.name));
}
fieldSetType() {
return this.schema.type(this.federationTypeNameInSchema(federationSpec_1.FederationTypeName.FIELD_SET));
}
allFederationTypes() {
const fedTypes = [
this.anyType(),
this.serviceType(),
];
const fedFeature = this.federationFeature();
if (fedFeature) {
const featureDef = federationSpec_1.FEDERATION_VERSIONS.find(fedFeature.url.version);
(0, utils_1.assert)(featureDef, () => `Federation spec should be known, but got ${fedFeature.url}`);
for (const typeSpec of featureDef.typeSpecs()) {
const type = this.schema.type(fedFeature.typeNameInSchema(typeSpec.name));
if (type) {
fedTypes.push(type);
}
}
}
else {
fedTypes.push(this.fieldSetType());
}
const entityType = this.entityType();
if (entityType) {
fedTypes.push(entityType);
}
return fedTypes;
}
}
exports.FederationMetadata = FederationMetadata;
function isFederationDirectiveDefinedInSchema(definition) {
return definition instanceof definitions_1.DirectiveDefinition;
}
exports.isFederationDirectiveDefinedInSchema = isFederationDirectiveDefinedInSchema;
function hasAppliedDirective(type, definition) {
return isFederationDirectiveDefinedInSchema(definition) && type.hasAppliedDirective(definition);
}
exports.hasAppliedDirective = hasAppliedDirective;
class FederationBlueprint extends definitions_1.SchemaBlueprint {
constructor(withRootTypeRenaming) {
super();
this.withRootTypeRenaming = withRootTypeRenaming;
}
onAddedCoreFeature(schema, feature) {
super.onAddedCoreFeature(schema, feature);
if (feature.url.identity === federationSpec_1.federationIdentity) {
const spec = federationSpec_1.FEDERATION_VERSIONS.find(feature.url.version);
if (spec) {
spec.addElementsToSchema(schema);
}
}
}
onMissingDirectiveDefinition(schema, directive) {
if (directive.name === coreSpec_1.linkDirectiveDefaultName) {
const args = directive.arguments();
const url = args && args['url'];
let as = undefined;
let imports = [];
if (url && url.startsWith(linkSpec.identity)) {
as = args['as'];
imports = (0, coreSpec_1.extractCoreFeatureImports)(linkSpec.url, directive);
}
const errors = linkSpec.addDefinitionsToSchema(schema, as, imports);
return errors.length > 0 ? errors : schema.directive(directive.name);
}
return super.onMissingDirectiveDefinition(schema, directive);
}
ignoreParsedField(type, fieldName) {
if (!exports.FEDERATION_OPERATION_FIELDS.includes(fieldName)) {
return false;
}
const metadata = federationMetadata(type.schema());
return !!metadata && !metadata.isFed2Schema();
}
onConstructed(schema) {
const existing = federationMetadata(schema);
if (!existing) {
schema['_federationMetadata'] = new FederationMetadata(schema);
}
}
onDirectiveDefinitionAndSchemaParsed(schema) {
const errors = completeSubgraphSchema(schema);
schema.schemaDefinition.processUnappliedDirectives();
return errors;
}
onInvalidation(schema) {
super.onInvalidation(schema);
const metadata = federationMetadata(schema);
(0, utils_1.assert)(metadata, 'Federation schema should have had its metadata set on construction');
FederationMetadata.prototype['onInvalidate'].call(metadata);
}
onValidation(schema) {
var _a, _b, _c, _d, _e, _f;
const errorCollector = super.onValidation(schema);
if (this.withRootTypeRenaming) {
for (const k of definitions_1.allSchemaRootKinds) {
const type = (_a = schema.schemaDefinition.root(k)) === null || _a === void 0 ? void 0 : _a.type;
const defaultName = (0, definitions_1.defaultRootName)(k);
if (type && type.name !== defaultName) {
const existing = schema.type(defaultName);
if (existing) {
errorCollector.push(error_1.ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err(`The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
+ 'this is not supported by federation. '
+ 'If a root type does not use its default name, there should be no other type with that default name.', { nodes: (0, definitions_1.sourceASTs)(type, existing) }));
}
type.rename(defaultName);
}
}
}
const metadata = federationMetadata(schema);
(0, utils_1.assert)(metadata, 'Federation schema should have had its metadata set on construction');
if (!metadata.isFed2Schema()) {
return errorCollector;
}
const keyDirective = metadata.keyDirective();
validateAllFieldSet({
definition: keyDirective,
targetTypeExtractor: type => type,
errorCollector,
metadata,
isOnParentType: true,
allowOnNonExternalLeafFields: true,
allowOnInterface: metadata.federationFeature().url.version.compareTo(new coreSpec_1.FeatureVersion(2, 3)) >= 0,
onFields: field => {
const type = (0, definitions_1.baseType)(field.type);
if ((0, definitions_1.isUnionType)(type) || (0, definitions_1.isInterfaceType)(type)) {
let kind = type.kind;
kind = kind.slice(0, kind.length - 'Type'.length);
throw error_1.ERRORS.KEY_FIELDS_SELECT_INVALID_TYPE.err(`field "${field.coordinate}" is a ${kind} type which is not allowed in @key`);
}
}
});
validateAllFieldSet({
definition: metadata.requiresDirective(),
targetTypeExtractor: field => field.parent,
errorCollector,
metadata,
allowFieldsWithArguments: true,
});
validateAllFieldSet({
definition: metadata.providesDirective(),
targetTypeExtractor: field => {
if (metadata.isFieldExternal(field)) {
throw error_1.ERRORS.EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE.err(`Cannot have both @provides and @external on field "${field.coordinate}"`, { nodes: field.sourceAST });
}
const type = (0, definitions_1.baseType)(field.type);
if (!(0, definitions_1.isCompositeType)(type)) {
throw error_1.ERRORS.PROVIDES_ON_NON_OBJECT_FIELD.err(`Invalid @provides directive on field "${field.coordinate}": field has type "${field.type}" which is not a Composite Type`, { nodes: field.sourceAST });
}
return type;
},
errorCollector,
metadata,
});
const contextDirective = metadata.contextDirective();
const contextToTypeMap = new Map();
for (const application of contextDirective.applications()) {
const parent = application.parent;
const name = application.arguments().name;
const match = name.match(/^([A-Za-z]\w*)$/);
if (name.includes('_')) {
errorCollector.push(error_1.ERRORS.CONTEXT_NAME_INVALID.err(`Context name "${name}" may not contain an underscore.`, { nodes: (0, definitions_1.sourceASTs)(application) }));
}
else if (!match) {
errorCollector.push(error_1.ERRORS.CONTEXT_NAME_INVALID.err(`Context name "${name}" is invalid. It should have only alphanumeric characters.`, { nodes: (0, definitions_1.sourceASTs)(application) }));
}
const types = contextToTypeMap.get(name);
if (types) {
types.push(parent);
}
else {
contextToTypeMap.set(name, [parent]);
}
}
const fromContextDirective = metadata.fromContextDirective();
for (const application of fromContextDirective.applications()) {
const { field } = application.arguments();
const { context, selection } = parseContext(field);
if (application.parent.parent.kind === 'DirectiveDefinition') {
errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext argument cannot be used on a directive definition "${application.parent.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
continue;
}
const parent = application.parent;
if (((_c = (_b = parent === null || parent === void 0 ? void 0 : parent.parent) === null || _b === void 0 ? void 0 : _b.parent) === null || _c === void 0 ? void 0 : _c.kind) !== 'ObjectType') {
errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext argument cannot be used on a field that exists on an abstract type "${application.parent.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
continue;
}
const objectType = parent.parent.parent;
for (const implementedInterfaceType of objectType.interfaces()) {
const implementedInterfaceField = implementedInterfaceType.field(parent.parent.name);
if (implementedInterfaceField) {
errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext argument cannot be used on a field implementing an interface field "${implementedInterfaceField.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
}
}
if (parent.defaultValue !== undefined) {
errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext arguments may not have a default value: "${parent.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
}
if (!context || !selection) {
errorCollector.push(error_1.ERRORS.NO_CONTEXT_IN_SELECTION.err(`@fromContext argument does not reference a context "${field}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
}
else {
const locations = contextToTypeMap.get(context);
if (!locations) {
errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`Context "${context}" is used at location "${parent.coordinate}" but is never set.`, { nodes: (0, definitions_1.sourceASTs)(application) }));
}
else {
validateFieldValue({
context,
selection,
fromContextParent: parent,
setContextLocations: locations,
errorCollector,
metadata,
});
}
const keyDirective = metadata.keyDirective();
const keyApplications = objectType.appliedDirectivesOf(keyDirective);
if (!keyApplications.some(app => app.arguments().resolvable || app.arguments().resolvable === undefined)) {
errorCollector.push(error_1.ERRORS.CONTEXT_NO_RESOLVABLE_KEY.err(`Object "${objectType.coordinate}" has no resolvable key but has a field with a contextual argument.`, { nodes: (0, definitions_1.sourceASTs)(objectType) }));
}
}
}
validateNoExternalOnInterfaceFields(metadata, errorCollector);
validateAllExternalFieldsUsed(metadata, errorCollector);
validateKeyOnInterfacesAreAlsoOnAllImplementations(metadata, errorCollector);
validateInterfaceObjectsAreOnEntities(metadata, errorCollector);
const tagDirective = metadata.tagDirective();
if (tagDirective) {
const error = tagSpec.checkCompatibleDirective(tagDirective);
if (error) {
errorCollector.push(error);
}
}
for (const objectType of schema.objectTypes()) {
validateShareableNotRepeatedOnSameDeclaration(objectType, metadata, errorCollector);
for (const field of objectType.fields()) {
validateShareableNotRepeatedOnSameDeclaration(field, metadata, errorCollector);
}
}
for (const shareableApplication of metadata.shareableDirective().applications()) {
const element = shareableApplication.parent;
if (element instanceof definitions_1.FieldDefinition && !(0, definitions_1.isObjectType)(element.parent)) {
errorCollector.push(error_1.ERRORS.INVALID_SHAREABLE_USAGE.err(`Invalid use of @shareable on field "${element.coordinate}": only object type fields can be marked with @shareable`, { nodes: (0, definitions_1.sourceASTs)(shareableApplication, element.parent) }));
}
}
const costFeature = (_d = schema.coreFeatures) === null || _d === void 0 ? void 0 : _d.getByIdentity(costSpec_1.costIdentity);
const costSpec = costFeature && costSpec_1.COST_VERSIONS.find(costFeature.url.version);
const costDirective = costSpec === null || costSpec === void 0 ? void 0 : costSpec.costDirective(schema);
const listSizeDirective = costSpec === null || costSpec === void 0 ? void 0 : costSpec.listSizeDirective(schema);
for (const application of (_e = costDirective === null || costDirective === void 0 ? void 0 : costDirective.applications()) !== null && _e !== void 0 ? _e : []) {
validateCostNotAppliedToInterface(application, errorCollector);
}
for (const application of (_f = listSizeDirective === null || listSizeDirective === void 0 ? void 0 : listSizeDirective.applications()) !== null && _f !== void 0 ? _f : []) {
const parent = application.parent;
(0, utils_1.assert)(parent instanceof definitions_1.FieldDefinition, "@listSize can only be applied to FIELD_DEFINITION");
validateListSizeAppliedToList(application, parent, errorCollector);
validateAssumedSizeNotNegative(application, parent, errorCollector);
validateSlicingArgumentsAreValidIntegers(application, parent, errorCollector);
validateSizedFieldsAreValidLists(application, parent, errorCollector);
}
return errorCollector;
}
validationRules() {
return FEDERATION_VALIDATION_RULES;
}
onUnknownDirectiveValidationError(schema, unknownDirectiveName, error) {
const metadata = federationMetadata(schema);
(0, utils_1.assert)(metadata, `This method should only have been called on a subgraph schema`);
if (ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES.includes(unknownDirectiveName)) {
if (metadata.isFed2Schema()) {
const federationFeature = metadata.federationFeature();
(0, utils_1.assert)(federationFeature, 'Fed2 subgraph _must_ link to the federation feature');
const directiveNameInSchema = federationFeature.directiveNameInSchema(unknownDirectiveName);
if (directiveNameInSchema.startsWith(federationFeature.nameInSchema + '__')) {
return (0, error_1.withModifiedErrorMessage)(error, `${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use fully-qualified name "@${directiveNameInSchema}" or add "@${unknownDirectiveName}" to the \`import\` argument of the @link to the federation specification.`);
}
else {
return (0, error_1.withModifiedErrorMessage)(error, `${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use "@${directiveNameInSchema}" as it is imported under that name in the @link to the federation specification of this schema.`);
}
}
else {
return (0, error_1.withModifiedErrorMessage)(error, `${error.message} If you meant the "@${unknownDirectiveName}" federation 2 directive, note that this schema is a federation 1 schema. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`);
}
}
else if (!metadata.isFed2Schema()) {
const suggestions = (0, suggestions_1.suggestionList)(unknownDirectiveName, ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES);
if (suggestions.length > 0) {
return (0, error_1.withModifiedErrorMessage)(error, `${error.message}${(0, suggestions_1.didYouMean)(suggestions.map((s) => '@' + s))} If so, note that ${suggestions.length === 1 ? 'it is a federation 2 directive' : 'they are federation 2 directives'} but this schema is a federation 1 one. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`);
}
}
return error;
}
applyDirectivesAfterParsing() {
return true;
}
}
exports.FederationBlueprint = FederationBlueprint;
function findUnusedNamedForLinkDirective(schema) {
if (!schema.directive(linkSpec.url.name)) {
return undefined;
}
const baseName = linkSpec.url.name;
const n = 1;
for (;;) {
const candidate = baseName + n;
if (!schema.directive(candidate)) {
return candidate;
}
}
}
function setSchemaAsFed2Subgraph(schema, useLatest = false) {
let core = schema.coreFeatures;
let spec;
if (core) {
spec = core.coreDefinition;
(0, utils_1.assert)(spec.url.version.satisfies(linkSpec.version), `Fed2 schema must use @link with version >= 1.0, but schema uses ${spec.url}`);
}
else {
const alias = findUnusedNamedForLinkDirective(schema);
const errors = linkSpec.addToSchema(schema, alias);
if (errors.length > 0) {
throw (0, definitions_1.ErrGraphQLValidationFailed)(errors);
}
spec = linkSpec;
core = schema.coreFeatures;
(0, utils_1.assert)(core, 'Schema should now be a core schema');
}
const fedSpec = useLatest ? latestFederationSpec : autoExpandedFederationSpec;
(0, utils_1.assert)(!core.getByIdentity(fedSpec.identity), 'Schema already set as a federation subgraph');
schema.schemaDefinition.applyDirective(core.coreItself.nameInSchema, {
url: fedSpec.url.toString(),
import: autoExpandedFederationSpec.directiveSpecs().map((spec) => `@${spec.name}`),
});
const errors = completeSubgraphSchema(schema);
if (errors.length > 0) {
throw (0, definitions_1.ErrGraphQLValidationFailed)(errors);
}
}
exports.setSchemaAsFed2Subgraph = setSchemaAsFed2Subgraph;
exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.10", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy", "@context", "@fromContext", "@cost", "@listSize"])';
exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.10", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS_UPGRADED = '@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
function asFed2SubgraphDocument(document, options) {
var _a, _b;
const importedDirectives = (options === null || options === void 0 ? void 0 : options.includeAllImports) ? latestFederationSpec.directiveSpecs() : autoExpandedFederationSpec.directiveSpecs();
const directiveToAdd = ({
kind: graphql_1.Kind.DIRECTIVE,
name: { kind: graphql_1.Kind.NAME, value: coreSpec_1.linkDirectiveDefaultName },
arguments: [
{
kind: graphql_1.Kind.ARGUMENT,
name: { kind: graphql_1.Kind.NAME, value: 'url' },
value: { kind: graphql_1.Kind.STRING, value: latestFederationSpec.url.toString() }
},
{
kind: graphql_1.Kind.ARGUMENT,
name: { kind: graphql_1.Kind.NAME, value: 'import' },
value: { kind: graphql_1.Kind.LIST, values: importedDirectives.map((spec) => ({ kind: graphql_1.Kind.STRING, value: `@${spec.name}` })) }
}
]
});
if ((_a = options === null || options === void 0 ? void 0 : options.addAsSchemaExtension) !== null && _a !== void 0 ? _a : true) {
return {
kind: graphql_1.Kind.DOCUMENT,
loc: document.loc,
definitions: document.definitions.concat({
kind: graphql_1.Kind.SCHEMA_EXTENSION,
directives: [directiveToAdd]
}),
};
}
const existingSchemaDefinition = document.definitions.find((d) => d.kind == graphql_1.Kind.SCHEMA_DEFINITION);
if (existingSchemaDefinition) {
return {
kind: graphql_1.Kind.DOCUMENT,
loc: document.loc,
definitions: document.definitions.filter((d) => d !== existingSchemaDefinition).concat([{
...existingSchemaDefinition,
directives: [directiveToAdd].concat((_b = existingSchemaDefinition.directives) !== null && _b !== void 0 ? _b : []),
}]),
};
}
else {
const hasMutation = document.definitions.some((d) => d.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && d.name.value === 'Mutation');
const makeOpType = (opType, name) => ({
kind: graphql_1.Kind.OPERATION_TYPE_DEFINITION,
operation: opType,
type: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: name,
}
},
});
return {
kind: graphql_1.Kind.DOCUMENT,
loc: document.loc,
definitions: document.definitions.concat({
kind: graphql_1.Kind.SCHEMA_DEFINITION,
directives: [directiveToAdd],
operationTypes: [makeOpType(graphql_1.OperationTypeNode.QUERY, 'Query')].concat(hasMutation ? makeOpType(graphql_1.OperationTypeNode.MUTATION, 'Mutation') : []),
}),
};
}
}
exports.asFed2SubgraphDocument = asFed2SubgraphDocument;
function printSubgraphNames(names) {
return (0, utils_1.printHumanReadableList)(names.map(n => `"${n}"`), {
prefix: 'subgraph',
prefixPlural: 'subgraphs',
});
}
exports.printSubgraphNames = printSubgraphNames;
function federationMetadata(schema) {
return schema['_federationMetadata'];
}
exports.federationMetadata = federationMetadata;
function isFederationSubgraphSchema(schema) {
return !!federationMetadata(schema);
}
exports.isFederationSubgraphSchema = isFederationSubgraphSchema;
function isFederationField(field) {
var _a;
if (field.parent === ((_a = field.schema().schemaDefinition.root("query")) === null || _a === void 0 ? void 0 : _a.type)) {
return exports.FEDERATION_OPERATION_FIELDS.includes(field.name);
}
return false;
}
exports.isFederationField = isFederationField;
function isEntityType(type) {
if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
return false;
}
const metadata = federationMetadata(type.schema());
return !!metadata && type.hasAppliedDirective(metadata.keyDirective());
}
exports.isEntityType = isEntityType;
function isInterfaceObjectType(type) {
if (!(0, definitions_1.isObjectType)(type)) {
return false;
}
const metadata = federationMetadata(type.schema());
return !!metadata && metadata.isInterfaceObjectType(type);
}
exports.isInterfaceObjectType = isInterfaceObjectType;
function buildSubgraph(name, url, source, withRootTypeRenaming = true) {
const buildOptions = {
blueprint: new FederationBlueprint(withRootTypeRenaming),
validate: false,
};
let subgraph;
try {
const schema = typeof source === 'string'
? (0, buildSchema_1.buildSchema)(new graphql_1.Source(source, name), buildOptions)
: (0, buildSchema_1.buildSchemaFromAST)(source, buildOptions);
subgraph = new Subgraph(name, url, schema);
}
catch (e) {
if (e instanceof graphql_1.GraphQLError && name !== exports.FEDERATION_UNNAMED_SUBGRAPH_NAME) {
throw addSubgraphToError(e, name, error_1.ERRORS.INVALID_GRAPHQL);
}
else {
throw e;
}
}
return subgraph.validate();
}
exports.buildSubgraph = buildSubgraph;
function newEmptyFederation2Schema(config) {
const schema = new definitions_1.Schema(new FederationBlueprint(true), config);
setSchemaAsFed2Subgraph(schema, true);
return schema;
}
exports.newEmptyFederation2Schema = newEmptyFederation2Schema;
function completeSubgraphSchema(schema) {
const coreFeatures = schema.coreFeatures;
if (coreFeatures) {
const fedFeature = coreFeatures.getByIdentity(federationSpec_1.federationIdentity);
if (fedFeature) {
return completeFed2SubgraphSchema(schema);
}
else {
return completeFed1SubgraphSchema(schema);
}
}
else {
const fedLink = schema.schemaDefinition.appliedDirectivesOf(coreSpec_1.linkDirectiveDefaultName).find(isFedSpecLinkDirective);
if (fedLink) {
const errors = linkSpec.addToSchema(schema);
if (errors.length > 0) {
return errors;
}
return completeFed2SubgraphSchema(schema);
}
else {
return completeFed1SubgraphSchema(schema);
}
}
}
function isFedSpecLinkDirective(directive) {
const args = directive.arguments();
return directive.name === coreSpec_1.linkDirectiveDefaultName && args['url'] && args['url'].startsWith(federationSpec_1.federationIdentity);
}
function completeFed1SubgraphSchema(schema) {
var _a, _b;
for (const name of [federationSpec_1.FederationDirectiveName.KEY, federationSpec_1.FederationDirectiveName.PROVIDES, federationSpec_1.FederationDirectiveName.REQUIRES]) {
const directive = schema.directive(name);
if (!directive) {
continue;
}
(0, utils_1.assert)(directive.applications().size === 0, `${directive} shouldn't have had validation at that places`);
const fieldType = (_b = (_a = directive.argument('fields')) === null || _a === void 0 ? void 0 : _a.type) === null || _b === void 0 ? void 0 : _b.toString();
const fieldTypeIsWrongInKnownWays = !!fieldType
&& directive.arguments().length === 1
&& (fieldType === 'String' || fieldType === '_FieldSet' || fieldType === 'FieldSet');
if (directive.arguments().length === 0 || fieldTypeIsWrongInKnownWays) {
directive.remove();
}
}
const errors = federationSpec_1.FEDERATION1_TYPES.map((spec) => spec.checkOrAdd(schema, FAKE_FED1_CORE_FEATURE_TO_RENAME_TYPES))
.concat(federationSpec_1.FEDERATION1_DIRECTIVES.map((spec) => spec.checkOrAdd(schema)))
.flat();
return errors.length === 0 ? expandKnownFeatures(schema) : errors;
}
function completeFed2SubgraphSchema(schema) {
const coreFeatures = schema.coreFeatures;
(0, utils_1.assert)(coreFeatures, 'This method should not have been called on a non-core schema');
const fedFeature = coreFeatures.getByIdentity(federationSpec_1.federationIdentity);
(0, utils_1.assert)(fedFeature, 'This method should not have been called on a schema with no @link for federation');
const spec = federationSpec_1.FEDERATION_VERSIONS.find(fedFeature.url.version);
if (!spec) {
return [error_1.ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err(`Invalid version ${fedFeature.url.version} for the federation feature in @link directive on schema`, { nodes: fedFeature.directive.sourceAST })];
}
const errors = spec.addElementsToSchema(schema);
return errors.length === 0 ? expandKnownFeatures(schema) : errors;
}
function expandKnownFeatures(schema) {
const coreFeatures = schema.coreFeatures;
if (!coreFeatures) {
return [];
}
let errors = [];
for (const feature of coreFeatures.allFeatures()) {
if (feature === coreFeatures.coreItself || feature.url.identity === federationSpec_1.federationIdentity || feature.url.identity === joinSpec_1.joinIdentity) {
continue;
}
const spec = (0, knownCoreFeatures_1.coreFeatureDefinitionIfKnown)(feature.url);
if (!spec) {
continue;
}
errors = errors.concat(spec.addElementsToSchema(schema));
}
return errors;
}
function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, decorateValidationErrors = true, normalize = false, }) {
try {
const selectionSet = (0, operations_1.parseSelectionSet)({
parentType,
source: validateFieldSetValue(directive),
fieldAccessor,
validate,
});
if (validate !== null && validate !== void 0 ? validate : true) {
selectionSet.forEachElement((elt) => {
if (elt.kind === 'Field' && elt.alias) {
throw new graphql_1.GraphQLError(`Cannot use alias "${elt.alias}" in "${elt}": aliases are not currently supported in @${directive.name}`);
}
});
}
return normalize
? selectionSet.normalize({ parentType, recursive: true })
: selectionSet;
}
catch (e) {
if (!(e instanceof graphql_1.GraphQLError) || !decorateValidationErrors) {
throw e;
}
throw handleFieldSetValidationError(directive, e, (msg) => {
if (msg.startsWith('Cannot query field')) {
if (msg.endsWith('.')) {
msg = msg.slice(0, msg.length - 1);
}
if (directive.name === federationSpec_1.FederationDirectiveName.KEY) {
msg = msg + ' (the field should either be added to this subgraph or, if it should not be resolved by this subgraph, you need to add it to this subgraph with @external).';
}
else {
msg = msg + ' (if the field is defined in another subgraph, you need to add it to this subgraph with @external).';
}
}
return msg;
});
}
}
exports.parseFieldSetArgument = parseFieldSetArgument;
function collectTargetFields({ parentType, directive, includeInterfaceFieldsImplementations, validate = true, }) {
const fields = [];
try {
parseFieldSetArgument({
parentType,
directive,
fieldAccessor: (t, f) => {
const field = t.field(f);
if (field) {
fields.push(field);
if (includeInterfaceFieldsImplementations && (0, definitions_1.isInterfaceType)(t)) {
for (const implType of t.possibleRuntimeTypes()) {
const implField = implType.field(f);
if (implField) {
fields.push(implField);
}
}
}
}
return field;
},
validate,
});
}
catch (e) {
const isGraphQLError = (0, error_1.errorCauses)(e) !== undefined;
if (!isGraphQLError || validate) {
throw e;
}
}
return fields;
}
exports.collectTargetFields = collectTargetFields;
function validateFieldSetValue(directive) {
var _a;
const fields = directive.arguments().fields;
const nodes = directive.sourceAST;
if (typeof fields !== 'string') {
throw error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err(`Invalid value for argument "${directive.definition.argument('fields').name}": must be a string.`, { nodes });
}
if (nodes && nodes.kind === 'Directive') {
for (const argNode of (_a = nodes.arguments) !== null && _a !== void 0 ? _a : []) {
if (argNode.name.value === 'fields') {
if (argNode.value.kind !== 'StringValue') {
throw error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err(`Invalid value for argument "${directive.definition.argument('fields').name}": must be a string.`, { nodes });
}
break;
}
}
}
return fields;
}
function subgraphsFromServiceList(serviceList) {
var _a;
let errors = [];
const subgraphs = new Subgraphs();
for (const service of serviceList) {
try {
subgraphs.add(buildSubgraph(service.name, (_a = service.url) !== null && _a !== void 0 ? _a : '', service.typeDefs));
}
catch (e) {
const causes = (0, error_1.errorCauses)(e);
if (causes) {
errors = errors.concat(causes);
}
else {
throw e;
}
}
}
return errors.length === 0 ? subgraphs : errors;
}
exports.subgraphsFromServiceList = subgraphsFromServiceList;
class Subgraphs {
constructor() {
this.subgraphs = new utils_1.OrderedMap();
}
add(subgraph) {
if (this.subgraphs.has(subgraph.name)) {
throw new Error(`A subgraph named ${subgraph.name} already exists` + (subgraph.url ? ` (with url '${subgraph.url}')` : ''));
}
this.subgraphs.add(subgraph.name, subgraph);
return subgraph;
}
get(name) {
return this.subgraphs.get(name);
}
size() {
return this.subgraphs.size;
}
names() {
return this.subgraphs.keys();
}
values() {
return this.subgraphs.values();
}
*[Symbol.iterator]() {
for (const subgraph of this.subgraphs) {
yield subgraph;
}
}
validate() {
let errors = [];
for (const subgraph of this.values()) {
try {
subgraph.validate();
}
catch (e) {
const causes = (0, error_1.errorCauses)(e);
if (!causes) {
throw e;
}
errors = errors.concat(causes);
}
}
return errors.length === 0 ? undefined : errors;
}
toString() {
return '[' + this.subgraphs.keys().join(', ') + ']';
}
}
exports.Subgraphs = Subgraphs;
exports.anyTypeSpec = (0, directiveAndTypeSpecification_1.createScalarTypeSpecification)({ name: '_Any' });
exports.serviceTypeSpec = (0, directiveAndTypeSpecification_1.createObjectTypeSpecification)({
name: '_Service',
fieldsFct: (schema) => [{ name: 'sdl', type: schema.stringType() }],
});
exports.entityTypeSpec = (0, directiveAndTypeSpecification_1.createUnionTypeSpecification)({
name: '_Entity',
membersFct: (schema) => {
return schema.objectTypes().filter(isEntityType).map((t) => t.name);
},
});
exports.FEDERATION_OPERATION_TYPES = [exports.anyTypeSpec, exports.serviceTypeSpec, exports.entityTypeSpec];
exports.serviceFieldName = '_service';
exports.entitiesFieldName = '_entities';
exports.FEDERATION_OPERATION_FIELDS = [exports.serviceFieldName, exports.entitiesFieldName];
class Subgraph {
constructor(name, url, schema) {
this.name = name;
this.url = url;
this.schema = schema;
if (name === exports.FEDERATION_RESERVED_SUBGRAPH_NAME) {
throw error_1.ERRORS.INVALID_SUBGRAPH_NAME.err(`Invalid name ${exports.FEDERATION_RESERVED_SUBGRAPH_NAME} for a subgraph: this name is reserved`);
}
}
metadata() {
const metadata = federationMetadata(this.schema);
(0, utils_1.assert)(metadata, 'The subgraph schema should have built with the federation built-ins.');
return metadata;
}
isFed2Subgraph() {
return this.metadata().isFed2Schema();
}
addFederationOperations() {
const metadata = this.metadata();
for (const type of exports.FEDERATION_OPERATION_TYPES) {
type.checkOrAdd(this.schema);
}
const queryRoot = this.schema.schemaDefinition.root("query");
const queryType = queryRoot ? queryRoot.type : this.schema.addType(new definitions_1.ObjectType("Query"));
const entityField = queryType.field(exports.entitiesFieldName);
const entityType = metadata.entityType();
if (entityType) {
const entityFieldType = new definitions_1.NonNullType(new definitions_1.ListType(entityType));
if (!entityField) {
queryType.addField(exports.entitiesFieldName, entityFieldType)
.addArgument('representations', new definitions_1.NonNullType(new definitions_1.ListType(new definitions_1.NonNullType(metadata.anyType()))));
}
else if (!entityField.type) {
entityField.type = entityType;
}
}
else if (entityField) {
entityField.remove();
}
if (!queryType.field(exports.serviceFieldName)) {
queryType.addField(exports.serviceFieldName, new definitions_1.NonNullType(metadata.serviceType()));
}
}
assumeValid() {
this.addFederationOperations();
this.schema.assumeValid();
return this;
}
validate() {
try {
this.addFederationOperations();
this.schema.validate();
return this;
}
catch (e) {
if (e instanceof graphql_1.GraphQLError) {
throw addSubgraphToError(e, this.name, error_1.ERRORS.INVALID_GRAPHQL);
}
else {
throw e;
}
}
}
isPrintedDirective(d) {
var _a;
if (this.metadata().allFederationDirectives().includes(d)) {
return false;
}
const core = this.schema.coreFeatures;
return !core || ((_a = core.sourceFeature(d)) === null || _a === void 0 ? void 0 : _a.feature.url.identity) !== coreSpec_1.linkIdentity;
}
isPrintedType(t) {
var _a;
if (this.metadata().allFederationTypes().includes(t)) {
return false;
}
if ((0, definitions_1.isObjectType)(t) && t.isQueryRootType() && t.fields().filter((f) => !isFederationField(f)).length === 0) {
return false;
}
const core = this.schema.coreFeatures;
return !core || ((_a = core.sourceFeature(t)) === null || _a === void 0 ? void 0 : _a.feature.url.identity) !== coreSpec_1.linkIdentity;
}
isPrintedDirectiveApplication(d) {
if (!this.schema.coreFeatures || d.name !== linkSpec.url.name) {
return true;
}
const args = d.arguments();
let urlArg = undefined;
if ('url' in args) {
try {
urlArg = coreSpec_1.FeatureUrl.parse(args['url']);
}
catch (e) {
}
}
const isDefaultLinkToLink = (urlArg === null || urlArg === void 0 ? void 0 : urlArg.identity) === coreSpec_1.linkIdentity && Object.keys(args).length === 1;
return !isDefaultLinkToLink;
}
toString(basePrintOptions = print_1.defaultPrintOptions) {
return (0, print_1.printSchema)(this.schema, {
...basePrintOptions,
directiveDefinitionFilter: (d) => this.isPrintedDirective(d),
typeFilter: (t) => this.isPrintedType(t),
fieldFilter: (f) => !isFederationField(f),
directiveApplicationFilter: (d) => this.isPrintedDirectiveApplication(d),
});
}
}
exports.Subgraph = Subgraph;
function addSubgraphToASTNode(node, subgraph) {
if ('subgraph' in node) {
return node;
}
return {
...node,
subgraph
};
}
exports.addSubgraphToASTNode = addSubgraphToASTNode;
function addSubgraphToError(e, subgraphName, errorCode) {
const updatedCauses = (0, error_1.errorCauses)(e).map(cause => {
var _a;
const message = `[${subgraphName}] ${cause.message}`;
const nodes = cause.nodes
? cause.nodes.map(node => addSubgraphToASTNode(node, subgraphName))
: undefined;
const code = (_a = (0, error_1.errorCodeDef)(cause)) !== null && _a !== void 0 ? _a : errorCode;
const options = {
...(0, error_1.extractGraphQLErrorOptions)(cause),
nodes,
originalError: cause,
};
return code
? code.err(message, options)
: new graphql_1.GraphQLError(message, options);
});
return updatedCauses.length === 1 ? updatedCauses[0] : (0, definitions_1.ErrGraphQLValidationFailed)(updatedCauses);
}
exports.addSubgraphToError = addSubgraphToError;
class ExternalTester {
constructor(schema, isFed2Schema) {
this.schema = schema;
this.isFed2Schema = isFed2Schema;
this.fakeExternalFields = new Set();
this.providedFields = new Set();
this.externalFieldsOnType = new Set();
this.externalDirective = this.metadata().externalDirective();
this.collectFakeExternals();
this.collectProvidedFields();
this.collectExternalsOnType();
}
metadata() {
const metadata = federationMetadata(this.schema);
(0, utils_1.assert)(metadata, 'Schema should be a subgraphs schema');
return metadata;
}
collectFakeExternals() {
const metadata = this.metadata();
const extendsDirective = metadata.extendsDirective();
for (const key of metadata.keyDirective().applications()) {
const parentType = key.parent;
if (!(key.ofExtension() || parentType.hasAppliedDirective(extendsDirective))) {
continue;
}
collectTargetFields({
parentType,
directive: key,
includeInterfaceFieldsImplementations: false,
validate: false,
}).filter((field) => field.hasAppliedDirective(this.externalDirective))
.forEach((field) => this.fakeExternalFields.add(field.coordinate));
}
}
collectProvidedFields() {
for (const provides of this.metadata().providesDirective().applications()) {
const parent = provides.parent;
const parentType = (0, definitions_1.baseType)(parent.type);
if ((0, definitions_1.isCompositeType)(parentType)) {
collectTargetFields({
parentType,
directive: provides,
includeInterfaceFieldsImplementations: true,
validate: false,
}).forEach((f) => this.providedFields.add(f.coordinate));
}
}
}
collectExternalsOnType() {
if (!this.isFed2Schema) {
return;
}
for (const type of this.schema.objectTypes()) {
if (type.hasAppliedDirective(this.externalDirective)) {
for (const field of type.fields()) {
this.externalFieldsOnType.add(field.coordinate);
}
}
}
}
isExternal(field) {
return (field.hasAppliedDirective(this.externalDirective) || this.externalFieldsOnType.has(field.coordinate)) && !this.isFakeExternal(field);
}
isFakeExternal(field) {
return this.fakeExternalFields.has(field.coordinate);
}
selectsAnyExternalField(selectionSet) {
for (const selection of selectionSet.selections()) {
if (selection.kind === 'FieldSelection' && this.isExternal(selection.element.definition)) {
return true;
}
if (selection.selectionSet) {
if (this.selectsAnyExternalField(selection.selectionSet)) {
return true;
}
}
}
return false;
}
isPartiallyExternal(field) {
return this.isExternal(field) && this.providedFields.has(field.coordinate);
}
isFullyExternal(field) {
return this.isExternal(field) && !this.providedFields.has(field.coordinate);
}
}
function removeInactiveProvidesAndRequires(schema, onModified = () => { }) {
const metadata = federationMetadata(schema);
if (!metadata) {
return;
}
const providesDirective = metadata.providesDirective();
const requiresDirective = metadata.requiresDirective();
for (const type of schema.types()) {
if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
continue;
}
for (const field of type.fields()) {
const fieldBaseType = (0, definitions_1.baseType)(field.type);
removeInactiveApplications(providesDirective, field, fieldBaseType, onModified);
removeInactiveApplications(requiresDirective, field, type, onModified);
}
}
}
exports.removeInactiveProvidesAndRequires = removeInactiveProvidesAndRequires;
function removeInactiveApplications(directiveDefinition, field, parentType, onModified) {
for (const application of field.appliedDirectivesOf(directiveDefinition)) {
let selection;
try {
selection = parseFieldSetArgument({ parentType, directive: application });
}
catch (e) {
continue;
}
if (selectsNonExternalLeafField(selection)) {
application.remove();
const updated = withoutNonExternalLeafFields(selection);
if (!updated.isEmpty()) {
const updatedDirective = field.applyDirective(directiveDefinition, { fields: updated.toString(true, false) });
onModified(field, application, updatedDirective);
}
else {
onModified(field, application);
}
}
}
}
function isExternalOrHasExternalImplementations(field) {
const metadata = federationMetadata(field.schema());
if (!metadata) {
return false;
}
if (field.hasAppliedDirective(metadata.externalDirective())) {
return true;
}
const parentType = field.parent;
if ((0, definitions_1.isInterfaceType)(parentType)) {
for (const implem of parentType.possibleRuntimeTypes()) {
const fieldInImplem = implem.field(field.name);
if (fieldInImplem && fieldInImplem.hasAppliedDirective(metadata.externalDirective())) {
return true;
}
}
}
return false;
}
function selectsNonExternalLeafField(selection) {
return selection.selections().some(s => {
if (s.kind === 'FieldSelection') {
if (isExternalOrHasExternalImplementations(s.element.definition)) {
return false;
}
return !s.selectionSet || selectsNonExternalLeafField(s.selectionSet);
}
else {
return selectsNonExternalLeafField(s.selectionSet);
}
});
}
function withoutNonExternalLeafFields(selectionSet) {
return selectionSet.lazyMap((selection) => {
if (selection.kind === 'FieldSelection') {
if (isExternalOrHasExternalImplementations(selection.element.definition)) {
return selection;
}
}
if (selection.selectionSet) {
const updated = withoutNonExternalLeafFields(selection.selectionSet);
if (!updated.isEmpty()) {
return selection.withUpdatedSelectionSet(updated);
}
}
return undefined;
});
}
//# sourceMappingURL=federation.js.map