@theguild/federation-composition
Version:
Open Source Composition library for Apollo Federation
303 lines (302 loc) • 13.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSimpleValidationContext = createSimpleValidationContext;
exports.createSubgraphValidationContext = createSubgraphValidationContext;
const graphql_1 = require("graphql");
const federation_js_1 = require("../../specifications/federation.js");
const link_js_1 = require("../../specifications/link.js");
const state_js_1 = require("../../utils/state.js");
const version_js_1 = require("../../utils/version.js");
const state_js_2 = require("../state.js");
const linkSpec = (0, graphql_1.parse)(link_js_1.sdl);
const linkSpecDirectives = linkSpec.definitions.filter((def) => def.kind === graphql_1.Kind.DIRECTIVE_DEFINITION);
const linkSpecTypes = linkSpec.definitions.filter(graphql_1.isTypeDefinitionNode);
function createSimpleValidationContext(typeDefs, typeNodeInfo) {
let reportedErrors = [];
const directiveDefinitionMap = new Map();
const typeDefinitionMap = new Map();
for (const definition of typeDefs.definitions) {
if (definition.kind === graphql_1.Kind.DIRECTIVE_DEFINITION) {
directiveDefinitionMap.set(definition.name.value, definition);
}
else if ('name' in definition && definition.name && definition.name.kind === graphql_1.Kind.NAME) {
typeDefinitionMap.set(definition.name.value, {
name: definition.name,
kind: definition.kind,
});
}
}
return {
getDocument() {
return typeDefs;
},
getKnownDirectiveDefinition(name) {
return directiveDefinitionMap.get(name);
},
getKnownTypeDefinition(name) {
return typeDefinitionMap.get(name);
},
getSchemaCoordinate(ancestors) {
let coordinate = '';
for (let i = 0; i < ancestors.length; i++) {
const ancestor = ancestors[i];
if ('kind' in ancestor && ancestor.kind !== graphql_1.Kind.DOCUMENT) {
const name = ancestor.kind === graphql_1.Kind.SCHEMA_DEFINITION || ancestor.kind === graphql_1.Kind.SCHEMA_EXTENSION
? 'schema'
: 'name' in ancestor && ancestor.name
? ancestor.name.value
: '';
if (coordinate.length > 0) {
coordinate = coordinate + '.' + name;
}
else {
coordinate = name;
}
}
}
return coordinate;
},
reportError(error) {
reportedErrors.push(error);
},
collectReportedErrors() {
const errors = reportedErrors;
reportedErrors = [];
return errors;
},
};
}
function createSubgraphValidationContext(subgraph, federation, typeNodeInfo, stateBuilder) {
const { version, imports } = federation;
const availableSpec = (0, federation_js_1.createSpecSchema)(version, imports);
const knownSpec = (0, federation_js_1.createSpecSchema)(version);
const knownSubgraphEntities = new Map();
for (const def of subgraph.typeDefs.definitions.filter(def => def.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION ||
def.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION ||
def.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION ||
def.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION)) {
const found = knownSubgraphEntities.get(def.name.value);
if (!found) {
knownSubgraphEntities.set(def.name.value, { ...def });
continue;
}
found.fields = (found.fields ?? []).concat(def.fields ?? []);
found.interfaces = (found.interfaces ?? []).concat(def.interfaces ?? []);
found.directives = (found.directives ?? []).concat(def.directives ?? []);
if (def.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && found.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
found.kind = graphql_1.Kind.OBJECT_TYPE_DEFINITION;
}
else if (def.kind === graphql_1.Kind.INTERFACE_TYPE_DEFINITION &&
found.kind === graphql_1.Kind.INTERFACE_TYPE_EXTENSION) {
found.kind = graphql_1.Kind.INTERFACE_TYPE_DEFINITION;
}
}
const knownSubgraphDirectiveDefinitions = new Map(subgraph.typeDefs.definitions.filter(def => def.kind === graphql_1.Kind.DIRECTIVE_DEFINITION).map(def => [def.name.value, def]));
const leafTypeNames = new Set(graphql_1.specifiedScalarTypes.map(type => type.name));
for (const def of subgraph.typeDefs.definitions) {
if (def.kind === graphql_1.Kind.SCALAR_TYPE_DEFINITION ||
def.kind === graphql_1.Kind.SCALAR_TYPE_EXTENSION ||
def.kind === graphql_1.Kind.ENUM_TYPE_DEFINITION ||
def.kind === graphql_1.Kind.ENUM_TYPE_EXTENSION) {
leafTypeNames.add(def.name.value);
}
}
let reportedErrors = [];
const markedAsExternal = new Set();
const markedAsUsed = new Set();
const markedAsKeyField = new Set();
const overwrittenFederationDefinitionNames = new Set();
const directiveAlternativeNamesMap = new Map();
for (const specDirective of availableSpec.directives) {
const isFederationPrefixed = specDirective.name.value.startsWith('federation__');
if (isFederationPrefixed) {
const normalizedName = specDirective.name.value.replace('federation__', '');
const setOfNames = directiveAlternativeNamesMap.get(normalizedName);
if (!setOfNames) {
directiveAlternativeNamesMap.set(normalizedName, new Set([specDirective.name.value]));
}
}
else {
const { alias } = imports.find(i => i.name.replace(/^@/, '') === specDirective.name.value) ?? {
alias: undefined,
};
let setOfNames = directiveAlternativeNamesMap.get(specDirective.name.value);
if (!setOfNames) {
directiveAlternativeNamesMap.set(specDirective.name.value, new Set());
setOfNames = directiveAlternativeNamesMap.get(specDirective.name.value);
}
setOfNames.add(alias ? alias.replace(/^@/, '') : specDirective.name.value);
setOfNames.add(`federation__${specDirective.name.value}`);
}
}
const typeAlternativeNamesMap = new Map();
for (const specType of availableSpec.types) {
const isFederationPrefixed = specType.name.value.startsWith('federation__');
if (isFederationPrefixed) {
const normalizedName = specType.name.value.replace('federation__', '');
const setOfNames = typeAlternativeNamesMap.get(normalizedName);
if (!setOfNames) {
typeAlternativeNamesMap.set(normalizedName, new Set([specType.name.value]));
}
}
else {
const { alias } = imports.find(i => i.name === specType.name.value) ?? {
alias: undefined,
};
let setOfNames = typeAlternativeNamesMap.get(specType.name.value);
if (!setOfNames) {
typeAlternativeNamesMap.set(specType.name.value, new Set());
setOfNames = typeAlternativeNamesMap.get(specType.name.value);
}
setOfNames.add(alias ? alias : specType.name.value);
setOfNames.add(`federation__${specType.name.value}`);
}
}
const importedTypesSet = new Set(availableSpec.types.map(t => t.name.value));
if (importedTypesSet.size) {
subgraph.typeDefs.definitions.forEach(def => {
if ('name' in def && def.name && importedTypesSet.has(def.name.value)) {
overwrittenFederationDefinitionNames.add(def.name.value);
}
});
}
return {
stateBuilder,
federationImports: imports,
isLinkSpecDirective(name) {
return linkSpecDirectives.some(d => d.name.value === name);
},
isLinkSpecType(name) {
return linkSpecTypes.some(t => t.name.value === name);
},
isAvailableFederationType(name) {
const alternativeNames = typeAlternativeNamesMap.get(name);
if (alternativeNames) {
return alternativeNames.has(name);
}
return false;
},
isAvailableFederationDirective(specDirectiveName, directiveNode) {
const alternativeNames = directiveAlternativeNamesMap.get(specDirectiveName);
if (alternativeNames) {
return alternativeNames.has(typeof directiveNode.name === 'string' ? directiveNode.name : directiveNode.name.value);
}
return false;
},
satisfiesVersionRange(range) {
return (0, version_js_1.satisfiesVersionRange)(version, range);
},
getKnownFederationDirectives() {
return knownSpec.directives;
},
getAvailableFederationDirectives() {
return availableSpec.directives;
},
isLeafType(typeName) {
return leafTypeNames.has(typeName);
},
getSubgraphObjectOrInterfaceTypes() {
return knownSubgraphEntities;
},
getSubgraphDirectiveDefinitions() {
return knownSubgraphDirectiveDefinitions;
},
getAvailableFederationTypeAndDirectiveDefinitions() {
return [].concat(availableSpec.directives.map(d => {
const alias = imports.find(i => i.name.replace(/^@/, '') === d.name.value)?.alias;
if (alias) {
d.name.value = alias.replace(/^@/, '');
}
return d;
}), availableSpec.types.map(t => {
const alias = imports.find(i => i.name === t.name.value)?.alias;
if (alias) {
t.name.value = alias;
}
return t;
}));
},
typeNodeInfo,
getDocument() {
return subgraph.typeDefs;
},
getSubgraphName() {
return subgraph.name;
},
getSubgraphId() {
return subgraph.id;
},
markAsExternal(coordinate) {
markedAsExternal.add(coordinate);
},
markAsUsed(reason, kind, typeName, fieldName) {
if (!fieldName.startsWith('__') && !typeName.startsWith('__') && reason === 'fields') {
switch (kind) {
case graphql_1.Kind.OBJECT_TYPE_DEFINITION:
case graphql_1.Kind.OBJECT_TYPE_EXTENSION: {
stateBuilder.objectType.field.setUsed(typeName, fieldName);
break;
}
case graphql_1.Kind.INTERFACE_TYPE_DEFINITION:
case graphql_1.Kind.INTERFACE_TYPE_EXTENSION: {
stateBuilder.interfaceType.field.setUsed(typeName, fieldName);
break;
}
}
}
markedAsUsed.add(`${typeName}.${fieldName}`);
},
markAsKeyField(coordinate) {
markedAsKeyField.add(coordinate);
},
markAsFederationDefinitionReplacement(name) {
overwrittenFederationDefinitionNames.add(name);
},
collectFederationDefinitionReplacements() {
return overwrittenFederationDefinitionNames;
},
collectUnusedExternal() {
if (version === 'v1.0') {
return Array.from(markedAsExternal).filter(c => !markedAsUsed.has(c) && markedAsKeyField.has(c));
}
const unused = Array.from(markedAsExternal).filter(c => !markedAsUsed.has(c));
return unused.filter(coordinate => {
const [typeName, fieldName] = coordinate.split('.');
const typeDef = stateBuilder.state.types.get(typeName);
if (!typeDef) {
return true;
}
if (typeDef.kind === state_js_2.TypeKind.OBJECT && !stateBuilder.isInterfaceObject(typeName)) {
const fieldDef = typeDef.fields.get(fieldName);
if (fieldDef) {
const outputTypeName = (0, state_js_1.stripTypeModifiers)(fieldDef.type);
const outputType = stateBuilder.state.types.get(outputTypeName);
if (outputType?.kind === state_js_2.TypeKind.OBJECT && outputType.shareable) {
return false;
}
}
for (const interfaceName of typeDef.interfaces) {
const iDef = stateBuilder.state.types.get(interfaceName);
if (!iDef) {
continue;
}
if (iDef.kind === state_js_2.TypeKind.INTERFACE && iDef.fields.has(fieldName)) {
if (iDef.fields.has(fieldName)) {
return false;
}
}
}
}
return true;
});
},
reportError(error) {
reportedErrors.push(error);
},
collectReportedErrors() {
const errors = reportedErrors;
reportedErrors = [];
return errors;
},
};
}
;