UNPKG

@apollo/query-graphs

Version:

Apollo Federation library to work with 'query graphs'

623 lines 32.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NonLocalSelectionsState = exports.NonLocalSelectionsMetadata = void 0; const federation_internals_1 = require("@apollo/federation-internals"); const querygraph_1 = require("./querygraph"); ; class NonLocalSelectionsMetadata { constructor(graph) { this.typesToIndirectOptions = new Map(); this.remainingVerticesToInterfaceObjectOptions = new Map; this.fieldsToEndpoints = new Map(); this.inlineFragmentsToEndpoints = new Map(); this.verticesToObjectTypeDowncasts = new Map(); this.fieldsToRebaseableParentVertices = new Map; this.inlineFragmentsToRebaseableParentVertices = new Map; this.precomputeNonLocalSelectionMetadata(graph); } precomputeNonLocalSelectionMetadata(graph) { this.precomputeNextVertexMetadata(graph); this.precomputeRebasingMetadata(graph); } precomputeNextVertexMetadata(graph) { const verticesToInterfaceObjectOptions = new Map(); for (const edge of graph.allEdges()) { switch (edge.transition.kind) { case 'FieldCollection': { if (!(0, federation_internals_1.isCompositeType)(edge.tail.type)) { continue; } const fieldName = edge.transition.definition.name; let endpointsEntry = this.fieldsToEndpoints.get(fieldName); if (!endpointsEntry) { endpointsEntry = new Map(); this.fieldsToEndpoints.set(fieldName, endpointsEntry); } endpointsEntry.set(edge.head, { tail: edge.tail, overrideCondition: edge.overrideCondition }); break; } case 'DownCast': { if ((0, federation_internals_1.isObjectType)(edge.transition.castedType)) { let downcastsEntry = this.verticesToObjectTypeDowncasts.get(edge.head); if (!downcastsEntry) { downcastsEntry = { kind: 'NonInterfaceObject', downcasts: new Map(), }; this.verticesToObjectTypeDowncasts.set(edge.head, downcastsEntry); } (0, federation_internals_1.assert)(downcastsEntry.kind === 'NonInterfaceObject', () => 'Unexpectedly found interface object with regular object downcasts'); downcastsEntry.downcasts.set(edge.transition.castedType.name, edge.tail); } const typeConditionName = edge.transition.castedType.name; let endpointsEntry = this.inlineFragmentsToEndpoints .get(typeConditionName); if (!endpointsEntry) { endpointsEntry = new Map(); this.inlineFragmentsToEndpoints.set(typeConditionName, endpointsEntry); } endpointsEntry.set(edge.head, edge.tail); break; } case 'InterfaceObjectFakeDownCast': { let downcastsEntry = this.verticesToObjectTypeDowncasts.get(edge.head); if (!downcastsEntry) { downcastsEntry = { kind: 'InterfaceObject', downcasts: new Set(), }; this.verticesToObjectTypeDowncasts.set(edge.head, downcastsEntry); } (0, federation_internals_1.assert)(downcastsEntry.kind === 'InterfaceObject', () => 'Unexpectedly found abstract type with interface object downcasts'); downcastsEntry.downcasts.add(edge.transition.castedTypeName); const typeConditionName = edge.transition.castedTypeName; let endpointsEntry = this.inlineFragmentsToEndpoints .get(typeConditionName); if (!endpointsEntry) { endpointsEntry = new Map(); this.inlineFragmentsToEndpoints.set(typeConditionName, endpointsEntry); } endpointsEntry.set(edge.head, edge.tail); break; } case 'KeyResolution': case 'RootTypeResolution': { const headTypeName = edge.head.type.name; const tailTypeName = edge.tail.type.name; if (headTypeName === tailTypeName) { let indirectOptionsEntry = this.typesToIndirectOptions .get(tailTypeName); if (!indirectOptionsEntry) { indirectOptionsEntry = { sameTypeOptions: new Set(), interfaceObjectOptions: new Set(), }; this.typesToIndirectOptions.set(tailTypeName, indirectOptionsEntry); } indirectOptionsEntry.sameTypeOptions.add(edge.tail); } else { let interfaceObjectOptionsEntry = verticesToInterfaceObjectOptions .get(edge.head); if (!interfaceObjectOptionsEntry) { interfaceObjectOptionsEntry = new Set(); verticesToInterfaceObjectOptions.set(edge.head, interfaceObjectOptionsEntry); } interfaceObjectOptionsEntry.add(tailTypeName); } break; } case 'SubgraphEnteringTransition': break; default: (0, federation_internals_1.assertUnreachable)(edge.transition); } } for (const [vertex, options] of verticesToInterfaceObjectOptions) { const optionsMetadata = this.typesToIndirectOptions.get(vertex.type.name); if (optionsMetadata) { if (optionsMetadata.sameTypeOptions.has(vertex)) { for (const option of options) { optionsMetadata.interfaceObjectOptions.add(option); } continue; } } this.remainingVerticesToInterfaceObjectOptions.set(vertex, options); } for (const [vertex, options] of this.remainingVerticesToInterfaceObjectOptions) { const indirectOptionsMetadata = this.typesToIndirectOptions .get(vertex.type.name); if (!indirectOptionsMetadata) { continue; } for (const option of options) { if (indirectOptionsMetadata.interfaceObjectOptions.has(option)) { options.delete(option); } } if (options.size === 0) { this.remainingVerticesToInterfaceObjectOptions.delete(vertex); } } for (const vertex of graph.allVertices()) { if (vertex.source === querygraph_1.FEDERATED_GRAPH_ROOT_SOURCE || !(0, federation_internals_1.isCompositeType)(vertex.type)) { continue; } const typeConditionName = vertex.type.name; let endpointsEntry = this.inlineFragmentsToEndpoints .get(typeConditionName); if (!endpointsEntry) { endpointsEntry = new Map(); this.inlineFragmentsToEndpoints.set(typeConditionName, endpointsEntry); } endpointsEntry.set(vertex, vertex); if (!(0, federation_internals_1.isObjectType)(vertex.type)) { continue; } const metadata = (0, federation_internals_1.federationMetadata)(vertex.type.schema()); (0, federation_internals_1.assert)(metadata, () => 'Subgraph schema unexpectedly did not have subgraph metadata'); if (metadata.isInterfaceObjectType(vertex.type)) { continue; } let downcastsEntry = this.verticesToObjectTypeDowncasts.get(vertex); if (!downcastsEntry) { downcastsEntry = { kind: 'NonInterfaceObject', downcasts: new Map(), }; this.verticesToObjectTypeDowncasts.set(vertex, downcastsEntry); } (0, federation_internals_1.assert)(downcastsEntry.kind === 'NonInterfaceObject', () => 'Unexpectedly found object type with interface object downcasts in supergraph'); downcastsEntry.downcasts.set(typeConditionName, vertex); } } precomputeRebasingMetadata(graph) { var _a; const compositeTypesToVerticesBySource = new Map(); for (const vertex of graph.allVertices()) { if (vertex.source === querygraph_1.FEDERATED_GRAPH_ROOT_SOURCE || !(0, federation_internals_1.isCompositeType)(vertex.type)) { continue; } let typesToVerticesEntry = compositeTypesToVerticesBySource .get(vertex.source); if (!typesToVerticesEntry) { typesToVerticesEntry = new Map(); compositeTypesToVerticesBySource.set(vertex.source, typesToVerticesEntry); } let verticesEntry = typesToVerticesEntry.get(vertex.type.name); if (!verticesEntry) { verticesEntry = new Set(); typesToVerticesEntry.set(vertex.type.name, verticesEntry); } verticesEntry.add(vertex); } for (const [source, schema] of graph.sources) { if (source === querygraph_1.FEDERATED_GRAPH_ROOT_SOURCE) { continue; } const fieldsToRebaseableTypes = new Map(); const objectTypesToImplementingCompositeTypes = new Map(); const metadata = (0, federation_internals_1.federationMetadata)(schema); (0, federation_internals_1.assert)(metadata, () => 'Subgraph schema unexpectedly did not have subgraph metadata'); const fromContextDirectiveName = metadata.fromContextDirective().name; for (const type of schema.types()) { switch (type.kind) { case 'ObjectType': { for (const field of type.fields()) { if (field.arguments().some((arg) => arg.hasAppliedDirective(fromContextDirectiveName))) { continue; } let rebaseableTypesEntry = fieldsToRebaseableTypes.get(field.name); if (!rebaseableTypesEntry) { rebaseableTypesEntry = new Set(); fieldsToRebaseableTypes.set(field.name, rebaseableTypesEntry); } rebaseableTypesEntry.add(type.name); } let rebaseableTypesEntry = fieldsToRebaseableTypes.get(federation_internals_1.typenameFieldName); if (!rebaseableTypesEntry) { rebaseableTypesEntry = new Set(); fieldsToRebaseableTypes.set(federation_internals_1.typenameFieldName, rebaseableTypesEntry); } rebaseableTypesEntry.add(type.name); let implementingObjectTypesEntry = objectTypesToImplementingCompositeTypes.get(type.name); if (!implementingObjectTypesEntry) { implementingObjectTypesEntry = new Set(); objectTypesToImplementingCompositeTypes.set(type.name, implementingObjectTypesEntry); } implementingObjectTypesEntry.add(type.name); for (const interfaceImplementation of type.interfaceImplementations()) { implementingObjectTypesEntry.add(interfaceImplementation.interface.name); } break; } case 'InterfaceType': { for (const field of type.fields()) { if (field.arguments().some((arg) => arg.hasAppliedDirective(fromContextDirectiveName))) { continue; } let rebaseableTypesEntry = fieldsToRebaseableTypes.get(field.name); if (!rebaseableTypesEntry) { rebaseableTypesEntry = new Set(); fieldsToRebaseableTypes.set(field.name, rebaseableTypesEntry); } rebaseableTypesEntry.add(type.name); } let rebaseableTypesEntry = fieldsToRebaseableTypes.get(federation_internals_1.typenameFieldName); if (!rebaseableTypesEntry) { rebaseableTypesEntry = new Set(); fieldsToRebaseableTypes.set(federation_internals_1.typenameFieldName, rebaseableTypesEntry); } rebaseableTypesEntry.add(type.name); break; } case 'UnionType': { let rebaseableTypesEntry = fieldsToRebaseableTypes.get(federation_internals_1.typenameFieldName); if (!rebaseableTypesEntry) { rebaseableTypesEntry = new Set(); fieldsToRebaseableTypes.set(federation_internals_1.typenameFieldName, rebaseableTypesEntry); } rebaseableTypesEntry.add(type.name); for (const member of type.members()) { let implementingObjectTypesEntry = objectTypesToImplementingCompositeTypes.get(member.type.name); if (!implementingObjectTypesEntry) { implementingObjectTypesEntry = new Set(); objectTypesToImplementingCompositeTypes.set(member.type.name, implementingObjectTypesEntry); } implementingObjectTypesEntry.add(type.name); } break; } case 'ScalarType': case 'EnumType': case 'InputObjectType': break; default: (0, federation_internals_1.assertUnreachable)(type); } } const inlineFragmentsToRebaseableTypes = new Map(); for (const implementingTypes of objectTypesToImplementingCompositeTypes.values()) { for (const typeName of implementingTypes) { let rebaseableTypesEntry = inlineFragmentsToRebaseableTypes.get(typeName); if (!rebaseableTypesEntry) { rebaseableTypesEntry = new Set(); fieldsToRebaseableTypes.set(typeName, rebaseableTypesEntry); } for (const implementingType of implementingTypes) { rebaseableTypesEntry.add(implementingType); } } } const compositeTypesToVertices = (_a = compositeTypesToVerticesBySource.get(source)) !== null && _a !== void 0 ? _a : new Map(); for (const [fieldName, types] of fieldsToRebaseableTypes) { let rebaseableParentVerticesEntry = this.fieldsToRebaseableParentVertices.get(fieldName); if (!rebaseableParentVerticesEntry) { rebaseableParentVerticesEntry = new Set(); this.fieldsToRebaseableParentVertices.set(fieldName, rebaseableParentVerticesEntry); } for (const type of types) { const vertices = compositeTypesToVertices.get(type); if (vertices) { for (const vertex of vertices) { rebaseableParentVerticesEntry.add(vertex); } } } } for (const [typeConditionName, types] of inlineFragmentsToRebaseableTypes) { let rebaseableParentVerticesEntry = this.inlineFragmentsToRebaseableParentVertices.get(typeConditionName); if (!rebaseableParentVerticesEntry) { rebaseableParentVerticesEntry = new Set(); this.inlineFragmentsToRebaseableParentVertices.set(typeConditionName, rebaseableParentVerticesEntry); } for (const type of types) { const vertices = compositeTypesToVertices.get(type); if (vertices) { for (const vertex of vertices) { rebaseableParentVerticesEntry.add(vertex); } } } } } } checkNonLocalSelectionsLimitExceededAtRoot(stack, state, supergraphSchema, inconsistentAbstractTypesRuntimes, overrideConditions) { for (const [selection, simultaneousPaths] of stack) { const tailVertices = new Set(); for (const simultaneousPath of simultaneousPaths) { for (const path of simultaneousPath.paths) { tailVertices.add(path.tail); } } const tailVerticesInfo = this.estimateVerticesWithIndirectOptions(tailVertices); if (this.updateCount(1, tailVertices.size, state)) { return true; } if (selection.selectionSet) { const selectionHasDefer = selection.hasDefer(); const nextVertices = this.estimateNextVerticesForSelection(selection.element, tailVerticesInfo, state, supergraphSchema, overrideConditions); if (this.checkNonLocalSelectionsLimitExceeded(selection.selectionSet, nextVertices, selectionHasDefer, state, supergraphSchema, inconsistentAbstractTypesRuntimes, overrideConditions)) { return true; } } } return false; } checkNonLocalSelectionsLimitExceeded(selectionSet, parentVertices, parentSelectionHasDefer, state, supergraphSchema, inconsistentAbstractTypesRuntimes, overrideConditions) { var _a; let selectionSetIsNonLocal = parentVertices.nextVerticesHaveReachableCrossSubgraphEdges || parentSelectionHasDefer; for (const selection of selectionSet.selections()) { const element = selection.element; const selectionHasDefer = element.hasDefer(); const selectionHasInconsistentRuntimeTypes = element.kind === 'FragmentElement' && element.typeCondition && inconsistentAbstractTypesRuntimes.has(element.typeCondition.name); const oldCount = state.count; if (selection.selectionSet) { const nextVertices = this.estimateNextVerticesForSelection(element, parentVertices, state, supergraphSchema, overrideConditions); if (this.checkNonLocalSelectionsLimitExceeded(selection.selectionSet, nextVertices, selectionHasDefer, state, supergraphSchema, inconsistentAbstractTypesRuntimes, overrideConditions)) { return true; } } selectionSetIsNonLocal || (selectionSetIsNonLocal = selectionHasDefer || selectionHasInconsistentRuntimeTypes || (oldCount != state.count)); } if (!selectionSetIsNonLocal && parentVertices.nextVertices.size > 0) { outer: for (const selection of selectionSet.selections()) { switch (selection.kind) { case 'FieldSelection': { const rebaseableParentVertices = this.fieldsToRebaseableParentVertices .get(selection.element.definition.name); if (!rebaseableParentVertices) { selectionSetIsNonLocal = true; break outer; } for (const vertex of parentVertices.nextVertices) { if (!rebaseableParentVertices.has(vertex)) { selectionSetIsNonLocal = true; break outer; } } break; } case 'FragmentSelection': { const typeConditionName = (_a = selection.element.typeCondition) === null || _a === void 0 ? void 0 : _a.name; if (!typeConditionName) { continue; } const rebaseableParentVertices = this.inlineFragmentsToRebaseableParentVertices .get(typeConditionName); if (!rebaseableParentVertices) { selectionSetIsNonLocal = true; break outer; } for (const vertex of parentVertices.nextVertices) { if (!rebaseableParentVertices.has(vertex)) { selectionSetIsNonLocal = true; break outer; } } break; } default: (0, federation_internals_1.assertUnreachable)(selection); } } } return selectionSetIsNonLocal && this.updateCount(selectionSet.selections().length, parentVertices.nextVertices.size, state); } updateCount(numSelections, numParentVertices, state) { const additional_count = numSelections * numParentVertices; const new_count = state.count + additional_count; if (new_count > NonLocalSelectionsMetadata.MAX_NON_LOCAL_SELECTIONS) { return true; } state.count = new_count; return false; } estimateNextVerticesForSelection(element, parentVertices, state, supergraphSchema, overrideConditions) { var _a; const selectionKey = element.kind === 'Field' ? element.definition.name : (_a = element.typeCondition) === null || _a === void 0 ? void 0 : _a.name; if (!selectionKey) { return parentVertices; } let cache = state.nextVerticesCache.get(selectionKey); if (!cache) { cache = { typesToNextVertices: new Map(), remainingVerticesToNextVertices: new Map(), }; state.nextVerticesCache.set(selectionKey, cache); } const nextVerticesInfo = { nextVertices: new Set(), nextVerticesHaveReachableCrossSubgraphEdges: false, nextVerticesWithIndirectOptions: { types: new Set(), remainingVertices: new Set(), } }; for (const typeName of parentVertices.nextVerticesWithIndirectOptions.types) { let cacheEntry = cache.typesToNextVertices.get(typeName); if (!cacheEntry) { const indirectOptions = this.typesToIndirectOptions.get(typeName); (0, federation_internals_1.assert)(indirectOptions, () => 'Unexpectedly missing vertex information for cached type'); cacheEntry = this.estimateNextVerticesForSelectionWithoutCaching(element, indirectOptions.sameTypeOptions, supergraphSchema, overrideConditions); cache.typesToNextVertices.set(typeName, cacheEntry); } this.mergeNextVerticesInfo(cacheEntry, nextVerticesInfo); } for (const vertex of parentVertices.nextVerticesWithIndirectOptions.remainingVertices) { let cacheEntry = cache.remainingVerticesToNextVertices.get(vertex); if (!cacheEntry) { cacheEntry = this.estimateNextVerticesForSelectionWithoutCaching(element, [vertex], supergraphSchema, overrideConditions); cache.remainingVerticesToNextVertices.set(vertex, cacheEntry); } this.mergeNextVerticesInfo(cacheEntry, nextVerticesInfo); } return nextVerticesInfo; } mergeNextVerticesInfo(source, target) { for (const vertex of source.nextVertices) { target.nextVertices.add(vertex); } target.nextVerticesHaveReachableCrossSubgraphEdges || (target.nextVerticesHaveReachableCrossSubgraphEdges = source.nextVerticesHaveReachableCrossSubgraphEdges); this.mergeVerticesWithIndirectOptionsInfo(source.nextVerticesWithIndirectOptions, target.nextVerticesWithIndirectOptions); } mergeVerticesWithIndirectOptionsInfo(source, target) { for (const type of source.types) { target.types.add(type); } for (const vertex of source.remainingVertices) { target.remainingVertices.add(vertex); } } estimateNextVerticesForSelectionWithoutCaching(element, parentVertices, supergraphSchema, overrideConditions) { var _a; const nextVertices = new Set(); switch (element.kind) { case 'Field': { const fieldEndpoints = this.fieldsToEndpoints .get(element.definition.name); const processHeadVertex = (vertex) => { const fieldTail = fieldEndpoints === null || fieldEndpoints === void 0 ? void 0 : fieldEndpoints.get(vertex); if (!fieldTail) { return; } if (fieldTail.overrideCondition) { if ((0, querygraph_1.checkOverrideCondition)(fieldTail.overrideCondition, overrideConditions)) { nextVertices.add(fieldTail.tail); } } else { nextVertices.add(fieldTail.tail); } }; for (const vertex of parentVertices) { processHeadVertex(vertex); const downcasts = this.verticesToObjectTypeDowncasts.get(vertex); if (!downcasts) { continue; } if (downcasts.kind === 'NonInterfaceObject') { for (const vertex of downcasts.downcasts.values()) { processHeadVertex(vertex); } } } break; } case 'FragmentElement': { const typeConditionName = (_a = element.typeCondition) === null || _a === void 0 ? void 0 : _a.name; (0, federation_internals_1.assert)(typeConditionName, () => 'Inline fragment unexpectedly had no type condition'); const inlineFragmentEndpoints = this.inlineFragmentsToEndpoints .get(typeConditionName); let runtimeTypes = null; for (const vertex of parentVertices) { const nextVertex = inlineFragmentEndpoints === null || inlineFragmentEndpoints === void 0 ? void 0 : inlineFragmentEndpoints.get(vertex); if (nextVertex) { nextVertices.add(nextVertex); continue; } const downcasts = this.verticesToObjectTypeDowncasts.get(vertex); if (!downcasts) { continue; } if (!runtimeTypes) { const typeInSupergraph = supergraphSchema.type(typeConditionName); (0, federation_internals_1.assert)(typeInSupergraph && (0, federation_internals_1.isCompositeType)(typeInSupergraph), () => 'Type unexpectedly missing or non-composite in supergraph schema'); runtimeTypes = new Set(); for (const type of (0, federation_internals_1.possibleRuntimeTypes)(typeInSupergraph)) { runtimeTypes.add(type.name); } } switch (downcasts.kind) { case 'NonInterfaceObject': { for (const [typeName, vertex] of downcasts.downcasts) { if (runtimeTypes.has(typeName)) { nextVertices.add(vertex); } } break; } case 'InterfaceObject': { for (const typeName of downcasts.downcasts) { if (runtimeTypes.has(typeName)) { nextVertices.add(vertex); break; } } break; } default: (0, federation_internals_1.assertUnreachable)(downcasts); } } break; } default: (0, federation_internals_1.assertUnreachable)(element); } return this.estimateVerticesWithIndirectOptions(nextVertices); } estimateVerticesWithIndirectOptions(nextVertices) { const nextVerticesInfo = { nextVertices, nextVerticesHaveReachableCrossSubgraphEdges: false, nextVerticesWithIndirectOptions: { types: new Set(), remainingVertices: new Set(), } }; for (const nextVertex of nextVertices) { nextVerticesInfo.nextVerticesHaveReachableCrossSubgraphEdges || (nextVerticesInfo.nextVerticesHaveReachableCrossSubgraphEdges = nextVertex.hasReachableCrossSubgraphEdges); const typeName = nextVertex.type.name; const optionsMetadata = this.typesToIndirectOptions.get(typeName); if (optionsMetadata) { if (!nextVerticesInfo.nextVerticesWithIndirectOptions.types.has(typeName)) { nextVerticesInfo.nextVerticesWithIndirectOptions.types.add(typeName); for (const option of optionsMetadata.interfaceObjectOptions) { nextVerticesInfo.nextVerticesWithIndirectOptions.types.add(option); } } if (optionsMetadata.sameTypeOptions.has(nextVertex)) { continue; } } if (!nextVerticesInfo.nextVerticesWithIndirectOptions.remainingVertices .has(nextVertex)) { nextVerticesInfo.nextVerticesWithIndirectOptions.remainingVertices .add(nextVertex); const options = this.remainingVerticesToInterfaceObjectOptions .get(nextVertex); if (options) { for (const option of options) { nextVerticesInfo.nextVerticesWithIndirectOptions.types.add(option); } } } } return nextVerticesInfo; } } exports.NonLocalSelectionsMetadata = NonLocalSelectionsMetadata; NonLocalSelectionsMetadata.MAX_NON_LOCAL_SELECTIONS = 100000; class NonLocalSelectionsState { constructor() { this.count = 0; this.nextVerticesCache = new Map; } } exports.NonLocalSelectionsState = NonLocalSelectionsState; //# sourceMappingURL=nonLocalSelectionsEstimation.js.map