@apollo/query-planner
Version:
Apollo Query Planner
2,960 lines • 144 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryPlanner = exports.compareOptionsComplexityOutOfContext = void 0;
const federation_internals_1 = require("@apollo/federation-internals");
const query_graphs_1 = require("@apollo/query-graphs");
const graphql_1 = require("graphql");
const conditions_1 = require("./conditions");
const config_1 = require("./config");
const generateAllPlans_1 = require("./generateAllPlans");
const QueryPlan_1 = require("./QueryPlan");
const recursiveSelectionsLimit_1 = require("./recursiveSelectionsLimit");
const debug = (0, federation_internals_1.newDebugLogger)('plan');
const SIBLING_TYPENAME_KEY = 'sibling_typename';
const fetchCost = 1000;
const pipeliningCost = 100;
const defaultCostFunction = {
onFetchGroup: (group) => (fetchCost + group.cost()),
onConditions: (_, value) => value,
reduceParallel: (values) => parallelCost(values),
reduceSequence: (values) => sequenceCost(values),
reduceDeferred(_, value) {
return value;
},
reduceDefer(nonDeferred, _, deferredValues) {
return sequenceCost([nonDeferred, parallelCost(deferredValues)]);
},
};
function parallelCost(values) {
return sum(values);
}
function sequenceCost(stages) {
return stages.reduceRight((acc, stage, idx) => (acc + (Math.max(1, idx * pipeliningCost) * stage)), 0);
}
function closedPathToString(p) {
const pathStr = (0, query_graphs_1.simultaneousPathsToString)(p.paths);
return p.selection ? `${pathStr} -> ${p.selection}` : pathStr;
}
function flattenClosedPath(p) {
return p.paths.map((path) => ({ path, selection: p.selection }));
}
function allTailVertices(options) {
const vertices = new Set();
for (const option of options) {
for (const path of option.paths) {
vertices.add(path.tail);
}
}
return vertices;
}
function selectionIsFullyLocalFromAllVertices(selection, vertices, inconsistentAbstractTypesRuntimes) {
let _useInconsistentAbstractTypes = undefined;
const useInconsistentAbstractTypes = () => {
if (_useInconsistentAbstractTypes === undefined) {
_useInconsistentAbstractTypes = selection.some((elt) => elt.kind === 'FragmentElement' && !!elt.typeCondition && inconsistentAbstractTypesRuntimes.has(elt.typeCondition.name));
}
return _useInconsistentAbstractTypes;
};
for (const vertex of vertices) {
if (vertex.hasReachableCrossSubgraphEdges
|| !(0, federation_internals_1.isCompositeType)(vertex.type)
|| !selection.canRebaseOn(vertex.type)
|| useInconsistentAbstractTypes()) {
return false;
}
}
return true;
}
function compareOptionsComplexityOutOfContext(opt1, opt2) {
if (opt1.length === 1) {
if (opt2.length === 1) {
return compareSinglePathOptionsComplexityOutOfContext(opt1[0], opt2[0]);
}
else {
return compareSingleVsMultiPathOptionsComplexityOutOfContext(opt1[0], opt2);
}
}
else if (opt2.length === 1) {
return -compareSingleVsMultiPathOptionsComplexityOutOfContext(opt2[0], opt1);
}
return 0;
}
exports.compareOptionsComplexityOutOfContext = compareOptionsComplexityOutOfContext;
function compareSinglePathOptionsComplexityOutOfContext(p1, p2) {
if (p1.tail.source !== p2.tail.source) {
const { thisJumps: p1Jumps, thatJumps: p2Jumps } = p1.countSubgraphJumpsAfterLastCommonVertex(p2);
if (p1Jumps === 0 && p2Jumps > 0) {
return -1;
}
else if (p1Jumps > 0 && p2Jumps === 0) {
return 1;
}
else {
return 0;
}
}
return 0;
}
function compareSingleVsMultiPathOptionsComplexityOutOfContext(p1, p2s) {
for (const p2 of p2s) {
if (compareSinglePathOptionsComplexityOutOfContext(p1, p2) >= 0) {
return 0;
}
}
return -1;
}
class QueryPlanningTraversal {
constructor(parameters, selectionSet, startFetchIdGen, hasDefers, rootKind, costFunction, initialContext, typeConditionedFetching, nonLocalSelectionsState, excludedDestinations = [], excludedConditions = []) {
var _a;
this.parameters = parameters;
this.startFetchIdGen = startFetchIdGen;
this.hasDefers = hasDefers;
this.rootKind = rootKind;
this.costFunction = costFunction;
this.closedBranches = [];
const { root, federatedQueryGraph } = parameters;
this.typeConditionedFetching = typeConditionedFetching || false;
this.isTopLevel = (0, query_graphs_1.isRootVertex)(root);
this.optionsLimit = (_a = parameters.config.debug) === null || _a === void 0 ? void 0 : _a.pathsLimit;
this.conditionResolver = (0, query_graphs_1.cachingConditionResolver)((edge, context, excludedEdges, excludedConditions, extras) => this.resolveConditionPlan(edge, context, excludedEdges, excludedConditions, extras));
const initialPath = query_graphs_1.GraphPath.create(federatedQueryGraph, root);
const initialOptions = (0, query_graphs_1.createInitialOptions)(initialPath, initialContext, this.conditionResolver, excludedDestinations, excludedConditions, parameters.overrideConditions);
this.stack = mapOptionsToSelections(selectionSet, initialOptions);
if (this.parameters.federatedQueryGraph.nonLocalSelectionsMetadata
&& nonLocalSelectionsState) {
if (this.parameters.federatedQueryGraph.nonLocalSelectionsMetadata
.checkNonLocalSelectionsLimitExceededAtRoot(this.stack, nonLocalSelectionsState, this.parameters.supergraphSchema, this.parameters.inconsistentAbstractTypesRuntimes, this.parameters.overrideConditions)) {
throw Error(`Number of non-local selections exceeds limit of ${query_graphs_1.NonLocalSelectionsMetadata.MAX_NON_LOCAL_SELECTIONS}`);
}
}
}
debugStack() {
if (this.isTopLevel && debug.enabled) {
debug.group('Query planning open branches:');
for (const [selection, options] of this.stack) {
debug.groupedValues(options, opt => `${(0, query_graphs_1.simultaneousPathsToString)(opt)}`, `${selection}:`);
}
debug.groupEnd();
}
}
findBestPlan() {
while (this.stack.length > 0) {
this.debugStack();
const [selection, options] = this.stack.pop();
this.handleOpenBranch(selection, options);
}
this.computeBestPlanFromClosedBranches();
return this.bestPlan;
}
recordClosedBranch(closed) {
const maybeTrimmed = this.maybeEliminateStrictlyMoreCostlyPaths(closed);
debug.log(() => `Closed branch has ${maybeTrimmed.length} options (eliminated ${closed.length - maybeTrimmed.length} that could be proved as unecessary)`);
this.closedBranches.push(maybeTrimmed);
}
handleOpenBranch(selection, options) {
const operation = selection.element;
debug.group(() => `Handling open branch: ${operation}`);
let newOptions = [];
for (const option of options) {
const followupForOption = (0, query_graphs_1.advanceSimultaneousPathsWithOperation)(this.parameters.supergraphSchema, option, operation, this.parameters.overrideConditions);
if (!followupForOption) {
continue;
}
if (followupForOption.length === 0) {
if (operation.kind === 'FragmentElement') {
this.recordClosedBranch(options.map((o) => ({
paths: o.paths.map(p => (0, query_graphs_1.terminateWithNonRequestedTypenameField)(p, this.parameters.overrideConditions))
})));
}
debug.groupEnd(() => `Terminating branch with no possible results`);
return;
}
newOptions = newOptions.concat(followupForOption);
if (this.optionsLimit && newOptions.length > this.optionsLimit) {
throw new Error(`Too many options generated for ${selection}, reached the limit of ${this.optionsLimit}`);
}
}
if (newOptions.length === 0) {
if (this.isTopLevel) {
debug.groupEnd(() => `No valid options to advance ${selection} from ${(0, query_graphs_1.advanceOptionsToString)(options)}`);
throw new Error(`Was not able to find any options for ${selection}: This shouldn't have happened.`);
}
else {
this.stack.splice(0, this.stack.length);
this.closedBranches.splice(0, this.closedBranches.length);
debug.groupEnd(() => `No possible plan for ${selection} from ${(0, query_graphs_1.advanceOptionsToString)(options)}; terminating condition`);
return;
}
}
if (selection.selectionSet) {
const allTails = allTailVertices(newOptions);
if (selectionIsFullyLocalFromAllVertices(selection.selectionSet, allTails, this.parameters.inconsistentAbstractTypesRuntimes)
&& !selection.hasDefer()) {
const selectionSet = addTypenameFieldForAbstractTypes(addBackTypenameInAttachments(selection.selectionSet));
this.recordClosedBranch(newOptions.map((opt) => ({ paths: opt.paths, selection: selectionSet })));
}
else {
for (const branch of mapOptionsToSelections(selection.selectionSet, newOptions)) {
this.stack.push(branch);
}
}
debug.groupEnd();
}
else {
this.recordClosedBranch(newOptions.map((opt) => ({ paths: opt.paths })));
debug.groupEnd(() => `Branch finished`);
}
}
maybeEliminateStrictlyMoreCostlyPaths(branch) {
if (branch.length <= 1) {
return branch;
}
const toHandle = branch.concat();
const keptOptions = [];
while (toHandle.length >= 2) {
const first = toHandle[0];
let shouldKeepFirst = true;
for (let i = toHandle.length - 1; i >= 1; i--) {
const other = toHandle[i];
const cmp = compareOptionsComplexityOutOfContext(first.paths, other.paths);
if (cmp < 0) {
toHandle.splice(i, 1);
}
else if (cmp > 0) {
toHandle.splice(0, 1);
shouldKeepFirst = false;
break;
}
}
if (shouldKeepFirst) {
keptOptions.push(first);
toHandle.splice(0, 1);
}
}
if (toHandle.length > 0) {
keptOptions.push(toHandle[0]);
}
return keptOptions;
}
newDependencyGraph() {
const { supergraphSchema, federatedQueryGraph } = this.parameters;
const rootType = this.isTopLevel && this.hasDefers ? supergraphSchema.schemaDefinition.rootType(this.rootKind) : undefined;
return FetchDependencyGraph.create(supergraphSchema, federatedQueryGraph, this.startFetchIdGen, rootType, this.parameters.config.generateQueryFragments);
}
reorderFirstBranch() {
const firstBranch = this.closedBranches[0];
let i = 1;
while (i < this.closedBranches.length && this.closedBranches[i].length > firstBranch.length) {
i++;
}
this.closedBranches[0] = this.closedBranches[i - 1];
this.closedBranches[i - 1] = firstBranch;
}
sortOptionsInClosedBranches() {
this.closedBranches.forEach((branch) => branch.sort((p1, p2) => {
const p1Jumps = Math.max(...p1.paths.map((p) => p.subgraphJumps()));
const p2Jumps = Math.max(...p2.paths.map((p) => p.subgraphJumps()));
return p1Jumps - p2Jumps;
}));
}
computeBestPlanFromClosedBranches() {
if (this.closedBranches.length === 0) {
return;
}
this.sortOptionsInClosedBranches();
this.closedBranches.sort((b1, b2) => b1.length > b2.length ? -1 : (b1.length < b2.length ? 1 : 0));
let planCount = possiblePlans(this.closedBranches);
debug.log(() => `Query has ${planCount} possible plans`);
let firstBranch = this.closedBranches[0];
const maxPlansToCompute = this.parameters.config.debug.maxEvaluatedPlans;
while (planCount > maxPlansToCompute && firstBranch.length > 1) {
const prevSize = BigInt(firstBranch.length);
firstBranch.pop();
planCount -= planCount / prevSize;
this.reorderFirstBranch();
firstBranch = this.closedBranches[0];
debug.log(() => `Reduced plans to consider to ${planCount} plans`);
}
if (this.parameters.statistics && this.isTopLevel) {
this.parameters.statistics.evaluatedPlanCount += Number(planCount);
}
debug.log(() => `All branches:${this.closedBranches.map((opts, i) => `\n${i}:${opts.map((opt => `\n - ${closedPathToString(opt)}`))}`)}`);
let idxFirstOfLengthOne = 0;
while (idxFirstOfLengthOne < this.closedBranches.length && this.closedBranches[idxFirstOfLengthOne].length > 1) {
idxFirstOfLengthOne++;
}
let initialTree;
let initialDependencyGraph;
const { federatedQueryGraph, root } = this.parameters;
if (idxFirstOfLengthOne === this.closedBranches.length) {
initialTree = query_graphs_1.PathTree.createOp(federatedQueryGraph, root);
initialDependencyGraph = this.newDependencyGraph();
}
else {
const singleChoiceBranches = this
.closedBranches
.slice(idxFirstOfLengthOne)
.flat()
.map((cp) => flattenClosedPath(cp))
.flat();
initialTree = query_graphs_1.PathTree.createFromOpPaths(federatedQueryGraph, root, singleChoiceBranches);
initialDependencyGraph = this.updatedDependencyGraph(this.newDependencyGraph(), initialTree);
if (idxFirstOfLengthOne === 0) {
this.bestPlan = [initialDependencyGraph, initialTree, this.cost(initialDependencyGraph)];
return;
}
}
const otherTrees = this
.closedBranches
.slice(0, idxFirstOfLengthOne)
.map(b => b.map(opt => query_graphs_1.PathTree.createFromOpPaths(federatedQueryGraph, root, flattenClosedPath(opt))));
const { best, cost } = (0, generateAllPlans_1.generateAllPlansAndFindBest)({
initial: { graph: initialDependencyGraph, tree: initialTree },
toAdd: otherTrees,
addFct: (p, t) => {
const updatedDependencyGraph = p.graph.clone();
this.updatedDependencyGraph(updatedDependencyGraph, t);
const updatedTree = p.tree.merge(t);
return { graph: updatedDependencyGraph, tree: updatedTree };
},
costFct: (p) => this.cost(p.graph),
onPlan: (p, cost, prevCost) => {
debug.log(() => {
if (!prevCost) {
return `Computed plan with cost ${cost}: ${p.tree}`;
}
else if (cost > prevCost) {
return `Ignoring plan with cost ${cost} (a better plan with cost ${prevCost} exists): ${p.tree}`;
}
else {
return `Found better with cost ${cost} (previous had cost ${prevCost}: ${p.tree}`;
}
});
},
});
this.bestPlan = [best.graph, best.tree, cost];
}
cost(dependencyGraph) {
const { main, deferred } = dependencyGraph.process(this.costFunction, this.rootKind);
return deferred.length === 0
? main
: this.costFunction.reduceDefer(main, dependencyGraph.deferTracking.primarySelection.get(), deferred);
}
updatedDependencyGraph(dependencyGraph, tree) {
return (0, query_graphs_1.isRootPathTree)(tree)
? computeRootFetchGroups(dependencyGraph, tree, this.rootKind, this.typeConditionedFetching)
: computeNonRootFetchGroups(dependencyGraph, tree, this.rootKind, this.typeConditionedFetching);
}
resolveConditionPlan(edge, context, excludedDestinations, excludedConditions, extraConditions) {
const bestPlan = new QueryPlanningTraversal({
...this.parameters,
root: edge.head,
}, extraConditions !== null && extraConditions !== void 0 ? extraConditions : edge.conditions, 0, false, 'query', this.costFunction, context, this.typeConditionedFetching, null, excludedDestinations, (0, query_graphs_1.addConditionExclusion)(excludedConditions, edge.conditions)).findBestPlan();
return bestPlan ? { satisfied: true, cost: bestPlan[2], pathTree: bestPlan[1] } : query_graphs_1.unsatisfiedConditionsResolution;
}
}
const conditionsMemoizer = (selectionSet) => ({ conditions: (0, conditions_1.conditionsOfSelectionSet)(selectionSet) });
class GroupInputs {
constructor(supergraphSchema) {
this.supergraphSchema = supergraphSchema;
this.usedContexts = new Map;
this.perType = new Map();
this.onUpdateCallback = undefined;
}
add(selection) {
var _a;
(0, federation_internals_1.assert)(selection.parentType.schema() === this.supergraphSchema, 'Inputs selections must be based on the supergraph schema');
const typeName = selection.parentType.name;
let typeSelection = this.perType.get(typeName);
if (!typeSelection) {
typeSelection = federation_internals_1.MutableSelectionSet.empty(selection.parentType);
this.perType.set(typeName, typeSelection);
}
typeSelection.updates().add(selection);
(_a = this.onUpdateCallback) === null || _a === void 0 ? void 0 : _a.call(this);
}
addContext(context, type) {
this.usedContexts.set(context, type);
}
addAll(other) {
for (const otherSelection of other.perType.values()) {
this.add(otherSelection.get());
}
for (const [context, type] of other.usedContexts) {
this.addContext(context, type);
}
}
selectionSets() {
return (0, federation_internals_1.mapValues)(this.perType).map((s) => s.get());
}
toSelectionSetNode(variablesDefinitions, handledConditions) {
const selectionSets = (0, federation_internals_1.mapValues)(this.perType).map((s) => (0, conditions_1.removeConditionsFromSelectionSet)(s.get(), handledConditions));
selectionSets.forEach((s) => s.validate(variablesDefinitions));
const selections = selectionSets.flatMap((sSet) => sSet.selections().map((s) => s.toSelectionNode()));
return {
kind: graphql_1.Kind.SELECTION_SET,
selections,
};
}
contains(other) {
for (const [type, otherSelection] of other.perType) {
const thisSelection = this.perType.get(type);
if (!thisSelection || !thisSelection.get().contains(otherSelection.get())) {
return false;
}
}
if (this.usedContexts.size < other.usedContexts.size) {
return false;
}
for (const [c, _] of other.usedContexts) {
if (!this.usedContexts.has(c)) {
return false;
}
}
return true;
}
equals(other) {
if (this.perType.size !== other.perType.size) {
return false;
}
for (const [type, thisSelection] of this.perType) {
const otherSelection = other.perType.get(type);
if (!otherSelection || !thisSelection.get().equals(otherSelection.get())) {
return false;
}
}
if (this.usedContexts.size !== other.usedContexts.size) {
return false;
}
for (const [c, _] of other.usedContexts) {
if (!this.usedContexts.has(c)) {
return false;
}
}
return true;
}
clone() {
const cloned = new GroupInputs(this.supergraphSchema);
for (const [type, selection] of this.perType.entries()) {
cloned.perType.set(type, selection.clone());
}
for (const [c, v] of this.usedContexts) {
cloned.usedContexts.set(c, v);
}
return cloned;
}
toString() {
const inputs = (0, federation_internals_1.mapValues)(this.perType);
if (inputs.length === 0) {
return '{}';
}
if (inputs.length === 1) {
return inputs[0].toString();
}
return '[' + inputs.join(',') + ']';
}
}
class FetchGroup {
constructor(dependencyGraph, index, subgraphName, rootKind, parentType, isEntityFetch, _selection, _inputs, _contextInputs, mergeAt, deferRef, subgraphAndMergeAtKey, cachedCost, generateQueryFragments = false, isKnownUseful = false, inputRewrites = []) {
this.dependencyGraph = dependencyGraph;
this.index = index;
this.subgraphName = subgraphName;
this.rootKind = rootKind;
this.parentType = parentType;
this.isEntityFetch = isEntityFetch;
this._selection = _selection;
this._inputs = _inputs;
this._contextInputs = _contextInputs;
this.mergeAt = mergeAt;
this.deferRef = deferRef;
this.subgraphAndMergeAtKey = subgraphAndMergeAtKey;
this.cachedCost = cachedCost;
this.generateQueryFragments = generateQueryFragments;
this.isKnownUseful = isKnownUseful;
this.inputRewrites = inputRewrites;
this._parents = [];
this._children = [];
this.mustPreserveSelection = false;
if (this._inputs) {
this._inputs.onUpdateCallback = () => {
this.isKnownUseful = false;
};
}
}
static create({ dependencyGraph, index, subgraphName, rootKind, parentType, hasInputs, mergeAt, deferRef, generateQueryFragments, }) {
var _a;
(0, federation_internals_1.assert)(parentType.schema() === dependencyGraph.subgraphSchemas.get(subgraphName), `Expected parent type ${parentType} to belong to ${subgraphName}`);
return new FetchGroup(dependencyGraph, index, subgraphName, rootKind, parentType, hasInputs, federation_internals_1.MutableSelectionSet.emptyWithMemoized(parentType, conditionsMemoizer), hasInputs ? new GroupInputs(dependencyGraph.supergraphSchema) : undefined, undefined, mergeAt, deferRef, hasInputs ? `${toValidGraphQLName(subgraphName)}-${(_a = mergeAt === null || mergeAt === void 0 ? void 0 : mergeAt.join('::')) !== null && _a !== void 0 ? _a : ''}` : undefined, undefined, generateQueryFragments);
}
cloneShallow(newDependencyGraph) {
var _a;
return new FetchGroup(newDependencyGraph, this.index, this.subgraphName, this.rootKind, this.parentType, this.isEntityFetch, this._selection.clone(), (_a = this._inputs) === null || _a === void 0 ? void 0 : _a.clone(), this._contextInputs ? this._contextInputs.map((c) => ({ ...c })) : undefined, this.mergeAt, this.deferRef, this.subgraphAndMergeAtKey, this.cachedCost, this.generateQueryFragments, this.isKnownUseful, [...this.inputRewrites]);
}
cost() {
if (!this.cachedCost) {
this.cachedCost = selectionCost(this.selection);
}
return this.cachedCost;
}
set id(id) {
(0, federation_internals_1.assert)(!this._id, () => `The id for fetch group ${this} is already set`);
this._id = id;
}
get id() {
return this._id;
}
get isTopLevel() {
return !this.mergeAt;
}
get selection() {
return this._selection.get();
}
selectionUpdates() {
this.cachedCost = undefined;
return this._selection.updates();
}
get inputs() {
return this._inputs;
}
addParents(parents) {
for (const parent of parents) {
this.addParent(parent);
}
}
addParent(parent) {
if (this.isChildOf(parent.group)) {
return;
}
(0, federation_internals_1.assert)(!parent.group.isParentOf(this), () => `Group ${parent.group} is a parent of ${this}, but the child relationship is broken`);
(0, federation_internals_1.assert)(!parent.group.isChildOf(this), () => `Group ${parent.group} is a child of ${this}: adding it as parent would create a cycle`);
this.dependencyGraph.onModification();
this._parents.push(parent);
parent.group._children.push(this);
}
removeChild(child) {
if (!this.isParentOf(child)) {
return;
}
this.dependencyGraph.onModification();
findAndRemoveInPlace((g) => g === child, this._children);
findAndRemoveInPlace((p) => p.group === this, child._parents);
}
isParentOf(maybeChild) {
return this._children.includes(maybeChild);
}
isChildOf(maybeParent) {
return !!this.parentRelation(maybeParent);
}
isDescendantOf(maybeAncestor) {
const children = Array.from(maybeAncestor.children());
while (children.length > 0) {
const child = children.pop();
if (child === this) {
return true;
}
child.children().forEach((c) => children.push(c));
}
return false;
}
isChildOfWithArtificialDependency(maybeParent) {
const relation = this.parentRelation(maybeParent);
if (!relation || !relation.path) {
return false;
}
if (!this.inputs) {
return true;
}
if (relation.path.some((elt) => elt.kind === 'Field')) {
return false;
}
return !!maybeParent.inputs && maybeParent.inputs.contains(this.inputs);
}
parentRelation(maybeParent) {
return this._parents.find(({ group }) => maybeParent === group);
}
parents() {
return this._parents;
}
parentGroups() {
return this.parents().map((p) => p.group);
}
children() {
return this._children;
}
addInputs(selection, rewrites) {
(0, federation_internals_1.assert)(this._inputs, "Shouldn't try to add inputs to a root fetch group");
this._inputs.add(selection);
if (rewrites) {
rewrites.forEach((r) => this.inputRewrites.push(r));
}
}
addInputContext(context, type) {
(0, federation_internals_1.assert)(this._inputs, "Shouldn't try to add inputs to a root fetch group");
this._inputs.addContext(context, type);
}
copyInputsOf(other) {
var _a;
if (other.inputs) {
(_a = this.inputs) === null || _a === void 0 ? void 0 : _a.addAll(other.inputs);
if (other.inputRewrites) {
other.inputRewrites.forEach((r) => {
if (!this.inputRewrites.some((r2) => r2 === r)) {
this.inputRewrites.push(r);
}
});
}
if (other._contextInputs) {
if (!this._contextInputs) {
this._contextInputs = [];
}
other._contextInputs.forEach((r) => {
if (!this._contextInputs.some((r2) => sameKeyRenamer(r, r2))) {
this._contextInputs.push(r);
}
});
}
}
}
addAtPath(path, selection) {
this.selectionUpdates().addAtPath(path, selection);
}
addSelections(selection) {
this.selectionUpdates().add(selection);
}
canMergeChildIn(child) {
var _a;
return this.deferRef === child.deferRef && !!((_a = child.parentRelation(this)) === null || _a === void 0 ? void 0 : _a.path);
}
removeInputsFromSelection() {
const inputs = this.inputs;
if (inputs) {
this.cachedCost = undefined;
const updated = inputs.selectionSets().reduce((prev, value) => prev.minus(value), this.selection);
this._selection = federation_internals_1.MutableSelectionSet.ofWithMemoized(updated, conditionsMemoizer);
}
}
isUseless() {
if (this.isKnownUseful || !this.inputs || this.mustPreserveSelection) {
return false;
}
const conditionInSupergraphIfInterfaceObject = (selection) => {
if (selection.kind === 'FragmentSelection') {
const condition = selection.element.typeCondition;
if (condition && (0, federation_internals_1.isObjectType)(condition)) {
const conditionInSupergraph = this.dependencyGraph.supergraphSchema.type(condition.name);
(0, federation_internals_1.assert)(conditionInSupergraph, () => `Type ${condition.name} should exists in the supergraph`);
if ((0, federation_internals_1.isInterfaceType)(conditionInSupergraph)) {
return conditionInSupergraph;
}
}
}
return undefined;
};
const isInterfaceTypeConditionOnInterfaceObject = (selection) => {
if (selection.kind === "FragmentSelection") {
const parentType = selection.element.typeCondition;
if (parentType && (0, federation_internals_1.isInterfaceType)(parentType)) {
return this.parents().some((p) => {
var _a;
const typeInParent = (_a = this.dependencyGraph.subgraphSchemas
.get(p.group.subgraphName)) === null || _a === void 0 ? void 0 : _a.type(parentType.name);
return typeInParent && (0, federation_internals_1.isInterfaceObjectType)(typeInParent);
});
}
}
return false;
};
const inputSelections = this.inputs.selectionSets().flatMap((s) => s.selections());
const isUseless = this.selection.selections().every((selection) => {
if (isInterfaceTypeConditionOnInterfaceObject(selection)) {
return false;
}
const conditionInSupergraph = conditionInSupergraphIfInterfaceObject(selection);
if (!conditionInSupergraph) {
return inputSelections.some((input) => input.contains(selection));
}
const implemTypeNames = conditionInSupergraph.possibleRuntimeTypes().map((t) => t.name);
const interfaceInputSelections = [];
const implementationInputSelections = [];
for (const inputSelection of inputSelections) {
(0, federation_internals_1.assert)(inputSelection.kind === 'FragmentSelection', () => `Unexpecting input selection ${inputSelection} on ${this}`);
const inputCondition = inputSelection.element.typeCondition;
(0, federation_internals_1.assert)(inputCondition, () => `Unexpecting input selection ${inputSelection} on ${this} (missing condition)`);
if (inputCondition.name == conditionInSupergraph.name) {
interfaceInputSelections.push(inputSelection);
}
else if (implemTypeNames.includes(inputCondition.name)) {
implementationInputSelections.push(inputSelection);
}
}
const subSelectionSet = selection.selectionSet;
(0, federation_internals_1.assert)(subSelectionSet, () => `Should not be here for ${selection}`);
if (interfaceInputSelections.length > 0) {
return interfaceInputSelections.some((input) => input.selectionSet.contains(subSelectionSet));
}
return implementationInputSelections.length > 0
&& implementationInputSelections.every((input) => input.selectionSet.contains(subSelectionSet));
});
this.isKnownUseful = !isUseless;
return isUseless;
}
mergeChildIn(child) {
const relationToChild = child.parentRelation(this);
(0, federation_internals_1.assert)(relationToChild, () => `Cannot merge ${child} into ${this}: the former is not a child of the latter`);
const childPathInThis = relationToChild.path;
(0, federation_internals_1.assert)(childPathInThis, () => `Cannot merge ${child} into ${this}: the path of the former into the later is unknown`);
this.mergeInInternal(child, childPathInThis);
}
canMergeSiblingIn(sibling) {
const ownParents = this.parents();
const siblingParents = sibling.parents();
return this.deferRef === sibling.deferRef
&& this.subgraphName === sibling.subgraphName
&& sameMergeAt(this.mergeAt, sibling.mergeAt)
&& ownParents.length === 1
&& siblingParents.length === 1
&& ownParents[0].group === siblingParents[0].group;
}
mergeSiblingIn(sibling) {
this.copyInputsOf(sibling);
this.mergeInInternal(sibling, []);
}
canMergeGrandChildIn(grandChild) {
var _a;
const gcParents = grandChild.parents();
if (gcParents.length !== 1) {
return false;
}
return this.deferRef === grandChild.deferRef && !!gcParents[0].path && !!((_a = gcParents[0].group.parentRelation(this)) === null || _a === void 0 ? void 0 : _a.path);
}
mergeGrandChildIn(grandChild) {
const gcParents = grandChild.parents();
(0, federation_internals_1.assert)(gcParents.length === 1, () => `Cannot merge ${grandChild} as it has multiple parents ([${gcParents}])`);
const gcParent = gcParents[0];
const gcGrandParent = gcParent.group.parentRelation(this);
(0, federation_internals_1.assert)(gcGrandParent, () => `Cannot merge ${grandChild} into ${this}: the former parent (${gcParent.group}) is not a child of the latter`);
(0, federation_internals_1.assert)(gcParent.path && gcGrandParent.path, () => `Cannot merge ${grandChild} into ${this}: some paths in parents are unknown`);
this.mergeInInternal(grandChild, (0, federation_internals_1.concatOperationPaths)(gcGrandParent.path, gcParent.path));
}
mergeInWithAllDependencies(other) {
(0, federation_internals_1.assert)(this.deferRef === other.deferRef, () => `Can only merge unrelated groups within the same @defer block: cannot merge ${this} and ${other}`);
(0, federation_internals_1.assert)(this.subgraphName === other.subgraphName, () => `Can only merge unrelated groups to the same subraphs: cannot merge ${this} and ${other}`);
(0, federation_internals_1.assert)(sameMergeAt(this.mergeAt, other.mergeAt), () => `Can only merge unrelated groups at the same "mergeAt": ${this} has mergeAt=${this.mergeAt}, but ${other} has mergeAt=${other.mergeAt}`);
this.copyInputsOf(other);
this.mergeInInternal(other, [], true);
}
mergeInInternal(merged, path, mergeParentDependencies = false) {
(0, federation_internals_1.assert)(!merged.isTopLevel, "Shouldn't remove top level groups");
if (path.length === 0) {
this.addSelections(merged.selection);
}
else {
const mergePathConditionalDirectives = (0, federation_internals_1.conditionalDirectivesInOperationPath)(path);
this.addAtPath(path, removeUnneededTopLevelFragmentDirectives(merged.selection, mergePathConditionalDirectives));
}
this.dependencyGraph.onModification();
this.relocateChildrenOnMergedIn(merged, path);
if (mergeParentDependencies) {
this.relocateParentsOnMergedIn(merged);
}
if (merged.mustPreserveSelection) {
this.mustPreserveSelection = true;
}
this.dependencyGraph.remove(merged);
}
removeUselessChild(child) {
const relationToChild = child.parentRelation(this);
(0, federation_internals_1.assert)(relationToChild, () => `Cannot remove useless ${child} of ${this}: the former is not a child of the latter`);
const childPathInThis = relationToChild.path;
(0, federation_internals_1.assert)(childPathInThis, () => `Cannot remove useless ${child} of ${this}: the path of the former into the later is unknown`);
this.dependencyGraph.onModification();
this.relocateChildrenOnMergedIn(child, childPathInThis);
this.dependencyGraph.remove(child);
}
relocateChildrenOnMergedIn(merged, pathInThis) {
var _a;
for (const child of merged.children()) {
if (this.isParentOf(child)) {
continue;
}
const pathInMerged = (_a = child.parentRelation(merged)) === null || _a === void 0 ? void 0 : _a.path;
child.addParent({ group: this, path: concatPathsInParents(pathInThis, pathInMerged) });
}
}
relocateParentsOnMergedIn(merged) {
for (const parent of merged.parents()) {
if (parent.group.isParentOf(this)) {
continue;
}
if (parent.group.isDescendantOf(this)) {
continue;
}
this.addParent(parent);
}
}
finalizeSelection(variableDefinitions, handledConditions) {
const selectionWithoutConditions = (0, conditions_1.removeConditionsFromSelectionSet)(this.selection, handledConditions);
const selectionWithTypenames = addTypenameFieldForAbstractTypes(selectionWithoutConditions);
const { updated: selection, outputRewrites } = addAliasesForNonMergingFields(selectionWithTypenames);
selection.validate(variableDefinitions, true);
return { selection, outputRewrites };
}
conditions() {
return this._selection.memoized().conditions;
}
toPlanNode(queryPlannerConfig, handledConditions, variableDefinitions, fragments, operationName, directives) {
var _a, _b;
if (this.selection.isEmpty()) {
return undefined;
}
for (const [context, type] of (_b = (_a = this.inputs) === null || _a === void 0 ? void 0 : _a.usedContexts) !== null && _b !== void 0 ? _b : []) {
(0, federation_internals_1.assert)((0, federation_internals_1.isInputType)(type), () => `Expected ${type} to be a input type`);
variableDefinitions.add(new federation_internals_1.VariableDefinition(type.schema(), new federation_internals_1.Variable(context), type));
}
const { selection, outputRewrites } = this.finalizeSelection(variableDefinitions, handledConditions);
const inputNodes = this._inputs ? this._inputs.toSelectionSetNode(variableDefinitions, handledConditions) : undefined;
const subgraphSchema = this.dependencyGraph.subgraphSchemas.get(this.subgraphName);
let operation = this.isEntityFetch
? operationForEntitiesFetch(subgraphSchema, selection, variableDefinitions, operationName, directives)
: operationForQueryFetch(subgraphSchema, this.rootKind, selection, variableDefinitions, operationName, directives);
if (this.generateQueryFragments) {
operation = operation.generateQueryFragments();
}
else {
operation = operation.optimize(fragments === null || fragments === void 0 ? void 0 : fragments.forSubgraph(this.subgraphName, subgraphSchema), federation_internals_1.DEFAULT_MIN_USAGES_TO_OPTIMIZE, variableDefinitions);
}
const collector = new federation_internals_1.VariableCollector();
selection.collectVariables(collector);
operation.collectVariablesInAppliedDirectives(collector);
if (operation.fragments) {
for (const namedFragment of operation.fragments.definitions()) {
namedFragment.collectVariables(collector);
}
}
const usedVariables = collector.variables();
const operationDocument = (0, federation_internals_1.operationToDocument)(operation);
const fetchNode = {
kind: 'Fetch',
id: this.id,
serviceName: this.subgraphName,
requires: inputNodes ? (0, QueryPlan_1.trimSelectionNodes)(inputNodes.selections) : undefined,
variableUsages: usedVariables.map((v) => v.name),
operation: (0, graphql_1.stripIgnoredCharacters)((0, graphql_1.print)(operationDocument)),
operationKind: schemaRootKindToOperationKind(operation.rootKind),
operationName: operation.name,
operationDocumentNode: queryPlannerConfig.exposeDocumentNodeInFetchNode ? operationDocument : undefined,
inputRewrites: this.inputRewrites.length === 0 ? undefined : this.inputRewrites,
outputRewrites: outputRewrites.length === 0 ? undefined : outputRewrites,
contextRewrites: this._contextInputs,
};
return this.isTopLevel
? fetchNode
: {
kind: 'Flatten',
path: this.mergeAt,
node: fetchNode,
};
}
addContextRenamer(renamer) {
if (!this._contextInputs) {
this._contextInputs = [];
}
if (!this._contextInputs.some((c) => sameKeyRenamer(c, renamer))) {
this._contextInputs.push(renamer);
}
}
toString() {
const base = `[${this.index}]${this.deferRef ? '(deferred)' : ''}${this._id ? `{id: ${this._id}}` : ''} ${this.subgraphName}`;
return this.isTopLevel
? `${base}[${this._selection}]`
: `${base}@(${this.mergeAt})[${this._inputs} => ${this._selection}]`;
}
}
class RebasedFragments {
constructor(queryFragments) {
this.queryFragments = queryFragments;
this.bySubgraph = new Map();
}
forSubgraph(name, schema) {
var _a;
let frags = this.bySubgraph.get(name);
if (frags === undefined) {
frags = (_a = this.queryFragments.rebaseOn(schema)) !== null && _a !== void 0 ? _a : null;
this.bySubgraph.set(name, frags);
}
return frags !== null && frags !== void 0 ? frags : undefined;
}
}
function genAliasName(baseName, unavailableNames) {
let counter = 0;
let candidate = `${baseName}__alias_${counter}`;
while (unavailableNames.has(candidate)) {
candidate = `${baseName}__alias_${++counter}`;
}
return candidate;
}
function selectionSetAsKeyRenamers(selectionSet, relPath, alias) {
if (!selectionSet || selectionSet.isEmpty()) {
return [
{
kind: 'KeyRenamer',
path: relPath,
renameKeyTo: alias,
}
];
}
return selectionSet.selections().map((selection) => {
if (selection.kind === 'FieldSelection') {
if (relPath[relPath.length - 1] === '..' && selectionSet.parentType.name !== 'Query') {
return (0, federation_internals_1.possibleRuntimeTypes)(selectionSet.parentType).map((t) => selectionSetAsKeyRenamers(selectionSet, [...relPath, `... on ${t.name}`], alias)).flat();
}
else {
return selectionSetAsKeyRenamers(selection.selectionSet, [...relPath, selection.element.name], alias);
}
}
else if (selection.kind === 'FragmentSelection') {
const element = selection.element;
if (element.typeCondition) {
return selectionSetAsKeyRenamers(selection.selectionSet, [...relPath, `... on ${element.typeCondition.name}`], alias);
}
}
return undefined;
}).filter(federation_internals_1.isDefined)
.reduce((acc, val) => acc.concat(val), []);
}
function computeAliasesForNonMergingFields(selections, aliasCollector) {
const seenResponseNames = new Map();
const rebasedFieldsInSet = (s) => (s.selections.fieldsInSet().map(({ path, field }) => ({ fieldPath: s.path.concat(path), field })));
for (const { fieldPath, field } of selections.map((s) => rebasedFieldsInSet(s)).flat()) {
const fieldName = field.element.name;
const responseName = field.element.responseName();
const fieldType = field.element.definition.type;
const previous = seenResponseNames.get(responseName);
if (previous) {
if (previous.fieldName === fieldName && (0, federation_internals_1.typesCanBeMerged)(previous.fieldType, fieldType)) {
if ((0, federation_internals_1.isCompositeType)((0, federation_internals_1.baseType)(fieldType))) {
(0, federation_internals_1.assert)(previous.selections, () => `Should have added selections for ${previous.fieldType}`);
const selections = previous.selections.concat({ path: fieldPath.concat(responseName), selections: field.selectionSet });
seenResponseNames.set(responseName, { ...previous, selections });
}
}
else {
const alias = genAliasName(responseName, seenResponseNames);
const selections = field.selectionSet ? [{ path: fieldPath.concat(alias), selections: field.selectionSet }] : undefined;
seenResponseNames.set(alias, { fieldName, fieldType, selections });
aliasCollector.push({
path: fieldPath,
responseName,
alias
});
}
}
else {
const selections = field.selectionSet ? [{ path: fieldPath.concat(responseName), selections: field.selectionSet }] : undefined;
seenResponseNames.set(responseName, { fieldName, fieldType, selections });
}
}
for (const selections of seenResponseNames.values()) {
if (!selections.selections) {
continue;
}
computeAliasesForNonMergingFields(selections.selections, aliasCollector);
}
}
function addAliasesForNonMergingFields(selectionSet) {
const aliases = [];
computeAliasesForNonMergingFields([{ path: [], selections: selectionSet }], aliases);
const updated = withFieldAliased(selectionSet, aliases);
const outputRewrites = aliases.map(({ path, responseName, alias }) => ({
kind: 'KeyRenamer',
path: path.concat(alias),
renameKeyTo: responseName,
}));
return { updated, outputRewrites };
}
function withFieldAliased(selectionSet, aliases) {
if (aliases.length === 0) {
return selectionSet;
}
const atCurrentLevel = new Map();
const remaining = new Array();
for (const alias of aliases) {
if (alias.path.length > 0) {
remaining.push(alias);
}
else {
atCurrentLevel.set(alias.responseName, alias);
}
}
return selectionSet.lazyMap((selection) => {
const pathElement = selection.element.asPathElement();
const subselectionAliases = remaining.map((alias) => {
if (alias.path[0] === pathElement) {
return {
...alias,
path: alias.path.slice(1),
};
}
else {
return undefined;
}
}).filter(federation_internals_1.isDefined);
const updatedSelectionSet = selection.selectionSet
? withFieldAliased(selection.selectionSet, subselectionAliases)
: undefined;
if (selection.kind === 'FieldSelection') {
const field = selection.element;
const alias = pathElement && atCurrentLevel.get(pathElement);
return !alias && selection.selectionSet === updatedSelectionSet
? selection
: selection.withUpdatedComponents(alias ? field.withUpdatedAlias(alias.alias) : field, updatedSelectionSet);
}
else {
return selection.selectionSet === updatedSelectionSet
? selection
: selection.withUpdatedSelectionSet(updatedSelectionSet);
}
});
}
class DeferredInfo {
constructor(label, path, subselection, deferred = new Set(), dependencies = new Set()) {
this.label = label;
this.path = path;
this.subselection = subselection;
this.deferred = deferred;
this.dependencies = dependencies;
}
static empty(label, path, parentType) {
return new DeferredInfo(label, path, federation_internals_1.MutableSelectionSet.empty(parentType));
}
clone() {
return new DeferredInfo(this.label, this.path, this.subselection.clone(), new Set(this.deferred), new Set(this.dependencies));
}
}
const emptyDeferContext = {
currentDeferRef: undefined,
pathToDeferParent: [],
activeDeferRef: undefined,
isPartOfQuery: true,
};
function deferContextForConditions(baseContext) {
return {
...baseContext,
isPartOfQuery: false,
currentDeferRef: baseContext.activeDeferRef,
};
}
function deferContextAfterSubgraphJump(baseContext) {
return baseContext.currentDeferRef === baseContext.activeDeferRef
? baseContext
: {
...baseContext,
activeDeferRef: baseContext.currentDeferRef,
};
}
function filterOperationPath(path, schema) {
return path.map((elt) => {
if (elt.kind === 'FragmentElement' && elt.typeCondition && !schema.type(elt.typeCondition.name)) {
return elt.appliedDirectives.length > 0 ? elt.withUpdatedCondition(undefined) : undefined;
}
return elt;
}).filter(federation_internals_1.isDefined);
}
class GroupPath {
constructor(fullPath, pathInGroup, responsePath, typeConditionedFetching, possibleTypes, possibleTypesAfterLastField) {
this.fullPath = fullPath;
this.pathInGroup = pathInGroup;
this.responsePath = responsePath;
this.typeConditionedFetching = typeConditionedFetching;
this.possibleTypes = possibleTypes;
this.possibleTypesAfterLastField = possibleTypesAfterLastField;
}
static empty(typeConditionedFetching, rootType) {
const rootPossibleRuntimeTypes = typeConditionedFetching ? Array.from((0, federation_internals_1.possibleRuntimeTypes)(rootType)) : [];
rootPossibleRuntimeTypes.sort();
return new GroupPath([], [], [], typeConditionedFetching, rootPossibleRuntimeTypes, rootPossibleRuntimeTypes);
}
inGroup() {
return this.pathInGroup;
}
full() {
return this.fullPath;
}
inResponse() {
return this.responsePath;
}
forNewKeyFetch(newGroupContext) {
return new GroupPath(this.fullPath, newGroupContext, this.responsePath, this.typeConditionedFetching, this.possibleTypes, this.possibleTypesAfterLastField);
}
forParentOfGroup(pathOfGroupInParent, parentSchema) {
return new GroupPath(this.fullPath, (0, federation_internals_1.concatOperationPaths)(pathOfGroupInParent, filterOperationPath(this.pathInGroup, parentSchema)), this.responsePath, this.typeConditionedFetching, this.possibleTypes, this.possibleTypesAfterLastField);
}
updatedResponsePath(element) {
switch (element.kind) {
case 'FragmentElement':
return this.responsePath;
case 'Field':
let newPath = this.responsePath;
if (this.possibleTypesAfterLastField.length !== this.possibleTypes.length) {
const conditions = `|[${this.possibleTypes.join(',')}]`;
const previousLastElement = newPath[newPath.length - 1] || '';
if (previousLastElement.startsWith('|[')) {
newPath = [...newPath.slice(0, -1), conditions];
}
else {
newPath = [...newPath.slice(0, -1), `${previousLastElement}${conditions}`];
}
}
let type = element.definition.type;
if (newPath.length === 0 && this.typeConditionedFetching) {
newPath = newPath.concat('');
}
newPath = newPath.concat(`${element.responseName()}`);
while (!(0, federation_internals_1.isNamedType)(type)) {
if ((0, federation_internals_1.isListType)(type)) {
newPath.push('@');
}
type = type.ofType;
}
return newPath;
}
}
add(element) {
const responsePath = this.updatedResponsePath(element);
const newPossibleTypes = this.computeNewPossibleTypes(element);
return new GroupPath(this.fullPath.concat(element), this.pathInGroup.concat(element), responsePath, this.typeConditionedFetching, newPossibleTypes, element.kind === 'Field' ? newPossibleTypes : this.possibleTypesAfterLastField);
}
toString() {
return this.inResponse().join('.');
}
computeNewPossibleTypes(element) {
if (!this.typeConditionedFetching) {
return [];
}
switch (element.kind) {
case 'FragmentElement':
if (!element.typeCondition) {
return this.possibleTypes;
}
const elementPossibleTypes = (0, federation_internals_1.possibleRuntimeTypes)(element.typeCondition);
return this.possibleTypes.filter((pt) => elementPossibleTypes.some((ept) => ept.name === pt.name));
case 'Field':
return this.advanceFieldType(element);
}
}
advanceFieldType(element) {
if (!(0, federation_internals_1.isCompositeType)(element.baseType())) {
return [];
}
const res = Array.from(new Set(this.possibleTypes.map((pt) => (0, federation_internals_1.possibleRuntimeTypes)((0, federation_internals_1.baseType)(pt.field(element.name).type))).flat()));
res.sort();
return res;
}
}
class DeferTracking {
constructor(primarySelection) {
this.primarySelection = primarySelection;
this.topLevelDeferred = new Set();
this.deferred = new federation_internals_1.MapWithCachedArrays();
}
static empty(rootType) {
return new DeferTracking(rootType ? federation_internals_1.MutableSelectionSet.empty(rootType) : undefined);
}
clone() {
var _a;
const cloned = new DeferTracking((_a = this.primarySelection) === null || _a === void 0 ? void 0 : _a.clone());
this.topLevelDeferred.forEach((label) => cloned.topLevelDeferred.add(label));
for (const deferredBlock of this.deferred.values()) {
cloned.deferred.set(deferredBlock.label, deferredBlock.clone());
}
return cloned;
}
registerDefer({ deferContext, deferArgs, path, parentType, }) {
if (!this.primarySelection) {
return;
}
(0, federation_internals_1.assert)(deferArgs.label, 'All @defer should have be labelled at this point');
let deferredBlock = this.deferred.get(deferArgs.label);
if (!deferredBlock) {
deferredBlock = DeferredInfo.empty(deferArgs.label, path, parentType);
this.deferred.set(deferArgs.label, deferredBlock);
}
const parentRef = deferContext.currentDeferRef;
if (!parentRef) {
this.topLevelDeferred.add(deferArgs.label);
this.primarySelection.updates().addAtPath(deferContext.pathToDeferParent);
}
else {
const parentInfo = this.deferred.get(parentRef);
(0, federation_internals_1.assert)(parentInfo, `Cannot find info for parent ${parentRef} or ${deferArgs.label}`);
parentInfo.deferred.add(deferArgs.label);
parentInfo.subselection.updates().addAtPath(deferContext.pathToDeferParent);
}
}
updateSubselection(deferContext, selection) {
if (!this.primarySelection || !deferContext.isPartOfQuery) {
return;
}
const parentRef = deferContext.currentDeferRef;
let updates;
if (parentRef) {
const info = this.deferred.get(parentRef);
(0, federation_internals_1.assert)(info, () => `Cannot find info for label ${parentRef}`);
updates = info.subselection.updates();
}
else {
updates = this.primarySelection.updates();
}
updates.addAtPath(deferContext.pathToDeferParent, selection);
}
getBlock(label) {
return this.deferred.get(label);
}
addDependency(label, idDependency) {
const info = this.deferred.get(label);
(0, federation_internals_1.assert)(info, () => `Cannot find info for label ${label}`);
info.dependencies.add(idDependency);
}
defersInParent(parentRef) {
var _a;
const labels = parentRef ? (_a = this.deferred.get(parentRef)) === null || _a === void 0 ? void 0 : _a.deferred : this.topLevelDeferred;
return labels
? (0, federation_internals_1.setValues)(labels).map((label) => {
const info = this.deferred.get(label);
(0, federation_internals_1.assert)(info, () => `Should not have referenced ${label} without an existing info`);
return info;
})
: [];
}
}
function printUnhandled(u) {
return '[' + u.map(([g, relations]) => `${g.index} (missing: [${relations.map((r) => r.group.index).join(', ')}])`).join(', ') + ']';
}
class ProcessingState {
constructor(next, unhandled) {
this.next = next;
this.unhandled = unhandled;
}
static empty() {
return new ProcessingState([], []);
}
static forChildrenOfProcessedGroup(processed, children) {
const ready = [];
const unhandled = [];
for (const c of children) {
const parents = c.parents();
if (parents.length === 1) {
ready.push(c);
}
else {
unhandled.push([c, parents.filter((p) => p.group !== processed)]);
}
}
return new ProcessingState(ready, unhandled);
}
static ofReadyGroups(groups) {
return new ProcessingState(groups, []);
}
withOnlyUnhandled() {
return new ProcessingState([], this.unhandled);
}
mergeWith(that) {
const next = this.next.concat(that.next.filter((g) => !this.next.includes(g)));
const unhandled = [];
const thatUnhandled = that.unhandled.concat();
for (const [g, edges] of this.unhandled) {
const newEdges = this.mergeRemaingsAndRemoveIfFound(g, edges, thatUnhandled);
if (newEdges.length == 0) {
if (!next.includes(g)) {
next.push(g);
}
}
else {
unhandled.push([g, newEdges]);
}
}
unhandled.push(...thatUnhandled);
return new ProcessingState(next, unhandled);
}
mergeRemaingsAndRemoveIfFound(group, inEdges, otherGroups) {
const idx = otherGroups.findIndex(g => g[0] === group);
if (idx < 0) {
return inEdges;
}
else {
const otherEdges = otherGroups[idx][1];
otherGroups.splice(idx, 1);
return inEdges.filter(e => otherEdges.includes(e));
}
}
updateForProcessedGroups(processed) {
const next = this.next.concat();
const unhandled = [];
for (const [g, edges] of this.unhandled) {
const newEdges = edges.filter((edge) => !processed.includes(edge.group));
if (newEdges.length === 0) {
if (!next.includes(g)) {
next.push(g);
}
}
else {
unhandled.push([g, newEdges]);
}
}
return new ProcessingState(next, unhandled);
}
}
class FetchDependencyGraph {
constructor(supergraphSchema, subgraphSchemas, federatedQueryGraph, startingIdGen, rootGroups, groups, deferTracking, generateQueryFragments) {
this.supergraphSchema = supergraphSchema;
this.subgraphSchemas = subgraphSchemas;
this.federatedQueryGraph = federatedQueryGraph;
this.startingIdGen = startingIdGen;
this.rootGroups = rootGroups;
this.groups = groups;
this.deferTracking = deferTracking;
this.generateQueryFragments = generateQueryFragments;
this.isReduced = false;
this.isOptimized = false;
this.fetchIdGen = startingIdGen;
}
static create(supergraphSchema, federatedQueryGraph, startingIdGen, rootTypeForDefer, generateQueryFragments) {
return new FetchDependencyGraph(supergraphSchema, federatedQueryGraph.sources, federatedQueryGraph, startingIdGen, new federation_internals_1.MapWithCachedArrays(), [], DeferTracking.empty(rootTypeForDefer), generateQueryFragments);
}
federationMetadata(subgraphName) {
const schema = this.subgraphSchemas.get(subgraphName);
(0, federation_internals_1.assert)(schema, () => `Unknown schema ${subgraphName}`);
const metadata = (0, federation_internals_1.federationMetadata)(schema);
(0, federation_internals_1.assert)(metadata, () => `Schema ${subgraphName} should be a federation subgraph`);
return metadata;
}
nextFetchId() {
return this.fetchIdGen;
}
clone() {
const cloned = new FetchDependencyGraph(this.supergraphSchema, this.subgraphSchemas, this.federatedQueryGraph, this.startingIdGen, new federation_internals_1.MapWithCachedArrays(), new Array(this.groups.length), this.deferTracking.clone(), this.generateQueryFragments);
for (const group of this.groups) {
cloned.groups[group.index] = group.cloneShallow(cloned);
}
for (const root of this.rootGroups.values()) {
cloned.rootGroups.set(root.subgraphName, cloned.groups[root.index]);
}
for (const group of this.groups) {
const clonedGroup = cloned.groups[group.index];
for (const parent of group.parents()) {
clonedGroup.addParent({
group: cloned.groups[parent.group.index],
path: parent.path
});
}
}
return cloned;
}
supergraphSchemaType(typeName) {
return this.supergraphSchema.type(typeName);
}
getOrCreateRootFetchGroup({ subgraphName, rootKind, parentType, }) {
let group = this.rootGroups.get(subgraphName);
if (!group) {
group = this.createRootFetchGroup({ subgraphName, rootKind, parentType });
this.rootGroups.set(subgraphName, group);
}
return group;
}
rootSubgraphs() {
return this.rootGroups.keys();
}
isRootGroup(group) {
return group === this.rootGroups.get(group.subgraphName);
}
createRootFetchGroup({ subgraphName, rootKind, parentType, }) {
const group = this.newFetchGroup({ subgraphName, parentType, rootKind, hasInputs: false });
this.rootGroups.set(subgraphName, group);
return group;
}
newFetchGroup({ subgraphName, parentType, hasInputs, rootKind, mergeAt, deferRef, }) {
this.onModification();
const newGroup = FetchGroup.create({
dependencyGraph: this,
index: this.groups.length,
subgraphName,
rootKind,
parentType,
hasInputs,
mergeAt,
deferRef,
generateQueryFragments: this.generateQueryFragments,
});
this.groups.push(newGroup);
return newGroup;
}
getOrCreateKeyFetchGroup({ subgraphName, mergeAt, type, parent, conditionsGroups, deferRef, }) {
var _a;
for (const existing of parent.group.children()) {
if (existing.subgraphName === subgraphName
&& existing.mergeAt
&& sameMergeAt(existing.mergeAt, mergeAt)
&& existing.selection.selections().every((s) => s.kind === 'FragmentSelection' && s.element.castedType() === type)
&& !this.isInGroupsOrTheirAncestors(existing, conditionsGroups)
&& existing.deferRef === deferRef
&& samePathsInParents((_a = existing.parentRelation(parent.group)) === null || _a === void 0 ? void 0 : _a.path, parent.path)) {
return existing;
}
}
const newGroup = this.newKeyFetchGroup({
subgraphName,
mergeAt,
deferRef
});
newGroup.addParent(parent);
return newGroup;
}
newRootTypeFetchGroup({ subgraphName, rootKind, parentType, mergeAt, deferRef, }) {
return this.newFetchGroup({
subgraphName,
parentType,
rootKind,
hasInputs: false,
mergeAt,
deferRef
});
}
isInGroupsOrTheirAncestors(toCheck, conditions) {
const stack = conditions.concat();
while (stack.length > 0) {
const group = stack.pop();
if (toCheck === group) {
return true;
}
stack.push(...group.parentGroups());
}
return false;
}
typeForFetchInputs(name) {
const type = this.supergraphSchema.type(name);
(0, federation_internals_1.assert)(type, `Type ${name} should exist in the supergraph`);
(0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(type), `Type ${type} should be a composite, but got ${type.kind}`);
return type;
}
newKeyFetchGroup({ subgraphName, mergeAt, deferRef, }) {
const parentType = this.federationMetadata(subgraphName).entityType();
(0, federation_internals_1.assert)(parentType, () => `Subgraph ${subgraphName} has no entities defined`);
return this.newFetchGroup({
subgraphName,
parentType,
hasInputs: true,
rootKind: 'query',
mergeAt,
deferRef
});
}
remove(toRemove) {
this.onModification();
const children = toRemove.children().concat();
const parents = toRemove.parents().concat();
for (const child of children) {
(0, federation_internals_1.assert)(child.parents().length > 1, () => `Cannot remove ${toRemove} as it is the *only* parent of ${child}`);
toRemove.removeChild(child);
}
for (const parent of parents) {
parent.group.removeChild(toRemove);
}
this.groups.splice(toRemove.index, 1);
for (let i = toRemove.index; i < this.groups.length; i++) {
--this.groups[i].index;
}
}
onModification() {
this.isReduced = false;
this.isOptimized = false;
}
reduce() {
if (this.isReduced) {
return;
}
for (const group of this.groups) {
this.dfsRemoveRedundantEdges(group);
}
this.isReduced = true;
}
reduceAndOptimize() {
if (this.isOptimized) {
return;
}
this.reduce();
for (const group of this.rootGroups.values()) {
this.removeEmptyGroups(group);
}
for (const group of this.rootGroups.values()) {
this.removeUselessGroups(group);
}
for (const group of this.rootGroups.values()) {
this.mergeChildFetchesForSameSubgraphAndPath(group);
}
this.mergeFetchesToSameSubgraphAndSameInputs();
this.isOptimized = true;
}
removeEmptyGroups(group) {
const children = group.children().concat();
if (group.selection.isEmpty() && !this.isRootGroup(group)) {
this.remove(group);
}
for (const g of children) {
this.removeEmptyGroups(g);
}
}
removeUselessGroups(group) {
const children = group.children().concat();
for (const child of children) {
this.removeUselessGroups(child);
}
if (group.isUseless()) {
if (group.children().length === 0) {
this.remove(group);
}
else {
const parents = group.parents();
const parent = parents[0];
if (parents.length === 1 && parent.path) {
parent.group.removeUselessChild(group);
}
}
}
}
mergeChildFetchesForSameSubgraphAndPath(group) {
const children = group.children();
if (children.length > 1) {
for (let i = 0; i < children.length; i++) {
const gi = children[i];
let j = i + 1;
while (j < children.length) {
const gj = children[j];
if (gi.canMergeSiblingIn(gj)) {
gi.mergeSiblingIn(gj);
this.dfsRemoveRedundantEdges(gi);
}
else {
++j;
}
}
}
}
for (const g of children) {
this.mergeChildFetchesForSameSubgraphAndPath(g);
}
}
mergeFetchesToSameSubgraphAndSameInputs() {
const bySubgraphs = new federation_internals_1.MultiMap();
for (const group of this.groups) {
if (group.inputs) {
bySubgraphs.add(group.subgraphAndMergeAtKey, group);
}
}
for (const groups of bySubgraphs.values()) {
if (groups.length <= 1) {
continue;
}
const toMergeBuckets = [];
while (groups.length > 1) {
const group = groups.pop();
const bucket = [group];
let i = 0;
while (i < groups.length) {
const current = groups[i];
if (group.deferRef === current.deferRef && group.inputs.equals(current.inputs)) {
bucket.push(current);
groups.splice(i, 1);
}
else {
++i;
}
}
if (bucket.length > 1) {
toMergeBuckets.push(bucket);
}
}
for (const bucket of toMergeBuckets) {
const group = bucket.pop();
for (const other of bucket) {
group.mergeInWithAllDependencies(other);
}
}
}
this.reduce();
}
dfsRemoveRedundantEdges(from) {
for (const startVertex of from.children()) {
const stack = startVertex.children().concat();
while (stack.length > 0) {
const v = stack.pop();
from.removeChild(v);
stack.push(...v.children());
}
}
}
extractChildrenAndDeferredDependencies(group) {
const children = [];
const deferredGroups = new federation_internals_1.SetMultiMap();
for (const child of group.children()) {
if (group.deferRef === child.deferRef) {
children.push(child);
}
else {
(0, federation_internals_1.assert)(child.deferRef, () => `${group} has deferRef "${group.deferRef}", so its child ${child} cannot have a top-level deferRef`);
if (!group.selection.isEmpty()) {
if (!group.id) {
group.id = String(this.fetchIdGen++);
}
this.deferTracking.addDependency(child.deferRef, group.id);
}
deferredGroups.add(child.deferRef, child);
}
}
return { children, deferredGroups };
}
processGroup(processor, group, handledConditions) {
const conditions = (0, conditions_1.updatedConditions)(group.conditions(), handledConditions);
const newHandledConditions = (0, conditions_1.mergeConditions)(conditions, handledConditions);
const { children, deferredGroups } = this.extractChildrenAndDeferredDependencies(group);
const processed = processor.onFetchGroup(group, newHandledConditions);
if (children.length == 0) {
return { main: processor.onConditions(conditions, processed), state: ProcessingState.empty(), deferredGroups };
}
const state = ProcessingState.forChildrenOfProcessedGroup(group, children);
if (state.next.length > 0) {
const { mainSequence, newState, deferredGroups: allDeferredGroups, } = this.processRootMainGroups({
processor,
state,
rootsAreParallel: true,
initialDeferredGroups: deferredGroups,
handledConditions: newHandledConditions,
});
return {
main: processor.onConditions(conditions, processor.reduceSequence([processed].concat(mainSequence))),
state: newState,
deferredGroups: allDeferredGroups,
};
}
else {
return {
main: processor.onConditions(conditions, processed),
state,
deferredGroups,
};
}
}
processGroups(processor, state, processInParallel, handledConditions) {
const processedNodes = [];
const allDeferredGroups = new federation_internals_1.SetMultiMap();
let newState = state.withOnlyUnhandled();
for (const group of state.next) {
const { main, deferredGroups, state: stateAfterGroup } = this.processGroup(processor, group, handledConditions);
processedNodes.push(main);
allDeferredGroups.addAll(deferredGroups);
newState = newState.mergeWith(stateAfterGroup);
}
return {
processed: processInParallel ? processor.reduceParallel(processedNodes) : processor.reduceSequence(processedNodes),
newState: newState.updateForProcessedGroups(state.next),
deferredGroups: allDeferredGroups,
};
}
processRootMainGroups({ processor, state, rootsAreParallel, initialDeferredGroups, handledConditions, }) {
const mainSequence = [];
const allDeferredGroups = initialDeferredGroups
? new federation_internals_1.SetMultiMap(initialDeferredGroups)
: new federation_internals_1.SetMultiMap();
let processInParallel = rootsAreParallel;
while (state.next.length > 0) {
const { processed, newState, deferredGroups } = this.processGroups(processor, state, processInParallel, handledConditions);
processInParallel = true;
mainSequence.push(processed);
state = newState;
allDeferredGroups.addAll(deferredGroups);
}
return {
mainSequence,
newState: state,
deferredGroups: allDeferredGroups,
};
}
processRootGroups({ processor, rootGroups, rootsAreParallel = true, currentDeferRef, otherDeferGroups = undefined, handledConditions, }) {
var _a;
const { mainSequence, newState, deferredGroups, } = this.processRootMainGroups({ processor, rootsAreParallel, state: ProcessingState.ofReadyGroups(rootGroups), handledConditions });
(0, federation_internals_1.assert)(newState.next.length === 0, () => `Should not have left some ready groups, but got ${newState.next}`);
(0, federation_internals_1.assert)(newState.unhandled.length == 0, () => `Root groups:\n${rootGroups.map((g) => ` - ${g}`).join('\n')}\nshould have no remaining groups unhandled, but got: ${printUnhandled(newState.unhandled)}`);
const allDeferredGroups = new federation_internals_1.SetMultiMap();
if (otherDeferGroups) {
allDeferredGroups.addAll(otherDeferGroups);
}
allDeferredGroups.addAll(deferredGroups);
const defersInCurrent = this.deferTracking.defersInParent(currentDeferRef);
const handledDefersInCurrent = new Set(defersInCurrent.map((d) => d.label));
const unhandledDefersInCurrent = (0, federation_internals_1.mapKeys)(allDeferredGroups).filter((label) => !handledDefersInCurrent.has(label));
let unhandledDeferGroups = undefined;
if (unhandledDefersInCurrent.length > 0) {
unhandledDeferGroups = new federation_internals_1.SetMultiMap();
for (const label of unhandledDefersInCurrent) {
unhandledDeferGroups.set(label, allDeferredGroups.get(label));
}
}
const allDeferred = [];
for (const defer of defersInCurrent) {
const groups = (_a = allDeferredGroups.get(defer.label)) !== null && _a !== void 0 ? _a : [];
const { mainSequence: mainSequenceOfDefer, deferred: deferredOfDefer } = this.processRootGroups({
processor,
rootGroups: Array.from(groups),
rootsAreParallel: true,
currentDeferRef: defer.label,
otherDeferGroups: unhandledDeferGroups,
handledConditions,
});
const mainReduced = processor.reduceSequence(mainSequenceOfDefer);
const processed = deferredOfDefer.length === 0
? mainReduced
: processor.reduceDefer(mainReduced, defer.subselection.get(), deferredOfDefer);
allDeferred.push(processor.reduceDeferred(defer, processed));
}
return { mainSequence, deferred: allDeferred };
}
process(processor, rootKind) {
this.reduceAndOptimize();
const { mainSequence, deferred } = this.processRootGroups({
processor,
rootGroups: this.rootGroups.values(),
rootsAreParallel: rootKind === 'query',
handledConditions: true,
});
return {
main: processor.reduceSequence(mainSequence),
deferred,
};
}
dumpOnConsole(msg) {
if (msg) {
console.log(msg);
}
console.log('Groups:');
for (const group of this.groups) {
console.log(` ${group}`);
}
console.log('Children relationships:');
for (const group of this.groups) {
const children = group.children();
if (children.length === 1) {
console.log(` [${group.index}] => [ ${children[0]} ]`);
}
else if (children.length !== 0) {
console.log(` [${group.index}] => [\n ${children.join('\n ')}\n ]`);
}
}
console.log('Parent relationships:');
const printParentRelation = (rel) => (rel.path ? `${rel.group} (path: [${rel.path.join(', ')}])` : rel.group.toString());
for (const group of this.groups) {
const parents = group.parents();
if (parents.length === 1) {
console.log(` [${group.index}] => [ ${printParentRelation(parents[0])} ]`);
}
else if (parents.length !== 0) {
console.log(` [${group.index}] => [\n ${parents.map(printParentRelation).join('\n ')}\n ]`);
}
}
console.log('--------');
}
toString() {
return this.rootGroups.values().map(g => this.toStringInternal(g, "")).join('\n');
}
toStringInternal(group, indent) {
const children = group.children();
return [indent + group.subgraphName + ' <- ' + children.map((child) => child.subgraphName).join(', ')]
.concat(children
.flatMap(g => g.children().length == 0
? []
: this.toStringInternal(g, indent + " ")))
.join('\n');
}
}
class QueryPlanner {
constructor(supergraph, config) {
this.supergraph = supergraph;
this._defaultOverrideConditions = new Map();
this.interfaceTypesWithInterfaceObjects = new Set();
this.inconsistentAbstractTypesRuntimes = new Set();
this.config = (0, config_1.enforceQueryPlannerConfigDefaults)(config);
(0, config_1.validateQueryPlannerConfig)(this.config);
this.federatedQueryGraph = (0, query_graphs_1.buildFederatedQueryGraph)(supergraph, true);
this.collectInterfaceTypesWithInterfaceObjects();
this.collectInconsistentAbstractTypesRuntimes();
this.collectAllOverrideLabels();
if (this.config.debug.bypassPlannerForSingleSubgraph && this.config.incrementalDelivery.enableDefer) {
throw new Error(`Cannot use the "debug.bypassPlannerForSingleSubgraph" query planner option when @defer support is enabled`);
}
}
collectInterfaceTypesWithInterfaceObjects() {
const isInterfaceObject = (name, schema) => {
const typeInSchema = schema.type(name);
return !!typeInSchema && (0, federation_internals_1.isInterfaceObjectType)(typeInSchema);
};
for (const itfType of this.supergraph.schema.interfaceTypes()) {
if ((0, federation_internals_1.mapValues)(this.federatedQueryGraph.sources).some((s) => isInterfaceObject(itfType.name, s))) {
this.interfaceTypesWithInterfaceObjects.add(itfType.name);
}
}
}
collectInconsistentAbstractTypesRuntimes() {
const subgraphs = (0, federation_internals_1.mapValues)(this.federatedQueryGraph.sources);
const isInconsistent = (name) => {
let expectedRuntimes = undefined;
for (const subgraph of subgraphs) {
const typeInSubgraph = subgraph.type(name);
if (!typeInSubgraph || (0, federation_internals_1.isObjectType)(typeInSubgraph)) {
continue;
}
(0, federation_internals_1.assert)((0, federation_internals_1.isAbstractType)(typeInSubgraph), () => `Expected type ${typeInSubgraph} to be abstract but is ${typeInSubgraph.kind}`);
const runtimes = (0, federation_internals_1.possibleRuntimeTypes)(typeInSubgraph);
if (!expectedRuntimes) {
expectedRuntimes = new Set(runtimes.map((t) => t.name));
}
else if (runtimes.length !== expectedRuntimes.size || runtimes.some((t) => !(expectedRuntimes === null || expectedRuntimes === void 0 ? void 0 : expectedRuntimes.has(t.name)))) {
return true;
}
}
return false;
};
for (const type of this.supergraph.schema.types()) {
if (!(0, federation_internals_1.isAbstractType)(type)) {
continue;
}
if ((0, federation_internals_1.isAbstractType)(type) && isInconsistent(type.name)) {
this.inconsistentAbstractTypesRuntimes.add(type.name);
}
}
}
collectAllOverrideLabels() {
var _a, _b;
const applications = (_b = (_a = this.supergraph.schema.directives()
.find((d) => d.name === 'join__field')) === null || _a === void 0 ? void 0 : _a.applications()) !== null && _b !== void 0 ? _b : new Set();
this._defaultOverrideConditions = new Map(Array.from(applications)
.map((application) => application.arguments().overrideLabel)
.filter(Boolean)
.map(label => [label, false]));
}
buildQueryPlan(operation, options) {
var _a;
if (operation.selectionSet.isEmpty()) {
return { kind: 'QueryPlan' };
}
if (!(options === null || options === void 0 ? void 0 : options.recursiveSelectionsLimitDisabled)) {
(0, recursiveSelectionsLimit_1.validateRecursiveSelections)(operation);
}
const isSubscription = operation.rootKind === 'subscription';
const statistics = {
evaluatedPlanCount: 0,
};
this._lastGeneratedPlanStatistics = statistics;
if (this.config.debug.bypassPlannerForSingleSubgraph) {
const subgraphs = (0, federation_internals_1.mapKeys)(this.federatedQueryGraph.sources).filter((name) => name !== query_graphs_1.FEDERATED_GRAPH_ROOT_SOURCE);
if (subgraphs.length === 1) {
const operationDocument = (0, federation_internals_1.operationToDocument)(operation);
const node = {
kind: 'Fetch',
serviceName: subgraphs[0],
variableUsages: operation.variableDefinitions.definitions().map(v => v.variable.name),
operation: (0, graphql_1.stripIgnoredCharacters)((0, graphql_1.print)(operationDocument)),
operationKind: schemaRootKindToOperationKind(operation.rootKind),
operationName: operation.name,
operationDocumentNode: this.config.exposeDocumentNodeInFetchNode ? operationDocument : undefined,
};
return { kind: 'QueryPlan', node };
}
}
const reuseQueryFragments = (_a = this.config.reuseQueryFragments) !== null && _a !== void 0 ? _a : true;
let fragments = operation.fragments;
if (fragments && !fragments.isEmpty() && reuseQueryFragments) {
fragments = addTypenameFieldForAbstractTypesInNamedFragments(fragments);
}
else {
fragments = undefined;
}
operation = operation.expandAllFragments();
operation = withoutIntrospection(operation);
operation = this.withSiblingTypenameOptimizedAway(operation);
let assignedDeferLabels = undefined;
let hasDefers = false;
let deferConditions = undefined;
if (this.config.incrementalDelivery.enableDefer) {
({ operation, hasDefers, assignedDeferLabels, deferConditions } = operation.withNormalizedDefer());
if (isSubscription && hasDefers) {
throw new Error(`@defer is not supported on subscriptions`);
}
}
else {
operation = operation.withoutDefer();
}
debug.group(() => `Computing plan for\n${operation}`);
if (operation.selectionSet.isEmpty()) {
debug.groupEnd('Empty plan');
return { kind: 'QueryPlan' };
}
const root = this.federatedQueryGraph.root(operation.rootKind);
(0, federation_internals_1.assert)(root, () => `Shouldn't have a ${operation.rootKind} operation if the subgraphs don't have a ${operation.rootKind} root`);
const processor = fetchGroupToPlanProcessor({
config: this.config,
variableDefinitions: operation.variableDefinitions,
fragments: fragments ? new RebasedFragments(fragments) : undefined,
operationName: operation.name,
directives: operation.appliedDirectives,
assignedDeferLabels,
});
const overrideConditions = new Map(this._defaultOverrideConditions);
if (options === null || options === void 0 ? void 0 : options.overrideConditions) {
for (const [label, value] of options.overrideConditions) {
overrideConditions.set(label, value);
}
}
const parameters = {
supergraphSchema: this.supergraph.schema,
federatedQueryGraph: this.federatedQueryGraph,
operation,
processor,
root,
statistics,
inconsistentAbstractTypesRuntimes: this.inconsistentAbstractTypesRuntimes,
config: this.config,
overrideConditions,
};
let rootNode;
let nonLocalSelectionsState = (options === null || options === void 0 ? void 0 : options.nonLocalSelectionsLimitDisabled)
? null
: new query_graphs_1.NonLocalSelectionsState();
if (deferConditions && deferConditions.size > 0) {
(0, federation_internals_1.assert)(hasDefers, 'Should not have defer conditions without @defer');
rootNode = computePlanForDeferConditionals({
parameters,
deferConditions,
nonLocalSelectionsState,
});
}
else {
rootNode = computePlanInternal({
parameters,
hasDefers,
nonLocalSelectionsState,
});
}
if (rootNode && isSubscription) {
switch (rootNode.kind) {
case 'Fetch':
{
rootNode = {
kind: 'Subscription',
primary: rootNode,
};
}
break;
case 'Sequence':
{
const [primary, ...rest] = rootNode.nodes;
(0, federation_internals_1.assert)(primary.kind === 'Fetch', 'Primary node of a subscription is not a Fetch');
rootNode = {
kind: 'Subscription',
primary,
rest: {
kind: 'Sequence',
nodes: rest,
},
};
}
break;
default:
throw new Error(`Unexpected top level PlanNode kind: '${rootNode.kind}' when processing subscription`);
}
}
debug.groupEnd('Query plan computed');
return { kind: 'QueryPlan', node: rootNode };
}
optimizeSiblingTypenames(selectionSet) {
const selections = selectionSet.selections();
const parentType = selectionSet.parentType;
const parentMaybeInterfaceObject = this.interfaceTypesWithInterfaceObjects.has(parentType.name);
let updatedSelections = undefined;
let typenameSelection = undefined;
let firstFieldSelection = undefined;
let firstFieldIndex = -1;
for (let i = 0; i < selections.length; i++) {
const selection = selections[i];
let updated;
if (!typenameSelection
&& selection.kind === 'FieldSelection'
&& selection.isPlainTypenameField()
&& !parentMaybeInterfaceObject) {
updated = undefined;
typenameSelection = selection;
}
else {
const updatedSubSelection = selection.selectionSet ? this.optimizeSiblingTypenames(selection.selectionSet) : undefined;
if (updatedSubSelection === selection.selectionSet) {
updated = selection;
}
else {
updated = selection.withUpdatedSelectionSet(updatedSubSelection);
}
if (!firstFieldSelection && updated.kind === 'FieldSelection') {
firstFieldSelection = updated;
firstFieldIndex = updatedSelections
? updatedSelections.length
: i;
}
}
if (updated !== selection && !updatedSelections) {
updatedSelections = [];
for (let j = 0; j < i; j++) {
updatedSelections.push(selections[j]);
}
}
if (updatedSelections && !!updated) {
updatedSelections.push(updated);
}
}
if (!updatedSelections || updatedSelections.length === 0) {
return selectionSet;
}
if (typenameSelection) {
if (firstFieldSelection) {
updatedSelections[firstFieldIndex] = firstFieldSelection.withAttachment(SIBLING_TYPENAME_KEY, typenameSelection.element.alias ? typenameSelection.element.alias : '');
}
else {
updatedSelections = [typenameSelection].concat(updatedSelections);
}
}
return new federation_internals_1.SelectionSetUpdates().add(updatedSelections).toSelectionSet(selectionSet.parentType);
}
withSiblingTypenameOptimizedAway(operation) {
const updatedSelectionSet = this.optimizeSiblingTypenames(operation.selectionSet);
if (updatedSelectionSet === operation.selectionSet) {
return operation;
}
return new federation_internals_1.Operation(operation.schema(), operation.rootKind, updatedSelectionSet, operation.variableDefinitions, operation.fragments, operation.name, operation.appliedDirectives);
}
lastGeneratedPlanStatistics() {
return this._lastGeneratedPlanStatistics;
}
}
exports.QueryPlanner = QueryPlanner;
function computePlanInternal({ parameters, hasDefers, nonLocalSelectionsState, }) {
let main = undefined;
let primarySelection = undefined;
let deferred = [];
const { operation, processor } = parameters;
if (operation.rootKind === 'mutation') {
const dependencyGraphs = computeRootSerialDependencyGraph(parameters, hasDefers, nonLocalSelectionsState);
for (const dependencyGraph of dependencyGraphs) {
const { main: localMain, deferred: localDeferred } = dependencyGraph.process(processor, operation.rootKind);
main = main ? processor.reduceSequence([main, localMain]) : localMain;
deferred = deferred.concat(localDeferred);
const newSelection = dependencyGraph.deferTracking.primarySelection;
if (newSelection) {
if (primarySelection) {
primarySelection.updates().add(newSelection.get());
}
else {
primarySelection = newSelection.clone();
}
}
}
}
else {
const dependencyGraph = computeRootParallelDependencyGraph(parameters, 0, hasDefers, nonLocalSelectionsState);
({ main, deferred } = dependencyGraph.process(processor, operation.rootKind));
primarySelection = dependencyGraph.deferTracking.primarySelection;
}
if (deferred.length > 0) {
(0, federation_internals_1.assert)(primarySelection, 'Should have had a primary selection created');
return processor.reduceDefer(main, primarySelection.get(), deferred);
}
return main;
}
function computePlanForDeferConditionals({ parameters, deferConditions, nonLocalSelectionsState, }) {
return generateConditionNodes(parameters.operation, Array.from(deferConditions.entries()), 0, (op) => computePlanInternal({
parameters: {
...parameters,
operation: op,
},
hasDefers: true,
nonLocalSelectionsState,
}));
}
function generateConditionNodes(operation, conditions, idx, onFinalOperation) {
if (idx >= conditions.length) {
return onFinalOperation(operation);
}
const [variable, labels] = conditions[idx];
const ifOperation = operation;
const elseOperation = operation.withoutDefer(labels);
return {
kind: 'Condition',
condition: variable,
ifClause: generateConditionNodes(ifOperation, conditions, idx + 1, onFinalOperation),
elseClause: generateConditionNodes(elseOperation, conditions, idx + 1, onFinalOperation),
};
}
function isIntrospectionSelection(selection) {
return selection.kind == 'FieldSelection' && selection.element.definition.isIntrospectionField();
}
function mapOptionsToSelections(selectionSet, options) {
return selectionSet.selectionsInReverseOrder().map(node => [node, options]);
}
function possiblePlans(closedBranches) {
let totalCombinations = BigInt(1);
for (let i = 0; i < closedBranches.length; ++i) {
const eltSize = BigInt(closedBranches[i].length);
if (eltSize === BigInt(0)) {
return BigInt(0);
}
totalCombinations *= eltSize;
}
return totalCombinations;
}
function sum(arr) {
return arr.reduce((a, b) => a + b, 0);
}
function selectionCost(selection, depth = 1) {
return selection ? selection.selections().reduce((prev, curr) => prev + depth + selectionCost(curr.selectionSet, depth + 1), 0) : 0;
}
function withoutIntrospection(operation) {
if (!operation.selectionSet.selections().some(isIntrospectionSelection)) {
return operation;
}
return new federation_internals_1.Operation(operation.schema(), operation.rootKind, operation.selectionSet.lazyMap((s) => isIntrospectionSelection(s) ? undefined : s), operation.variableDefinitions, operation.fragments, operation.name, operation.appliedDirectives);
}
function computeRootParallelDependencyGraph(parameters, startFetchIdGen, hasDefer, nonLocalSelectionsState) {
return computeRootParallelBestPlan(parameters, parameters.operation.selectionSet, startFetchIdGen, hasDefer, nonLocalSelectionsState)[0];
}
function computeRootParallelBestPlan(parameters, selection, startFetchIdGen, hasDefers, nonLocalSelectionsState) {
const planningTraversal = new QueryPlanningTraversal(parameters, selection, startFetchIdGen, hasDefers, parameters.root.rootKind, defaultCostFunction, query_graphs_1.emptyContext, parameters.config.typeConditionedFetching, nonLocalSelectionsState);
const plan = planningTraversal.findBestPlan();
return plan !== null && plan !== void 0 ? plan : createEmptyPlan(parameters);
}
function createEmptyPlan(parameters) {
const { supergraphSchema, federatedQueryGraph, root, config } = parameters;
return [
FetchDependencyGraph.create(supergraphSchema, federatedQueryGraph, 0, undefined, config.generateQueryFragments),
query_graphs_1.PathTree.createOp(federatedQueryGraph, root),
0
];
}
function onlyRootSubgraph(graph) {
const subgraphs = graph.rootSubgraphs();
(0, federation_internals_1.assert)(subgraphs.length === 1, () => `${graph} should have only one root, but has [${graph.rootSubgraphs()}]`);
return subgraphs[0];
}
function computeRootSerialDependencyGraph(parameters, hasDefers, nonLocalSelectionsState) {
const { supergraphSchema, federatedQueryGraph, operation, root } = parameters;
const rootType = hasDefers ? supergraphSchema.schemaDefinition.rootType(root.rootKind) : undefined;
const splittedRoots = splitTopLevelFields(operation.selectionSet);
const graphs = [];
let startingFetchId = 0;
let [prevDepGraph, prevPaths] = computeRootParallelBestPlan(parameters, splittedRoots[0], startingFetchId, hasDefers, nonLocalSelectionsState);
let prevSubgraph = onlyRootSubgraph(prevDepGraph);
for (let i = 1; i < splittedRoots.length; i++) {
const [newDepGraph, newPaths] = computeRootParallelBestPlan(parameters, splittedRoots[i], prevDepGraph.nextFetchId(), hasDefers, nonLocalSelectionsState);
const newSubgraph = onlyRootSubgraph(newDepGraph);
if (prevSubgraph === newSubgraph) {
prevPaths = prevPaths.concat(newPaths);
prevDepGraph = computeRootFetchGroups(FetchDependencyGraph.create(supergraphSchema, federatedQueryGraph, startingFetchId, rootType, parameters.config.generateQueryFragments), prevPaths, root.rootKind, parameters.config.typeConditionedFetching);
}
else {
startingFetchId = prevDepGraph.nextFetchId();
graphs.push(prevDepGraph);
[prevDepGraph, prevPaths, prevSubgraph] = [newDepGraph, newPaths, newSubgraph];
}
}
graphs.push(prevDepGraph);
return graphs;
}
function splitTopLevelFields(selectionSet) {
return selectionSet.selections().flatMap(selection => {
if (selection.kind === 'FieldSelection') {
return [(0, federation_internals_1.selectionSetOf)(selectionSet.parentType, selection)];
}
else {
return splitTopLevelFields(selection.selectionSet).map(s => (0, federation_internals_1.selectionSetOfElement)(selection.element, s));
}
});
}
function toValidGraphQLName(subgraphName) {
const sanitized = subgraphName
.replace(/-/ig, '_')
.replace(/[^_0-9A-Za-z]/ig, '');
return sanitized.match(/^[0-9].*/i) ? '_' + sanitized : sanitized;
}
function sanitizeAndPrintSubselection(subSelection) {
var _a;
return (_a = subSelection.withoutEmptyBranches()) === null || _a === void 0 ? void 0 : _a.toString();
}
function fetchGroupToPlanProcessor({ config, variableDefinitions, fragments, operationName, directives, assignedDeferLabels, }) {
let counter = 0;
return {
onFetchGroup: (group, handledConditions) => {
const opName = operationName ? `${operationName}__${toValidGraphQLName(group.subgraphName)}__${counter++}` : undefined;
return group.toPlanNode(config, handledConditions, variableDefinitions, fragments, opName, directives);
},
onConditions: (conditions, value) => {
if (!value) {
return undefined;
}
if ((0, conditions_1.isConstantCondition)(conditions)) {
return conditions ? value : undefined;
}
else {
return conditions.reduce((node, condition) => ({
kind: 'Condition',
condition: condition.variable.name,
ifClause: condition.negated ? undefined : node,
elseClause: condition.negated ? node : undefined,
}), value);
}
},
reduceParallel: (values) => flatWrapNodes('Parallel', values),
reduceSequence: (values) => flatWrapNodes('Sequence', values),
reduceDeferred: (deferInfo, value) => ({
depends: [...deferInfo.dependencies].map((id) => ({ id })),
label: (assignedDeferLabels === null || assignedDeferLabels === void 0 ? void 0 : assignedDeferLabels.has(deferInfo.label)) ? undefined : deferInfo.label,
queryPath: (0, federation_internals_1.operationPathToStringPath)(deferInfo.path.full()),
subselection: deferInfo.deferred.size === 0 ? sanitizeAndPrintSubselection(deferInfo.subselection.get()) : undefined,
node: value,
}),
reduceDefer: (main, subselection, deferredBlocks) => ({
kind: 'Defer',
primary: {
subselection: sanitizeAndPrintSubselection(subselection),
node: main,
},
deferred: deferredBlocks,
}),
};
}
function flatWrapNodes(kind, nodes) {
const filteredNodes = nodes.filter((n) => !!n);
if (filteredNodes.length === 0) {
return undefined;
}
if (filteredNodes.length === 1) {
return filteredNodes[0];
}
return {
kind,
nodes: filteredNodes.flatMap((n) => n.kind === kind ? n.nodes : [n]),
};
}
function addTypenameFieldForAbstractTypesInNamedFragments(fragments) {
(0, federation_internals_1.assert)(!fragments.isEmpty(), 'Should not pass empty fragments to this method');
const updated = fragments.mapToExpandedSelectionSets(addTypenameFieldForAbstractTypes);
(0, federation_internals_1.assert)(updated, 'No fragments should have been removed');
return updated;
}
function removeUnneededTopLevelFragmentDirectives(selectionSet, unneededDirectives) {
return selectionSet.lazyMap((selection) => {
if (selection.kind !== 'FragmentSelection') {
return selection;
}
const fragment = selection.element;
const fragmentType = fragment.typeCondition;
if (!fragmentType) {
return selection;
}
let neededDirectives = [];
if (fragment.appliedDirectives.length > 0) {
neededDirectives = (0, federation_internals_1.directiveApplicationsSubstraction)(fragment.appliedDirectives, unneededDirectives);
}
const updated = removeUnneededTopLevelFragmentDirectives(selection.selectionSet, unneededDirectives);
if (neededDirectives.length === fragment.appliedDirectives.length) {
return selection.selectionSet === updated ? selection : selection.withUpdatedSelectionSet(updated);
}
return selection.withUpdatedComponents(fragment.withUpdatedDirectives(neededDirectives), updated);
});
}
function schemaRootKindToOperationKind(operation) {
switch (operation) {
case "query": return graphql_1.OperationTypeNode.QUERY;
case "mutation": return graphql_1.OperationTypeNode.MUTATION;
case "subscription": return graphql_1.OperationTypeNode.SUBSCRIPTION;
}
}
function findAndRemoveInPlace(predicate, array) {
const idx = array.findIndex((v) => predicate(v));
if (idx >= 0) {
array.splice(idx, 1);
}
return idx;
}
function sameMergeAt(m1, m2) {
if (!m1) {
return !m2;
}
if (!m2) {
return false;
}
return (0, federation_internals_1.arrayEquals)(m1, m2);
}
function concatPathsInParents(first, second) {
return first && second ? (0, federation_internals_1.concatOperationPaths)(first, second) : undefined;
}
function samePathsInParents(first, second) {
if (!first) {
return !second;
}
return !!second && (0, federation_internals_1.sameOperationPaths)(first, second);
}
function computeRootFetchGroups(dependencyGraph, pathTree, rootKind, typeConditionedFetching) {
for (const [edge, _trigger, _conditions, child] of pathTree.childElements()) {
(0, federation_internals_1.assert)(edge !== null, `The root edge should not be null`);
const subgraphName = edge.tail.source;
const rootType = edge.tail.type;
const group = dependencyGraph.getOrCreateRootFetchGroup({ subgraphName, rootKind, parentType: rootType });
const rootTypeInSupergraph = dependencyGraph.supergraphSchemaType(rootType.name);
computeGroupsForTree({
dependencyGraph,
pathTree: child,
startGroup: group,
initialGroupPath: GroupPath.empty(typeConditionedFetching, rootTypeInSupergraph),
initialDeferContext: emptyDeferContext,
});
}
return dependencyGraph;
}
function computeNonRootFetchGroups(dependencyGraph, pathTree, rootKind, typeConditionedFetching) {
const subgraphName = pathTree.vertex.source;
const rootType = pathTree.vertex.type;
(0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(rootType), () => `Should not have condition on non-selectable type ${rootType}`);
const group = dependencyGraph.getOrCreateRootFetchGroup({ subgraphName, rootKind, parentType: rootType });
const rootTypeInSupergraph = dependencyGraph.supergraphSchemaType(rootType.name);
computeGroupsForTree({
dependencyGraph,
pathTree,
startGroup: group,
initialGroupPath: GroupPath.empty(typeConditionedFetching, rootTypeInSupergraph),
initialDeferContext: emptyDeferContext,
});
return dependencyGraph;
}
function wrapInputsSelections(wrappingType, selections, context) {
return wrapSelectionWithTypeAndConditions(wrappingType, selections, (fragment, currentSeletions) => (0, federation_internals_1.selectionSetOf)(fragment.parentType, (0, federation_internals_1.selectionOfElement)(fragment, currentSeletions)), context);
}
function createFetchInitialPath(supergraphSchema, wrappingType, context) {
const rebasedType = supergraphSchema.type(wrappingType.name);
(0, federation_internals_1.assert)(rebasedType && (0, federation_internals_1.isCompositeType)(rebasedType), () => `${wrappingType} should be composite in the supergraph but got ${rebasedType === null || rebasedType === void 0 ? void 0 : rebasedType.kind}`);
return wrapSelectionWithTypeAndConditions(rebasedType, [], (fragment, path) => [fragment].concat(path), context);
}
function wrapSelectionWithTypeAndConditions(wrappingType, initialSelection, wrapInFragment, context) {
if (context.conditionals.length === 0) {
return wrapInFragment(new federation_internals_1.FragmentElement(wrappingType, wrappingType.name), initialSelection);
}
const { kind: name0, value: ifs0 } = context.conditionals[0];
let updatedSelection = wrapInFragment(new federation_internals_1.FragmentElement(wrappingType, wrappingType.name, [new federation_internals_1.Directive(name0, { 'if': ifs0 })]), initialSelection);
for (let i = 1; i < context.conditionals.length; i++) {
const { kind: name, value: ifs } = context.conditionals[i];
updatedSelection = wrapInFragment(new federation_internals_1.FragmentElement(wrappingType, wrappingType.name, [new federation_internals_1.Directive(name, { 'if': ifs })]), updatedSelection);
}
return updatedSelection;
}
function maybeSubstratPathPrefix(basePath, maybePrefix) {
if (maybePrefix.length <= basePath.length && (0, federation_internals_1.sameOperationPaths)(maybePrefix, basePath.slice(0, maybePrefix.length))) {
return basePath.slice(maybePrefix.length);
}
return undefined;
}
function updateCreatedGroups(createdGroups, ...newCreatedGroups) {
for (const newGroup of newCreatedGroups) {
if (!createdGroups.includes(newGroup)) {
createdGroups.push(newGroup);
}
}
}
function computeGroupsForTree({ dependencyGraph, pathTree, startGroup, initialGroupPath, initialDeferContext, initialContext = query_graphs_1.emptyContext, initialContextsToConditionsGroups = new Map(), }) {
const stack = [{
tree: pathTree,
group: startGroup,
path: initialGroupPath,
context: initialContext,
deferContext: initialDeferContext,
contextToConditionsGroups: initialContextsToConditionsGroups,
}];
const createdGroups = [];
while (stack.length > 0) {
const { tree, group, path, context, deferContext, contextToConditionsGroups } = stack.pop();
if (tree.localSelections) {
for (const selection of tree.localSelections) {
group.addAtPath(path.inGroup(), selection);
dependencyGraph.deferTracking.updateSubselection(deferContext, selection);
}
}
if (tree.isLeaf()) {
group.addAtPath(path.inGroup());
dependencyGraph.deferTracking.updateSubselection(deferContext);
}
else {
for (const [edge, operation, conditions, child, contextToSelection, parameterToContext] of tree.childElements(true)) {
if ((0, query_graphs_1.isPathContext)(operation)) {
const newContext = operation;
(0, federation_internals_1.assert)(edge !== null, () => `Unexpected 'null' edge with no trigger at ${path}`);
if (edge.transition.kind === 'KeyResolution') {
(0, federation_internals_1.assert)(conditions, () => `Key edge ${edge} should have some conditions paths`);
const conditionsGroups = computeGroupsForTree({
dependencyGraph,
pathTree: conditions,
startGroup: group,
initialGroupPath: path,
initialDeferContext: deferContextForConditions(deferContext),
});
updateCreatedGroups(createdGroups, ...conditionsGroups);
const sourceType = edge.head.type;
const destType = edge.tail.type;
const pathInParent = path.inGroup();
const updatedDeferContext = deferContextAfterSubgraphJump(deferContext);
const newGroup = dependencyGraph.getOrCreateKeyFetchGroup({
subgraphName: edge.tail.source,
mergeAt: path.inResponse(),
type: destType,
parent: { group, path: pathInParent },
conditionsGroups,
deferRef: updatedDeferContext.activeDeferRef,
});
updateCreatedGroups(createdGroups, newGroup);
newGroup.addParents(conditionsGroups.map((conditionGroup) => {
const conditionGroupParents = conditionGroup.parents();
let path = undefined;
if (conditionGroupParents.length === 1 && conditionGroupParents[0].group === group && conditionGroupParents[0].path) {
path = maybeSubstratPathPrefix(conditionGroupParents[0].path, pathInParent);
}
return { group: conditionGroup, path };
}));
const inputType = dependencyGraph.typeForFetchInputs(sourceType.name);
const inputSelections = newCompositeTypeSelectionSet(inputType);
inputSelections.updates().add(edge.conditions);
newGroup.addInputs(wrapInputsSelections(inputType, inputSelections.get(), newContext), computeInputRewritesOnKeyFetch(inputType.name, destType));
group.addAtPath(path.inGroup().concat(new federation_internals_1.Field(sourceType.typenameField())));
stack.push({
tree: child,
group: newGroup,
path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, edge.tail.type, newContext)),
context: newContext,
deferContext: updatedDeferContext,
contextToConditionsGroups,
});
}
else {
(0, federation_internals_1.assert)(edge.transition.kind === 'RootTypeResolution', () => `Unexpected non-collecting edge ${edge}`);
const rootKind = edge.transition.rootKind;
(0, federation_internals_1.assert)(!conditions, () => `Root type resolution edge ${edge} should not have conditions`);
(0, federation_internals_1.assert)((0, federation_internals_1.isObjectType)(edge.head.type) && (0, federation_internals_1.isObjectType)(edge.tail.type), () => `Expected an objects for the vertices of ${edge}`);
const type = edge.tail.type;
(0, federation_internals_1.assert)(type === type.schema().schemaDefinition.rootType(rootKind), () => `Expected ${type} to be the root ${rootKind} type, but that is ${type.schema().schemaDefinition.rootType(rootKind)}`);
if (path.inGroup().length > 0) {
group.addAtPath(path.inGroup().concat(new federation_internals_1.Field(edge.head.type.typenameField())));
}
const updatedDeferContext = deferContextAfterSubgraphJump(deferContext);
const newGroup = dependencyGraph.newRootTypeFetchGroup({
subgraphName: edge.tail.source,
rootKind,
parentType: type,
mergeAt: path.inResponse(),
deferRef: updatedDeferContext.activeDeferRef,
});
newGroup.addParent({ group, path: path.inGroup() });
stack.push({
tree: child,
group: newGroup,
path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, type, newContext)),
context: newContext,
deferContext: updatedDeferContext,
contextToConditionsGroups,
});
}
}
else if (edge === null) {
const { updatedOperation, updatedDeferContext } = extractDeferFromOperation({
dependencyGraph,
operation,
deferContext,
path,
});
let newPath = path;
if (updatedOperation && updatedOperation.appliedDirectives.length > 0) {
newPath = path.add(updatedOperation);
}
stack.push({
tree: child,
group,
path: newPath,
context,
deferContext: updatedDeferContext,
contextToConditionsGroups,
});
}
else {
(0, federation_internals_1.assert)(edge.head.source === edge.tail.source, () => `Collecting edge ${edge} for ${operation} should not change the underlying subgraph`);
const typenameAttachment = operation.getAttachment(SIBLING_TYPENAME_KEY);
if (typenameAttachment !== undefined) {
const alias = typenameAttachment === '' ? undefined : typenameAttachment;
const typenameField = new federation_internals_1.Field(operation.parentType.typenameField(), undefined, undefined, alias);
group.addAtPath(path.inGroup().concat(typenameField));
dependencyGraph.deferTracking.updateSubselection({
...deferContext,
pathToDeferParent: deferContext.pathToDeferParent.concat(typenameField),
});
}
const { updatedOperation, updatedDeferContext } = extractDeferFromOperation({
dependencyGraph,
operation,
deferContext,
path,
});
(0, federation_internals_1.assert)(updatedOperation, () => `Extracting @defer from ${operation} should not have resulted in no operation`);
const updated = {
tree: child,
group,
path,
context,
deferContext: updatedDeferContext,
contextToConditionsGroups,
};
if (conditions) {
let addTypenameAtPath;
if (contextToSelection) {
(0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(edge.head.type), () => `Expected a composite type for ${edge.head.type}`);
addTypenameAtPath = {
pathType: edge.head.type,
};
}
const handleRequiresResult = handleRequiresTree(dependencyGraph, conditions, group, path, deferContext, addTypenameAtPath);
updateCreatedGroups(createdGroups, ...handleRequiresResult.createdGroups);
if (contextToSelection) {
const newContextToConditionsGroups = new Map([...contextToConditionsGroups]);
for (const context of contextToSelection) {
newContextToConditionsGroups.set(context, [handleRequiresResult.conditionsMergeGroup, ...handleRequiresResult.createdGroups]);
}
updated.contextToConditionsGroups = newContextToConditionsGroups;
}
if (edge.conditions) {
const createPostRequiresResult = createPostRequiresGroup(dependencyGraph, edge, group, path, context, handleRequiresResult);
updated.group = createPostRequiresResult.group;
updated.path = createPostRequiresResult.path;
updateCreatedGroups(createdGroups, ...createPostRequiresResult.createdGroups);
}
}
if (parameterToContext && Array.from(parameterToContext.values()).some(({ contextId }) => { var _a; return ((_a = updated.contextToConditionsGroups.get(contextId)) === null || _a === void 0 ? void 0 : _a[0]) === updated.group; })) {
(0, federation_internals_1.assert)(group === updated.group, "Group created by @requires handling shouldn't have set context already");
const conditionGroups = new Set();
for (const { contextId } of parameterToContext.values()) {
const groups = updated.contextToConditionsGroups.get(contextId);
(0, federation_internals_1.assert)(groups, () => `Could not find groups for context ${contextId}`);
for (const conditionGroup of groups) {
conditionGroups.add(conditionGroup);
}
}
(0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(edge.head.type), () => `Expected a composite type for ${edge.head.type}`);
updated.group = dependencyGraph.getOrCreateKeyFetchGroup({
subgraphName: edge.head.source,
mergeAt: updated.path.inResponse(),
type: edge.head.type,
parent: { group: group, path: path.inGroup() },
conditionsGroups: [...conditionGroups],
});
updateCreatedGroups(createdGroups, updated.group);
updated.path = path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, edge.head.type, context));
const keyCondition = (0, query_graphs_1.getLocallySatisfiableKey)(dependencyGraph.federatedQueryGraph, edge.head);
(0, federation_internals_1.assert)(keyCondition, () => `canSatisfyConditions() validation should have required a key to be present for ${edge}`);
const keyInputs = newCompositeTypeSelectionSet(edge.head.type);
keyInputs.updates().add(keyCondition);
group.addAtPath(path.inGroup(), keyInputs.get());
const inputType = dependencyGraph.typeForFetchInputs(edge.head.type.name);
const inputSelectionSet = newCompositeTypeSelectionSet(inputType);
inputSelectionSet.updates().add(keyCondition);
const inputs = wrapInputsSelections(inputType, inputSelectionSet.get(), context);
updated.group.addInputs(inputs, computeInputRewritesOnKeyFetch(edge.head.type.name, edge.head.type));
for (const parentGroup of conditionGroups) {
updated.group.addParent({ group: parentGroup });
}
for (const [_, { contextId, selectionSet, relativePath, subgraphArgType }] of parameterToContext) {
updated.group.addInputContext(contextId, subgraphArgType);
const keyRenamers = selectionSetAsKeyRenamers(selectionSet, relativePath, contextId);
for (const keyRenamer of keyRenamers) {
updated.group.addContextRenamer(keyRenamer);
}
}
}
else {
if (parameterToContext) {
const numFields = updated.path.inGroup().filter((e) => e.kind === 'Field').length;
for (const [_, { selectionSet, relativePath, contextId, subgraphArgType }] of parameterToContext) {
const newRelativePath = relativePath.slice(0, relativePath.length - numFields);
updated.group.addInputContext(contextId, subgraphArgType);
const keyRenamers = selectionSetAsKeyRenamers(selectionSet, newRelativePath, contextId);
for (const keyRenamer of keyRenamers) {
updated.group.addContextRenamer(keyRenamer);
}
}
}
}
if (updatedOperation.kind === 'Field' && updatedOperation.name === federation_internals_1.typenameFieldName) {
updated.group.mustPreserveSelection = true;
}
if (edge.transition.kind === 'InterfaceObjectFakeDownCast') {
(0, federation_internals_1.assert)(updatedOperation.kind === 'FragmentElement', () => `Unexpected operation ${updatedOperation} for edge ${edge}`);
if (updatedOperation.appliedDirectives.length > 0) {
updated.path = updated.path.add(updatedOperation.withUpdatedCondition(undefined));
}
}
else {
updated.path = updated.path.add(updatedOperation);
}
stack.push(updated);
}
}
}
}
return createdGroups;
}
function computeInputRewritesOnKeyFetch(inputTypeName, destType) {
if ((0, federation_internals_1.isInterfaceObjectType)(destType) || (0, federation_internals_1.isInterfaceType)(destType)) {
return [{
kind: 'ValueSetter',
path: [`... on ${inputTypeName}`, federation_internals_1.typenameFieldName],
setValueTo: destType.name,
}];
}
return undefined;
}
function extractDeferFromOperation({ dependencyGraph, operation, deferContext, path, }) {
const deferArgs = operation.deferDirectiveArgs();
if (!deferArgs) {
return {
updatedOperation: operation,
updatedDeferContext: {
...deferContext,
pathToDeferParent: deferContext.pathToDeferParent.concat(operation),
}
};
}
(0, federation_internals_1.assert)(deferArgs.label, 'All defers should have a lalel at this point');
const updatedDeferRef = deferArgs.label;
const updatedOperation = operation.withoutDefer();
const updatedPathToDeferParent = updatedOperation ? [updatedOperation] : [];
dependencyGraph.deferTracking.registerDefer({
deferContext,
deferArgs,
path,
parentType: operation.parentType,
});
return {
updatedOperation,
updatedDeferContext: {
...deferContext,
currentDeferRef: updatedDeferRef,
pathToDeferParent: updatedPathToDeferParent,
},
};
}
function subselectionTypeIfAbstract(selection) {
if (selection.kind === 'FieldSelection') {
const fieldBaseType = (0, federation_internals_1.baseType)(selection.element.definition.type);
return (0, federation_internals_1.isAbstractType)(fieldBaseType) ? fieldBaseType : undefined;
}
else {
const conditionType = selection.element.typeCondition;
return conditionType && (0, federation_internals_1.isAbstractType)(conditionType) ? conditionType : undefined;
}
}
function addTypenameFieldForAbstractTypes(selectionSet, parentTypeIfAbstract) {
const handleSelection = (selection) => {
if (!selection.selectionSet) {
return selection;
}
const typeIfAbstract = subselectionTypeIfAbstract(selection);
const updatedSelectionSet = addTypenameFieldForAbstractTypes(selection.selectionSet, typeIfAbstract);
if (updatedSelectionSet === selection.selectionSet) {
return selection;
}
else {
return selection.withUpdatedSelectionSet(updatedSelectionSet);
}
};
if (!parentTypeIfAbstract || selectionSet.hasTopLevelTypenameField()) {
return selectionSet.lazyMap((selection) => handleSelection(selection));
}
const updates = new federation_internals_1.SelectionSetUpdates();
updates.add(new federation_internals_1.FieldSelection(new federation_internals_1.Field(parentTypeIfAbstract.typenameField())));
selectionSet.selections().forEach((selection) => updates.add(handleSelection(selection)));
return updates.toSelectionSet(selectionSet.parentType);
}
function addBackTypenameInAttachments(selectionSet) {
return selectionSet.lazyMap((s) => {
const updated = s.mapToSelectionSet((ss) => addBackTypenameInAttachments(ss));
const typenameAttachment = s.element.getAttachment(SIBLING_TYPENAME_KEY);
if (typenameAttachment === undefined) {
return updated;
}
else {
const alias = typenameAttachment === '' ? undefined : typenameAttachment;
const typenameField = new federation_internals_1.Field(s.element.parentType.typenameField(), undefined, undefined, alias);
return [
(0, federation_internals_1.selectionOfElement)(typenameField),
updated,
];
}
});
}
function pathHasOnlyFragments(path) {
return path.every((element) => element.kind === 'FragmentElement');
}
function typeAtPath(parentType, path) {
let type = parentType;
for (const element of path) {
if (element.kind === 'Field') {
const fieldType = (0, federation_internals_1.baseType)(type.field(element.name).type);
(0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(fieldType), () => `Invalid call fro ${path} starting at ${parentType}: ${element.definition.coordinate} is not composite`);
type = fieldType;
}
else if (element.typeCondition) {
const rebasedType = parentType.schema().type(element.typeCondition.name);
(0, federation_internals_1.assert)(rebasedType && (0, federation_internals_1.isCompositeType)(rebasedType), () => `Type condition of ${element} should be composite`);
type = rebasedType;
}
}
return type;
}
function createPostRequiresGroup(dependencyGraph, edge, group, path, context, result) {
const { fullyLocalRequires, createdGroups } = result;
const entityType = edge.head.type;
if (fullyLocalRequires) {
return { group, path, ...result };
}
const parents = group.parents();
const triedToMerge = parents.length === 1 && pathHasOnlyFragments(path.inGroup());
if (triedToMerge) {
const parent = parents[0];
if (createdGroups.length === 0) {
group.addInputs(inputsForRequire(dependencyGraph, entityType, edge, context, false).inputs);
return { group, path, createdGroups: [] };
}
const postRequireGroup = dependencyGraph.newKeyFetchGroup({
subgraphName: group.subgraphName,
mergeAt: group.mergeAt,
deferRef: group.deferRef
});
postRequireGroup.addParents(createdGroups.map((group) => ({ group })));
if (result.requiresParent) {
postRequireGroup.addParent(result.requiresParent);
}
(0, federation_internals_1.assert)(parent.path, `Missing path-in-parent for @requires on ${edge} with group ${group} and parent ${parent}`);
addPostRequireInputs(dependencyGraph, path.forParentOfGroup(parent.path, parent.group.parentType.schema()), entityType, edge, context, parent.group, postRequireGroup);
return {
group: postRequireGroup,
path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, entityType, context)),
createdGroups: [postRequireGroup],
};
}
else {
const postRequireGroup = dependencyGraph.newKeyFetchGroup({
subgraphName: group.subgraphName,
mergeAt: path.inResponse(),
});
postRequireGroup.addParents(createdGroups.map((group) => ({
group,
path: sameMergeAt(group.mergeAt, postRequireGroup.mergeAt)
&& (0, federation_internals_1.sameType)(group.parentType, postRequireGroup.parentType)
? []
: undefined,
})));
addPostRequireInputs(dependencyGraph, path, entityType, edge, context, group, postRequireGroup);
return {
group: postRequireGroup,
path: path.forNewKeyFetch(createFetchInitialPath(dependencyGraph.supergraphSchema, entityType, context)),
createdGroups: [postRequireGroup],
};
}
}
function handleRequiresTree(dependencyGraph, requiresConditions, group, path, deferContext, addTypenameAtPath) {
dependencyGraph.reduce();
const parents = group.parents();
let groupCopy;
if (parents.length === 1 && pathHasOnlyFragments(path.inGroup())) {
const parent = parents[0];
groupCopy = dependencyGraph.newKeyFetchGroup({
subgraphName: group.subgraphName,
mergeAt: group.mergeAt,
deferRef: group.deferRef
});
groupCopy.addParent(parent);
groupCopy.copyInputsOf(group);
if (addTypenameAtPath) {
groupCopy.addAtPath(path.inGroup().concat(new federation_internals_1.Field(addTypenameAtPath.pathType.typenameField())));
}
}
else {
if (addTypenameAtPath) {
group.addAtPath(path.inGroup().concat(new federation_internals_1.Field(addTypenameAtPath.pathType.typenameField())));
}
}
const createdGroups = computeGroupsForTree({
dependencyGraph,
pathTree: requiresConditions,
startGroup: groupCopy !== null && groupCopy !== void 0 ? groupCopy : group,
initialGroupPath: path,
initialDeferContext: deferContextForConditions(deferContext)
});
if (createdGroups.length == 0) {
if (groupCopy) {
(0, federation_internals_1.assert)(group.canMergeSiblingIn(groupCopy), () => `We should be able to merge ${groupCopy} into ${group} by construction`);
group.mergeSiblingIn(groupCopy);
}
return { fullyLocalRequires: true, createdGroups: [], requiresParent: undefined, conditionsMergeGroup: group };
}
if (groupCopy) {
const parent = groupCopy.parents()[0];
groupCopy.removeInputsFromSelection();
const newGroupIsUnneeded = parent.path && groupCopy.selection.canRebaseOn(typeAtPath(parent.group.selection.parentType, parent.path));
const unmergedGroups = [];
if (newGroupIsUnneeded) {
parent.group.mergeChildIn(groupCopy);
for (const created of createdGroups) {
if (created.subgraphName === parent.group.subgraphName && parent.group.canMergeChildIn(created)) {
parent.group.mergeChildIn(created);
}
else {
unmergedGroups.push(created);
let currentParent = parent;
while (currentParent
&& !currentParent.group.isTopLevel
&& created.isChildOfWithArtificialDependency(currentParent.group)) {
currentParent.group.removeChild(created);
const grandParents = currentParent.group.parents();
(0, federation_internals_1.assert)(grandParents.length > 0, `${currentParent.group} is not top-level, so it should have parents`);
for (const grandParent of grandParents) {
created.addParent({
group: grandParent.group,
path: concatPathsInParents(grandParent.path, currentParent.path),
});
}
currentParent = grandParents.length === 1 ? grandParents[0] : undefined;
}
}
}
}
else {
(0, federation_internals_1.assert)(group.canMergeSiblingIn(groupCopy), () => `We should be able to merge ${groupCopy} into ${group} by construction`);
group.mergeSiblingIn(groupCopy);
if (parent.path) {
for (const created of createdGroups) {
if (created.subgraphName === parent.group.subgraphName
&& parent.group.canMergeGrandChildIn(created)
&& sameMergeAt(created.mergeAt, group.mergeAt)
&& group.inputs.contains(created.inputs)) {
parent.group.mergeGrandChildIn(created);
}
else {
unmergedGroups.push(created);
}
}
}
}
return {
fullyLocalRequires: false,
createdGroups: unmergedGroups,
requiresParent: newGroupIsUnneeded ? parent : { group, path: [] },
conditionsMergeGroup: newGroupIsUnneeded ? parent.group : group,
};
}
else {
return {
fullyLocalRequires: false,
createdGroups,
requiresParent: undefined,
conditionsMergeGroup: group,
};
}
}
function addPostRequireInputs(dependencyGraph, requirePath, entityType, edge, context, preRequireGroup, postRequireGroup) {
const { inputs, keyInputs } = inputsForRequire(dependencyGraph, entityType, edge, context);
postRequireGroup.addInputs(inputs, computeInputRewritesOnKeyFetch(entityType.name, entityType));
if (keyInputs) {
preRequireGroup.addAtPath(requirePath.inGroup(), keyInputs.selections());
}
}
function newCompositeTypeSelectionSet(type) {
const selectionSet = federation_internals_1.MutableSelectionSet.empty(type);
selectionSet.updates().add(new federation_internals_1.FieldSelection(new federation_internals_1.Field(type.typenameField())));
return selectionSet;
}
function inputsForRequire(dependencyGraph, entityType, edge, context, includeKeyInputs = true) {
const isInterfaceObjectDownCast = edge.transition.kind === 'InterfaceObjectFakeDownCast';
const inputTypeName = isInterfaceObjectDownCast ? edge.transition.castedTypeName : entityType.name;
const inputType = dependencyGraph.supergraphSchema.type(inputTypeName);
(0, federation_internals_1.assert)(inputType && (0, federation_internals_1.isCompositeType)(inputType), () => `Type ${inputTypeName} should exist in the supergraph and be a composite type`);
const fullSelectionSet = newCompositeTypeSelectionSet(inputType);
if (edge.conditions) {
fullSelectionSet.updates().add(edge.conditions);
}
let keyInputs = undefined;
if (includeKeyInputs) {
const keyCondition = (0, query_graphs_1.getLocallySatisfiableKey)(dependencyGraph.federatedQueryGraph, edge.head);
(0, federation_internals_1.assert)(keyCondition, () => `Due to @require, validation should have required a key to be present for ${edge}`);
let keyConditionAsInput = keyCondition;
if (isInterfaceObjectDownCast) {
const supergraphItfType = dependencyGraph.supergraphSchema.type(entityType.name);
(0, federation_internals_1.assert)(supergraphItfType && (0, federation_internals_1.isInterfaceType)(supergraphItfType), () => `Type ${entityType} should be an interface in the supergraph`);
keyConditionAsInput = keyConditionAsInput.rebaseOn({ parentType: supergraphItfType, fragments: undefined, errorIfCannotRebase: true });
}
fullSelectionSet.updates().add(keyConditionAsInput);
keyInputs = newCompositeTypeSelectionSet(entityType);
keyInputs.updates().add(keyCondition);
}
return {
inputs: wrapInputsSelections(inputType, fullSelectionSet.get(), context),
keyInputs: keyInputs === null || keyInputs === void 0 ? void 0 : keyInputs.get(),
};
}
const representationsVariable = new federation_internals_1.Variable('representations');
function representationsVariableDefinition(schema) {
const metadata = (0, federation_internals_1.federationMetadata)(schema);
(0, federation_internals_1.assert)(metadata, 'Expected schema to be a federation subgraph');
const representationsType = new federation_internals_1.NonNullType(new federation_internals_1.ListType(new federation_internals_1.NonNullType(metadata.anyType())));
return new federation_internals_1.VariableDefinition(schema, representationsVariable, representationsType);
}
function collectUsedVariables(selectionSet, operationDirectives) {
const collector = new federation_internals_1.VariableCollector();
selectionSet.collectVariables(collector);
if (operationDirectives) {
for (const applied of operationDirectives) {
collector.collectInArguments(applied.arguments());
}
}
return collector.variables();
}
function operationForEntitiesFetch(subgraphSchema, selectionSet, allVariableDefinitions, operationName, directives) {
const variableDefinitions = new federation_internals_1.VariableDefinitions();
variableDefinitions.add(representationsVariableDefinition(subgraphSchema));
variableDefinitions.addAll(allVariableDefinitions.filter(collectUsedVariables(selectionSet, directives)));
const queryType = subgraphSchema.schemaDefinition.rootType('query');
(0, federation_internals_1.assert)(queryType, `Subgraphs should always have a query root (they should at least provides _entities)`);
const entities = queryType.field(federation_internals_1.entitiesFieldName);
(0, federation_internals_1.assert)(entities, `Subgraphs should always have the _entities field`);
const entitiesCall = (0, federation_internals_1.selectionSetOfElement)(new federation_internals_1.Field(entities, { representations: representationsVariable }), selectionSet);
return new federation_internals_1.Operation(subgraphSchema, 'query', entitiesCall, variableDefinitions, undefined, operationName, directives);
}
function operationForQueryFetch(subgraphSchema, rootKind, selectionSet, allVariableDefinitions, operationName, directives) {
return new federation_internals_1.Operation(subgraphSchema, rootKind, selectionSet, allVariableDefinitions.filter(collectUsedVariables(selectionSet, directives)), undefined, operationName, directives);
}
const sameKeyRenamer = (k1, k2) => {
if (k1.renameKeyTo !== k2.renameKeyTo || k1.path.length !== k2.path.length) {
return false;
}
for (let i = 0; i < k1.path.length; i++) {
if (k1.path[i] !== k2.path[i]) {
return false;
}
}
return true;
};
//# sourceMappingURL=buildPlan.js.map