@apollo/federation-internals
Version:
Apollo Federation internal utilities
1,152 lines • 130 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreFeature = exports.defaultSchemaBlueprint = exports.SchemaBlueprint = exports.NamedSchemaElementWithType = exports.NamedSchemaElement = exports.SchemaElement = exports.Extension = exports.sourceASTs = exports.DirectiveTargetElement = exports.isLeafType = exports.typeFromAST = exports.typeToAST = exports.isTypeSystemDirectiveLocation = exports.typeSystemDirectiveLocations = exports.isExecutableDirectiveLocation = exports.executableDirectiveLocations = exports.isConditionalDirective = exports.supertypes = exports.runtimeTypesIntersects = exports.possibleRuntimeTypes = exports.isCompositeType = exports.isAbstractType = exports.isNullableType = exports.baseType = exports.filterTypesOfKind = exports.isTypeOfKind = exports.isInputType = exports.isOutputType = exports.isInputObjectType = exports.isUnionType = exports.isEnumType = exports.isInterfaceType = exports.isObjectType = exports.isIDType = exports.isBooleanType = exports.isFloatType = exports.isStringType = exports.isIntType = exports.isCustomScalarType = exports.isScalarType = exports.isNonNullType = exports.isListType = exports.isWrapperType = exports.isNamedType = exports.isSchemaRootType = exports.defaultRootName = exports.allSchemaRootKinds = exports.typenameFieldName = exports.ErrGraphQLAPISchemaValidationFailed = exports.ErrGraphQLValidationFailed = void 0;
exports.isElementNamedType = exports.isFieldDefinition = exports.copyDirectiveDefinitionToSchema = exports.newNamedType = exports.variableDefinitionFromAST = exports.variableDefinitionsFromAST = exports.VariableDefinitions = exports.VariableDefinition = exports.isVariable = exports.VariableCollector = exports.Variable = exports.directiveApplicationsSubstraction = exports.isDirectiveApplicationsSubset = exports.sameDirectiveApplications = exports.sameDirectiveApplication = exports.directivesToDirectiveNodes = exports.directivesToString = exports.Directive = exports.DirectiveDefinition = exports.EnumValue = exports.ArgumentDefinition = exports.InputFieldDefinition = exports.FieldDefinition = exports.NonNullType = exports.ListType = exports.InputObjectType = exports.EnumType = exports.UnionType = exports.UnionMember = exports.InterfaceType = exports.ObjectType = exports.InterfaceImplementation = exports.ScalarType = exports.SchemaDefinition = exports.RootType = exports.Schema = exports.CoreFeatures = void 0;
const graphql_1 = require("graphql");
const coreSpec_1 = require("./specs/coreSpec");
const utils_1 = require("./utils");
const values_1 = require("./values");
const tagSpec_1 = require("./specs/tagSpec");
const inaccessibleSpec_1 = require("./specs/inaccessibleSpec");
const print_1 = require("./print");
const types_1 = require("./types");
const introspection_1 = require("./introspection");
const validate_1 = require("graphql/validation/validate");
const specifiedRules_1 = require("graphql/validation/specifiedRules");
const validate_2 = require("./validate");
const directiveAndTypeSpecification_1 = require("./directiveAndTypeSpecification");
const suggestions_1 = require("./suggestions");
const error_1 = require("./error");
const knownCoreFeatures_1 = require("./knownCoreFeatures");
const validationErrorCode = 'GraphQLValidationFailed';
const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL schema.';
const EMPTY_SET = new Set();
const ErrGraphQLValidationFailed = (causes, message = DEFAULT_VALIDATION_ERROR_MESSAGE) => (0, error_1.aggregateError)(validationErrorCode, message, causes);
exports.ErrGraphQLValidationFailed = ErrGraphQLValidationFailed;
const apiSchemaValidationErrorCode = 'GraphQLAPISchemaValidationFailed';
const ErrGraphQLAPISchemaValidationFailed = (causes) => (0, error_1.aggregateError)(apiSchemaValidationErrorCode, 'The supergraph schema failed to produce a valid API schema', causes);
exports.ErrGraphQLAPISchemaValidationFailed = ErrGraphQLAPISchemaValidationFailed;
exports.typenameFieldName = '__typename';
exports.allSchemaRootKinds = ['query', 'mutation', 'subscription'];
function defaultRootName(rootKind) {
return rootKind.charAt(0).toUpperCase() + rootKind.slice(1);
}
exports.defaultRootName = defaultRootName;
function checkDefaultSchemaRoot(type) {
if (type.kind !== 'ObjectType') {
return undefined;
}
switch (type.name) {
case 'Query': return 'query';
case 'Mutation': return 'mutation';
case 'Subscription': return 'subscription';
default: return undefined;
}
}
function isSchemaRootType(type) {
return isObjectType(type) && type.isRootType();
}
exports.isSchemaRootType = isSchemaRootType;
function isNamedType(type) {
return type instanceof BaseNamedType;
}
exports.isNamedType = isNamedType;
function isWrapperType(type) {
return isListType(type) || isNonNullType(type);
}
exports.isWrapperType = isWrapperType;
function isListType(type) {
return type.kind == 'ListType';
}
exports.isListType = isListType;
function isNonNullType(type) {
return type.kind == 'NonNullType';
}
exports.isNonNullType = isNonNullType;
function isScalarType(type) {
return type.kind == 'ScalarType';
}
exports.isScalarType = isScalarType;
function isCustomScalarType(type) {
return isScalarType(type) && !graphQLBuiltInTypes.includes(type.name);
}
exports.isCustomScalarType = isCustomScalarType;
function isIntType(type) {
return type === type.schema().intType();
}
exports.isIntType = isIntType;
function isStringType(type) {
return type === type.schema().stringType();
}
exports.isStringType = isStringType;
function isFloatType(type) {
return type === type.schema().floatType();
}
exports.isFloatType = isFloatType;
function isBooleanType(type) {
return type === type.schema().booleanType();
}
exports.isBooleanType = isBooleanType;
function isIDType(type) {
return type === type.schema().idType();
}
exports.isIDType = isIDType;
function isObjectType(type) {
return type.kind == 'ObjectType';
}
exports.isObjectType = isObjectType;
function isInterfaceType(type) {
return type.kind == 'InterfaceType';
}
exports.isInterfaceType = isInterfaceType;
function isEnumType(type) {
return type.kind == 'EnumType';
}
exports.isEnumType = isEnumType;
function isUnionType(type) {
return type.kind == 'UnionType';
}
exports.isUnionType = isUnionType;
function isInputObjectType(type) {
return type.kind == 'InputObjectType';
}
exports.isInputObjectType = isInputObjectType;
function isOutputType(type) {
switch (baseType(type).kind) {
case 'ScalarType':
case 'ObjectType':
case 'UnionType':
case 'EnumType':
case 'InterfaceType':
return true;
default:
return false;
}
}
exports.isOutputType = isOutputType;
function isInputType(type) {
switch (baseType(type).kind) {
case 'ScalarType':
case 'EnumType':
case 'InputObjectType':
return true;
default:
return false;
}
}
exports.isInputType = isInputType;
function isTypeOfKind(type, kind) {
return type.kind === kind;
}
exports.isTypeOfKind = isTypeOfKind;
function filterTypesOfKind(types, kind) {
return types.reduce((acc, type) => {
if (isTypeOfKind(type, kind)) {
acc.push(type);
}
return acc;
}, []);
}
exports.filterTypesOfKind = filterTypesOfKind;
function baseType(type) {
return isWrapperType(type) ? type.baseType() : type;
}
exports.baseType = baseType;
function isNullableType(type) {
return !isNonNullType(type);
}
exports.isNullableType = isNullableType;
function isAbstractType(type) {
return isInterfaceType(type) || isUnionType(type);
}
exports.isAbstractType = isAbstractType;
function isCompositeType(type) {
return isObjectType(type) || isInterfaceType(type) || isUnionType(type);
}
exports.isCompositeType = isCompositeType;
function possibleRuntimeTypes(type) {
switch (type.kind) {
case 'InterfaceType': return type.possibleRuntimeTypes();
case 'UnionType': return type.types();
case 'ObjectType': return [type];
}
}
exports.possibleRuntimeTypes = possibleRuntimeTypes;
function runtimeTypesIntersects(t1, t2) {
if (t1 === t2) {
return true;
}
const rt1 = possibleRuntimeTypes(t1);
const rt2 = possibleRuntimeTypes(t2);
for (const obj1 of rt1) {
if (rt2.some(obj2 => obj1.name === obj2.name)) {
return true;
}
}
return false;
}
exports.runtimeTypesIntersects = runtimeTypesIntersects;
function supertypes(type) {
switch (type.kind) {
case 'InterfaceType': return type.interfaces();
case 'UnionType': return [];
case 'ObjectType': return type.interfaces().concat(type.unionsWhereMember());
}
}
exports.supertypes = supertypes;
function isConditionalDirective(directive) {
return ['include', 'skip'].includes(directive.name);
}
exports.isConditionalDirective = isConditionalDirective;
exports.executableDirectiveLocations = [
graphql_1.DirectiveLocation.QUERY,
graphql_1.DirectiveLocation.MUTATION,
graphql_1.DirectiveLocation.SUBSCRIPTION,
graphql_1.DirectiveLocation.FIELD,
graphql_1.DirectiveLocation.FRAGMENT_DEFINITION,
graphql_1.DirectiveLocation.FRAGMENT_SPREAD,
graphql_1.DirectiveLocation.INLINE_FRAGMENT,
graphql_1.DirectiveLocation.VARIABLE_DEFINITION,
];
const executableDirectiveLocationsSet = new Set(exports.executableDirectiveLocations);
function isExecutableDirectiveLocation(loc) {
return executableDirectiveLocationsSet.has(loc);
}
exports.isExecutableDirectiveLocation = isExecutableDirectiveLocation;
exports.typeSystemDirectiveLocations = [
graphql_1.DirectiveLocation.SCHEMA,
graphql_1.DirectiveLocation.SCALAR,
graphql_1.DirectiveLocation.OBJECT,
graphql_1.DirectiveLocation.FIELD_DEFINITION,
graphql_1.DirectiveLocation.ARGUMENT_DEFINITION,
graphql_1.DirectiveLocation.INTERFACE,
graphql_1.DirectiveLocation.UNION,
graphql_1.DirectiveLocation.ENUM,
graphql_1.DirectiveLocation.ENUM_VALUE,
graphql_1.DirectiveLocation.INPUT_OBJECT,
graphql_1.DirectiveLocation.INPUT_FIELD_DEFINITION,
];
const typeSystemDirectiveLocationsSet = new Set(exports.typeSystemDirectiveLocations);
function isTypeSystemDirectiveLocation(loc) {
return typeSystemDirectiveLocationsSet.has(loc);
}
exports.isTypeSystemDirectiveLocation = isTypeSystemDirectiveLocation;
function typeToAST(type) {
switch (type.kind) {
case 'ListType':
return {
kind: graphql_1.Kind.LIST_TYPE,
type: typeToAST(type.ofType)
};
case 'NonNullType':
return {
kind: graphql_1.Kind.NON_NULL_TYPE,
type: typeToAST(type.ofType)
};
default:
return {
kind: graphql_1.Kind.NAMED_TYPE,
name: { kind: graphql_1.Kind.NAME, value: type.name }
};
}
}
exports.typeToAST = typeToAST;
function typeFromAST(schema, node) {
switch (node.kind) {
case graphql_1.Kind.LIST_TYPE:
return new ListType(typeFromAST(schema, node.type));
case graphql_1.Kind.NON_NULL_TYPE:
return new NonNullType(typeFromAST(schema, node.type));
default:
const type = schema.type(node.name.value);
if (!type) {
throw error_1.ERRORS.INVALID_GRAPHQL.err(`Unknown type "${node.name.value}"`, { nodes: node });
}
return type;
}
}
exports.typeFromAST = typeFromAST;
function isLeafType(type) {
return isScalarType(type) || isEnumType(type);
}
exports.isLeafType = isLeafType;
class DirectiveTargetElement {
constructor(_schema, directives = []) {
this._schema = _schema;
this.appliedDirectives = directives.map((d) => this.attachDirective(d));
}
schema() {
return this._schema;
}
attachDirective(directive) {
const toAdd = directive.isAttached()
? new Directive(directive.name, directive.arguments())
: directive;
Element.prototype['setParent'].call(toAdd, this);
return toAdd;
}
appliedDirectivesOf(nameOrDefinition) {
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
return this.appliedDirectives.filter(d => d.name == directiveName);
}
hasAppliedDirective(nameOrDefinition) {
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
return this.appliedDirectives.some(d => d.name == directiveName);
}
appliedDirectivesToDirectiveNodes() {
return directivesToDirectiveNodes(this.appliedDirectives);
}
appliedDirectivesToString() {
return directivesToString(this.appliedDirectives);
}
collectVariablesInAppliedDirectives(collector) {
for (const applied of this.appliedDirectives) {
collector.collectInArguments(applied.arguments());
}
}
}
exports.DirectiveTargetElement = DirectiveTargetElement;
function sourceASTs(...elts) {
return elts.map(elt => elt === null || elt === void 0 ? void 0 : elt.sourceAST).filter((elt) => elt !== undefined);
}
exports.sourceASTs = sourceASTs;
class Element {
schema() {
const schema = this.schemaInternal();
(0, utils_1.assert)(schema, 'requested schema does not exist. Probably because the element is unattached');
return schema;
}
schemaInternal() {
if (!this._parent) {
return undefined;
}
else if (this._parent instanceof Schema) {
return this._parent;
}
else if (this._parent instanceof SchemaElement) {
return this._parent.schemaInternal();
}
else if (this._parent instanceof DirectiveTargetElement) {
return this._parent.schema();
}
(0, utils_1.assert)(false, 'unreachable code. parent is of unknown type');
}
get parent() {
(0, utils_1.assert)(this._parent, 'trying to access non-existent parent');
return this._parent;
}
isAttached() {
return !!this._parent;
}
setParent(parent) {
(0, utils_1.assert)(!this._parent, "Cannot set parent of an already attached element");
this._parent = parent;
this.onAttached();
}
onAttached() {
}
checkUpdate() {
(0, utils_1.assert)(this.isAttached(), () => `Cannot modify detached element ${this}`);
}
}
class Extension {
get extendedElement() {
return this._extendedElement;
}
setExtendedElement(element) {
(0, utils_1.assert)(!this._extendedElement, "Cannot attached already attached extension");
this._extendedElement = element;
}
}
exports.Extension = Extension;
class SchemaElement extends Element {
addUnappliedDirective({ nameOrDef, args, extension, directive }) {
const toAdd = {
nameOrDef,
args: args !== null && args !== void 0 ? args : {},
extension,
directive,
};
if (this._unappliedDirectives) {
this._unappliedDirectives.push(toAdd);
}
else {
this._unappliedDirectives = [toAdd];
}
}
processUnappliedDirectives() {
var _a;
for (const { nameOrDef, args, extension, directive } of (_a = this._unappliedDirectives) !== null && _a !== void 0 ? _a : []) {
const d = this.applyDirective(nameOrDef, args);
d.setOfExtension(extension);
d.sourceAST = directive;
}
this._unappliedDirectives = undefined;
}
get appliedDirectives() {
var _a;
return (_a = this._appliedDirectives) !== null && _a !== void 0 ? _a : [];
}
appliedDirectivesOf(nameOrDefinition) {
const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition.name;
return this.appliedDirectives.filter(d => d.name == directiveName);
}
hasAppliedDirective(nameOrDefinition) {
return (typeof nameOrDefinition === 'string'
? this.appliedDirectivesOf(nameOrDefinition)
: this.appliedDirectivesOf(nameOrDefinition)).length !== 0;
}
applyDirective(nameOrDef, args, asFirstDirective = false) {
var _a;
let toAdd;
if (typeof nameOrDef === 'string') {
this.checkUpdate();
toAdd = new Directive(nameOrDef, args !== null && args !== void 0 ? args : Object.create(null));
const def = (_a = this.schema().directive(nameOrDef)) !== null && _a !== void 0 ? _a : this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), toAdd);
if (!def) {
throw this.schema().blueprint.onGraphQLJSValidationError(this.schema(), error_1.ERRORS.INVALID_GRAPHQL.err(`Unknown directive "@${nameOrDef}".`));
}
if (Array.isArray(def)) {
throw (0, exports.ErrGraphQLValidationFailed)(def);
}
}
else {
this.checkUpdate(nameOrDef);
toAdd = new Directive(nameOrDef.name, args !== null && args !== void 0 ? args : Object.create(null));
}
Element.prototype['setParent'].call(toAdd, this);
if (this._appliedDirectives) {
if (asFirstDirective) {
this._appliedDirectives.unshift(toAdd);
}
else {
this._appliedDirectives.push(toAdd);
}
}
else {
this._appliedDirectives = [toAdd];
}
DirectiveDefinition.prototype['addReferencer'].call(toAdd.definition, toAdd);
this.onModification();
return toAdd;
}
removeAppliedDirectives() {
if (!this._appliedDirectives) {
return;
}
const applied = this._appliedDirectives.concat();
applied.forEach(d => d.remove());
}
onModification() {
const schema = this.schemaInternal();
if (schema) {
Schema.prototype['onModification'].call(schema);
}
}
isElementBuiltIn() {
return false;
}
removeTypeReferenceInternal(type) {
this.removeTypeReference(type);
}
checkRemoval() {
(0, utils_1.assert)(!this.isElementBuiltIn() || Schema.prototype['canModifyBuiltIn'].call(this.schema()), () => `Cannot modify built-in ${this}`);
}
checkUpdate(addedElement) {
super.checkUpdate();
if (!Schema.prototype['canModifyBuiltIn'].call(this.schema())) {
let thisElement = this;
while (thisElement && thisElement instanceof SchemaElement) {
(0, utils_1.assert)(!thisElement.isElementBuiltIn(), () => `Cannot modify built-in (or part of built-in) ${this}`);
thisElement = thisElement.parent;
}
}
if (addedElement && addedElement.isAttached()) {
const thatSchema = addedElement.schema();
(0, utils_1.assert)(!thatSchema || thatSchema === this.schema(), () => `Cannot add element ${addedElement} to ${this} as it is attached to another schema`);
}
}
}
exports.SchemaElement = SchemaElement;
class NamedSchemaElement extends SchemaElement {
constructor(name) {
super();
this._name = name;
}
get name() {
return this._name;
}
}
exports.NamedSchemaElement = NamedSchemaElement;
class BaseNamedType extends NamedSchemaElement {
constructor(name, isBuiltIn = false) {
super(name);
this.isBuiltIn = isBuiltIn;
this.preserveEmptyDefinition = false;
}
addReferencer(referencer) {
var _a;
(_a = this._referencers) !== null && _a !== void 0 ? _a : (this._referencers = new Set());
this._referencers.add(referencer);
}
removeReferencer(referencer) {
var _a;
(_a = this._referencers) === null || _a === void 0 ? void 0 : _a.delete(referencer);
}
get coordinate() {
return this.name;
}
*allChildElements() {
}
extensions() {
var _a;
return (_a = this._extensions) !== null && _a !== void 0 ? _a : [];
}
hasExtension(extension) {
var _a, _b;
return (_b = (_a = this._extensions) === null || _a === void 0 ? void 0 : _a.includes(extension)) !== null && _b !== void 0 ? _b : false;
}
newExtension() {
return this.addExtension(new Extension());
}
addExtension(extension) {
this.checkUpdate();
if (this.hasExtension(extension)) {
return extension;
}
(0, utils_1.assert)(!extension.extendedElement, () => `Cannot add extension to type ${this}: it is already added to another type`);
if (this._extensions) {
this._extensions.push(extension);
}
else {
this._extensions = [extension];
}
Extension.prototype['setExtendedElement'].call(extension, this);
this.onModification();
return extension;
}
removeExtensions() {
if (!this._extensions) {
return;
}
this._extensions = undefined;
for (const directive of this.appliedDirectives) {
directive.removeOfExtension();
}
this.removeInnerElementsExtensions();
}
isIntrospectionType() {
return (0, introspection_1.isIntrospectionName)(this.name);
}
hasExtensionElements() {
return !!this._extensions;
}
hasNonExtensionElements() {
return this.preserveEmptyDefinition
|| this.appliedDirectives.some(d => d.ofExtension() === undefined)
|| this.hasNonExtensionInnerElements();
}
isElementBuiltIn() {
return this.isBuiltIn;
}
rename(newName) {
this.checkUpdate();
const oldName = this._name;
this._name = newName;
Schema.prototype['renameTypeInternal'].call(this._parent, oldName, newName);
this.onModification();
}
remove() {
var _a;
if (!this._parent) {
return [];
}
this.checkRemoval();
this.onModification();
this.sourceAST = undefined;
this.removeAppliedDirectives();
this.removeInnerElements();
const toReturn = [];
(_a = this._referencers) === null || _a === void 0 ? void 0 : _a.forEach(r => {
SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
toReturn.push(r);
});
this._referencers = undefined;
Schema.prototype['removeTypeInternal'].call(this._parent, this);
this._parent = undefined;
return toReturn;
}
removeRecursive() {
this.remove().forEach(ref => this.removeReferenceRecursive(ref));
}
referencers() {
var _a;
return (_a = this._referencers) !== null && _a !== void 0 ? _a : EMPTY_SET;
}
isReferenced() {
return !!this._referencers;
}
toString() {
return this.name;
}
}
class NamedSchemaElementWithType extends NamedSchemaElement {
get type() {
return this._type;
}
set type(type) {
if (type) {
this.checkUpdate(type);
}
else {
this.checkRemoval();
}
if (this._type) {
removeReferenceToType(this, this._type);
}
this._type = type;
if (type) {
addReferenceToType(this, type);
}
}
removeTypeReference(type) {
(0, utils_1.assert)(this._type && baseType(this._type) === type, () => `Cannot remove reference to type ${type} on ${this} as its type is ${this._type}`);
this._type = undefined;
}
}
exports.NamedSchemaElementWithType = NamedSchemaElementWithType;
class BaseExtensionMember extends Element {
ofExtension() {
return this._extension;
}
removeOfExtension() {
this._extension = undefined;
}
setOfExtension(extension) {
var _a;
this.checkUpdate();
(0, utils_1.assert)(!extension || ((_a = this._parent) === null || _a === void 0 ? void 0 : _a.hasExtension(extension)), () => `Cannot set object as part of the provided extension: it is not an extension of parent ${this.parent}`);
this._extension = extension;
}
remove() {
this.removeInner();
Schema.prototype['onModification'].call(this.schema());
this._extension = undefined;
this._parent = undefined;
}
}
class SchemaBlueprint {
onMissingDirectiveDefinition(_schema, _directive) {
return undefined;
}
onDirectiveDefinitionAndSchemaParsed(_) {
return [];
}
ignoreParsedField(_type, _fieldName) {
return false;
}
onConstructed(_) {
}
onAddedCoreFeature(_schema, _feature) {
}
onInvalidation(_) {
}
onValidation(_schema) {
return [];
}
validationRules() {
return specifiedRules_1.specifiedSDLRules;
}
onGraphQLJSValidationError(schema, error) {
var _a;
const matcher = /^Unknown directive "@(?<directive>[_A-Za-z][_0-9A-Za-z]*)"\.$/.exec(error.message);
const name = (_a = matcher === null || matcher === void 0 ? void 0 : matcher.groups) === null || _a === void 0 ? void 0 : _a.directive;
if (!name) {
return error;
}
const allDefinedDirectiveNames = schema.allDirectives().map((d) => d.name);
const suggestions = (0, suggestions_1.suggestionList)(name, allDefinedDirectiveNames);
if (suggestions.length === 0) {
return this.onUnknownDirectiveValidationError(schema, name, error);
}
else {
return (0, error_1.withModifiedErrorMessage)(error, `${error.message}${(0, suggestions_1.didYouMean)(suggestions.map((s) => '@' + s))}`);
}
}
onUnknownDirectiveValidationError(_schema, _unknownDirectiveName, error) {
return error;
}
applyDirectivesAfterParsing() {
return false;
}
}
exports.SchemaBlueprint = SchemaBlueprint;
exports.defaultSchemaBlueprint = new SchemaBlueprint();
class CoreFeature {
constructor(url, nameInSchema, directive, imports, purpose) {
this.url = url;
this.nameInSchema = nameInSchema;
this.directive = directive;
this.imports = imports;
this.purpose = purpose;
}
isFeatureDefinition(element) {
const importName = element.kind === 'DirectiveDefinition'
? '@' + element.name
: element.name;
return element.name.startsWith(this.nameInSchema + '__')
|| (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
|| !!this.imports.find((i) => { var _a; return importName === ((_a = i.as) !== null && _a !== void 0 ? _a : i.name); });
}
directiveNameInSchema(name) {
return CoreFeature.directiveNameInSchemaForCoreArguments(this.url, this.nameInSchema, this.imports, name);
}
static directiveNameInSchemaForCoreArguments(specUrl, specNameInSchema, imports, directiveNameInSpec) {
var _a, _b;
const elementImport = imports.find((i) => i.name.charAt(0) === '@' && i.name.slice(1) === directiveNameInSpec);
return elementImport
? ((_b = (_a = elementImport.as) === null || _a === void 0 ? void 0 : _a.slice(1)) !== null && _b !== void 0 ? _b : directiveNameInSpec)
: (directiveNameInSpec === specUrl.name
? specNameInSchema
: specNameInSchema + '__' + directiveNameInSpec);
}
typeNameInSchema(name) {
var _a;
const elementImport = this.imports.find((i) => i.name === name);
return elementImport ? ((_a = elementImport.as) !== null && _a !== void 0 ? _a : name) : this.nameInSchema + '__' + name;
}
minimumFederationVersion() {
var _a;
return (_a = (0, knownCoreFeatures_1.coreFeatureDefinitionIfKnown)(this.url)) === null || _a === void 0 ? void 0 : _a.minimumFederationVersion;
}
}
exports.CoreFeature = CoreFeature;
class CoreFeatures {
constructor(coreItself) {
this.coreItself = coreItself;
this.byAlias = new Map();
this.byIdentity = new Map();
this.byImportName = new Map();
this.conflictsByAlias = new utils_1.SetMultiMap();
this.add(coreItself);
const coreDef = (0, coreSpec_1.findCoreSpecVersion)(coreItself.url);
if (!coreDef) {
throw error_1.ERRORS.UNKNOWN_LINK_VERSION.err(`Schema uses unknown version ${coreItself.url.version} of the ${coreItself.url.name} spec`);
}
this.coreDefinition = coreDef;
}
getByIdentity(identity) {
var _a;
return (_a = this.byIdentity.get(identity)) === null || _a === void 0 ? void 0 : _a[0];
}
allFeatures() {
return [...this.byIdentity.values()].map(([feature]) => feature);
}
removeFeature(featureIdentity) {
const entry = this.byIdentity.get(featureIdentity);
if (entry) {
const [feature] = entry;
this.byIdentity.delete(featureIdentity);
const alias = feature.nameInSchema;
this.byAlias.delete(alias);
for (const { name: importInSpec, as } of feature.imports) {
const importInSchema = as !== null && as !== void 0 ? as : importInSpec;
const isDirective = importInSpec.charAt(0) === "@";
const nameInSchema = isDirective
? importInSchema.slice(1)
: importInSchema;
this.byImportName.delete(importInSchema);
const split = CoreFeatures.splitPrefixedName(nameInSchema);
if (!split) {
continue;
}
const [splitAlias] = split;
if (splitAlias === alias) {
continue;
}
let conflicts = this.conflictsByAlias.get(importInSchema);
if (!conflicts) {
continue;
}
conflicts.delete(importInSchema);
if (conflicts.size) {
continue;
}
this.conflictsByAlias.delete(importInSchema);
}
}
}
maybeAddFeature(directive) {
var _a, _b;
if (((_a = directive.definition) === null || _a === void 0 ? void 0 : _a.name) !== this.coreItself.nameInSchema) {
return undefined;
}
const typedDirective = directive;
const args = typedDirective.arguments();
const url = this.coreDefinition.extractFeatureUrl(args);
const imports = (0, coreSpec_1.extractCoreFeatureImports)(url, typedDirective);
const feature = new CoreFeature(url, (_b = args.as) !== null && _b !== void 0 ? _b : url.name, directive, imports, args.for);
this.add(feature);
directive.schema().blueprint.onAddedCoreFeature(directive.schema(), feature);
return feature;
}
add(feature) {
var _a, _b;
const identity = feature.url.identity;
if (this.byIdentity.has(identity)) {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot link feature "${identity}" since it has already been linked in the schema.`);
}
const alias = feature.nameInSchema;
if (!(identity === tagSpec_1.tagIdentity &&
alias === 'federation__tag' &&
feature.imports.length === 0) &&
!(identity === inaccessibleSpec_1.inaccessibleIdentity &&
alias === 'federation__inaccessible' &&
feature.imports.length === 0)) {
if (alias.indexOf('__') !== -1) {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot link feature "${identity}" as "${alias}" since it contains "__". Please rename to a compliant name via "as".`);
}
}
if (alias.charAt(alias.length - 1) === '_') {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot link feature "${identity}" as "${alias}" since it ends in "_". Please rename to a compliant name via "as".`);
}
if (!aliasRegexp.test(alias)) {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot link feature "${identity}" as "${alias}" since it is not a valid GraphQL name. Please rename to a compliant name via "as".`);
}
const conflicts = this.conflictsByAlias.get(alias);
if (conflicts) {
const importInSchema = (_b = (_a = conflicts === null || conflicts === void 0 ? void 0 : conflicts.values()) === null || _a === void 0 ? void 0 : _a.next()) === null || _b === void 0 ? void 0 : _b.value;
(0, utils_1.assert)(importInSchema !== undefined, `Unexpectedly empty conflicts set`);
const entry = this.byImportName.get(importInSchema);
(0, utils_1.assert)(entry, `Unexpectedly cannot find feature for import`);
const [conflictFeature, importInSpec] = entry;
const conflictIdentity = conflictFeature.url.identity;
this.checkTagInaccessibleConflict(conflictIdentity, identity);
const importInErrorMessage = importInSchema !== importInSpec
? `"${importInSpec}" as "${importInSchema}"`
: `"${importInSpec}"`;
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${conflictIdentity}" since it can be confused with a namespaced name from another linked feature "${identity}". Please rename the import or feature to avoid conflicts via "as".`);
}
const importInSchema = "@" + alias;
const entry = this.byImportName.get(importInSchema);
if (entry) {
const [conflictFeature, importInSpec] = entry;
const conflictIdentity = conflictFeature.url.identity;
this.checkTagInaccessibleConflict(conflictIdentity, identity);
const importInErrorMessage = importInSchema !== importInSpec
? `"${importInSpec}" as "${importInSchema}"`
: `"${importInSpec}"`;
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${conflictIdentity}" since it can be confused with a namespaced name from another linked feature "${identity}". Please rename the import or feature to avoid conflicts via "as".`);
}
const existingFeature = this.byAlias.get(alias);
if (existingFeature !== undefined) {
const existingIdentity = existingFeature.url.identity;
this.checkTagInaccessibleConflict(existingIdentity, identity);
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot link feature ${identity} as "${alias}" since another feature "${existingIdentity}" already uses that alias. Please rename the feature to avoid conflicts via "as".`);
}
const importsMap = new Map();
for (const { name: importInSpec, as } of feature.imports) {
const importInSchema = as !== null && as !== void 0 ? as : importInSpec;
const importInErrorMessage = importInSchema !== importInSpec
? `"${importInSpec}" as "${importInSchema}"`
: `"${importInSpec}"`;
const isDirective = importInSpec.charAt(0) === "@";
const nameInSpec = isDirective
? importInSpec.slice(1)
: importInSpec;
const nameInSchema = isDirective
? importInSchema.slice(1)
: importInSchema;
const split = CoreFeatures.splitPrefixedName(nameInSchema);
if (split) {
const [splitAlias, splitNameInSpec] = split;
if (splitAlias === alias) {
if (splitNameInSpec !== nameInSpec) {
const splitImportInSpec = isDirective
? "@" + splitNameInSpec
: splitNameInSpec;
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${identity}" since it can be confused with the namespaced name for "${splitImportInSpec}". Please rename the import to avoid conflicts via "as".`);
}
}
else {
const conflictFeature = this.byAlias.get(splitAlias);
if (conflictFeature) {
const conflictIdentity = conflictFeature.url.identity;
this.checkTagInaccessibleConflict(conflictIdentity, identity);
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${identity}" since it can be confused with a namespaced name from another linked feature "${conflictIdentity}". Please rename the import or feature to avoid conflicts via "as".`);
}
else {
this.conflictsByAlias.add(splitAlias, importInSchema);
}
}
}
if (isDirective) {
if (nameInSchema === alias) {
if (nameInSpec !== feature.url.name) {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${identity}" since it can be confused with the namespaced name for "@${feature.url.name}". Please rename the import to avoid conflicts via "as".`);
}
}
else {
const conflictFeature = this.byAlias.get(nameInSchema);
if (conflictFeature) {
const conflictIdentity = conflictFeature.url.identity;
this.checkTagInaccessibleConflict(conflictIdentity, identity);
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${identity}" since it can be confused with a namespaced name from another linked feature "${conflictIdentity}". Please rename the import or feature to avoid conflicts via "as".`);
}
}
}
const existingImportInSchema = importsMap.get(importInSpec);
if (existingImportInSchema === undefined) {
importsMap.set(importInSpec, importInSchema);
}
else {
if (existingImportInSchema !== importInSchema) {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${identity}" since it was previously imported as "${existingImportInSchema}". Please remove one of these imports.`);
}
}
const entry = this.byImportName.get(importInSchema);
if (entry === undefined) {
this.byImportName.set(importInSchema, [feature, importInSpec]);
}
else {
const [existingFeature, existingImportInSpec] = entry;
const existingIdentity = existingFeature.url.identity;
if (existingIdentity !== identity) {
this.checkTagInaccessibleConflict(existingIdentity, identity);
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${identity}" since it was previously imported from feature "${existingIdentity}". Please rename the import to avoid conflicts via "as".`);
}
if (existingImportInSpec !== importInSpec) {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${identity}" since it was previously imported for "${existingImportInSpec}". Please rename the import to avoid conflicts via "as".`);
}
}
}
this.byAlias.set(alias, feature);
this.byIdentity.set(identity, [feature, importsMap]);
}
isAliasValid(alias, identity, importConflictsByIdentity) {
if (alias.indexOf('__') !== -1) {
return false;
}
if (alias.charAt(alias.length - 1) === '_') {
return false;
}
if (!nameRegexp.test(alias)) {
return false;
}
for (const [otherIdentity, importConflicts] of importConflictsByIdentity.entries()) {
if (identity === otherIdentity) {
if (importConflicts.self.has(alias)) {
return false;
}
}
else {
if (importConflicts.other.has(alias)) {
return false;
}
}
}
if (this.byAlias.has(alias)) {
return false;
}
return true;
}
static computeAliasConflicts(specAliases, elementNames) {
const trieNames = elementNames;
const importConflictsByIdentity = new Map();
for (const { url, alias, imports } of specAliases) {
trieNames.add(alias);
const self = new Set();
const other = new Set();
for (const { name: importInSpec, as } of imports) {
const importInSchema = as !== null && as !== void 0 ? as : importInSpec;
const isDirective = importInSpec.charAt(0) === "@";
const nameInSpec = isDirective
? importInSpec.slice(1)
: importInSpec;
const nameInSchema = isDirective
? importInSchema.slice(1)
: importInSchema;
trieNames.add(nameInSchema);
const split = CoreFeatures.splitPrefixedName(nameInSchema);
if (split) {
const [splitAlias, splitNameInSpec] = split;
if (splitNameInSpec !== nameInSpec) {
self.add(splitAlias);
}
other.add(splitAlias);
}
if (isDirective) {
if (nameInSpec !== url.name) {
self.add(nameInSchema);
}
other.add(nameInSchema);
}
}
importConflictsByIdentity.set(url.identity, { self, other });
}
let prefix = null;
let index = 0;
let computeUniqueAlias = (specName) => {
if (prefix === null) {
const aliasStart = [
'_',
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
].join('');
const aliasContinue = [
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789',
].join('');
const root = { children: new Map(), parent: null, char: '' };
for (const name of trieNames) {
let node = root;
for (const char of name) {
let child = node.children.get(char);
if (!child) {
child = { children: new Map(), parent: node, char };
node.children.set(char, child);
}
node = child;
}
}
const queue = [root];
let head = 0;
while (prefix === null) {
const possibleChars = head === 0 ? aliasStart : aliasContinue;
const node = queue[head++];
for (const char of possibleChars) {
const child = node.children.get(char);
if (child) {
queue.push(child);
}
else {
const chars = [char];
for (let cur = node; cur === null || cur === void 0 ? void 0 : cur.parent; cur = cur.parent) {
chars.push(cur.char);
}
prefix = chars.reverse().join('');
break;
}
}
}
}
const suffix = specName.replace(/[^a-zA-Z]/g, '');
return `${prefix}${index++}${suffix}`;
};
return {
importConflictsByIdentity,
computeUniqueAlias,
};
}
checkTagInaccessibleConflict(identity1, identity2) {
const federationIdentity = 'https://specs.apollo.dev/federation';
const identities = new Set([identity1, identity2]);
if (!identities.has(federationIdentity)) {
return;
}
const [directive, identity] = identities.has(tagSpec_1.tagIdentity)
? ['tag', tagSpec_1.tagIdentity]
: identities.has(inaccessibleSpec_1.inaccessibleIdentity)
? ['inaccessible', inaccessibleSpec_1.inaccessibleIdentity]
: [undefined, undefined];
if (directive && identity) {
throw error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Please import "@${directive}" from the feature "${federationIdentity}" instead of using "${identity}" to avoid potential unexpected behavior in the future.`);
}
}
sourceFeature(element) {
const isDirective = element instanceof DirectiveDefinition || element instanceof Directive;
const importName = isDirective ? '@' + element.name : element.name;
const entry = this.byImportName.get(importName);
if (entry) {
const [feature, importInSpec] = entry;
return {
feature,
nameInFeature: isDirective ? importInSpec.slice(1) : importInSpec,
isImported: true,
};
}
const defaultEntry = this.sourceDefaultName(isDirective, element.name);
if (!defaultEntry) {
return undefined;
}
const [feature, nameInSpec] = defaultEntry;
const importInSpec = isDirective ? '@' + nameInSpec : nameInSpec;
return {
feature,
nameInFeature: this.getImportName(feature, importInSpec) === undefined
? nameInSpec
: null,
isImported: false,
};
}
validateNoShadowingImports(schema) {
const errors = [];
for (const element of [...schema.allTypes(), ...schema.allDirectives()]) {
const shadowingImport = this.getShadowingImport(element);
if (!shadowingImport) {
continue;
}
const isUsed = element instanceof DirectiveDefinition
? element.applications().size !== 0
: this.getReferencingRootElements(element)
.some((referencer) => {
return referencer.kind === 'SchemaDefinition'
? true
: !this.getShadowingImport(referencer);
});
if (!isUsed) {
continue;
}
const { feature, importInSpec, importInSchema } = shadowingImport;
const importInErrorMessage = importInSchema !== importInSpec
? `"${importInSpec}" as "${importInSchema}"`
: `"${importInSpec}"`;
errors.push(error_1.ERRORS.INVALID_LINK_DIRECTIVE_USAGE.err(`Cannot import ${importInErrorMessage} from feature "${feature.url.identity}" since there's a used definition for the namespaced name "${element.coordinate}". Please switch usages of the namespaced name to the import name and remove the definition.`));
}
return errors;
}
getShadowingImport(element) {
const isDirective = element instanceof DirectiveDefinition || element instanceof Directive;
const defaultEntry = this.sourceDefaultName(isDirective, element.name);
if (!defaultEntry) {
return undefined;
}
const importName = isDirective ? '@' + element.name : element.name;
const [feature, nameInSpec] = defaultEntry;
const importInSpec = isDirective ? '@' + nameInSpec : nameInSpec;
const importInSchema = this.getImportName(feature, importInSpec);
return importInSchema !== undefined && importInSchema !== importName
? {
feature,
importInSpec,
importInSchema,
}
: undefined;
}
getReferencingRootElements(element) {
const referencers = [];
for (const referencer of element.referencers()) {
switch (referencer.kind) {
case 'ObjectType':
referencers.push(referencer);
break;
case 'InterfaceType':
referencers.push(referencer);
break;
case 'UnionType':
referencers.push(referencer);
break;
case 'SchemaDefinition':
referencers.push(referencer);
break;
case 'FieldDefinition':
referencers.push(referencer.parent);
break;
case 'InputFieldDefinition':
referencers.push(referencer.parent);
break;
case 'ArgumentDefinition':
const parent = referencer.parent;
switch (parent.kind) {
case 'DirectiveDefi