UNPKG

@theguild/federation-composition

Version:

Open Source Composition library for Apollo Federation

925 lines (924 loc) 44.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SatisfiabilityRule = void 0; const graphql_1 = require("graphql"); const helpers_js_1 = require("../../../subgraph/helpers.js"); const state_js_1 = require("../../../subgraph/state.js"); const dependency_graph_js_1 = require("../../../utils/dependency-graph.js"); const helpers_js_2 = require("../../../utils/helpers.js"); const state_js_2 = require("../../../utils/state.js"); function canGraphMoveToGraphByEntity(supergraphState, entityName, sourceGraphId, targetGraphId) { const objectTypeState = supergraphState.objectTypes.get(entityName); if (!objectTypeState) { throw new Error(`Type "${entityName}" not found in supergraph state`); } const objectTypeStateInSourceGraph = objectTypeState.byGraph.get(sourceGraphId); const objectTypeStateInTargetGraph = objectTypeState.byGraph.get(targetGraphId); const sourceGraphKeys = objectTypeStateInSourceGraph?.keys || []; const targetGraphKeys = objectTypeStateInTargetGraph?.keys || []; if (sourceGraphKeys.length === 0 && targetGraphKeys.length === 0) { return false; } if (sourceGraphKeys.length === 0) { return targetGraphKeys .filter(k => k.resolvable === true) .some(k => { const targetKeyFields = resolveFieldsFromFieldSet(k.fields, objectTypeState.name, targetGraphId, supergraphState); return Array.from(targetKeyFields.coordinates).every(fieldPath => { const [typeName, fieldName] = fieldPath.split('.'); if (typeName === objectTypeState.name) { const fieldState = objectTypeState.fields.get(fieldName); if (!fieldState) { throw new Error(`Field "${fieldPath}" not found in object type "${typeName}"`); } const fieldStateByGraph = fieldState.byGraph.get(targetGraphId); if (!fieldStateByGraph) { throw new Error(`Field "${fieldPath}" not found in object type "${typeName}" in graph "${targetGraphId}"`); } return fieldStateByGraph.external === false; } const currentTypeState = supergraphState.objectTypes.get(typeName) ?? supergraphState.interfaceTypes.get(typeName); if (!currentTypeState) { throw new Error(`Type "${typeName}" not found`); } const fieldState = currentTypeState.fields.get(fieldName); if (!fieldState) { throw new Error(`Field "${fieldPath}" not found in object type "${typeName}"`); } const fieldStateByGraph = fieldState.byGraph.get(targetGraphId); if (!fieldStateByGraph) { throw new Error(`Field "${fieldPath}" not found in object type "${typeName}" in graph "${targetGraphId}"`); } if ('external' in fieldStateByGraph) { return fieldStateByGraph.external === false; } return true; }); }); } return sourceGraphKeys.some(sourceGraphKey => { const sourceKeyFields = resolveFieldsFromFieldSet(sourceGraphKey.fields, objectTypeState.name, sourceGraphId, supergraphState); const hasNonKeyFields = Array.from(objectTypeState.fields).some(([_, fState]) => { const f = fState.byGraph.get(sourceGraphId); if (f && f.usedAsKey === false) { return true; } return false; }); if (Array.from(sourceKeyFields.coordinates).every(fieldPath => { const [typeName, fieldName] = fieldPath.split('.'); const objectTypeState = supergraphState.objectTypes.get(typeName); if (!objectTypeState) { throw new Error(`Type "${typeName}" not found`); } const fieldState = objectTypeState.fields.get(fieldName); if (!fieldState) { throw new Error(`Field "${fieldPath}" not found in object type "${typeName}"`); } const fieldStateByGraph = fieldState.byGraph.get(sourceGraphId); if (!fieldStateByGraph) { throw new Error(`Field "${fieldPath}" not found in object type "${typeName}" in graph "${sourceGraphId}"`); } return (!hasNonKeyFields && objectTypeState.byGraph.get(sourceGraphId).extension !== true && fieldStateByGraph.external === true && fieldStateByGraph.usedAsKey); })) { return false; } return (targetGraphKeys .filter(k => k.resolvable === true) .some(k => { const targetKeyFields = resolveFieldsFromFieldSet(k.fields, objectTypeState.name, targetGraphId, supergraphState); for (const fieldPath of targetKeyFields.paths) { if (!sourceKeyFields.paths.has(fieldPath)) { return false; } } return true; })); }); } function canGraphResolveFieldDirectly(objectTypeSuperState, fieldSuperState, graphId, supergraphState) { const objectTypeInGraph = objectTypeSuperState.byGraph.get(graphId); if (!objectTypeInGraph) { throw new Error(`Object type "${objectTypeSuperState.name}" not found in graph "${graphId}"`); } const fieldInGraph = fieldSuperState.byGraph.get(graphId); if (!fieldInGraph) { return false; } if ((fieldInGraph.shareable === true || objectTypeSuperState.byGraph.get(graphId).shareable === true) && supergraphState.graphs.get(graphId).version !== 'v1.0') { return true; } if (fieldInGraph.external === true) { if (fieldInGraph.usedAsKey === true) { const graphHasAtLeastOneResolvableField = Array.from(objectTypeSuperState.fields.values()).some(f => { if (f.name === fieldSuperState.name) { return false; } const fInGraph = f.byGraph.get(graphId); if (!fInGraph) { return false; } if (fInGraph.external === true) { return false; } if (fInGraph.inaccessible === true) { return false; } if (typeof fInGraph.override === 'string') { return false; } return true; }); if (graphHasAtLeastOneResolvableField) { return true; } } return false; } return true; } function canGraphResolveField(objectTypeSuperState, fieldSuperState, graphId, supergraphState, movabilityGraph) { const objectTypeInGraph = objectTypeSuperState.byGraph.get(graphId); if (!objectTypeInGraph) { throw new Error(`Object type "${objectTypeSuperState.name}" not found in graph "${graphId}"`); } const fieldInGraph = fieldSuperState.byGraph.get(graphId); if (fieldInGraph && fieldInGraph.external === false && canGraphResolveFieldDirectly(objectTypeSuperState, fieldSuperState, graphId, supergraphState)) { return true; } const graphsWithField = Array.from(fieldSuperState.byGraph).filter(([g, _]) => { if (g === graphId) { return false; } const fieldInGraph = fieldSuperState.byGraph.get(g); if (!fieldInGraph || fieldInGraph.external === true) { return false; } return true; }); const canMoveToGraphWithField = graphsWithField.some(([g, _]) => { return canGraphMoveToGraphBasedOnMovabilityGraph(movabilityGraph, graphId, g); }); return canMoveToGraphWithField; } function canGraphMoveToGraphBasedOnMovabilityGraph(movabilityGraph, sourceId, destinationId, visited = new Set()) { const key = `${sourceId} => ${destinationId}`; if (visited.has(key)) { return false; } else { visited.add(key); } const deps = movabilityGraph.directDependenciesOf(sourceId); if (deps.includes(destinationId)) { return true; } return deps.some(depId => canGraphMoveToGraphBasedOnMovabilityGraph(movabilityGraph, depId, destinationId, visited)); } function findLeafs(movabilityGraph, sourceId, destinationId, leafs, visited = new Set()) { const key = `${sourceId} => ${destinationId}`; if (leafs === undefined) { leafs = new Set(); } const deps = movabilityGraph.directDependenciesOf(sourceId); if (visited.has(key)) { return Array.from(leafs); } else { visited.add(key); } for (const depId of deps) { if (!canGraphMoveToGraphBasedOnMovabilityGraph(movabilityGraph, depId, destinationId)) { leafs.add(depId); findLeafs(movabilityGraph, depId, destinationId, leafs, visited); } } return Array.from(leafs); } function SatisfiabilityRule(context, supergraphState) { const typeDependencies = buildOutputTypesDependencies(supergraphState); let movabilityGraph = new Map(); function getMovabilityGraphForType(typeName) { const existingMovabilityGraph = movabilityGraph.get(typeName); if (existingMovabilityGraph) { return existingMovabilityGraph; } const objectState = supergraphState.objectTypes.get(typeName); if (!objectState) { throw new Error(`State of object type "${typeName}" not found in Supergraph state`); } const graph = new dependency_graph_js_1.DepGraph({ circular: true, }); const graphIds = Array.from(objectState.byGraph.keys()); for (const sourceGraphId of objectState.byGraph.keys()) { graph.addNode(sourceGraphId); } for (const sourceGraphId of objectState.byGraph.keys()) { const otherGraphIds = graphIds.filter(g => g !== sourceGraphId); for (const destGraphId of otherGraphIds) { if (canGraphMoveToGraphByEntity(supergraphState, objectState.name, sourceGraphId, destGraphId)) { graph.addDependency(sourceGraphId, destGraphId); } } } movabilityGraph.set(typeName, graph); return graph; } return { ObjectTypeField(objectState, fieldState) { if (objectState.name === 'Query' || objectState.name === 'Mutation' || objectState.name === 'Subscription') { return; } if (objectState.byGraph.size === 1) { return; } const dependenciesOfObjectType = typeDependencies.dependentsOf(objectState.name); const isReachableByRootType = { query: dependenciesOfObjectType.includes('Query'), mutation: dependenciesOfObjectType.includes('Mutation'), subscription: dependenciesOfObjectType.includes('Subscription'), }; const isReachable = isReachableByRootType.query || isReachableByRootType.mutation || isReachableByRootType.subscription; if (!isReachable) { return; } const objectStateGraphPairs = Array.from(objectState.byGraph); const fieldStateGraphPairs = Array.from(fieldState.byGraph); if (objectStateGraphPairs.some(([_, objectTypeStateInGraph]) => objectTypeStateInGraph.inaccessible === true) || fieldStateGraphPairs.some(([_, fieldStateInGraph]) => fieldStateInGraph.inaccessible === true)) { return; } const isFieldShareableInAllSubgraphs = Array.from(fieldState.byGraph).every(([graphId, fieldStateInGraph]) => { const fieldShareable = fieldStateInGraph.shareable && context.subgraphStates.get(graphId).version !== 'v1.0'; const typeShareable = objectState.byGraph.get(graphId).shareable === true; return fieldShareable || typeShareable; }); if (isFieldShareableInAllSubgraphs) { return; } if (fieldState.byGraph.size === objectState.byGraph.size && fieldStateGraphPairs.every(([_, f]) => f.usedAsKey === true && f.external === false && !f.override)) { return; } const keysInAllGraphs = objectStateGraphPairs.every(([_, o]) => o.keys.length > 0); const uniqueKeyFieldsSet = new Set(objectStateGraphPairs .map(([_, o]) => o.keys.map(k => k.fields)) .flat(1)); if (keysInAllGraphs && uniqueKeyFieldsSet.size === 1) { return; } const aggregatedErrorByRootType = { Query: { query: null, reasons: [], }, Mutation: { query: null, reasons: [], }, Subscription: { query: null, reasons: [], }, }; const currentObjectTypeMovabilityGraph = getMovabilityGraphForType(objectState.name); if (!currentObjectTypeMovabilityGraph) { throw new Error(`Movability graph for object type "${objectState.name}" not found in Supergraph state`); } if (uniqueKeyFieldsSet.size > 0) { for (const graphId of objectState.byGraph.keys()) { const fieldStateInGraph = fieldState.byGraph.get(graphId); if (canGraphResolveField(objectState, fieldState, graphId, supergraphState, currentObjectTypeMovabilityGraph)) { continue; } if (fieldStateInGraph?.external === true) { const objectStateInGraph = objectState.byGraph.get(graphId); if (objectStateInGraph.extension === true) { continue; } if (fieldStateInGraph.required) { continue; } if (fieldStateInGraph.provided) { continue; } } const subgraphState = context.subgraphStates.get(graphId); const schemaDefinitionOfGraph = subgraphState.schema; const rootTypes = [ schemaDefinitionOfGraph.queryType ? [ 'Query', subgraphState.types.get(schemaDefinitionOfGraph.queryType), ] : undefined, schemaDefinitionOfGraph.mutationType ? [ 'Mutation', subgraphState.types.get(schemaDefinitionOfGraph.mutationType), ] : undefined, schemaDefinitionOfGraph.subscriptionType ? [ 'Subscription', subgraphState.types.get(schemaDefinitionOfGraph.subscriptionType), ] : undefined, ].filter(helpers_js_2.isDefined); if (rootTypes.length === 0) { continue; } const otherGraphIds = objectStateGraphPairs .filter(([g, _]) => g !== graphId) .map(([g, _]) => g); const graphsWithField = fieldStateGraphPairs .filter(([g, _]) => canGraphResolveFieldDirectly(objectState, fieldState, g, supergraphState)) .map(([g, _]) => g); const leafs = graphsWithField .map(g => findLeafs(currentObjectTypeMovabilityGraph, graphId, g)) .flat(1); if (leafs.length === 0 && currentObjectTypeMovabilityGraph.directDependenciesOf(graphId).length > 0) { continue; } for (const [normalizedName, rootType] of rootTypes) { const query = printExampleQuery(supergraphState, normalizedName, Array.from(rootType.fields.keys()), objectState.name, fieldState.name, graphId, dependenciesOfObjectType); const reasons = []; const canBeIndirectlyResolved = leafs.length > 0; const cannotMoveToList = (sourceGraphId) => canBeIndirectlyResolved ? graphsWithField .map(gid => { const keys = objectState.byGraph.get(gid).keys.map(k => k.fields); if (keys.length > 0) { return objectState.byGraph .get(gid) .keys.map(k => k.fields) .map(fields => `cannot move to subgraph "${context.graphIdToName(gid)}" using @key(fields: "${fields}") of "${objectState.name}", the key field(s) cannot be resolved from subgraph "${context.graphIdToName(sourceGraphId)}".`); } return `cannot move to subgraph "${context.graphIdToName(gid)}", which has field "${objectState.name}.${fieldState.name}", because type "${objectState.name}" has no @key defined in subgraph "${context.graphIdToName(gid)}".`; }) .flat(1) : otherGraphIds .filter(g => !currentObjectTypeMovabilityGraph.directDependenciesOf(graphId).includes(g)) .map(gid => { const keys = objectState.byGraph.get(gid).keys.map(k => k.fields); if (keys.length > 0) { return objectState.byGraph .get(gid) .keys.map(k => k.fields) .map(fields => `cannot move to subgraph "${context.graphIdToName(gid)}" using @key(fields: "${fields}") of "${objectState.name}", the key field(s) cannot be resolved from subgraph "${context.graphIdToName(sourceGraphId)}".`); } return `cannot move to subgraph "${context.graphIdToName(gid)}", which has field "${objectState.name}.${fieldState.name}", because type "${objectState.name}" has no @key defined in subgraph "${context.graphIdToName(gid)}".`; }) .flat(1); const fromSubgraphs = [graphId].concat(leafs); if (!fieldStateInGraph) { fromSubgraphs.forEach(gid => { reasons.push([ gid, [`cannot find field "${objectState.name}.${fieldState.name}".`].concat(cannotMoveToList(gid)), ]); }); } else if (fieldStateInGraph.external) { fromSubgraphs.forEach(gid => { reasons.push([ gid, [ `field "${objectState.name}.${fieldState.name}" is not resolvable because marked @external.`, ].concat(cannotMoveToList(gid)), ]); }); } else { console.log('can NOT resolve field', fieldState.name, 'in graph', graphId, 'reason: unknown'); } if (!query || reasons.length === 0) { continue; } context.reportError(new graphql_1.GraphQLError([ 'The following supergraph API query:', query, 'cannot be satisfied by the subgraphs because:', ...reasons.map(([gid, reasons]) => { return (`- from subgraph "${context.graphIdToName(gid)}":\n` + reasons.map(r => ` - ${r}`).join('\n')); }), ].join('\n'), { extensions: { code: 'SATISFIABILITY_ERROR', }, })); } } } else { const graphsWithoutField = objectStateGraphPairs.filter(([graphId]) => !fieldState.byGraph.has(graphId)); const graphsWithField = fieldStateGraphPairs.map(([graphId]) => graphId); for (const [graphId] of graphsWithoutField) { const subgraphState = context.subgraphStates.get(graphId); const isShareableWithOtherGraphs = Array.from(subgraphState.types.values()) .filter(t => dependenciesOfObjectType.includes(t.name)) .every(t => { if (t.kind === state_js_1.TypeKind.OBJECT) { if (t.shareable && subgraphState.version !== 'v1.0') { return true; } const fields = Array.from(t.fields.values()); if (fields .filter(f => (0, state_js_2.stripTypeModifiers)(f.type) === objectState.name) .every(f => f.shareable === true && subgraphState.version !== 'v1.0')) { return true; } } return false; }); if (isShareableWithOtherGraphs) { continue; } const graphHasAtLeastOneResolvableField = Array.from(objectState.fields.values()).some(f => { const fieldInGraph = f.byGraph.get(graphId); if (!fieldInGraph) { return false; } if (fieldInGraph.inaccessible === true) { return false; } return true; }); if (!graphHasAtLeastOneResolvableField) { continue; } const entityTypesReferencingLookingObject = dependenciesOfObjectType .map(typeName => supergraphState.objectTypes.get(typeName)) .filter((t) => !!t && Array.from(t.byGraph.values()).some(tg => tg.keys.length > 0)); const graphIdsUnableToResolveFieldViaEntityType = []; for (const [graphId] of graphsWithoutField) { const localEntityTypes = entityTypesReferencingLookingObject.filter(et => et.byGraph.has(graphId)); const isFieldResolvableThroughEntity = localEntityTypes.some(et => graphsWithField.some(targetGraphId => canGraphMoveToGraphByEntity(supergraphState, et.name, graphId, targetGraphId))); if (!isFieldResolvableThroughEntity) { graphIdsUnableToResolveFieldViaEntityType.push(graphId); } } if (graphIdsUnableToResolveFieldViaEntityType.length === 0) { continue; } const schemaDefinitionOfGraph = subgraphState.schema; const rootTypes = [ schemaDefinitionOfGraph.queryType ? [ 'Query', subgraphState.types.get(schemaDefinitionOfGraph.queryType), ] : undefined, schemaDefinitionOfGraph.mutationType ? [ 'Mutation', subgraphState.types.get(schemaDefinitionOfGraph.mutationType), ] : undefined, schemaDefinitionOfGraph.subscriptionType ? [ 'Subscription', subgraphState.types.get(schemaDefinitionOfGraph.subscriptionType), ] : undefined, ].filter(helpers_js_2.isDefined); if (rootTypes.length === 0) { continue; } for (const [normalizedName, rootType] of rootTypes) { const rootTypeFields = Array.from(rootType.fields.keys()).filter(f => f !== '_entities' && f !== '_service'); if (rootTypeFields.length === 0) { continue; } const supergraphRootType = supergraphState.objectTypes.get(normalizedName); const rootFieldsReferencingObjectType = Array.from(supergraphRootType.fields.values()).filter(f => { const fieldOutputTypeName = (0, state_js_2.stripTypeModifiers)(f.type); return (fieldOutputTypeName === objectState.name || dependenciesOfObjectType.includes(fieldOutputTypeName)); }); if (rootFieldsReferencingObjectType.length === 0) { continue; } const graphIdsImplementingObjectType = Array.from(objectState.byGraph.keys()); if (rootFieldsReferencingObjectType.every(field => graphIdsImplementingObjectType.every(g => field.byGraph.has(g)))) { continue; } const areRootFieldsShared = graphsWithField.every(g => { const localVersion = context.subgraphStates.get(g).version; if (localVersion !== 'v1.0') { return false; } const localSubgraph = context.subgraphStates.get(g); const localSchemaDefinition = localSubgraph.schema; const localRootTypeName = normalizedName === 'Query' ? localSchemaDefinition.queryType : normalizedName === 'Mutation' ? localSchemaDefinition.mutationType : normalizedName === 'Subscription' ? localSchemaDefinition.subscriptionType : undefined; if (!localRootTypeName) { return true; } const localRootType = localSubgraph.types.get(localRootTypeName); const localRootFields = Array.from(localRootType.fields.keys()); return rootFieldsReferencingObjectType.every(f => localRootFields.includes(f.name)); }); if (areRootFieldsShared) { continue; } if (!aggregatedErrorByRootType[normalizedName].query) { aggregatedErrorByRootType[normalizedName].query = printExampleQuery(supergraphState, normalizedName, rootTypeFields, objectState.name, fieldState.name, graphId, dependenciesOfObjectType); } const firstFieldImplementingGraph = graphsWithField[0]; const graphNameOwningField = context.graphIdToName(firstFieldImplementingGraph); aggregatedErrorByRootType[normalizedName].reasons.push([ graphId, [ `cannot find field "${objectState.name}.${fieldState.name}".`, `cannot move to subgraph "${graphNameOwningField}", which has field "${objectState.name}.${fieldState.name}", because type "${objectState.name}" has no @key defined in subgraph "${graphNameOwningField}".`, ], ]); } } } for (const rootTypeName in aggregatedErrorByRootType) { const details = aggregatedErrorByRootType[rootTypeName]; if (!details.query || details.reasons.length === 0) { continue; } context.reportError(new graphql_1.GraphQLError([ 'The following supergraph API query:', details.query, 'cannot be satisfied by the subgraphs because:', ...details.reasons.map(([graphId, reasons]) => { return (`- from subgraph "${context.graphIdToName(graphId)}":\n` + reasons.map(r => ` - ${r}`).join('\n')); }), ].join('\n'), { extensions: { code: 'SATISFIABILITY_ERROR', }, })); } }, }; } exports.SatisfiabilityRule = SatisfiabilityRule; function buildOutputTypesDependencies(supergraphState) { const graph = new dependency_graph_js_1.DepGraph({ circular: true, }); for (const [typeName, typeState] of supergraphState.objectTypes) { if (typeName === '_Service') { continue; } graph.addNode(typeName); for (const [_, fieldState] of typeState.fields) { const referencedTypeName = (0, state_js_2.stripTypeModifiers)(fieldState.type); if (!graph.hasNode(referencedTypeName)) { graph.addNode(referencedTypeName); } graph.addDependency(typeName, referencedTypeName); } } for (const [typeName, typeState] of supergraphState.unionTypes) { if (typeName === '_Entity') { continue; } graph.addNode(typeName); for (const memberType of typeState.members) { const referencedTypeName = memberType; if (!graph.hasNode(referencedTypeName)) { graph.addNode(referencedTypeName); } graph.addDependency(typeName, referencedTypeName); } } return graph; } function printExampleQuery(supergraphState, rootTypeName, rootTypeFieldsToStartWith, leafTypeName, leafFieldName, graphId, typesInBetweenRootAndLeaf = []) { const rootType = supergraphState.objectTypes.get(rootTypeName); function visitType(typeState, descendants, visitedTypes) { if (visitedTypes.includes(typeState.name)) { return null; } if ('members' in typeState) { for (const member of typeState.members) { const result = member === leafTypeName ? visitLeafType(supergraphState.objectTypes.get(member), descendants.concat([ { kind: graphql_1.Kind.INLINE_FRAGMENT, typeCondition: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: member, }, }, selectionSet: { kind: graphql_1.Kind.SELECTION_SET, selections: [], }, }, ])) : visitType(supergraphState.objectTypes.get(member), descendants.concat([ { kind: graphql_1.Kind.INLINE_FRAGMENT, typeCondition: { kind: graphql_1.Kind.NAMED_TYPE, name: { kind: graphql_1.Kind.NAME, value: member, }, }, selectionSet: { kind: graphql_1.Kind.SELECTION_SET, selections: [], }, }, ]), visitedTypes.concat(typeState.name)); if (result) { return result; } } return null; } for (const [fieldName, fieldState] of Array.from(typeState.fields.entries()).reverse()) { if (typeState.name === rootTypeName && !rootTypeFieldsToStartWith.includes(fieldName)) { continue; } const fieldOutputTypeName = (0, state_js_2.stripTypeModifiers)(fieldState.type); if (fieldOutputTypeName === leafTypeName) { return visitLeafType(supergraphState.objectTypes.get(fieldOutputTypeName), descendants.concat([ { kind: graphql_1.Kind.FIELD, name: { kind: graphql_1.Kind.NAME, value: fieldName, }, arguments: Array.from(fieldState.args).map(([argName, argState]) => ({ kind: graphql_1.Kind.ARGUMENT, name: { kind: graphql_1.Kind.NAME, value: argName, }, value: createEmptyValueNode(argState.type, supergraphState), })), }, ])); } else if (typesInBetweenRootAndLeaf.includes(fieldOutputTypeName) && !visitedTypes.includes(fieldOutputTypeName)) { const referencedType = supergraphState.objectTypes.get(fieldOutputTypeName) ?? supergraphState.unionTypes.get(fieldOutputTypeName); return visitType(referencedType, descendants.concat([ { kind: graphql_1.Kind.FIELD, name: { kind: graphql_1.Kind.NAME, value: fieldName, }, arguments: Array.from(fieldState.args).map(([argName, argState]) => ({ kind: graphql_1.Kind.ARGUMENT, name: { kind: graphql_1.Kind.NAME, value: argName, }, value: createEmptyValueNode(argState.type, supergraphState), })), }, ]), visitedTypes.concat(typeState.name)); } } return null; } function visitLeafType(objectTypeState, descendants) { for (const [fieldName, fieldState] of objectTypeState.fields) { if (fieldName !== leafFieldName) { continue; } const fieldOutputTypeName = (0, state_js_2.stripTypeModifiers)(fieldState.type); const isObjectSpreadCapable = supergraphState.objectTypes.has(fieldOutputTypeName) || supergraphState.interfaceTypes.has(fieldOutputTypeName) || supergraphState.unionTypes.has(fieldOutputTypeName); return descendants.concat([ { kind: graphql_1.Kind.FIELD, name: { kind: graphql_1.Kind.NAME, value: fieldName, }, arguments: Array.from(fieldState.args).map(([argName, argState]) => ({ kind: graphql_1.Kind.ARGUMENT, name: { kind: graphql_1.Kind.NAME, value: argName, }, value: createEmptyValueNode(argState.type, supergraphState), })), selectionSet: isObjectSpreadCapable ? { kind: graphql_1.Kind.SELECTION_SET, selections: [ { kind: graphql_1.Kind.FRAGMENT_SPREAD, name: { kind: graphql_1.Kind.NAME, value: '', }, }, ], } : undefined, }, ]); } return null; } const tree = visitType(rootType, [], []); let currentField = null; if (!tree) { return null; } tree.reverse(); for (const field of tree) { if (!currentField) { currentField = { ...field, }; } else { currentField = { ...field, selectionSet: { kind: graphql_1.Kind.SELECTION_SET, selections: [currentField], }, }; } } const query = { kind: graphql_1.Kind.DOCUMENT, definitions: [ { kind: graphql_1.Kind.OPERATION_DEFINITION, operation: rootTypeName === 'Query' ? graphql_1.OperationTypeNode.QUERY : rootTypeName === 'Mutation' ? graphql_1.OperationTypeNode.MUTATION : graphql_1.OperationTypeNode.SUBSCRIPTION, selectionSet: { kind: graphql_1.Kind.SELECTION_SET, selections: [currentField], }, }, ], }; return (0, graphql_1.print)(query); } function createEmptyValueNode(fullType, supergraphState) { if ((0, state_js_2.isList)(fullType)) { return { kind: graphql_1.Kind.LIST, values: [], }; } if ((0, state_js_2.isNonNull)(fullType)) { const innerType = (0, state_js_2.stripNonNull)(fullType); return createEmptyValueNode(innerType, supergraphState); } if (supergraphState.enumTypes.has(fullType)) { const enumState = supergraphState.enumTypes.get(fullType); return { kind: graphql_1.Kind.ENUM, value: Array.from(enumState.values.keys())[0], }; } if (supergraphState.scalarTypes.has(fullType)) { return { kind: graphql_1.Kind.STRING, value: 'A string value', }; } if (supergraphState.inputObjectTypes.has(fullType)) { const inputObjectTypeState = supergraphState.inputObjectTypes.get(fullType); return { kind: graphql_1.Kind.OBJECT, fields: Array.from(inputObjectTypeState.fields) .filter(([_, fieldState]) => (0, state_js_2.isNonNull)(fieldState.type)) .map(([fieldName, fieldState]) => ({ kind: graphql_1.Kind.OBJECT_FIELD, name: { kind: graphql_1.Kind.NAME, value: fieldName, }, value: createEmptyValueNode(fieldState.type, supergraphState), })), }; } const specifiedScalar = graphql_1.specifiedScalarTypes.find(s => s.name === fullType); if (!specifiedScalar) { throw new Error(`Type "${fullType}" is not defined.`); } if (specifiedScalar.name === 'String') { return { kind: graphql_1.Kind.STRING, value: 'A string value', }; } if (specifiedScalar.name === 'Int' || specifiedScalar.name === 'Float') { return { kind: graphql_1.Kind.INT, value: '0', }; } if (specifiedScalar.name === 'Boolean') { return { kind: graphql_1.Kind.BOOLEAN, value: true, }; } if (specifiedScalar.name === 'ID') { return { kind: graphql_1.Kind.STRING, value: '<any id>', }; } throw new Error(`Type "${fullType}" is not supported.`); } function resolveFieldsFromFieldSet(fields, typeName, graphId, supergraphState) { const paths = new Set(); const coordinates = new Set(); const selectionSet = (0, helpers_js_1.parseFields)(fields); if (!selectionSet) { return { coordinates, paths, }; } findFieldPathsFromSelectionSet(paths, coordinates, typeName, selectionSet, typeName, graphId, supergraphState); return { coordinates, paths, }; } function findFieldPathsFromSelectionSet(fieldPaths, coordinates, typeName, selectionSet, currentPath, graphId, supergraphState) { for (const selection of selectionSet.selections) { if (selection.kind === graphql_1.Kind.FIELD) { const innerPath = `${currentPath}.${selection.name.value}`; fieldPaths.add(innerPath); if (Array.isArray(typeName)) { for (const t of typeName) { coordinates.add(`${t}.${selection.name.value}`); } } else { coordinates.add(`${typeName}.${selection.name.value}`); } if (selection.selectionSet) { const types = (Array.isArray(typeName) ? typeName : [typeName]).map(tName => { const outputType = supergraphState.objectTypes.get(tName) ?? supergraphState.interfaceTypes.get(tName); if (!outputType) { throw new Error(`Type "${tName}" is not defined.`); } return outputType; }); const typesWithField = types.filter(t => t.fields.has(selection.name.value)); if (typesWithField.length === 0) { throw new Error(`Type "${typeName.toString()}" does not have field "${selection.name.value}".`); } const outputTypes = typesWithField.map(t => (0, state_js_2.stripTypeModifiers)(t.fields.get(selection.name.value).type)); findFieldPathsFromSelectionSet(fieldPaths, coordinates, outputTypes, selection.selectionSet, innerPath, graphId, supergraphState); } } else if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) { if (!selection.typeCondition) { throw new Error(`Inline fragment without type condition is not supported.`); } findFieldPathsFromSelectionSet(fieldPaths, coordinates, selection.typeCondition.name.value, selection.selectionSet, `${currentPath}.(${selection.typeCondition.name.value})`, graphId, supergraphState); } else { throw new Error(`Fragment spread is not supported.`); } } }