@apollo/query-graphs
Version:
Apollo Federation library to work with 'query graphs'
49 lines (45 loc) • 2.65 kB
text/typescript
import { assert } from "@apollo/federation-internals";
import { Edge, QueryGraph, QueryGraphState, simpleTraversal } from "./querygraph";
export function preComputeNonTrivialFollowupEdges(graph: QueryGraph): (previousEdge: Edge) => readonly Edge[] {
const state = new QueryGraphState<undefined, readonly Edge[]>();
simpleTraversal(graph, () => {}, (edge) => {
const followupEdges = graph.outEdges(edge.tail);
state.setEdgeState(edge, computeNonTrivialFollowups(edge, followupEdges));
return true;
});
return (previousEdge) => {
const nonTrivialFollowups = state.getEdgeState(previousEdge);
assert(nonTrivialFollowups, () => `Non-trivial followup edges of ${previousEdge} should have been computed`);
return nonTrivialFollowups;
}
}
function computeNonTrivialFollowups(edge: Edge, allFollowups: readonly Edge[]): readonly Edge[] {
switch (edge.transition.kind) {
case 'KeyResolution':
// After taking a key from subgraph A to B, there is no point of following that up with another key
// to subgraph C if that key has _the same_ conditions. This is because, due to the way key edges
// are created, if we have a key (with some conditions X) from B to C, then we are guaranteed to
// also have a key (with the same conditions X) from A to C, and so it's that later key we
// should be using in the first place. In other words, it's never better to do 2 hops rather than 1.
return allFollowups.filter((followup) => followup.transition.kind !== 'KeyResolution' || !sameConditions(edge, followup));
case 'RootTypeResolution':
// A 'RootTypeResolution' means that a query reached the query type (or another root type) in some
// subgraph A and we're looking at jumping to another subgraph B. But like for keys, there is
// no point in trying to jump directly to yet another subgpraph C from B, since we can always
// jump directly from A to C and it's better.
return allFollowups.filter((followup) => followup.transition.kind !== 'RootTypeResolution');
case 'SubgraphEnteringTransition':
// This is somewhat similar to 'RootTypeResolution' except that we're starting the query.
// But still, not doing "start of query" -> B -> C, since we can do "start of query" -> C
// and that's always better.
return allFollowups.filter((followup) => followup.transition.kind !== 'RootTypeResolution');
default:
return allFollowups;
}
}
function sameConditions(e1: Edge, e2: Edge): boolean {
if (!e1.conditions) {
return !e2.conditions;
}
return !!e2.conditions && e1.conditions.equals(e2.conditions);
}