@apollo/query-graphs
Version:
Apollo Federation library to work with 'query graphs'
981 lines (980 loc) • 97.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInitialOptions = exports.advanceSimultaneousPathsWithOperation = exports.advanceOptionsToString = exports.simultaneousPathsToString = exports.SimultaneousPathsWithLazyIndirectPaths = exports.getLocallySatisfiableKey = exports.addConditionExclusion = exports.sameExcludedDestinations = exports.advancePathWithTransition = exports.TransitionPathWithLazyIndirectPaths = exports.isUnadvanceableClosures = exports.UnadvanceableClosures = exports.Unadvanceables = exports.UnadvanceableReason = exports.unsatisfiedConditionsResolution = exports.noConditionsResolution = exports.UnsatisfiedConditionReason = exports.traversePath = exports.terminateWithNonRequestedTypenameField = exports.isRootPath = exports.GraphPath = void 0;
const federation_internals_1 = require("@apollo/federation-internals");
const pathTree_1 = require("./pathTree");
const querygraph_1 = require("./querygraph");
const transition_1 = require("./transition");
const pathContext_1 = require("./pathContext");
const uuid_1 = require("uuid");
const debug = (0, federation_internals_1.newDebugLogger)('path');
function updateRuntimeTypes(currentRuntimeTypes, edge) {
var _a;
if (!edge) {
return currentRuntimeTypes;
}
switch (edge.transition.kind) {
case 'FieldCollection':
const field = edge.transition.definition;
if (!(0, federation_internals_1.isCompositeType)((0, federation_internals_1.baseType)(field.type))) {
return [];
}
const newRuntimeTypes = [];
for (const parentType of currentRuntimeTypes) {
const fieldType = (_a = parentType.field(field.name)) === null || _a === void 0 ? void 0 : _a.type;
if (fieldType) {
for (const type of (0, federation_internals_1.possibleRuntimeTypes)((0, federation_internals_1.baseType)(fieldType))) {
if (!newRuntimeTypes.includes(type)) {
newRuntimeTypes.push(type);
}
}
}
}
return newRuntimeTypes;
case 'DownCast':
const castedType = edge.transition.castedType;
const castedRuntimeTypes = (0, federation_internals_1.possibleRuntimeTypes)(castedType);
return currentRuntimeTypes.filter(t => castedRuntimeTypes.includes(t));
case 'InterfaceObjectFakeDownCast':
return currentRuntimeTypes;
case 'KeyResolution':
const currentType = edge.tail.type;
return (0, federation_internals_1.possibleRuntimeTypes)(currentType);
case 'RootTypeResolution':
case 'SubgraphEnteringTransition':
(0, federation_internals_1.assert)((0, federation_internals_1.isObjectType)(edge.tail.type), () => `Query edge should be between object type but got ${edge}`);
return [edge.tail.type];
}
}
function withReplacedLastElement(arr, newLast) {
(0, federation_internals_1.assert)(arr.length > 0, 'Should not have been called on empty array');
const newArr = new Array(arr.length);
for (let i = 0; i < arr.length - 1; i++) {
newArr[i] = arr[i];
}
newArr[arr.length - 1] = newLast;
return newArr;
}
class GraphPath {
constructor(props) {
this.props = props;
}
get graph() {
return this.props.graph;
}
get root() {
return this.props.root;
}
get tail() {
return this.props.tail;
}
get deferOnTail() {
return this.props.deferOnTail;
}
get subgraphEnteringEdge() {
return this.props.subgraphEnteringEdge;
}
static create(graph, root) {
const runtimeTypes = (0, querygraph_1.isFederatedGraphRootType)(root.type) ? [] : (0, federation_internals_1.possibleRuntimeTypes)(root.type);
return new GraphPath({
graph,
root,
tail: root,
edgeTriggers: [],
edgeIndexes: [],
edgeConditions: [],
ownPathIds: [],
overriddingPathIds: [],
runtimeTypesOfTail: runtimeTypes,
contextToSelection: [],
parameterToContext: [],
});
}
static fromGraphRoot(graph, rootKind) {
const root = graph.root(rootKind);
return root ? this.create(graph, root) : undefined;
}
get size() {
return this.props.edgeIndexes.length;
}
countSubgraphJumpsAfterLastCommonVertex(that) {
const { vertex, index } = this.findLastCommonVertex(that);
return {
thisJumps: this.subgraphJumpsAtIdx(vertex, index),
thatJumps: that.subgraphJumpsAtIdx(vertex, index),
};
}
findLastCommonVertex(that) {
let vertex = this.root;
(0, federation_internals_1.assert)(that.root === vertex, () => `Expected both path to start on the same root, but 'this' has root ${vertex} while 'that' has ${that.root}`);
const minSize = Math.min(this.size, that.size);
let index = 0;
for (; index < minSize; index++) {
const thisEdge = this.edgeAt(index, vertex);
const thatEdge = that.edgeAt(index, vertex);
if (thisEdge !== thatEdge) {
break;
}
if (thisEdge) {
vertex = thisEdge.tail;
}
}
return { vertex, index };
}
subgraphJumpsAtIdx(vertex, index) {
let jumps = 0;
let v = vertex;
for (let i = index; i < this.size; i++) {
const edge = this.edgeAt(i, v);
if (!edge) {
continue;
}
if (edge.changesSubgraph()) {
++jumps;
}
v = edge.tail;
}
return jumps;
}
subgraphJumps() {
return this.subgraphJumpsAtIdx(this.root, 0);
}
isEquivalentSaveForTypeExplosionTo(that) {
if (this.root !== that.root || this.tail !== that.tail || this.size !== that.size - 1) {
return false;
}
let thisV = this.root;
let thatV = that.root;
for (let i = 0; i < this.size; i++) {
let thisEdge = this.edgeAt(i, thisV);
let thatEdge = that.edgeAt(i, thatV);
if (thisEdge !== thatEdge) {
if (!thisEdge || !thatEdge || !(0, federation_internals_1.isInterfaceType)(thatV.type) || thatEdge.transition.kind !== 'DownCast') {
return false;
}
thatEdge = that.edgeAt(i + 1, thatEdge.tail);
if (!thatEdge) {
return false;
}
thisV = thisEdge.tail;
thatV = thatEdge.tail;
if (thisEdge.transition.kind !== 'KeyResolution'
|| thatEdge.transition.kind !== 'KeyResolution'
|| thisEdge.tail.source !== thatEdge.tail.source
|| thisV !== thatV
|| !thisEdge.conditions.equals(thatEdge.conditions)) {
return false;
}
for (let j = i + 1; j < this.size; j++) {
thisEdge = this.edgeAt(j, thisV);
thatEdge = that.edgeAt(j + 1, thatV);
if (thisEdge !== thatEdge) {
return false;
}
if (thisEdge) {
thisV = thisEdge.tail;
thatV = thatEdge.tail;
}
}
return true;
}
if (thisEdge) {
thisV = thisEdge.tail;
thatV = thatEdge.tail;
}
}
return true;
}
[Symbol.iterator]() {
const path = this;
return {
currentIndex: 0,
currentVertex: this.root,
next() {
if (this.currentIndex >= path.size) {
return { done: true, value: undefined };
}
const idx = this.currentIndex++;
const edge = path.edgeAt(idx, this.currentVertex);
if (edge) {
this.currentVertex = edge.tail;
}
return { done: false, value: [
edge,
path.props.edgeTriggers[idx],
path.props.edgeConditions[idx],
path.props.contextToSelection[idx],
path.props.parameterToContext[idx],
] };
}
};
}
lastEdge() {
return this.props.edgeToTail;
}
lastTrigger() {
return this.props.edgeTriggers[this.size - 1];
}
tailPossibleRuntimeTypes() {
return this.props.runtimeTypesOfTail;
}
lastIsIntefaceObjectFakeDownCastAfterEnteringSubgraph() {
var _a;
return this.lastIsInterfaceObjectFakeDownCast()
&& ((_a = this.subgraphEnteringEdge) === null || _a === void 0 ? void 0 : _a.index) === this.size - 2;
}
lastIsInterfaceObjectFakeDownCast() {
var _a;
return ((_a = this.lastEdge()) === null || _a === void 0 ? void 0 : _a.transition.kind) === 'InterfaceObjectFakeDownCast';
}
add(trigger, edge, conditionsResolution, defer) {
var _a, _b, _c;
(0, federation_internals_1.assert)(!edge || this.tail.index === edge.head.index, () => `Cannot add edge ${edge} to path ending at ${this.tail}`);
(0, federation_internals_1.assert)(conditionsResolution.satisfied, 'Should add to a path if the conditions cannot be satisfied');
(0, federation_internals_1.assert)(!edge || edge.conditions || edge.requiredContexts.length > 0 || !conditionsResolution.pathTree, () => `Shouldn't have conditions paths (got ${conditionsResolution.pathTree}) for edge without conditions (edge: ${edge})`);
let subgraphEnteringEdge = defer ? undefined : this.subgraphEnteringEdge;
if (edge) {
if (edge.transition.kind === 'DownCast' && this.props.edgeToTail) {
const previousOperation = this.lastTrigger();
if (previousOperation instanceof federation_internals_1.FragmentElement && previousOperation.appliedDirectives.length === 0) {
const runtimeTypesWithoutPreviousCast = updateRuntimeTypes(this.props.runtimeTypesBeforeTailIfLastIsCast, edge);
if (runtimeTypesWithoutPreviousCast.length > 0
&& runtimeTypesWithoutPreviousCast.every(t => this.props.runtimeTypesOfTail.includes(t))) {
const updatedEdge = this.graph.outEdges(this.props.edgeToTail.head).find(e => e.tail.type === edge.tail.type);
if (updatedEdge) {
debug.log(() => `Previous cast ${previousOperation} is made obsolete by new cast ${trigger}, removing from path.`);
return new GraphPath({
...this.props,
tail: updatedEdge.tail,
edgeTriggers: withReplacedLastElement(this.props.edgeTriggers, trigger),
edgeIndexes: withReplacedLastElement(this.props.edgeIndexes, updatedEdge.index),
edgeConditions: withReplacedLastElement(this.props.edgeConditions, (_a = conditionsResolution.pathTree) !== null && _a !== void 0 ? _a : null),
edgeToTail: updatedEdge,
runtimeTypesOfTail: runtimeTypesWithoutPreviousCast,
deferOnTail: defer !== null && defer !== void 0 ? defer : this.props.deferOnTail,
});
}
}
}
}
if (!defer && edge.changesSubgraph()) {
subgraphEnteringEdge = {
index: this.size,
edge,
cost: conditionsResolution.cost,
};
}
if (edge.transition.kind === 'KeyResolution') {
if (this.lastIsInterfaceObjectFakeDownCast() && (0, federation_internals_1.isInterfaceType)(edge.tail.type)) {
return new GraphPath({
...this.props,
tail: edge.tail,
edgeTriggers: withReplacedLastElement(this.props.edgeTriggers, trigger),
edgeIndexes: withReplacedLastElement(this.props.edgeIndexes, edge.index),
edgeConditions: withReplacedLastElement(this.props.edgeConditions, (_b = conditionsResolution.pathTree) !== null && _b !== void 0 ? _b : null),
subgraphEnteringEdge,
edgeToTail: edge,
runtimeTypesOfTail: updateRuntimeTypes(this.props.runtimeTypesOfTail, edge),
runtimeTypesBeforeTailIfLastIsCast: undefined,
deferOnTail: defer,
});
}
}
}
const { edgeConditions, contextToSelection, parameterToContext } = this.mergeEdgeConditionsWithResolution(conditionsResolution);
const lastParameterToContext = parameterToContext[parameterToContext.length - 1];
let newTrigger = trigger;
if (lastParameterToContext !== null && trigger.kind === 'Field') {
const args = Array.from(lastParameterToContext).reduce((acc, [key, value]) => {
acc[key] = new federation_internals_1.Variable(value.contextId);
return acc;
}, {});
newTrigger = trigger.withUpdatedArguments(args);
}
return new GraphPath({
...this.props,
tail: edge ? edge.tail : this.tail,
edgeTriggers: this.props.edgeTriggers.concat(newTrigger),
edgeIndexes: this.props.edgeIndexes.concat((edge ? edge.index : null)),
edgeConditions,
subgraphEnteringEdge,
edgeToTail: edge,
runtimeTypesOfTail: updateRuntimeTypes(this.props.runtimeTypesOfTail, edge),
runtimeTypesBeforeTailIfLastIsCast: ((_c = edge === null || edge === void 0 ? void 0 : edge.transition) === null || _c === void 0 ? void 0 : _c.kind) === 'DownCast' ? this.props.runtimeTypesOfTail : undefined,
deferOnTail: defer !== null && defer !== void 0 ? defer : (edge && edge.transition.kind === 'DownCast' ? this.props.deferOnTail : undefined),
contextToSelection,
parameterToContext,
});
}
mergeEdgeConditionsWithResolution(conditionsResolution) {
var _a, _b, _c, _d, _e;
const edgeConditions = this.props.edgeConditions.concat((_a = conditionsResolution.pathTree) !== null && _a !== void 0 ? _a : null);
const contextToSelection = this.props.contextToSelection.concat(null);
const parameterToContext = this.props.parameterToContext.concat(null);
if (conditionsResolution.contextMap === undefined || conditionsResolution.contextMap.size === 0) {
return {
edgeConditions,
contextToSelection,
parameterToContext,
};
}
parameterToContext[parameterToContext.length - 1] = new Map();
for (const [_, entry] of conditionsResolution.contextMap) {
const idx = edgeConditions.length - entry.levelsInQueryPath - 1;
(0, federation_internals_1.assert)(idx >= 0, 'calculated condition index must be positive');
if (entry.pathTree) {
edgeConditions[idx] = (_c = (_b = edgeConditions[idx]) === null || _b === void 0 ? void 0 : _b.merge(entry.pathTree)) !== null && _c !== void 0 ? _c : entry.pathTree;
}
if (contextToSelection[idx] === null) {
contextToSelection[idx] = new Set();
}
(_d = contextToSelection[idx]) === null || _d === void 0 ? void 0 : _d.add(entry.id);
(_e = parameterToContext[parameterToContext.length - 1]) === null || _e === void 0 ? void 0 : _e.set(entry.paramName, { contextId: entry.id, relativePath: Array(entry.levelsInDataPath).fill(".."), selectionSet: entry.selectionSet, subgraphArgType: entry.argType });
}
return {
edgeConditions,
contextToSelection,
parameterToContext,
};
}
concat(tailPath) {
var _a, _b;
(0, federation_internals_1.assert)(this.tail.index === tailPath.root.index, () => `Cannot concat ${tailPath} after ${this}`);
if (tailPath.size === 0) {
return this;
}
let prevRuntimeTypes = this.props.runtimeTypesBeforeTailIfLastIsCast;
let runtimeTypes = this.props.runtimeTypesOfTail;
for (const [edge] of tailPath) {
prevRuntimeTypes = runtimeTypes;
runtimeTypes = updateRuntimeTypes(runtimeTypes, edge);
}
return new GraphPath({
...this.props,
tail: tailPath.tail,
edgeTriggers: this.props.edgeTriggers.concat(tailPath.props.edgeTriggers),
edgeIndexes: this.props.edgeIndexes.concat(tailPath.props.edgeIndexes),
edgeConditions: this.props.edgeConditions.concat(tailPath.props.edgeConditions),
subgraphEnteringEdge: tailPath.subgraphEnteringEdge ? tailPath.subgraphEnteringEdge : this.subgraphEnteringEdge,
ownPathIds: this.props.ownPathIds.concat(tailPath.props.ownPathIds),
overriddingPathIds: this.props.overriddingPathIds.concat(tailPath.props.overriddingPathIds),
edgeToTail: tailPath.props.edgeToTail,
runtimeTypesOfTail: runtimeTypes,
runtimeTypesBeforeTailIfLastIsCast: ((_b = (_a = tailPath.props.edgeToTail) === null || _a === void 0 ? void 0 : _a.transition) === null || _b === void 0 ? void 0 : _b.kind) === 'DownCast' ? prevRuntimeTypes : undefined,
deferOnTail: tailPath.deferOnTail,
});
}
checkDirectPathFromPreviousSubgraphTo(typeName, triggerToEdge, overrideConditions, prevSubgraphStartingVertex) {
const enteringEdge = this.subgraphEnteringEdge;
if (!enteringEdge) {
return undefined;
}
if (this.graph.subgraphToArgs.size > 0) {
return undefined;
}
if (enteringEdge.edge.transition.kind === 'SubgraphEnteringTransition' && !prevSubgraphStartingVertex) {
return undefined;
}
let prevSubgraphVertex = prevSubgraphStartingVertex !== null && prevSubgraphStartingVertex !== void 0 ? prevSubgraphStartingVertex : enteringEdge.edge.head;
for (let i = enteringEdge.index + 1; i < this.size; i++) {
const triggerToMatch = this.props.edgeTriggers[i];
const prevSubgraphMatchingEdge = triggerToEdge(this.graph, prevSubgraphVertex, triggerToMatch, overrideConditions);
if (prevSubgraphMatchingEdge === null) {
continue;
}
if (!prevSubgraphMatchingEdge || prevSubgraphMatchingEdge.conditions) {
return undefined;
}
prevSubgraphVertex = prevSubgraphMatchingEdge.tail;
}
return prevSubgraphVertex.type.name === typeName ? prevSubgraphVertex : undefined;
}
nextEdges() {
if (this.deferOnTail) {
return this.graph.outEdges(this.tail, true);
}
const tailEdge = this.props.edgeToTail;
return tailEdge
? this.graph.nonTrivialFollowupEdges(tailEdge)
: this.graph.outEdges(this.tail);
}
isTerminal() {
return this.graph.isTerminal(this.tail);
}
isRootPath() {
return (0, querygraph_1.isRootVertex)(this.root);
}
mapMainPath(mapper) {
const result = new Array(this.size);
let v = this.root;
for (let i = 0; i < this.size; i++) {
const edge = this.edgeAt(i, v);
result[i] = mapper(edge, i);
if (edge) {
v = edge.tail;
}
}
return result;
}
edgeAt(index, v) {
const edgeIdx = this.props.edgeIndexes[index];
return (edgeIdx !== null ? this.graph.outEdge(v, edgeIdx) : null);
}
reduceMainPath(reducer, initialValue) {
let value = initialValue;
let v = this.root;
for (let i = 0; i < this.size; i++) {
const edge = this.edgeAt(i, v);
value = reducer(value, edge, i);
if (edge) {
v = edge.tail;
}
}
return value;
}
hasJustCycled() {
if (this.root.index == this.tail.index) {
return true;
}
let v = this.root;
for (let i = 0; i < this.size - 1; i++) {
const edge = this.edgeAt(i, v);
if (!edge) {
continue;
}
v = edge.tail;
if (v.index == this.tail.index) {
return true;
}
}
return false;
}
hasAnyEdgeConditions() {
return this.props.edgeConditions.some(c => c !== null);
}
isOnTopLevelQueryRoot() {
if (!(0, querygraph_1.isRootVertex)(this.root)) {
return false;
}
let vertex = this.root;
for (let i = 0; i < this.size; i++) {
const edge = this.edgeAt(i, vertex);
if (!edge) {
continue;
}
if (edge.transition.kind === 'FieldCollection' || !(0, federation_internals_1.isSchemaRootType)(edge.tail.type)) {
return false;
}
vertex = edge.tail;
}
return true;
}
truncateTrailingDowncasts() {
let lastNonDowncastIdx = -1;
let v = this.root;
let lastNonDowncastVertex = v;
let lastNonDowncastEdge;
let runtimeTypes = (0, querygraph_1.isFederatedGraphRootType)(this.root.type) ? [] : (0, federation_internals_1.possibleRuntimeTypes)(this.root.type);
let runtimeTypesAtLastNonDowncastEdge = runtimeTypes;
for (let i = 0; i < this.size; i++) {
const edge = this.edgeAt(i, v);
runtimeTypes = updateRuntimeTypes(runtimeTypes, edge);
if (edge) {
v = edge.tail;
if (edge.transition.kind !== 'DownCast') {
lastNonDowncastIdx = i;
lastNonDowncastVertex = v;
lastNonDowncastEdge = edge;
runtimeTypesAtLastNonDowncastEdge = runtimeTypes;
}
}
}
if (lastNonDowncastIdx < 0 || lastNonDowncastIdx === this.size - 1) {
return this;
}
const newSize = lastNonDowncastIdx + 1;
return new GraphPath({
...this.props,
tail: lastNonDowncastVertex,
edgeTriggers: this.props.edgeTriggers.slice(0, newSize),
edgeIndexes: this.props.edgeIndexes.slice(0, newSize),
edgeConditions: this.props.edgeConditions.slice(0, newSize),
edgeToTail: lastNonDowncastEdge,
runtimeTypesOfTail: runtimeTypesAtLastNonDowncastEdge,
runtimeTypesBeforeTailIfLastIsCast: undefined,
});
}
markOverridding(otherOptions) {
const newId = (0, uuid_1.v4)();
return {
thisPath: new GraphPath({
...this.props,
ownPathIds: this.props.ownPathIds.concat(newId),
}),
otherOptions: otherOptions.map((paths) => paths.map((p) => new GraphPath({
...p.props,
overriddingPathIds: p.props.overriddingPathIds.concat(newId),
}))),
};
}
isOverriddenBy(otherPath) {
for (const overriddingId of this.props.overriddingPathIds) {
if (otherPath.props.ownPathIds.includes(overriddingId)) {
return true;
}
}
return false;
}
tailIsInterfaceObject() {
var _a;
if (!(0, federation_internals_1.isObjectType)(this.tail.type)) {
return false;
}
const schema = this.graph.sources.get(this.tail.source);
const metadata = (0, federation_internals_1.federationMetadata)(schema);
return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.isInterfaceObjectType(this.tail.type)) !== null && _a !== void 0 ? _a : false;
}
toString() {
const isRoot = (0, querygraph_1.isRootVertex)(this.root);
if (isRoot && this.size === 0) {
return '_';
}
const pathStr = this.mapMainPath((edge, idx) => {
if (edge) {
if (isRoot && idx == 0) {
return edge.tail.toString();
}
const label = edge.label();
return ` -${label === "" ? "" : '-[' + label + ']-'}-> ${edge.tail}`;
}
return ` (${this.props.edgeTriggers[idx]}) `;
}).join('');
const deferStr = this.deferOnTail ? ` <defer='${this.deferOnTail.label}'>` : '';
const typeStr = this.props.runtimeTypesOfTail.length > 0 ? ` (types: [${this.props.runtimeTypesOfTail.join(', ')}])` : '';
return `${isRoot ? '' : this.root}${pathStr}${deferStr}${typeStr}`;
}
}
exports.GraphPath = GraphPath;
function isRootPath(path) {
return (0, querygraph_1.isRootVertex)(path.root);
}
exports.isRootPath = isRootPath;
function terminateWithNonRequestedTypenameField(path, overrideConditions) {
path = path.truncateTrailingDowncasts();
if (!(0, federation_internals_1.isCompositeType)(path.tail.type)) {
return path;
}
const typenameField = new federation_internals_1.Field(path.tail.type.typenameField());
const edge = edgeForField(path.graph, path.tail, typenameField, overrideConditions);
(0, federation_internals_1.assert)(edge, () => `We should have an edge from ${path.tail} for ${typenameField}`);
return path.add(typenameField, edge, exports.noConditionsResolution);
}
exports.terminateWithNonRequestedTypenameField = terminateWithNonRequestedTypenameField;
function traversePath(path, onEdges) {
for (const [edge, _, conditions] of path) {
if (conditions) {
(0, pathTree_1.traversePathTree)(conditions, onEdges);
}
onEdges(edge);
}
}
exports.traversePath = traversePath;
var UnsatisfiedConditionReason;
(function (UnsatisfiedConditionReason) {
UnsatisfiedConditionReason[UnsatisfiedConditionReason["NO_POST_REQUIRE_KEY"] = 0] = "NO_POST_REQUIRE_KEY";
UnsatisfiedConditionReason[UnsatisfiedConditionReason["NO_CONTEXT_SET"] = 1] = "NO_CONTEXT_SET";
})(UnsatisfiedConditionReason || (exports.UnsatisfiedConditionReason = UnsatisfiedConditionReason = {}));
exports.noConditionsResolution = { satisfied: true, cost: 0 };
exports.unsatisfiedConditionsResolution = { satisfied: false, cost: -1 };
var UnadvanceableReason;
(function (UnadvanceableReason) {
UnadvanceableReason[UnadvanceableReason["UNSATISFIABLE_KEY_CONDITION"] = 0] = "UNSATISFIABLE_KEY_CONDITION";
UnadvanceableReason[UnadvanceableReason["UNSATISFIABLE_REQUIRES_CONDITION"] = 1] = "UNSATISFIABLE_REQUIRES_CONDITION";
UnadvanceableReason[UnadvanceableReason["UNRESOLVABLE_INTERFACE_OBJECT"] = 2] = "UNRESOLVABLE_INTERFACE_OBJECT";
UnadvanceableReason[UnadvanceableReason["NO_MATCHING_TRANSITION"] = 3] = "NO_MATCHING_TRANSITION";
UnadvanceableReason[UnadvanceableReason["UNREACHABLE_TYPE"] = 4] = "UNREACHABLE_TYPE";
UnadvanceableReason[UnadvanceableReason["IGNORED_INDIRECT_PATH"] = 5] = "IGNORED_INDIRECT_PATH";
UnadvanceableReason[UnadvanceableReason["UNSATISFIABLE_OVERRIDE_CONDITION"] = 6] = "UNSATISFIABLE_OVERRIDE_CONDITION";
})(UnadvanceableReason || (exports.UnadvanceableReason = UnadvanceableReason = {}));
class Unadvanceables {
constructor(reasons) {
this.reasons = reasons;
}
toString() {
return '[' + this.reasons.map((r) => `[${r.reason}](${r.sourceSubgraph}->${r.destSubgraph}) ${r.details}`).join(', ') + ']';
}
}
exports.Unadvanceables = Unadvanceables;
class UnadvanceableClosures {
constructor(closures) {
if (Array.isArray(closures)) {
this.closures = closures;
}
else {
this.closures = [closures];
}
}
toUnadvanceables() {
if (!this._unadvanceables) {
this._unadvanceables = new Unadvanceables(this.closures.map((c) => c()).flat());
}
return this._unadvanceables;
}
}
exports.UnadvanceableClosures = UnadvanceableClosures;
function isUnadvanceableClosures(result) {
return result instanceof UnadvanceableClosures;
}
exports.isUnadvanceableClosures = isUnadvanceableClosures;
function pathTransitionToEdge(graph, vertex, transition, overrideConditions) {
for (const edge of graph.outEdges(vertex)) {
if (!edge.matchesSupergraphTransition(transition)) {
continue;
}
if (edge.satisfiesOverrideConditions(overrideConditions)) {
return edge;
}
}
return undefined;
}
class TransitionPathWithLazyIndirectPaths {
constructor(path, conditionResolver, overrideConditions) {
this.path = path;
this.conditionResolver = conditionResolver;
this.overrideConditions = overrideConditions;
}
static initial(initialPath, conditionResolver, overrideConditions) {
return new TransitionPathWithLazyIndirectPaths(initialPath, conditionResolver, overrideConditions);
}
indirectOptions() {
if (!this.lazilyComputedIndirectPaths) {
this.lazilyComputedIndirectPaths = this.computeIndirectPaths();
}
return this.lazilyComputedIndirectPaths;
}
computeIndirectPaths() {
return advancePathWithNonCollectingAndTypePreservingTransitions(this.path, pathContext_1.emptyContext, this.conditionResolver, [], [], (t) => t, pathTransitionToEdge, this.overrideConditions, getFieldParentTypeForEdge);
}
toString() {
return this.path.toString();
}
}
exports.TransitionPathWithLazyIndirectPaths = TransitionPathWithLazyIndirectPaths;
function advancePathWithTransition(subgraphPath, transition, targetType, overrideConditions) {
if (transition.kind === 'DownCast' && !((0, federation_internals_1.isInterfaceType)(transition.sourceType) && (0, federation_internals_1.isObjectType)(subgraphPath.path.tail.type))) {
const supergraphRuntimeTypes = (0, federation_internals_1.possibleRuntimeTypes)(targetType);
const subgraphRuntimeTypes = subgraphPath.path.tailPossibleRuntimeTypes();
const intersection = supergraphRuntimeTypes.filter(t1 => subgraphRuntimeTypes.some(t2 => t1.name === t2.name)).map(t => t.name);
if (intersection.length === 0) {
debug.log(() => `No intersection between casted type ${targetType} and the possible types in this subgraph`);
return [];
}
}
debug.group(() => `Trying to advance ${subgraphPath} for ${transition}`);
debug.group('Direct options:');
const directOptions = advancePathWithDirectTransition(subgraphPath.path, transition, subgraphPath.conditionResolver, overrideConditions);
let options;
const deadEndClosures = [];
if (isUnadvanceableClosures(directOptions)) {
options = [];
debug.groupEnd(() => 'No direct options');
deadEndClosures.push(...directOptions.closures);
}
else {
debug.groupEnd(() => advanceOptionsToString(directOptions));
if (directOptions.length > 0 && (0, federation_internals_1.isLeafType)(targetType)) {
debug.groupEnd(() => `reached leaf type ${targetType} so not trying indirect paths`);
return createLazyTransitionOptions(directOptions, subgraphPath, overrideConditions);
}
options = directOptions;
}
debug.group(`Computing indirect paths:`);
const pathsWithNonCollecting = subgraphPath.indirectOptions();
if (pathsWithNonCollecting.paths.length > 0) {
debug.groupEnd(() => `${pathsWithNonCollecting.paths.length} indirect paths: ${pathsWithNonCollecting.paths}`);
debug.group('Validating indirect options:');
for (const nonCollectingPath of pathsWithNonCollecting.paths) {
debug.group(() => `For indirect path ${nonCollectingPath}:`);
const pathsWithTransition = advancePathWithDirectTransition(nonCollectingPath, transition, subgraphPath.conditionResolver, overrideConditions);
if (isUnadvanceableClosures(pathsWithTransition)) {
debug.groupEnd(() => `Cannot be advanced with ${transition}`);
deadEndClosures.push(...pathsWithTransition.closures);
}
else {
debug.groupEnd(() => `Adding valid option: ${pathsWithTransition}`);
options = options.concat(pathsWithTransition);
}
}
debug.groupEnd();
}
else {
debug.groupEnd('no indirect paths');
}
debug.groupEnd(() => options.length > 0 ? advanceOptionsToString(options) : `Cannot advance ${transition} for this path`);
if (options.length > 0) {
return createLazyTransitionOptions(options, subgraphPath, overrideConditions);
}
const indirectDeadEndClosures = pathsWithNonCollecting.deadEnds.closures;
return new UnadvanceableClosures(() => {
const allDeadEnds = new UnadvanceableClosures(deadEndClosures.concat(indirectDeadEndClosures))
.toUnadvanceables().reasons;
if (transition.kind === 'FieldCollection') {
const typeName = transition.definition.parent.name;
const fieldName = transition.definition.name;
const subgraphsWithDeadEnd = new Set(allDeadEnds.map(e => e.destSubgraph));
for (const [subgraph, schema] of subgraphPath.path.graph.sources.entries()) {
if (subgraphsWithDeadEnd.has(subgraph)) {
continue;
}
const type = schema.type(typeName);
if (type && (0, federation_internals_1.isCompositeType)(type) && type.field(fieldName)) {
const typenameOfTail = subgraphPath.path.tail.type.name;
const typeOfTailInSubgraph = schema.type(typenameOfTail);
if (!typeOfTailInSubgraph) {
allDeadEnds.push({
sourceSubgraph: subgraphPath.path.tail.source,
destSubgraph: subgraph,
reason: UnadvanceableReason.UNREACHABLE_TYPE,
details: `cannot move to subgraph "${subgraph}", which has field "${transition.definition.coordinate}", because interface "${typenameOfTail}" is not defined in this subgraph (to jump to "${subgraph}", it would need to both define interface "${typenameOfTail}" and have a @key on it)`,
});
}
else {
(0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(typeOfTailInSubgraph), () => `Type ${typeOfTailInSubgraph} in ${subgraph} should be composite`);
const metadata = (0, federation_internals_1.federationMetadata)(schema);
const keys = metadata ? typeOfTailInSubgraph.appliedDirectivesOf(metadata.keyDirective()) : [];
const allNonResolvable = keys.length > 0 && keys.every((k) => { var _a; return !((_a = k.arguments().resolvable) !== null && _a !== void 0 ? _a : true); });
(0, federation_internals_1.assert)(keys.length === 0 || allNonResolvable, () => `After ${subgraphPath} and for transition ${transition}, expected type ${type} in ${subgraph} to have no resolvable keys`);
const kindOfType = typeOfTailInSubgraph === type ? 'type' : 'interface';
const explanation = keys.length === 0
? `${kindOfType} "${typenameOfTail}" has no @key defined in subgraph "${subgraph}"`
: `none of the @key defined on ${kindOfType} "${typenameOfTail}" in subgraph "${subgraph}" are resolvable (they are all declared with their "resolvable" argument set to false)`;
allDeadEnds.push({
sourceSubgraph: subgraphPath.path.tail.source,
destSubgraph: subgraph,
reason: UnadvanceableReason.UNREACHABLE_TYPE,
details: `cannot move to subgraph "${subgraph}", which has field "${transition.definition.coordinate}", because ${explanation}`
});
}
}
}
}
return allDeadEnds;
});
}
exports.advancePathWithTransition = advancePathWithTransition;
function createLazyTransitionOptions(options, origin, overrideConditions) {
return options.map(option => new TransitionPathWithLazyIndirectPaths(option, origin.conditionResolver, overrideConditions));
}
function isDestinationExcluded(destination, excluded) {
return excluded.includes(destination);
}
function sameExcludedDestinations(ex1, ex2) {
if (ex1 === ex2) {
return true;
}
if (ex1.length !== ex2.length) {
return false;
}
return ex1.every((d) => ex2.includes(d));
}
exports.sameExcludedDestinations = sameExcludedDestinations;
function addDestinationExclusion(excluded, destination) {
return excluded.includes(destination) ? excluded : excluded.concat(destination);
}
function isConditionExcluded(condition, excluded) {
if (!condition) {
return false;
}
return excluded.find(e => condition.equals(e)) !== undefined;
}
function addConditionExclusion(excluded, newExclusion) {
return newExclusion ? excluded.concat(newExclusion) : excluded;
}
exports.addConditionExclusion = addConditionExclusion;
function popMin(stack) {
let minIdx = 0;
let minSize = stack[0].size;
for (let i = 1; i < stack.length; i++) {
if (stack[i].size < minSize) {
minSize = stack[i].size;
minIdx = i;
}
}
const min = stack[minIdx];
stack.splice(minIdx, 1);
return min;
}
function advancePathWithNonCollectingAndTypePreservingTransitions(path, context, conditionResolver, excludedDestinations, excludedConditions, convertTransitionWithCondition, triggerToEdge, overrideConditions, getFieldParentType) {
if (path.lastIsIntefaceObjectFakeDownCastAfterEnteringSubgraph()) {
return {
paths: [],
deadEnds: new UnadvanceableClosures(() => {
const reachableSubgraphs = new Set(path.nextEdges().filter((e) => !e.transition.collectOperationElements && e.tail.source !== path.tail.source).map((e) => e.tail.source));
return Array.from(reachableSubgraphs).map((s) => ({
sourceSubgraph: path.tail.source,
destSubgraph: s,
reason: UnadvanceableReason.IGNORED_INDIRECT_PATH,
details: `ignoring moving from "${path.tail.source}" to "${s}" as a more direct option exists`,
}));
}),
};
}
const isTopLevelPath = path.isOnTopLevelQueryRoot();
const typeName = (0, querygraph_1.isFederatedGraphRootType)(path.tail.type) ? undefined : path.tail.type.name;
const originalSource = path.tail.source;
const bestPathBySource = new Map();
const deadEndClosures = [];
const toTry = [path];
while (toTry.length > 0) {
const toAdvance = popMin(toTry);
const nextEdges = toAdvance.nextEdges().filter(e => !e.transition.collectOperationElements);
if (nextEdges.length === 0) {
const outEdges = toAdvance.graph.outEdges(toAdvance.tail).filter(e => !e.transition.collectOperationElements);
if (outEdges.length > 0) {
debug.log(() => `Nothing to try for ${toAdvance}: it only has "trivial" non-collecting outbound edges`);
deadEndClosures.push(() => {
var _a;
const unadvanceables = [];
for (const edge of outEdges) {
if (edge.tail.source !== toAdvance.tail.source && edge.tail.source !== originalSource) {
unadvanceables.push({
sourceSubgraph: toAdvance.tail.source,
destSubgraph: edge.tail.source,
reason: UnadvanceableReason.IGNORED_INDIRECT_PATH,
details: `ignoring moving to subgraph "${edge.tail.source}" using @key(fields: "${(_a = edge.conditions) === null || _a === void 0 ? void 0 : _a.toString(true, false)}") of "${edge.head.type}" because there is a more direct path in ${edge.tail.source} that avoids ${toAdvance.tail.source} altogether`
});
}
}
return unadvanceables;
});
}
else {
debug.log(() => `Nothing to try for ${toAdvance}: it has no non-collecting outbound edges`);
}
continue;
}
debug.group(() => `From ${toAdvance}:`);
for (const edge of nextEdges) {
debug.group(() => `Testing edge ${edge}`);
const target = edge.tail;
if (isDestinationExcluded(target.source, excludedDestinations)) {
debug.groupEnd(`Ignored: edge is excluded`);
continue;
}
if (target.source === originalSource && !toAdvance.deferOnTail) {
debug.groupEnd('Ignored: edge get us back to our original source');
continue;
}
if (isTopLevelPath && edge.transition.kind === 'RootTypeResolution' && !(toAdvance.deferOnTail && edge.isKeyOrRootTypeEdgeToSelf())) {
debug.groupEnd(`Ignored: edge is a top-level "RootTypeResolution"`);
continue;
}
const prevForSource = bestPathBySource.get(target.source);
if (prevForSource === null) {
debug.groupEnd(() => `Ignored: we've shown before than going to ${target.source} is not productive`);
continue;
}
if (prevForSource
&& (prevForSource[0].size < toAdvance.size + 1
|| (prevForSource[0].size == toAdvance.size + 1 && prevForSource[1] <= 1))) {
debug.groupEnd(() => `Ignored: a better (shorter) path to the same subgraph already added`);
continue;
}
if (isConditionExcluded(edge.conditions, excludedConditions)) {
debug.groupEnd(`Ignored: edge condition is excluded`);
continue;
}
debug.group(() => `Validating conditions ${edge.conditions}`);
const conditionResolution = canSatisfyConditions(toAdvance, edge, conditionResolver, context, addDestinationExclusion(excludedDestinations, target.source), excludedConditions, getFieldParentType);
if (conditionResolution.satisfied) {
debug.groupEnd('Condition satisfied');
if (prevForSource && prevForSource[0].size === toAdvance.size + 1 && prevForSource[1] <= conditionResolution.cost) {
debug.groupEnd('Ignored: a better (less costly) path to the same subgraph already added');
continue;
}
const subgraphEnteringEdge = toAdvance.subgraphEnteringEdge;
if (subgraphEnteringEdge && subgraphEnteringEdge.edge.tail.type.name !== typeName) {
let prevSubgraphEnteringVertex = undefined;
let backToPreviousSubgraph;
if (subgraphEnteringEdge.edge.transition.kind === 'SubgraphEnteringTransition') {
(0, federation_internals_1.assert)(toAdvance.root instanceof querygraph_1.RootVertex, () => `${toAdvance} should be a root path if it starts with subgraph entering edge ${subgraphEnteringEdge.edge}`);
prevSubgraphEnteringVertex = rootVertexForSubgraph(toAdvance.graph, edge.tail.source, toAdvance.root.rootKind);
backToPreviousSubgraph = true;
}
else {
backToPreviousSubgraph = subgraphEnteringEdge.edge.head.source === edge.tail.source;
}
const prevSubgraphVertex = toAdvance.checkDirectPathFromPreviousSubgraphTo(edge.tail.type.name, triggerToEdge, overrideConditions, prevSubgraphEnteringVertex);
const maxCost = toAdvance.subgraphEnteringEdge.cost + (backToPreviousSubgraph ? 0 : conditionResolution.cost);
if (prevSubgraphVertex
&& (backToPreviousSubgraph
|| hasValidDirectKeyEdge(toAdvance.graph, prevSubgraphVertex, edge.tail.source, conditionResolver, maxCost))) {
debug.groupEnd(() => `Ignored: edge correspond to a detour by subgraph ${edge.head.source} from subgraph ${subgraphEnteringEdge.edge.head.source}: `
+ `we have a direct path from ${subgraphEnteringEdge.edge.head.type} to ${edge.tail.type} in ${subgraphEnteringEdge.edge.head.source}`
+ (backToPreviousSubgraph ? '.' : ` and can move to ${edge.tail.source} from there`));
bestPathBySource.set(edge.tail.source, null);
deadEndClosures.push(() => {
var _a;
return {
sourceSubgraph: toAdvance.tail.source,
destSubgraph: edge.tail.source,
reason: UnadvanceableReason.IGNORED_INDIRECT_PATH,
details: `ignoring moving to subgraph "${edge.tail.source}" using @key(fields: "${(_a = edge.conditions) === null || _a === void 0 ? void 0 : _a.toString(true, false)}") of "${edge.head.type}" because there is a more direct path in ${edge.tail.source} that avoids ${toAdvance.tail.source} altogether`
};
});
continue;
}
}
const updatedPath = toAdvance.add(convertTransitionWithCondition(edge.transition, context), edge, conditionResolution);
debug.log(() => `Using edge, advance path: ${updatedPath}`);
bestPathBySource.set(target.source, [updatedPath, conditionResolution.cost]);
if (edge.transition.kind === 'KeyResolution' && edge.head.source !== edge.tail.source) {
toTry.push(updatedPath);
}
}
else {
debug.groupEnd('Condition unsatisfiable');
deadEndClosures.push(() => {
var _a;
const source = toAdvance.tail.source;
const dest = edge.tail.source;
const hasOverriddenField = conditionHasOverriddenFieldsInSource(path.graph.sources.get(toAdvance.tail.source), edge.conditions);
const extraMsg = hasOverriddenField
? ` (note that some of those key fields are overridden in "${source}")`
: "";
return {
sourceSubgraph: source,
destSubgraph: dest,
reason: UnadvanceableReason.UNSATISFIABLE_KEY_CONDITION,
details: `cannot move to subgraph "${dest}" using @key(fields: "${(_a = edge.conditions) === null || _a === void 0 ? void 0 : _a.toString(true, false)}") of "${edge.head.type}", the key field(s) cannot be resolved from subgraph "${source}"${extraMsg}`
};
});
}
debug.groupEnd();
}
debug.groupEnd();
}
return {
paths: (0, federation_internals_1.mapValues)(bestPathBySource).filter(p => p !== null).map(b => b[0]),
deadEnds: new UnadvanceableClosures(deadEndClosures)
};
}
function rootVertexForSubgraph(graph, subgraphName, rootKind) {
const root = graph.root(rootKind);
(0, federation_internals_1.assert)(root, () => `Should not have ask for ${rootKind} as the graph does not have one`);
const subgraphRootEdge = graph.outEdges(root).find((e) => e.tail.source === subgraphName);
return subgraphRootEdge === null || subgraphRootEdge === void 0 ? void 0 : subgraphRootEdge.tail;
}
function conditionHasOverriddenFieldsInSource(schema, condition) {
const externalDirective = (0, federation_internals_1.federationMetadata)(schema).externalDirective();
return (0, federation_internals_1.allFieldDefinitionsInSelectionSet)(condition).some((field) => {
var _a, _b;
const typeInSource = schema.type(field.parent.name);
const fieldInSource = typeInSource && (0, federation_internals_1.isObjectType)(typeInSource) && typeInSource.field(field.name);
return fieldInSource && ((_b = (_a = fieldInSource.appliedDirectivesOf(externalDirective)) === null || _a === void 0 ? void 0 : _a.pop()) === null || _b === void 0 ? void 0 : _b.arguments().reason) === '[overridden]';
});
}
function hasValidDirectKeyEdge(graph, from, to, conditionResolver, maxCost) {
for (const edge of graph.outEdges(from)) {
if (edge.transition.kind !== 'KeyResolution' || edge.tail.source !== to) {
continue;