@theguild/federation-composition
Version:
Open Source Composition library for Apollo Federation
925 lines (924 loc) • 44.9 kB
JavaScript
"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 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 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 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 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 .`,
].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 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.`);
}
}
}