UNPKG

@apollo/query-graphs

Version:

Apollo Federation library to work with 'query graphs'

45 lines (41 loc) 3.48 kB
import { SelectionSet, assert } from "@apollo/federation-internals"; import { ConditionResolution, ConditionResolver, ExcludedConditions, ExcludedDestinations, sameExcludedDestinations } from "./graphPath"; import { PathContext } from "./pathContext"; import { Edge, QueryGraphState } from "./querygraph"; export function cachingConditionResolver(resolver: ConditionResolver): ConditionResolver { // For every edge having a condition, we cache the resolution its conditions when possible. // We save resolution with the set of excluded edges that were used to compute it: the reason we do this is // that excluded edges impact the resolution, so we should only used a cached value if we know the excluded // edges are the same as when caching, and while we could decide to cache only when we have no excluded edges // at all, this would sub-optimal for types that have multiple keys, as the algorithm will always at least // include the previous key edges to the excluded edges of other keys. In other words, if we only cached // when we have no excluded edges, we'd only ever use the cache for the first key of every type. However, // as the algorithm always try keys in the same order (the order of the edges in the query graph), including // the excluded edges we see on the first ever call is actually the proper thing to do. const cache = new QueryGraphState<undefined, [ConditionResolution, ExcludedDestinations]>(); return (edge: Edge, context: PathContext, excludedDestinations: ExcludedDestinations, excludedConditions: ExcludedConditions, extraConditions?: SelectionSet) => { assert(edge.conditions || extraConditions, 'Should not have been called for edge without conditions'); // We don't cache if there is a context or excluded conditions because those would impact the resolution and // we don't want to cache a value per-context and per-excluded-conditions (we also don't cache per-excluded-edges though // instead we cache a value only for the first-see excluded edges; see above why that work in practice). // TODO: we could actually have a better handling of the context: it doesn't really change how we'd resolve the condition, it's only // that the context, if not empty, would have to be added to the trigger of key edges in the resolution path tree when appropriate // and we currently don't handle that. But we could cache with an empty context, and then apply the proper transformation on the // cached value `pathTree` when the context is not empty. That said, the context is about active @include/@skip and it's not use // that commonly, so this is probably not an urgent improvement. if (!context.isEmpty() || excludedConditions.length > 0 || extraConditions) { return resolver(edge, context, excludedDestinations, excludedConditions, extraConditions); } const cachedResolutionAndExcludedEdges = cache.getEdgeState(edge); if (cachedResolutionAndExcludedEdges) { const [cachedResolution, forExcludedEdges] = cachedResolutionAndExcludedEdges; return sameExcludedDestinations(forExcludedEdges, excludedDestinations) ? cachedResolution : resolver(edge, context, excludedDestinations, excludedConditions, extraConditions); } else { const resolution = resolver(edge, context, excludedDestinations, excludedConditions, extraConditions); cache.setEdgeState(edge, [resolution, excludedDestinations]); return resolution; } }; }