UNPKG

@apollo/query-graphs

Version:

Apollo Federation library to work with 'query graphs'

981 lines (980 loc) 97.6 kB
"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;