UNPKG

@theguild/federation-composition

Version:
303 lines (302 loc) 13.6 kB
"use strict"; 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; }, }; }