UNPKG

@apollo/query-graphs

Version:

Apollo Federation library to work with 'query graphs'

890 lines 46.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.simpleTraversal = exports.buildFederatedQueryGraph = exports.buildSupergraphAPIQueryGraph = exports.buildQueryGraph = exports.QueryGraphState = exports.QueryGraph = exports.Edge = exports.checkOverrideCondition = exports.isRootVertex = exports.RootVertex = exports.Vertex = exports.isFederatedGraphRootType = exports.federatedGraphRootTypeName = exports.FEDERATED_GRAPH_ROOT_SOURCE = void 0; const federation_internals_1 = require("@apollo/federation-internals"); const util_1 = require("util"); const transition_1 = require("./transition"); const nonTrivialEdgePrecomputing_1 = require("./nonTrivialEdgePrecomputing"); const nonLocalSelectionsEstimation_1 = require("./nonLocalSelectionsEstimation"); exports.FEDERATED_GRAPH_ROOT_SOURCE = federation_internals_1.FEDERATION_RESERVED_SUBGRAPH_NAME; const FEDERATED_GRAPH_ROOT_SCHEMA = new federation_internals_1.Schema(); function federatedGraphRootTypeName(rootKind) { return `[${rootKind}]`; } exports.federatedGraphRootTypeName = federatedGraphRootTypeName; function isFederatedGraphRootType(type) { return type.name.startsWith('[') && type.name.endsWith(']'); } exports.isFederatedGraphRootType = isFederatedGraphRootType; class Vertex { constructor(index, type, source) { this.index = index; this.type = type; this.source = source; this.hasReachableCrossSubgraphEdges = false; } toString() { const label = `${this.type}(${this.source})`; return this.provideId ? `${label}-${this.provideId}` : label; } } exports.Vertex = Vertex; class RootVertex extends Vertex { constructor(rootKind, index, type, source) { super(index, type, source); this.rootKind = rootKind; } toString() { return super.toString() + '*'; } } exports.RootVertex = RootVertex; function toRootVertex(vertex, rootKind) { return new RootVertex(rootKind, vertex.index, vertex.type, vertex.source); } function isRootVertex(vertex) { return vertex instanceof RootVertex; } exports.isRootVertex = isRootVertex; function checkOverrideCondition(overrideCondition, conditionsToCheck) { const { label, condition } = overrideCondition; return conditionsToCheck.has(label) ? conditionsToCheck.get(label) === condition : false; } exports.checkOverrideCondition = checkOverrideCondition; class Edge { constructor(index, head, tail, transition, conditions, overrideCondition, requiredContexts) { this.index = index; this.head = head; this.tail = tail; this.transition = transition; this.overrideCondition = overrideCondition; this.requiredContexts = []; this._conditions = conditions; if (requiredContexts) { this.requiredContexts = [...requiredContexts]; } } get conditions() { return this._conditions; } isEdgeForField(name) { return this.transition.kind === 'FieldCollection' && this.transition.definition.name === name; } matchesSupergraphTransition(otherTransition) { (0, federation_internals_1.assert)(otherTransition.collectOperationElements, () => `Supergraphs shouldn't have transition that don't collect elements; got ${otherTransition}"`); const transition = this.transition; switch (transition.kind) { case 'FieldCollection': return otherTransition.kind === 'FieldCollection' && transition.definition.name === otherTransition.definition.name; case 'DownCast': return otherTransition.kind === 'DownCast' && transition.castedType.name === otherTransition.castedType.name; case 'InterfaceObjectFakeDownCast': return otherTransition.kind === 'DownCast' && transition.castedTypeName === otherTransition.castedType.name; default: return false; } } changesSubgraph() { return this.head.source !== this.tail.source; } label() { var _a; if (this.transition instanceof transition_1.SubgraphEnteringTransition && !this._conditions) { return ""; } let conditionsString = ((_a = this._conditions) !== null && _a !== void 0 ? _a : '').toString(); if (this.overrideCondition) { if (conditionsString.length) conditionsString += ', '; conditionsString += `${this.overrideCondition.label} = ${this.overrideCondition.condition}`; } if (conditionsString.length) conditionsString += ' ⊢ '; return conditionsString + this.transition.toString(); } withNewHead(newHead) { return new Edge(this.index, newHead, this.tail, this.transition, this._conditions, this.overrideCondition, this.requiredContexts); } addToConditions(newConditions) { this._conditions = this._conditions ? new federation_internals_1.SelectionSetUpdates().add(this._conditions).add(newConditions).toSelectionSet(this._conditions.parentType) : newConditions; } addToContextConditions(contextConditions) { this.requiredContexts.push(...contextConditions); } isKeyOrRootTypeEdgeToSelf() { return this.head === this.tail && (this.transition.kind === 'KeyResolution' || this.transition.kind === 'RootTypeResolution'); } satisfiesOverrideConditions(conditionsToCheck) { if (!this.overrideCondition) return true; return checkOverrideCondition(this.overrideCondition, conditionsToCheck); } toString() { return `${this.head} -> ${this.tail} (${this.label()})`; } } exports.Edge = Edge; class QueryGraph { constructor(name, vertices, _outEdges, typesToVertices, rootVertices, sources, subgraphToArgs, subgraphToArgIndices, schema, isFederatedAndForQueryPlanning) { this.name = name; this.vertices = vertices; this._outEdges = _outEdges; this.typesToVertices = typesToVertices; this.rootVertices = rootVertices; this.sources = sources; this.subgraphToArgs = subgraphToArgs; this.subgraphToArgIndices = subgraphToArgIndices; this.schema = schema; this.nonTrivialFollowupEdges = (0, nonTrivialEdgePrecomputing_1.preComputeNonTrivialFollowupEdges)(this); this.nonLocalSelectionsMetadata = isFederatedAndForQueryPlanning ? new nonLocalSelectionsEstimation_1.NonLocalSelectionsMetadata(this) : null; } verticesCount() { return this.vertices.length; } edgesCount() { return this._outEdges.reduce((acc, v) => acc + v.length, 0); } rootKinds() { return this.rootVertices.keys(); } roots() { return this.rootVertices.values(); } root(kind) { return this.rootVertices.get(kind); } outEdges(vertex, includeKeyAndRootTypeEdgesToSelf = false) { const allEdges = this._outEdges[vertex.index]; return includeKeyAndRootTypeEdgesToSelf ? allEdges : allEdges.filter((e) => !e.isKeyOrRootTypeEdgeToSelf()); } outEdgesCount(vertex) { return this._outEdges[vertex.index].length; } outEdge(vertex, edgeIndex) { return this._outEdges[vertex.index][edgeIndex]; } allVertices() { return this.vertices; } *allEdges() { for (const vertexOutEdges of this._outEdges) { for (const outEdge of vertexOutEdges) { yield outEdge; } } } isTerminal(vertex) { return this.outEdgesCount(vertex) === 0; } verticesForType(typeName) { const indexes = this.typesToVertices.get(typeName); return indexes == undefined ? [] : indexes.map(i => this.vertices[i]); } } exports.QueryGraph = QueryGraph; class QueryGraphState { constructor() { this.verticesStates = new Map(); this.adjacenciesStates = new Map(); } setVertexState(vertex, state) { this.verticesStates.set(vertex.index, state); } removeVertexState(vertex) { this.verticesStates.delete(vertex.index); } getVertexState(vertex) { return this.verticesStates.get(vertex.index); } setEdgeState(edge, state) { let edgeMap = this.adjacenciesStates.get(edge.head.index); if (!edgeMap) { edgeMap = new Map(); this.adjacenciesStates.set(edge.head.index, edgeMap); } edgeMap.set(edge.index, state); } removeEdgeState(edge) { const edgeMap = this.adjacenciesStates.get(edge.head.index); if (edgeMap) { edgeMap.delete(edge.index); if (edgeMap.size === 0) { this.adjacenciesStates.delete(edge.head.index); } } } getEdgeState(edge) { var _a; return (_a = this.adjacenciesStates.get(edge.head.index)) === null || _a === void 0 ? void 0 : _a.get(edge.index); } toDebugString(vertexMapper, edgeMapper) { const vs = Array.from(this.verticesStates.entries()).sort(([a], [b]) => a - b).map(([idx, state]) => ` ${idx}: ${!state ? "<null>" : vertexMapper(state)}`).join("\n"); const es = Array.from(this.adjacenciesStates.entries()).sort(([a], [b]) => a - b).map(([vIdx, adj]) => Array.from(adj.entries()).sort(([a], [b]) => a - b).map(([eIdx, state]) => ` ${vIdx}[${eIdx}]: ${!state ? "<null>" : edgeMapper(state)}`).join("\n")).join("\n"); return `vertices = {${vs}\n}, edges = {${es}\n}`; } } exports.QueryGraphState = QueryGraphState; function buildQueryGraph(name, schema, overrideLabelsByCoordinate) { return buildGraphInternal(name, schema, false, undefined, overrideLabelsByCoordinate); } exports.buildQueryGraph = buildQueryGraph; function buildGraphInternal(name, schema, addAdditionalAbstractTypeEdges, supergraphSchema, overrideLabelsByCoordinate) { const builder = new GraphBuilderFromSchema(name, schema, supergraphSchema ? { apiSchema: supergraphSchema.toAPISchema(), isFed1: (0, federation_internals_1.isFed1Supergraph)(supergraphSchema) } : undefined, overrideLabelsByCoordinate); for (const rootType of schema.schemaDefinition.roots()) { builder.addRecursivelyFromRoot(rootType.rootKind, rootType.type); } if (builder.isFederatedSubgraph) { builder.addInterfaceEntityEdges(); } if (addAdditionalAbstractTypeEdges) { builder.addAdditionalAbstractTypeEdges(); } return builder.build(); } function buildSupergraphAPIQueryGraph(supergraph) { const apiSchema = supergraph.apiSchema(); const overrideLabelsByCoordinate = new Map(); const joinFieldApplications = (0, federation_internals_1.validateSupergraph)(supergraph.schema)[1] .fieldDirective(supergraph.schema).applications(); for (const application of joinFieldApplications) { const overrideLabel = application.arguments().overrideLabel; if (overrideLabel) { overrideLabelsByCoordinate.set(application.parent.coordinate, overrideLabel); } } return buildQueryGraph("supergraph", apiSchema, overrideLabelsByCoordinate); } exports.buildSupergraphAPIQueryGraph = buildSupergraphAPIQueryGraph; function buildFederatedQueryGraph(supergraph, forQueryPlanning) { const subgraphs = supergraph.subgraphs(); const graphs = []; for (const subgraph of subgraphs) { graphs.push(buildGraphInternal(subgraph.name, subgraph.schema, forQueryPlanning, supergraph.schema)); } return federateSubgraphs(supergraph.schema, graphs, forQueryPlanning); } exports.buildFederatedQueryGraph = buildFederatedQueryGraph; function federatedProperties(subgraphs) { let vertices = 0; const rootKinds = new Set(); const schemas = []; for (const subgraph of subgraphs) { vertices += subgraph.verticesCount(); subgraph.rootKinds().forEach(k => rootKinds.add(k)); (0, federation_internals_1.assert)(subgraph.sources.size === 1, () => `Subgraphs should only have one sources, got ${subgraph.sources.size} ([${(0, federation_internals_1.mapKeys)(subgraph.sources).join(', ')}])`); schemas.push((0, federation_internals_1.firstOf)(subgraph.sources.values())); } return [vertices + rootKinds.size, rootKinds, schemas]; } function resolvableKeyApplications(keyDirective, type) { const applications = type.appliedDirectivesOf(keyDirective); return applications.filter((application) => { var _a; return (_a = application.arguments().resolvable) !== null && _a !== void 0 ? _a : true; }); } function federateSubgraphs(supergraph, subgraphs, forQueryPlanning) { var _a; const [verticesCount, rootKinds, schemas] = federatedProperties(subgraphs); const builder = new GraphBuilder(supergraph, verticesCount); rootKinds.forEach(k => builder.createRootVertex(k, new federation_internals_1.ObjectType(federatedGraphRootTypeName(k)), exports.FEDERATED_GRAPH_ROOT_SOURCE, FEDERATED_GRAPH_ROOT_SCHEMA)); const copyPointers = new Array(subgraphs.length); for (const [i, subgraph] of subgraphs.entries()) { copyPointers[i] = builder.copyGraph(subgraph); } for (const [i, subgraph] of subgraphs.entries()) { const copyPointer = copyPointers[i]; for (const rootKind of subgraph.rootKinds()) { const rootVertex = copyPointer.copiedVertex(subgraph.root(rootKind)); builder.addEdge(builder.root(rootKind), rootVertex, transition_1.subgraphEnteringTransition); for (const [j, otherSubgraph] of subgraphs.entries()) { const otherRootVertex = otherSubgraph.root(rootKind); if (otherRootVertex) { const otherCopyPointer = copyPointers[j]; builder.addEdge(rootVertex, otherCopyPointer.copiedVertex(otherRootVertex), new transition_1.RootTypeResolution(rootKind)); } } } } for (const [i, subgraph] of subgraphs.entries()) { const subgraphSchema = schemas[i]; const subgraphMetadata = (0, federation_internals_1.federationMetadata)(subgraphSchema); (0, federation_internals_1.assert)(subgraphMetadata, `Subgraph ${i} is not a valid federation subgraph`); const keyDirective = subgraphMetadata.keyDirective(); const requireDirective = subgraphMetadata.requiresDirective(); simpleTraversal(subgraph, v => { const type = v.type; for (const keyApplication of resolvableKeyApplications(keyDirective, type)) { (0, federation_internals_1.assert)((0, federation_internals_1.isInterfaceType)(type) || (0, federation_internals_1.isObjectType)(type), () => `Invalid "@key" application on non Object || Interface type "${type}"`); const isInterfaceObject = subgraphMetadata.isInterfaceObjectType(type); const conditions = (0, federation_internals_1.parseFieldSetArgument)({ parentType: type, directive: keyApplication, normalize: true }); const tail = copyPointers[i].copiedVertex(v); for (const [j, otherSubgraph] of subgraphs.entries()) { const otherVertices = otherSubgraph.verticesForType(type.name); if (otherVertices.length > 0) { (0, federation_internals_1.assert)(otherVertices.length == 1, () => `Subgraph ${j} should have a single vertex for type ${type.name} but got ${otherVertices.length}: ${(0, util_1.inspect)(otherVertices)}`); const otherVertex = otherVertices[0]; const head = copyPointers[j].copiedVertex(otherVertex); const tail = copyPointers[i].copiedVertex(v); builder.addEdge(head, tail, new transition_1.KeyResolution(), conditions); } if (isInterfaceObject) { const typeInSupergraph = supergraph.type(type.name); (0, federation_internals_1.assert)(typeInSupergraph && (0, federation_internals_1.isInterfaceType)(typeInSupergraph), () => `Type ${type} is an interfaceObject in subgraph ${i}; should be an interface in the supergraph`); for (const implemTypeInSupergraph of typeInSupergraph.possibleRuntimeTypes()) { const implemVertice = otherSubgraph.verticesForType(implemTypeInSupergraph.name)[0]; if (!implemVertice) { continue; } const implemHead = copyPointers[j].copiedVertex(implemVertice); const implemType = implemVertice.type; (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(implemType), () => `${implemType} should be composite since it implements ${typeInSupergraph} in the supergraph`); try { const implConditions = (0, federation_internals_1.parseFieldSetArgument)({ parentType: implemType, directive: keyApplication, validate: false, normalize: true }); builder.addEdge(implemHead, tail, new transition_1.KeyResolution(), implConditions); } catch (e) { } } } } } }, e => { if (e.transition.kind === 'FieldCollection') { const type = e.head.type; const field = e.transition.definition; (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(type), () => `Non composite type "${type}" should not have field collection edge ${e}`); for (const requiresApplication of field.appliedDirectivesOf(requireDirective)) { const conditions = (0, federation_internals_1.parseFieldSetArgument)({ parentType: type, directive: requiresApplication, normalize: true }); const head = copyPointers[i].copiedVertex(e.head); const copiedEdge = builder.edge(head, e.index); copiedEdge.addToConditions(conditions); } } return true; }); } const subgraphsByName = new Map(subgraphs.map((s) => [s.name, s])); for (const [i, toSubgraph] of subgraphs.entries()) { const subgraphSchema = schemas[i]; const subgraphMetadata = (0, federation_internals_1.federationMetadata)(subgraphSchema); (0, federation_internals_1.assert)(subgraphMetadata, `Subgraph ${i} is not a valid federation subgraph`); for (const application of subgraphMetadata.overrideDirective().applications()) { const { from, label } = application.arguments(); if (!label) continue; const fromSubgraph = subgraphsByName.get(from); (0, federation_internals_1.assert)(fromSubgraph, () => `Subgraph ${from} not found`); function updateEdgeWithOverrideCondition(subgraph, label, condition) { const field = application.parent; (0, federation_internals_1.assert)(field instanceof federation_internals_1.NamedSchemaElement, () => `@override should have been on a field, got ${field}`); const typeName = field.parent.name; const [vertex, ...unexpectedAdditionalVertices] = subgraph.verticesForType(typeName); (0, federation_internals_1.assert)(vertex && unexpectedAdditionalVertices.length === 0, () => `Subgraph ${subgraph.name} should have exactly one vertex for type ${typeName}`); const subgraphEdges = subgraph.outEdges(vertex); for (const edge of subgraphEdges) { if (edge.transition.kind === "FieldCollection" && edge.transition.definition.name === field.name) { const head = copyPointers[subgraphs.indexOf(subgraph)].copiedVertex(vertex); const copiedEdge = builder.edge(head, edge.index); copiedEdge.overrideCondition = { label, condition, }; } } } updateEdgeWithOverrideCondition(toSubgraph, label, true); updateEdgeWithOverrideCondition(fromSubgraph, label, false); } } const subgraphToArgs = new Map(); const subgraphToArgIndices = new Map(); for (const [i, subgraph] of subgraphs.entries()) { const subgraphSchema = schemas[i]; const subgraphMetadata = (0, federation_internals_1.federationMetadata)(subgraphSchema); (0, federation_internals_1.assert)(subgraphMetadata, `Subgraph ${i} is not a valid federation subgraph`); const contextNameToTypes = new Map(); for (const application of subgraphMetadata.contextDirective().applications()) { const { name: context } = application.arguments(); if (contextNameToTypes.has(context)) { contextNameToTypes.get(context).add(application.parent.name); } else { contextNameToTypes.set(context, new Set([application.parent.name])); } } const coordinateMap = new Map(); for (const application of subgraphMetadata.fromContextDirective().applications()) { const { field } = application.arguments(); const { context, selection } = (0, federation_internals_1.parseContext)(field); (0, federation_internals_1.assert)(context, () => `FieldValue has invalid format. Context not found ${field}`); (0, federation_internals_1.assert)(selection, () => `FieldValue has invalid format. Selection not found ${field}`); const namedParameter = application.parent.name; const argCoordinate = application.parent.coordinate; const args = (_a = subgraphToArgs.get(subgraph.name)) !== null && _a !== void 0 ? _a : []; args.push(argCoordinate); subgraphToArgs.set(subgraph.name, args); const fieldCoordinate = application.parent.parent.coordinate; const typesWithContextSet = contextNameToTypes.get(context); (0, federation_internals_1.assert)(typesWithContextSet, () => `Context ${context} is never set in subgraph`); const z = coordinateMap.get(fieldCoordinate); if (z) { z.push({ namedParameter, coordinate: argCoordinate, context, selection, typesWithContextSet, subgraphName: subgraph.name, argType: application.parent.type }); } else { coordinateMap.set(fieldCoordinate, [{ namedParameter, coordinate: argCoordinate, context, selection, typesWithContextSet, subgraphName: subgraph.name, argType: application.parent.type }]); } } simpleTraversal(subgraph, _v => { return undefined; }, e => { if (e.head.type.kind === 'ObjectType' && e.transition.kind === 'FieldCollection') { const coordinate = `${e.head.type.name}.${e.transition.definition.name}`; const requiredContexts = coordinateMap.get(coordinate); if (requiredContexts) { const headInSupergraph = copyPointers[i].copiedVertex(e.head); (0, federation_internals_1.assert)(headInSupergraph, () => `Vertex for type ${e.head.type.name} not found in supergraph`); const edgeInSupergraph = builder.edge(headInSupergraph, e.index); edgeInSupergraph.addToContextConditions(requiredContexts); } } return true; }); } for (const [i, subgraph] of subgraphs.entries()) { const subgraphName = subgraph.name; const args = subgraphToArgs.get(subgraph.name); if (args) { args.sort(); const argToIndex = new Map(); for (let idx = 0; idx < args.length; idx++) { argToIndex.set(args[idx], `contextualArgument_${i + 1}_${idx}`); } subgraphToArgIndices.set(subgraphName, argToIndex); } } builder.setContextMaps(subgraphToArgs, subgraphToArgIndices); let provideId = 0; for (const [i, subgraph] of subgraphs.entries()) { const subgraphSchema = schemas[i]; const subgraphMetadata = (0, federation_internals_1.federationMetadata)(subgraphSchema); (0, federation_internals_1.assert)(subgraphMetadata, `Subgraph ${i} is not a valid federation subgraph`); const providesDirective = subgraphMetadata.providesDirective(); simpleTraversal(subgraph, _ => undefined, e => { if (e.transition.kind === 'FieldCollection') { const type = e.head.type; const field = e.transition.definition; (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(type), () => `Non composite type "${type}" should not have field collection edge ${e}`); for (const providesApplication of field.appliedDirectivesOf(providesDirective)) { ++provideId; const fieldType = (0, federation_internals_1.baseType)(field.type); (0, federation_internals_1.assert)((0, federation_internals_1.isCompositeType)(fieldType), () => `Invalid @provide on field "${field}" whose type "${fieldType}" is not a composite type`); const provided = (0, federation_internals_1.parseFieldSetArgument)({ parentType: fieldType, directive: providesApplication }); const head = copyPointers[i].copiedVertex(e.head); const tail = copyPointers[i].copiedVertex(e.tail); const copiedEdge = builder.edge(head, e.index); const copiedTail = builder.makeCopy(tail, provideId); builder.updateEdgeTail(copiedEdge, copiedTail); addProvidesEdges(subgraphSchema, builder, copiedTail, provided, provideId); } } return true; }); } for (const [i, subgraph] of subgraphs.entries()) { const subgraphSchema = schemas[i]; const subgraphMetadata = (0, federation_internals_1.federationMetadata)(subgraphSchema); (0, federation_internals_1.assert)(subgraphMetadata, `Subgraph ${i} is not a valid federation subgraph`); const interfaceObjectDirective = subgraphMetadata.interfaceObjectDirective(); for (const application of interfaceObjectDirective.applications()) { const type = application.parent; (0, federation_internals_1.assert)((0, federation_internals_1.isObjectType)(type), '@interfaceObject should have been on an object type'); const vertex = copyPointers[i].copiedVertex(subgraph.verticesForType(type.name)[0]); const supergraphItf = supergraph.type(type.name); (0, federation_internals_1.assert)(supergraphItf && (0, federation_internals_1.isInterfaceType)(supergraphItf), () => `${type} has @interfaceObject in subgraph but has kind ${supergraphItf === null || supergraphItf === void 0 ? void 0 : supergraphItf.kind} in supergraph`); const condition = (0, federation_internals_1.selectionSetOfElement)(new federation_internals_1.Field(type.typenameField())); for (const implementation of supergraphItf.possibleRuntimeTypes()) { builder.addEdge(vertex, vertex, new transition_1.InterfaceObjectFakeDownCast(type, implementation.name), condition); } } } return builder.build(exports.FEDERATED_GRAPH_ROOT_SOURCE, forQueryPlanning); } function addProvidesEdges(schema, builder, from, provided, provideId) { const stack = [[from, provided]]; const source = from.source; while (stack.length > 0) { const [v, selectionSet] = stack.pop(); for (const selection of selectionSet.selectionsInReverseOrder()) { const element = selection.element; if (element.kind == 'Field') { const fieldDef = element.definition; const existingEdge = builder.edges(v).find(e => e.transition.kind === 'FieldCollection' && e.transition.definition.name === fieldDef.name); if (existingEdge) { if (selection.selectionSet) { const copiedTail = builder.makeCopy(existingEdge.tail, provideId); builder.updateEdgeTail(existingEdge, copiedTail); stack.push([copiedTail, selection.selectionSet]); } } else { const fieldType = (0, federation_internals_1.baseType)(fieldDef.type); const existingTail = builder.verticesForType(fieldType.name).find(v => v.source === source); const newTail = existingTail ? existingTail : builder.createNewVertex(fieldType, v.source, schema); if (selection.selectionSet) { const copiedTail = existingTail ? builder.makeCopy(existingTail, provideId) : newTail; builder.addEdge(v, copiedTail, new transition_1.FieldCollection(fieldDef, true)); stack.push([copiedTail, selection.selectionSet]); } else { builder.addEdge(v, newTail, new transition_1.FieldCollection(fieldDef, true)); } } } else { const typeCondition = element.typeCondition; if (typeCondition) { const existingEdge = builder.edges(v).find(e => e.transition.kind === 'DownCast' && e.transition.castedType.name === typeCondition.name); (0, federation_internals_1.assert)(existingEdge, () => `Shouldn't have ${selection} with no corresponding edge on ${v} (edges are: [${builder.edges(v)}])`); const copiedTail = builder.makeCopy(existingEdge.tail, provideId); builder.updateEdgeTail(existingEdge, copiedTail); stack.push([copiedTail, selection.selectionSet]); } else { stack.push([v, selection.selectionSet]); } } } } } class GraphBuilder { constructor(schema, verticesCount) { this.nextIndex = 0; this.typesToVertices = new federation_internals_1.MultiMap(); this.rootVertices = new federation_internals_1.MapWithCachedArrays(); this.sources = new Map(); this.subgraphToArgs = new Map(); this.subgraphToArgIndices = new Map(); this.vertices = verticesCount ? new Array(verticesCount) : []; this.outEdges = verticesCount ? new Array(verticesCount) : []; this.inEdges = verticesCount ? new Array(verticesCount) : []; this.schema = schema; } verticesForType(typeName) { const indexes = this.typesToVertices.get(typeName); return indexes == undefined ? [] : indexes.map(i => this.vertices[i]); } root(kind) { return this.rootVertices.get(kind); } addEdge(head, tail, transition, conditions, overrideCondition, requiredContexts) { const headOutEdges = this.outEdges[head.index]; const tailInEdges = this.inEdges[tail.index]; const edge = new Edge(headOutEdges.length, head, tail, transition, conditions, overrideCondition, requiredContexts); headOutEdges.push(edge); tailInEdges.push(edge); if (head.source !== tail.source) { this.markInEdgesHasReachingCrossSubgraphEdge(head); } } markInEdgesHasReachingCrossSubgraphEdge(from) { if (from.hasReachableCrossSubgraphEdges) { return; } const stack = [from]; while (stack.length > 0) { const v = stack.pop(); v.hasReachableCrossSubgraphEdges = true; for (const edge of this.inEdges[v.index]) { if (edge.head.source === edge.tail.source && !edge.head.hasReachableCrossSubgraphEdges) { stack.push(edge.head); } } } } createNewVertex(type, source, schema, index) { if (!index) { index = this.nextIndex++; } const vertex = new Vertex(index, type, source); const previous = this.vertices[index]; (0, federation_internals_1.assert)(!previous, () => `Overriding existing vertex ${previous} with ${vertex}`); this.vertices[index] = vertex; this.typesToVertices.add(type.name, index); this.outEdges[index] = []; this.inEdges[index] = []; if (!this.sources.has(source)) { this.sources.set(source, schema); } return vertex; } createRootVertex(kind, type, source, schema) { const vertex = this.createNewVertex(type, source, schema); (0, federation_internals_1.assert)(!this.rootVertices.has(kind), () => `Root vertex for ${kind} (${this.rootVertices.get(kind)}) already exists: cannot replace by ${vertex}`); this.setAsRoot(kind, vertex.index); } setAsRoot(kind, index) { const vertex = this.vertices[index]; (0, federation_internals_1.assert)(vertex, () => `Cannot set non-existing vertex at index ${index} as root ${kind}`); const rootVertex = toRootVertex(vertex, kind); this.vertices[vertex.index] = rootVertex; this.rootVertices.set(kind, rootVertex); const rootEdges = this.outEdges[vertex.index]; for (let i = 0; i < rootEdges.length; i++) { rootEdges[i] = rootEdges[i].withNewHead(rootVertex); } } copyGraph(graph) { const offset = this.nextIndex; for (const vertex of graph.vertices) { const newHead = this.getOrCopyVertex(vertex, offset, graph); for (const edge of graph.outEdges(vertex, true)) { const newTail = this.getOrCopyVertex(edge.tail, offset, graph); this.addEdge(newHead, newTail, edge.transition, edge.conditions, edge.overrideCondition, edge.requiredContexts); } } this.nextIndex += graph.verticesCount(); const that = this; return { copiedVertex(original) { const vertex = that.vertices[original.index + offset]; (0, federation_internals_1.assert)(vertex, () => `Vertex ${original} has no copy for offset ${offset}`); return vertex; } }; } vertex(index) { return this.vertices[index]; } edge(head, index) { return this.outEdges[head.index][index]; } edges(head) { return this.outEdges[head.index]; } makeCopy(vertex, provideId) { const newVertex = this.createNewVertex(vertex.type, vertex.source, this.sources.get(vertex.source)); newVertex.provideId = provideId; newVertex.hasReachableCrossSubgraphEdges = vertex.hasReachableCrossSubgraphEdges; for (const edge of this.outEdges[vertex.index]) { this.addEdge(newVertex, edge.tail, edge.transition, edge.conditions, edge.overrideCondition, edge.requiredContexts); } return newVertex; } updateEdgeTail(edge, newTail) { const newEdge = new Edge(edge.index, edge.head, newTail, edge.transition, edge.conditions, edge.overrideCondition, edge.requiredContexts); this.outEdges[edge.head.index][edge.index] = newEdge; this.inEdges[edge.tail.index] = this.inEdges[edge.tail.index].filter((e) => e !== edge); this.inEdges[newTail.index].push(newEdge); return newEdge; } getOrCopyVertex(toCopy, indexOffset, graph) { const index = toCopy.index + indexOffset; let v = this.vertices[index]; if (!v) { v = this.createNewVertex(toCopy.type, toCopy.source, graph.sources.get(toCopy.source), index); } return v; } build(name, isFederatedAndForQueryPlanning) { return new QueryGraph(name, this.vertices, this.outEdges, this.typesToVertices, this.rootVertices, this.sources, this.subgraphToArgs, this.subgraphToArgIndices, this.schema, isFederatedAndForQueryPlanning); } setContextMaps(subgraphToArgs, subgraphToArgIndices) { this.subgraphToArgs = subgraphToArgs; this.subgraphToArgIndices = subgraphToArgIndices; } } class GraphBuilderFromSchema extends GraphBuilder { constructor(name, schema, supergraph, overrideLabelsByCoordinate) { super(schema); this.name = name; this.supergraph = supergraph; this.overrideLabelsByCoordinate = overrideLabelsByCoordinate; this.isFederatedSubgraph = !!supergraph && (0, federation_internals_1.isFederationSubgraphSchema)(schema); } hasDirective(elt, directiveFct) { const metadata = (0, federation_internals_1.federationMetadata)(this.schema); return !!metadata && elt.hasAppliedDirective(directiveFct(metadata)); } isExternal(field) { const metadata = (0, federation_internals_1.federationMetadata)(this.schema); return !!metadata && metadata.isFieldExternal(field); } addRecursivelyFromRoot(kind, root) { this.setAsRoot(kind, this.addTypeRecursively(root).index); } addTypeRecursively(type) { const namedType = (0, federation_internals_1.baseType)(type); const existing = this.verticesForType(namedType.name); if (existing.length > 0) { (0, federation_internals_1.assert)(existing.length == 1, () => `Only one vertex should have been created for type ${namedType.name}, got ${existing.length}: ${(0, util_1.inspect)(this)}`); return existing[0]; } const vertex = this.createNewVertex(namedType, this.name, this.schema); if ((0, federation_internals_1.isObjectType)(namedType)) { this.addObjectTypeEdges(namedType, vertex); } else if ((0, federation_internals_1.isInterfaceType)(namedType)) { if (this.isFederatedSubgraph) { this.maybeAddInterfaceFieldsEdges(namedType, vertex); } this.addAbstractTypeEdges(namedType, vertex); } else if ((0, federation_internals_1.isUnionType)(namedType)) { this.addEdgeForField(namedType.typenameField(), vertex); this.addAbstractTypeEdges(namedType, vertex); } return vertex; } addObjectTypeEdges(type, head) { var _a, _b; const isInterfaceObject = (_b = (_a = (0, federation_internals_1.federationMetadata)(this.schema)) === null || _a === void 0 ? void 0 : _a.isInterfaceObjectType(type)) !== null && _b !== void 0 ? _b : false; for (const field of type.allFields()) { if (field.isSchemaIntrospectionField() || (isInterfaceObject && field.name === federation_internals_1.typenameFieldName)) { continue; } if (this.isExternal(field)) { this.addTypeRecursively(field.type); } else { this.addEdgeForField(field, head); } } } addEdgeForField(field, head) { var _a; const tail = this.addTypeRecursively(field.type); const overrideLabel = (_a = this.overrideLabelsByCoordinate) === null || _a === void 0 ? void 0 : _a.get(field.coordinate); if (overrideLabel) { this.addEdge(head, tail, new transition_1.FieldCollection(field), undefined, { label: overrideLabel, condition: true, }); this.addEdge(head, tail, new transition_1.FieldCollection(field), undefined, { label: overrideLabel, condition: false, }); } else { this.addEdge(head, tail, new transition_1.FieldCollection(field)); } } isDirectlyProvidedByType(type, fieldName) { const field = type.field(fieldName); return field && !this.isExternal(field) && !this.hasDirective(field, (m) => m.requiresDirective()); } maybeAddInterfaceFieldsEdges(type, head) { (0, federation_internals_1.assert)(this.supergraph, 'Missing supergraph schema when building a subgraph'); const supergraphType = this.supergraph.apiSchema.type(type.name); if (!supergraphType) { return; } const supergraphRuntimeTypes = supergraphType.possibleRuntimeTypes().map(t => t.name); const localRuntimeTypes = supergraphRuntimeTypes.map(t => this.schema.type(t)).filter(t => t !== undefined); for (const field of type.allFields()) { if (this.isExternal(field) || localRuntimeTypes.some(t => !this.isDirectlyProvidedByType(t, field.name))) { continue; } this.addEdgeForField(field, head); } } addAbstractTypeEdges(type, head) { const implementations = (0, federation_internals_1.isInterfaceType)(type) ? type.possibleRuntimeTypes() : type.types(); for (const implementationType of implementations) { const tail = this.addTypeRecursively(implementationType); this.addEdge(head, tail, new transition_1.DownCast(type, implementationType)); } } addAdditionalAbstractTypeEdges() { if (!this.supergraph) { return; } const abstractTypesWithTheirRuntimeTypes = []; for (const type of this.schema.types()) { if ((0, federation_internals_1.isAbstractType)(type)) { const typeInSupergraph = this.supergraph.apiSchema.type(type.name); if (!typeInSupergraph) { continue; } (0, federation_internals_1.assert)((0, federation_internals_1.isAbstractType)(typeInSupergraph), () => `${type} should not be a ${type.kind} in a subgraph but a ${typeInSupergraph.kind} in the supergraph`); abstractTypesWithTheirRuntimeTypes.push({ type, runtimeTypesInSubgraph: (0, federation_internals_1.possibleRuntimeTypes)(type), runtimeTypesInSupergraph: (0, federation_internals_1.possibleRuntimeTypes)(typeInSupergraph), }); } } for (let i = 0; i < abstractTypesWithTheirRuntimeTypes.length - 1; i++) { const t1 = abstractTypesWithTheirRuntimeTypes[i]; const t1Vertex = this.addTypeRecursively(t1.type); for (let j = i; j < abstractTypesWithTheirRuntimeTypes.length; j++) { const t2 = abstractTypesWithTheirRuntimeTypes[j]; let addT1ToT2 = false; let addT2ToT1 = false; if (t1.type === t2.type) { addT1ToT2 = true; } else { const intersectingLocal = t1.runtimeTypesInSubgraph.filter(o1 => t2.runtimeTypesInSubgraph.includes(o1)); if (intersectingLocal.length >= 2) { const isInLocalOtherTypeButNotLocalIntersection = (type, otherType) => (otherType.runtimeTypesInSubgraph.some((t) => t.name === type.name) && !intersectingLocal.some((t) => t.name === type.name)); if (!((0, federation_internals_1.isUnionType)(t2.type) || t2.runtimeTypesInSupergraph.some((rt) => isInLocalOtherTypeButNotLocalIntersection(rt, t1)))) { addT1ToT2 = true; } if (!((0, federation_internals_1.isUnionType)(t1.type) || t1.runtimeTypesInSupergraph.some((rt) => isInLocalOtherTypeButNotLocalIntersection(rt, t2)))) { addT2ToT1 = true; } } } if (addT1ToT2 || addT2ToT1) { const t2Vertex = this.addTypeRecursively(t2.type); if (addT1ToT2) { this.addEdge(t1Vertex, t2Vertex, new transition_1.DownCast(t1.type, t2.type)); } if (addT2ToT1) { this.addEdge(t2Vertex, t1Vertex, new transition_1.DownCast(t2.type, t1.type)); } } } } } addInterfaceEntityEdges() { const subgraphMetadata = (0, federation_internals_1.federationMetadata)(this.schema); (0, federation_internals_1.assert)(subgraphMetadata, () => `${this.name} does not correspond to a subgraph`); const entityType = subgraphMetadata.entityType(); if (!entityType) { return; } const entityTypeVertex = this.addTypeRecursively(entityType); const keyDirective = subgraphMetadata.keyDirective(); for (const itfType of this.schema.interfaceTypes()) { if (resolvableKeyApplications(keyDirective, itfType).length > 0) { const itfTypeVertex = this.addTypeRecursively(itfType); this.addEdge(entityTypeVertex, itfTypeVertex, new transition_1.DownCast(entityType, itfType)); } } } build() { return super.build(this.name); } } function simpleTraversal(graph, onVertex, onEdges) { const marked = new Array(graph.verticesCount()); const stack = []; const maybeAdd = function (vertex) { if (!marked[vertex.index]) { stack.push(vertex); marked[vertex.index] = true; } }; graph.roots().forEach(maybeAdd); while (stack.length > 0) { const vertex = stack.pop(); onVertex(vertex); for (const edge of graph.outEdges(vertex)) { const shouldTraverse = onEdges(edge); if (shouldTraverse) { maybeAdd(edge.tail); } } } } exports.simpleTraversal = simpleTraversal; //# sourceMappingURL=querygraph.js.map