@apollo/query-graphs
Version:
Apollo Federation library to work with 'query graphs'
890 lines • 46.3 kB
JavaScript
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
;