@apollo/query-graphs
Version:
Apollo Federation library to work with 'query graphs'
300 lines • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.traversePathTree = exports.isRootPathTree = exports.PathTree = void 0;
const federation_internals_1 = require("@apollo/federation-internals");
const querygraph_1 = require("./querygraph");
const pathContext_1 = require("./pathContext");
function opTriggerEquality(t1, t2) {
if (t1 === t2) {
return true;
}
if ((0, pathContext_1.isPathContext)(t1)) {
return (0, pathContext_1.isPathContext)(t2) && t1.equals(t2);
}
if ((0, pathContext_1.isPathContext)(t2)) {
return false;
}
return t1.equals(t2);
}
function findTriggerIdx(triggerEquality, forIndex, trigger) {
for (let i = 0; i < forIndex.length; i++) {
if (triggerEquality(forIndex[i][0], trigger)) {
return i;
}
}
return -1;
}
class PathTree {
constructor(graph, vertex, localSelections, triggerEquality, childs) {
this.graph = graph;
this.vertex = vertex;
this.localSelections = localSelections;
this.triggerEquality = triggerEquality;
this.childs = childs;
}
static create(graph, root, triggerEquality) {
return new PathTree(graph, root, undefined, triggerEquality, []);
}
static createOp(graph, root) {
return this.create(graph, root, opTriggerEquality);
}
static createFromOpPaths(graph, root, paths) {
(0, federation_internals_1.assert)(paths.length > 0, `Should compute on empty paths`);
return this.createFromPaths(graph, opTriggerEquality, root, paths.map(({ path, selection }) => ({ path: path[Symbol.iterator](), selection })));
}
static createFromPaths(graph, triggerEquality, currentVertex, pathAndSelections) {
const maxEdges = graph.outEdgesCount(currentVertex);
const forEdgeIndex = new Array(maxEdges + 1);
const newVertices = new Array(maxEdges);
const order = new Array(maxEdges + 1);
let currentOrder = 0;
let totalChilds = 0;
let localSelections = undefined;
for (const ps of pathAndSelections) {
const iterResult = ps.path.next();
if (iterResult.done) {
if (ps.selection) {
localSelections = localSelections ? localSelections.concat(ps.selection) : [ps.selection];
}
continue;
}
const [edge, trigger, conditions, contextToSelection, parameterToContext] = iterResult.value;
const idx = edge ? edge.index : maxEdges;
if (edge) {
newVertices[idx] = edge.tail;
}
const forIndex = forEdgeIndex[idx];
if (forIndex) {
const triggerIdx = findTriggerIdx(triggerEquality, forIndex, trigger);
if (triggerIdx < 0) {
forIndex.push([trigger, conditions, [ps], contextToSelection, parameterToContext]);
totalChilds++;
}
else {
const existing = forIndex[triggerIdx];
const existingCond = existing[1];
const mergedConditions = existingCond ? (conditions ? existingCond.mergeIfNotEqual(conditions) : existingCond) : conditions;
const newPaths = existing[2];
const mergedContextToSelection = (0, federation_internals_1.composeSets)(existing[3], contextToSelection);
const mergedParameterToContext = (0, federation_internals_1.mergeMapOrNull)(existing[4], parameterToContext);
newPaths.push(ps);
forIndex[triggerIdx] = [trigger, mergedConditions, newPaths, mergedContextToSelection, mergedParameterToContext];
}
}
else {
order[currentOrder++] = idx;
forEdgeIndex[idx] = [[trigger, conditions, [ps], contextToSelection, parameterToContext]];
totalChilds++;
}
}
const childs = new Array(totalChilds);
let idx = 0;
for (let i = 0; i < currentOrder; i++) {
const edgeIndex = order[i];
const index = (edgeIndex === maxEdges ? null : edgeIndex);
const newVertex = index === null ? currentVertex : newVertices[edgeIndex];
const values = forEdgeIndex[edgeIndex];
for (const [trigger, conditions, subPathAndSelections, contextToSelection, parameterToContext] of values) {
childs[idx++] = {
index,
trigger,
conditions,
tree: this.createFromPaths(graph, triggerEquality, newVertex, subPathAndSelections),
contextToSelection,
parameterToContext,
};
}
}
(0, federation_internals_1.assert)(idx === totalChilds, () => `Expected to have ${totalChilds} childs but only ${idx} added`);
return new PathTree(graph, currentVertex, localSelections, triggerEquality, childs);
}
childCount() {
return this.childs.length;
}
isLeaf() {
return this.childCount() === 0;
}
*childElements(reverseOrder = false) {
if (reverseOrder) {
for (let i = this.childs.length - 1; i >= 0; i--) {
yield this.element(i);
}
}
else {
for (let i = 0; i < this.childs.length; i++) {
yield this.element(i);
}
}
}
element(i) {
const child = this.childs[i];
return [
(child.index === null ? null : this.graph.outEdge(this.vertex, child.index)),
child.trigger,
child.conditions,
child.tree,
child.contextToSelection,
child.parameterToContext,
];
}
mergeChilds(c1, c2) {
const cond1 = c1.conditions;
const cond2 = c2.conditions;
return {
index: c1.index,
trigger: c1.trigger,
conditions: cond1 ? (cond2 ? cond1.mergeIfNotEqual(cond2) : cond1) : cond2,
tree: c1.tree.merge(c2.tree),
contextToSelection: (0, federation_internals_1.composeSets)(c1.contextToSelection, c2.contextToSelection),
parameterToContext: (0, federation_internals_1.mergeMapOrNull)(c1.parameterToContext, c2.parameterToContext),
};
}
mergeIfNotEqual(other) {
if (this.equalsSameRoot(other)) {
return this;
}
return this.merge(other);
}
mergeLocalSelectionsWith(other) {
return this.localSelections
? (other.localSelections ? this.localSelections.concat(other.localSelections) : this.localSelections)
: other.localSelections;
}
merge(other) {
if (this === other) {
return this;
}
(0, federation_internals_1.assert)(other.graph === this.graph, 'Cannot merge path tree build on another graph');
(0, federation_internals_1.assert)(other.vertex.index === this.vertex.index, () => `Cannot merge path tree rooted at vertex ${other.vertex} into tree rooted at other vertex ${this.vertex}`);
if (!other.childs.length) {
return this;
}
if (!this.childs.length) {
return other;
}
const localSelections = this.mergeLocalSelectionsWith(other);
const mergeIndexes = new Array(other.childs.length);
let countToAdd = 0;
for (let i = 0; i < other.childs.length; i++) {
const otherChild = other.childs[i];
const idx = this.findIndex(otherChild.trigger, otherChild.index);
mergeIndexes[i] = idx;
if (idx < 0) {
++countToAdd;
}
}
const thisSize = this.childs.length;
const newSize = thisSize + countToAdd;
const newChilds = (0, federation_internals_1.copyWitNewLength)(this.childs, newSize);
let addIdx = thisSize;
for (let i = 0; i < other.childs.length; i++) {
const idx = mergeIndexes[i];
if (idx < 0) {
newChilds[addIdx++] = other.childs[i];
}
else {
newChilds[idx] = this.mergeChilds(newChilds[idx], other.childs[i]);
}
}
(0, federation_internals_1.assert)(addIdx === newSize, () => `Expected ${newSize} childs but only got ${addIdx}`);
return new PathTree(this.graph, this.vertex, localSelections, this.triggerEquality, newChilds);
}
equalsSameRoot(that) {
if (this === that) {
return true;
}
return (0, federation_internals_1.arrayEquals)(this.childs, that.childs, (c1, c2) => {
return c1.index === c2.index
&& c1.trigger === c2.trigger
&& (c1.conditions ? (c2.conditions ? c1.conditions.equalsSameRoot(c2.conditions) : false) : !c2.conditions)
&& c1.tree.equalsSameRoot(c2.tree)
&& (0, federation_internals_1.setsEqual)(c1.contextToSelection, c2.contextToSelection)
&& PathTree.parameterToContextEquals(c1.parameterToContext, c2.parameterToContext);
});
}
static parameterToContextEquals(ptc1, ptc2) {
var _a, _b;
if (ptc1 === ptc2) {
return true;
}
const thisKeys = Array.from((_a = ptc1 === null || ptc1 === void 0 ? void 0 : ptc1.keys()) !== null && _a !== void 0 ? _a : []);
const thatKeys = Array.from((_b = ptc2 === null || ptc2 === void 0 ? void 0 : ptc2.keys()) !== null && _b !== void 0 ? _b : []);
if (thisKeys.length !== thatKeys.length) {
return false;
}
for (const key of thisKeys) {
const thisSelection = ptc1.get(key);
const thatSelection = ptc2.get(key);
(0, federation_internals_1.assert)(thisSelection, () => `Expected to have a selection for key ${key}`);
if (!thatSelection
|| (thisSelection.contextId !== thatSelection.contextId)
|| !(0, federation_internals_1.arrayEquals)(thisSelection.relativePath, thatSelection.relativePath)
|| !thisSelection.selectionSet.equals(thatSelection.selectionSet)
|| (thisSelection.subgraphArgType !== thatSelection.subgraphArgType)) {
return false;
}
}
return true;
}
concat(other) {
(0, federation_internals_1.assert)(other.graph === this.graph, 'Cannot concat path tree build on another graph');
(0, federation_internals_1.assert)(other.vertex.index === this.vertex.index, () => `Cannot concat path tree rooted at vertex ${other.vertex} into tree rooted at other vertex ${this.vertex}`);
if (!other.childs.length) {
return this;
}
if (!this.childs.length) {
return other;
}
const localSelections = this.mergeLocalSelectionsWith(other);
const newChilds = this.childs.concat(other.childs);
return new PathTree(this.graph, this.vertex, localSelections, this.triggerEquality, newChilds);
}
findIndex(trigger, edgeIndex) {
for (let i = 0; i < this.childs.length; i++) {
const child = this.childs[i];
if (child.index === edgeIndex && this.triggerEquality(child.trigger, trigger)) {
return i;
}
}
return -1;
}
isAllInSameSubgraph() {
return this.isAllInSameSubgraphInternal(this.vertex.source);
}
isAllInSameSubgraphInternal(target) {
return this.vertex.source === target
&& this.childs.every(c => c.tree.isAllInSameSubgraphInternal(target));
}
toString(indent = "", includeConditions = false) {
return this.toStringInternal(indent, includeConditions);
}
toStringInternal(indent, includeConditions) {
if (this.isLeaf()) {
return this.vertex.toString();
}
return this.vertex + ':\n' +
this.childs.map(child => indent
+ ` -> [${child.index}] `
+ (includeConditions && child.conditions ? `!! {\n${indent + " "}${child.conditions.toString(indent + " ", true)}\n${indent} } ` : "")
+ `${child.trigger} = `
+ child.tree.toStringInternal(indent + " ", includeConditions)).join('\n');
}
}
exports.PathTree = PathTree;
function isRootPathTree(tree) {
return (0, querygraph_1.isRootVertex)(tree.vertex);
}
exports.isRootPathTree = isRootPathTree;
function traversePathTree(pathTree, onEdges) {
for (const [edge, _, conditions, childTree] of pathTree.childElements()) {
if (edge) {
onEdges(edge);
}
if (conditions) {
traversePathTree(conditions, onEdges);
}
traversePathTree(childTree, onEdges);
}
}
exports.traversePathTree = traversePathTree;
//# sourceMappingURL=pathTree.js.map