@theguild/federation-composition
Version:
Open Source Composition library for Apollo Federation
971 lines (970 loc) • 47 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFederationExtension = isFederationExtension;
exports.objectTypeBuilder = objectTypeBuilder;
const graphql_1 = require("graphql");
const helpers_js_1 = require("../../subgraph/helpers.js");
const helpers_js_2 = require("../../utils/helpers.js");
const graphql_js_1 = require("../../utils/graphql.js");
const ast_js_1 = require("./ast.js");
const common_js_1 = require("./common.js");
const auth_js_1 = require("../../utils/auth.js");
function isFederationExtension(meta, version) {
const hasExtendsDirective = meta.extensionType === "@extends";
if (meta.extension) {
if (version === "v1.0" && !hasExtendsDirective) {
return false;
}
if (hasExtendsDirective) {
return true;
}
if (meta.hasDefinition) {
return false;
}
return true;
}
return false;
}
function objectTypeBuilder() {
let requiresUsageIndex = null;
function getRequiresUsageIndexLazy(supergraphState, graphNameToId) {
if (requiresUsageIndex === null) {
requiresUsageIndex = buildRequiresUsageIndex(supergraphState, graphNameToId);
}
return requiresUsageIndex;
}
return {
visitSubgraphState(graph, state, typeName, type) {
const objectTypeState = getOrCreateObjectType(state, typeName);
type.tags.forEach((tag) => objectTypeState.tags.add(tag));
if (type.inaccessible) {
objectTypeState.inaccessible = true;
}
if (type.authenticated) {
objectTypeState.authenticated = true;
}
if (type.policies) {
objectTypeState.policies = (0, auth_js_1.mergeScopePolicies)(objectTypeState.policies, type.policies);
}
if (type.scopes) {
objectTypeState.scopes = (0, auth_js_1.mergeScopePolicies)(objectTypeState.scopes, type.scopes);
}
if (type.cost !== null) {
objectTypeState.cost = (0, helpers_js_2.mathMax)(type.cost, objectTypeState.cost);
}
const isDefinition = type.isDefinition &&
(graph.version === "v1.0" ? type.extensionType !== "@extends" : true);
if (type.description && !objectTypeState.description) {
objectTypeState.description = type.description;
}
if (isDefinition) {
objectTypeState.hasDefinition = true;
}
if (type.ast.directives) {
type.ast.directives.forEach((directive) => {
objectTypeState.ast.directives.push(directive);
});
}
type.interfaces.forEach((interfaceName) => objectTypeState.interfaces.add(interfaceName));
if (type.keys.length) {
objectTypeState.isEntity = true;
}
objectTypeState.byGraph.set(graph.id, {
hasDefinition: isDefinition,
extension: type.extension,
extensionType: type.extensionType,
external: type.external,
keys: type.keys,
inaccessible: type.inaccessible,
shareable: type.shareable,
interfaces: type.interfaces,
version: graph.version,
authenticated: type.authenticated,
policies: type.policies.slice(),
scopes: type.scopes.slice(),
});
const typeInGraph = objectTypeState.byGraph.get(graph.id);
for (const field of type.fields.values()) {
const fieldState = getOrCreateField(objectTypeState, field.name, field.type);
field.tags.forEach((tag) => fieldState.tags.add(tag));
const usedAsKey = type.fieldsUsedAsKeys.has(field.name);
if (usedAsKey) {
fieldState.usedAsKey = true;
}
const isExternal = graph.version === "v1.0"
? field.external &&
isFederationExtension(typeInGraph, graph.version)
: field.external;
const shouldForceType = !usedAsKey && !isExternal && !fieldState.internal.seenNonExternal;
const shouldChangeType = shouldForceType ||
(!isExternal &&
fieldState.type.lastIndexOf("!") > field.type.lastIndexOf("!"));
if (shouldChangeType) {
fieldState.type = field.type;
}
if (field.isLeaf) {
fieldState.isLeaf = true;
}
if (!fieldState.internal.seenNonExternal && !isExternal) {
fieldState.internal.seenNonExternal = true;
}
if (field.inaccessible) {
fieldState.inaccessible = true;
}
if (field.authenticated) {
fieldState.authenticated = true;
}
if (field.policies) {
fieldState.policies = (0, auth_js_1.mergeScopePolicies)(fieldState.policies, field.policies);
}
if (field.scopes) {
fieldState.scopes = (0, auth_js_1.mergeScopePolicies)(fieldState.scopes, field.scopes);
}
if (field.cost !== null) {
fieldState.cost = (0, helpers_js_2.mathMax)(field.cost, fieldState.cost);
}
if (field.listSize !== null) {
fieldState.listSize = {
printRequireOneSlicingArgument: false,
assumedSize: (0, helpers_js_2.mathMaxNullable)(fieldState.listSize?.assumedSize, field.listSize.assumedSize),
requireOneSlicingArgument: (fieldState.listSize?.requireOneSlicingArgument ?? true) &&
field.listSize.requireOneSlicingArgument,
slicingArguments: (0, helpers_js_2.nullableArrayUnion)(fieldState.listSize?.slicingArguments, field.listSize.slicingArguments),
sizedFields: (0, helpers_js_2.nullableArrayUnion)(fieldState.listSize?.sizedFields, field.listSize.sizedFields),
};
}
if (field.description && !fieldState.description) {
fieldState.description = field.description;
}
if (field.override) {
fieldState.override = field.override;
}
if (field.overrideLabel) {
fieldState.overrideLabel = field.overrideLabel;
}
if (field.deprecated && !fieldState.deprecated) {
fieldState.deprecated = field.deprecated;
}
field.ast.directives.forEach((directive) => {
fieldState.ast.directives.push(directive);
});
fieldState.byGraph.set(graph.id, {
type: field.type,
external: field.external,
inaccessible: field.inaccessible,
description: field.description ?? null,
override: field.override,
overrideLabel: field.overrideLabel,
provides: field.provides,
requires: field.requires,
provided: field.provided,
required: field.required,
shareable: field.shareable,
extension: field.extension,
authenticated: field.authenticated,
policies: field.policies,
scopes: field.scopes,
used: field.used,
usedAsKey,
version: graph.version,
});
for (const arg of field.args.values()) {
const argState = getOrCreateArg(fieldState, arg.name, arg.type, arg.kind);
arg.tags.forEach((tag) => argState.tags.add(tag));
if (arg.type.endsWith("!")) {
argState.type = arg.type;
}
if (arg.inaccessible) {
argState.inaccessible = true;
}
if (arg.description && !argState.description) {
argState.description = arg.description;
}
if (arg.deprecated && !argState.deprecated) {
argState.deprecated = arg.deprecated;
}
arg.ast.directives.forEach((directive) => {
argState.ast.directives.push(directive);
});
if (typeof arg.defaultValue !== "undefined") {
argState.defaultValue = arg.defaultValue;
}
if (arg.cost !== null) {
argState.cost = (0, helpers_js_2.mathMax)(arg.cost, argState.cost);
}
argState.kind = arg.kind;
argState.byGraph.set(graph.id, {
type: arg.type,
kind: arg.kind,
inaccessible: arg.inaccessible,
defaultValue: arg.defaultValue,
version: graph.version,
});
}
}
},
composeSupergraphState(objectType, _graphs, { supergraphState }) {
for (const interfaceName of objectType.interfaces) {
const interfaceState = supergraphState.interfaceTypes.get(interfaceName);
if (!interfaceState) {
throw new Error(`Interface "${interfaceName}" not found in Supergraph state`);
}
for (const [interfaceFieldName, interfaceField,] of interfaceState.fields) {
if (!interfaceState.hasInterfaceObject) {
continue;
}
const fieldState = objectType.fields.get(interfaceFieldName);
if (fieldState) {
fieldState.scopes = (0, auth_js_1.mergeScopePolicies)(fieldState.scopes, interfaceField.scopes);
fieldState.policies = (0, auth_js_1.mergeScopePolicies)(fieldState.policies, interfaceField.policies);
if (interfaceField.authenticated) {
fieldState.authenticated = true;
}
}
}
}
},
composeSupergraphNode(objectType, graphs, { graphNameToId, supergraphState }) {
const isQuery = objectType.name === "Query";
const requiresUsageIndex = getRequiresUsageIndexLazy(supergraphState, graphNameToId);
let joinTypes = isQuery
?
Array.from(graphs.values()).map((graph) => ({
graph: graph.graph.id,
}))
:
Array.from(objectType.byGraph.entries())
.map(([graphId, meta]) => {
if (meta.keys.length) {
return meta.keys.map((key) => ({
graph: graphId,
key: key.fields,
extension: isFederationExtension(meta, graphs.get(graphId).federation.version),
resolvable: key.resolvable,
}));
}
return [
{
graph: graphId,
},
];
})
.flat(1);
const interfaceFieldGraphsByFieldName = new Map();
const resolvableFieldsFromInterfaceObjects = [];
for (const interfaceName of objectType.interfaces) {
const interfaceState = supergraphState.interfaceTypes.get(interfaceName);
if (!interfaceState) {
throw new Error(`Interface "${interfaceName}" not found in Supergraph state`);
}
for (const [interfaceFieldName, interfaceField,] of interfaceState.fields) {
const found = interfaceFieldGraphsByFieldName.get(interfaceFieldName);
if (found) {
for (const graphId of interfaceField.byGraph.keys()) {
found.add(graphId);
}
}
else {
interfaceFieldGraphsByFieldName.set(interfaceFieldName, new Set(interfaceField.byGraph.keys()));
}
if (!interfaceState.hasInterfaceObject) {
continue;
}
if (!resolvableFieldsFromInterfaceObjects.some((f) => f.name === interfaceFieldName)) {
resolvableFieldsFromInterfaceObjects.push(interfaceField);
}
}
}
if (!isQuery) {
joinTypes = joinTypes.filter((joinType) => shouldKeepObjectTypeJoinTypeInGraph({
graphId: joinType.graph,
objectType,
requiresUsageIndex,
interfaceFieldGraphsByFieldName,
graphNameToId,
}));
}
if (objectType.isEntity) {
for (const [_, field] of objectType.fields) {
if (!field.description) {
continue;
}
if (!field.override) {
continue;
}
for (const [_, fieldInGraph] of field.byGraph) {
if (fieldInGraph.override && !fieldInGraph.shareable) {
field.description = fieldInGraph.description ?? undefined;
}
}
}
}
function shouldSetExternalOnJoinField(fieldStateInGraph, graphId, fieldState, hasOnlyOverriddenRequiresUsage) {
if (!fieldStateInGraph.external) {
return false;
}
if (hasOnlyOverriddenRequiresUsage) {
return false;
}
if (fieldStateInGraph.provided) {
return true;
}
if (fieldState.usedAsKey &&
objectType.byGraph.get(graphId).extension === true) {
return false;
}
return true;
}
function shouldDropFedV1UnusedExternalJoinField(graphId, fieldStateInGraph, isRequiredOrProvided, usedOverridden) {
return (fieldStateInGraph.external &&
!fieldStateInGraph.usedAsKey &&
!isRequiredOrProvided &&
!usedOverridden &&
graphs.get(graphId).federation.version === "v1.0");
}
function createJoinFields(fieldInGraphs, field, { hasDifferentOutputType, isEffectivelyRequired, hasOnlyOverriddenRequiresUsage, }) {
return fieldInGraphs
.map(([graphId, meta]) => {
const type = hasDifferentOutputType ? meta.type : undefined;
const override = meta.override ?? undefined;
const overrideLabel = meta.overrideLabel ?? undefined;
const usedOverridden = isUsedOverriddenInGraph(field, meta, interfaceFieldGraphsByFieldName, graphId, graphNameToId);
const inInterfaceGraphs = interfaceFieldGraphsByFieldName.get(field.name);
const isInterfaceFieldInGraph = inInterfaceGraphs && inInterfaceGraphs.has(graphId);
const external = shouldSetExternalOnJoinField(meta, graphId, field, hasOnlyOverriddenRequiresUsage(graphId));
const provides = meta.provides ?? undefined;
const requires = meta.requires ?? undefined;
const hasAnyJoinFieldMetadata = !!type ||
!!override ||
!!provides ||
!!requires ||
!!usedOverridden;
const isRequiredOrProvided = meta.provided || isEffectivelyRequired(graphId, meta);
if (shouldDropFedV1UnusedExternalJoinField(graphId, meta, isRequiredOrProvided, usedOverridden)) {
return null;
}
if (external &&
objectType.byGraph.get(graphId).extension === true &&
!hasAnyJoinFieldMetadata &&
!isRequiredOrProvided &&
!isInterfaceFieldInGraph) {
return null;
}
if (meta.external &&
!external &&
hasOnlyOverriddenRequiresUsage(graphId) &&
!hasAnyJoinFieldMetadata &&
!isRequiredOrProvided &&
!isInterfaceFieldInGraph) {
return null;
}
return {
graph: graphId,
type,
override,
overrideLabel,
usedOverridden,
external,
provides,
requires,
};
})
.filter(helpers_js_2.isDefined);
}
function collectOverrideJoinFieldInfo(fieldInGraphs) {
const overrideLabels = {};
const overriddenGraphs = new Set();
for (const [toGraphId, meta] of fieldInGraphs) {
if (!meta.override) {
continue;
}
const fromGraphId = graphNameToId(meta.override);
if (!fromGraphId) {
continue;
}
overriddenGraphs.add(fromGraphId);
if (meta.overrideLabel) {
overrideLabels[fromGraphId] = meta.overrideLabel;
overrideLabels[toGraphId] = meta.overrideLabel;
}
}
return {
overrideLabels,
overriddenGraphs,
};
}
function buildJoinFieldAst(graphId, meta, field, hasDifferentOutputType, overrideLabel, external, usedOverridden) {
return {
graph: graphId,
override: meta.override ?? undefined,
overrideLabel,
usedOverridden: usedOverridden ??
isUsedOverriddenInGraph(field, meta, interfaceFieldGraphsByFieldName, graphId, graphNameToId),
type: hasDifferentOutputType ? meta.type : undefined,
external,
provides: meta.provides ?? undefined,
requires: meta.requires ?? undefined,
};
}
return (0, ast_js_1.createObjectTypeNode)({
name: objectType.name,
ast: {
directives: (0, common_js_1.convertToConst)(objectType.ast.directives),
},
cost: objectType.cost !== null
? {
cost: objectType.cost,
directiveName: (0, helpers_js_2.ensureValue)(supergraphState.specs.cost.names.cost, "Directive name of @cost is not defined"),
}
: null,
description: objectType.description,
fields: Array.from(objectType.fields.values())
.map((field) => {
const fieldInGraphs = Array.from(field.byGraph.entries());
const fieldCoordinate = toFieldCoordinate(objectType.name, field.name);
const isEffectivelyRequired = (graphId, meta) => isFieldCoordinateEffectivelyRequired(requiresUsageIndex, fieldCoordinate, graphId, meta.required);
const hasOnlyOverriddenRequiresUsage = (graphId) => hasOnlyOverriddenRequiresDependency(requiresUsageIndex, fieldCoordinate, graphId);
const hasDifferentOutputType = fieldInGraphs.some(([_, meta]) => meta.type !== field.type);
const isDefinedEverywhere = field.byGraph.size ===
(isQuery ? graphs.size : objectType.byGraph.size);
let joinFields = [];
const differencesBetweenGraphs = {
override: false,
overrideLabel: false,
type: false,
external: false,
provides: false,
requires: false,
};
for (const [graphId, meta] of fieldInGraphs) {
if (meta.external) {
differencesBetweenGraphs.external = field.usedAsKey
? objectType.byGraph.get(graphId).extension !== true
: true;
}
if (meta.override !== null) {
differencesBetweenGraphs.override = true;
}
if (meta.overrideLabel !== null) {
differencesBetweenGraphs.overrideLabel = true;
}
if (meta.provides !== null) {
differencesBetweenGraphs.provides = true;
}
if (meta.requires !== null) {
differencesBetweenGraphs.requires = true;
}
if (meta.type !== field.type) {
differencesBetweenGraphs.type = true;
}
}
const overrideJoinFieldInfo = differencesBetweenGraphs.override
? collectOverrideJoinFieldInfo(fieldInGraphs)
: null;
const graphJoinFieldState = new Map();
for (const [graphId, meta] of fieldInGraphs) {
const hasOnlyRequiresUsage = hasOnlyOverriddenRequiresUsage(graphId);
graphJoinFieldState.set(graphId, {
isRequired: isEffectivelyRequired(graphId, meta),
externalOnJoinField: shouldSetExternalOnJoinField(meta, graphId, field, hasOnlyRequiresUsage),
usedOverridden: isUsedOverriddenInGraph(field, meta, interfaceFieldGraphsByFieldName, graphId, graphNameToId),
});
}
function shouldEmitOverrideJoinField(graphId, meta, mode) {
const overrideLabels = overrideJoinFieldInfo?.overrideLabels ?? {};
const overriddenGraphs = overrideJoinFieldInfo?.overriddenGraphs;
const overrideLabel = overrideLabels[graphId];
if (mode === "query") {
const hasExplicitOverride = meta.override !== null;
const hasOverrideLabel = Boolean(overrideLabel);
const isShareableSourceGraph = meta.shareable && overriddenGraphs?.has(graphId) === false;
return (hasExplicitOverride ||
hasOverrideLabel ||
isShareableSourceGraph);
}
const graphState = graphJoinFieldState.get(graphId);
const isOverriddenSourceGraph = overriddenGraphs?.has(graphId) === true;
const needsOverrideLabel = typeof meta.overrideLabel === "string" ||
Boolean(overrideLabel);
const needsExternalForRequired = graphState.externalOnJoinField && graphState.isRequired;
const hasUsedOverridden = graphState.usedOverridden ||
(isOverriddenSourceGraph &&
graphState.isRequired &&
!graphState.externalOnJoinField);
return (needsExternalForRequired ||
needsOverrideLabel ||
hasUsedOverridden ||
!isOverriddenSourceGraph);
}
function buildOverrideJoinFields(mode, graphsToEmit) {
const overrideLabels = overrideJoinFieldInfo?.overrideLabels ?? {};
return graphsToEmit.map(([graphId, meta]) => {
const graphState = graphJoinFieldState.get(graphId);
const isOverriddenSourceGraph = overrideJoinFieldInfo?.overriddenGraphs.has(graphId) === true;
const usedOverridden = mode === "object"
? graphState.usedOverridden ||
(isOverriddenSourceGraph &&
graphState.isRequired &&
!graphState.externalOnJoinField)
: undefined;
return buildJoinFieldAst(graphId, meta, field, differencesBetweenGraphs.type, mode === "query"
? (meta.overrideLabel ?? undefined)
: (overrideLabels[graphId] ?? undefined), mode === "query"
? (meta.external ?? undefined)
: graphState.externalOnJoinField
? true
: undefined, usedOverridden);
});
}
if (!isQuery && field.byGraph.size === 1) {
const graphId = field.byGraph.keys().next().value;
const fieldInGraph = field.byGraph.get(graphId);
if (fieldInGraph.external &&
!fieldInGraph.usedAsKey &&
!isEffectivelyRequired(graphId, fieldInGraph) &&
!fieldInGraph.provided &&
!isUsedOverriddenInGraph(field, fieldInGraph, interfaceFieldGraphsByFieldName, graphId, graphNameToId) &&
graphs.get(graphId).federation.version === "v1.0") {
return null;
}
}
if (isQuery) {
if (differencesBetweenGraphs.override && graphs.size > 1) {
const graphsToEmit = fieldInGraphs.filter(([graphId, meta]) => shouldEmitOverrideJoinField(graphId, meta, "query"));
joinFields = buildOverrideJoinFields("query", graphsToEmit);
}
else {
joinFields =
graphs.size > 1 && !isDefinedEverywhere
? fieldInGraphs.map(([graphId, meta]) => ({
graph: graphId,
provides: differencesBetweenGraphs.provides
? (meta.provides ?? undefined)
: undefined,
}))
: [];
}
}
else if (isDefinedEverywhere) {
const hasDifferencesBetweenGraphs = Object.values(differencesBetweenGraphs).some((value) => value === true);
if (differencesBetweenGraphs.override) {
const graphsToEmit = fieldInGraphs.filter(([graphId, f]) => {
return shouldEmitOverrideJoinField(graphId, f, "object");
});
if (!(graphsToEmit.length === 1 &&
joinTypes.length === 1 &&
joinTypes[0].graph === graphsToEmit[0][0])) {
joinFields = buildOverrideJoinFields("object", graphsToEmit);
}
}
else if (hasDifferencesBetweenGraphs) {
joinFields = createJoinFields(fieldInGraphs, field, {
hasDifferentOutputType,
isEffectivelyRequired,
hasOnlyOverriddenRequiresUsage,
});
}
}
else {
if (differencesBetweenGraphs.override) {
const graphsToEmit = fieldInGraphs.filter(([graphId, meta]) => shouldEmitOverrideJoinField(graphId, meta, "object"));
joinFields = buildOverrideJoinFields("object", graphsToEmit);
}
else {
joinFields = createJoinFields(fieldInGraphs, field, {
hasDifferentOutputType,
isEffectivelyRequired,
hasOnlyOverriddenRequiresUsage,
});
}
}
return {
name: field.name,
type: field.type,
inaccessible: field.inaccessible,
authenticated: field.authenticated,
policies: field.policies,
scopes: field.scopes,
tags: Array.from(field.tags),
description: field.description,
deprecated: field.deprecated,
cost: field.cost !== null
? {
cost: field.cost,
directiveName: (0, helpers_js_2.ensureValue)(supergraphState.specs.cost.names.cost, "Directive name of @cost is not defined"),
}
: null,
listSize: field.listSize !== null
? {
...field.listSize,
directiveName: (0, helpers_js_2.ensureValue)(supergraphState.specs.cost.names.listSize, "Directive name of @listSize is not defined"),
}
: null,
ast: {
directives: (0, common_js_1.convertToConst)(field.ast.directives),
},
join: {
field: joinFields.length === 1 &&
joinTypes.length === 1 &&
!joinFields[0].external &&
!joinFields[0].override &&
!joinFields[0].overrideLabel &&
!joinFields[0].provides &&
!joinFields[0].requires &&
!joinFields[0].usedOverridden &&
!joinFields[0].type
? []
: joinFields,
},
arguments: Array.from(field.args.values())
.filter((arg) => {
if (arg.byGraph.size !== field.byGraph.size) {
return false;
}
return true;
})
.map((arg) => {
return {
name: arg.name,
type: arg.type,
kind: arg.kind,
inaccessible: arg.inaccessible,
cost: arg.cost !== null
? {
cost: arg.cost,
directiveName: (0, helpers_js_2.ensureValue)(supergraphState.specs.cost.names.cost, "Directive name of @cost is not defined"),
}
: null,
tags: Array.from(arg.tags),
defaultValue: arg.defaultValue,
description: arg.description,
deprecated: arg.deprecated,
ast: {
directives: (0, common_js_1.convertToConst)(arg.ast.directives),
},
};
}),
};
})
.filter(helpers_js_2.isDefined)
.concat(resolvableFieldsFromInterfaceObjects
.filter((f) => !objectType.fields.has(f.name))
.map((field) => {
let scopes = [];
let policies = [];
let authenticated = false;
for (const fieldInGraph of field.byGraph.values()) {
scopes = (0, auth_js_1.mergeScopePolicies)(scopes, fieldInGraph.scopes);
policies = (0, auth_js_1.mergeScopePolicies)(policies, fieldInGraph.policies);
if (fieldInGraph.authenticated) {
authenticated = true;
}
}
return {
name: field.name,
type: field.type,
inaccessible: field.inaccessible,
authenticated,
policies,
scopes,
cost: field.cost !== null
? {
cost: field.cost,
directiveName: (0, helpers_js_2.ensureValue)(supergraphState.specs.cost.names.cost, "Directive name of @cost is not defined"),
}
: null,
listSize: field.listSize !== null
? {
...field.listSize,
directiveName: (0, helpers_js_2.ensureValue)(supergraphState.specs.cost.names.listSize, "Directive name of @listSize is not defined"),
}
: null,
tags: Array.from(field.tags),
description: field.description,
deprecated: field.deprecated,
ast: {
directives: (0, common_js_1.convertToConst)(field.ast.directives),
},
join: {
field: [{}],
},
arguments: Array.from(field.args.values())
.filter((arg) => {
if (arg.byGraph.size !== field.byGraph.size) {
return false;
}
return true;
})
.map((arg) => {
return {
name: arg.name,
type: arg.type,
kind: arg.kind,
inaccessible: false,
cost: arg.cost !== null
? {
cost: arg.cost,
directiveName: (0, helpers_js_2.ensureValue)(supergraphState.specs.cost.names.cost, "Directive name of @cost is not defined"),
}
: null,
tags: Array.from(arg.tags),
defaultValue: arg.defaultValue,
description: arg.description,
deprecated: arg.deprecated,
ast: {
directives: (0, common_js_1.convertToConst)(arg.ast.directives),
},
};
}),
};
})),
interfaces: Array.from(objectType.interfaces),
tags: Array.from(objectType.tags),
inaccessible: objectType.inaccessible,
authenticated: objectType.authenticated,
policies: objectType.policies,
scopes: objectType.scopes,
join: {
type: joinTypes,
implements: objectType.interfaces.size > 0
? Array.from(objectType.byGraph.entries())
.map(([graph, meta]) => {
if (meta.interfaces.size > 0) {
return Array.from(meta.interfaces).map((interfaceName) => ({
graph: graph.toUpperCase(),
interface: interfaceName,
}));
}
return [];
})
.flat(1)
: [],
},
});
},
};
}
function isUsedOverriddenInGraph(field, fieldStateInGraph, interfaceFieldGraphsByFieldName, graphId, graphNameToId) {
const interfaceGraphsForField = interfaceFieldGraphsByFieldName.get(field.name);
const isInterfaceFieldInGraph = interfaceGraphsForField instanceof Set &&
interfaceGraphsForField.has(graphId);
const isUsedAsNonExternalKey = fieldStateInGraph.usedAsKey && !fieldStateInGraph.external;
const isOverridden = field.override && graphNameToId(field.override) === graphId;
if (isOverridden && (isUsedAsNonExternalKey || isInterfaceFieldInGraph)) {
return true;
}
return false;
}
function toFieldCoordinate(typeName, fieldName) {
return `${typeName}.${fieldName}`;
}
function getRequiresUsageState(requiresUsageIndex, coordinate, graphId) {
return requiresUsageIndex.get(coordinate)?.get(graphId);
}
function isFieldCoordinateEffectivelyRequired(requiresUsageIndex, coordinate, graphId, fallbackRequired) {
const usageInGraph = getRequiresUsageState(requiresUsageIndex, coordinate, graphId);
if (usageInGraph?.isActivelyRequired) {
return true;
}
if (usageInGraph?.isOnlyOverriddenRequired) {
return false;
}
return fallbackRequired;
}
function hasOnlyOverriddenRequiresDependency(requiresUsageIndex, coordinate, graphId) {
const usageInGraph = getRequiresUsageState(requiresUsageIndex, coordinate, graphId);
return (!!usageInGraph?.isOnlyOverriddenRequired && !usageInGraph.isActivelyRequired);
}
function shouldKeepObjectTypeJoinTypeInGraph({ graphId, objectType, requiresUsageIndex, interfaceFieldGraphsByFieldName, graphNameToId, }) {
const objectTypeInGraph = objectType.byGraph.get(graphId);
if (!objectTypeInGraph) {
return false;
}
if (objectTypeInGraph.keys.length > 0) {
return true;
}
if (objectTypeInGraph.hasDefinition) {
return true;
}
for (const field of objectType.fields.values()) {
const fieldInGraph = field.byGraph.get(graphId);
if (!fieldInGraph) {
continue;
}
const fieldCoordinate = toFieldCoordinate(objectType.name, field.name);
const isEffectivelyRequired = isFieldCoordinateEffectivelyRequired(requiresUsageIndex, fieldCoordinate, graphId, fieldInGraph.required);
const hasOnlyOverriddenRequires = hasOnlyOverriddenRequiresDependency(requiresUsageIndex, fieldCoordinate, graphId);
if (!fieldInGraph.external ||
fieldInGraph.provided ||
isEffectivelyRequired ||
(objectTypeInGraph.extension && !hasOnlyOverriddenRequires) ||
isUsedOverriddenInGraph(field, fieldInGraph, interfaceFieldGraphsByFieldName, graphId, graphNameToId)) {
return true;
}
}
return false;
}
function collectOverriddenGraphsByField(field, graphNameToId) {
const overriddenGraphs = new Set();
for (const [_, meta] of field.byGraph) {
if (!meta.override) {
continue;
}
const sourceGraphId = graphNameToId(meta.override);
if (sourceGraphId) {
overriddenGraphs.add(sourceGraphId);
}
}
return overriddenGraphs;
}
function buildRequiresUsageIndex(supergraphState, graphNameToId) {
const index = new Map();
for (const objectType of supergraphState.objectTypes.values()) {
for (const field of objectType.fields.values()) {
const overriddenGraphs = collectOverriddenGraphsByField(field, graphNameToId);
for (const [graphId, meta] of field.byGraph) {
if (!meta.requires) {
continue;
}
const parsedFields = (0, helpers_js_1.parseFields)(meta.requires);
if (!parsedFields) {
continue;
}
markRequiresUsageFromSelectionSet(supergraphState, objectType, parsedFields, graphId, overriddenGraphs.has(graphId), index);
}
}
}
return index;
}
function markRequiresUsageFromSelectionSet(supergraphState, currentType, selectionSet, graphId, overriddenSourceUsage, index) {
for (const selection of selectionSet.selections) {
if (selection.kind === graphql_1.Kind.FIELD) {
const fieldName = selection.name.value;
const fieldState = currentType.fields.get(fieldName);
if (!fieldState) {
continue;
}
markRequiresUsageForCoordinate(toFieldCoordinate(currentType.name, fieldName), graphId, overriddenSourceUsage, index);
if (!selection.selectionSet) {
continue;
}
const outputTypeName = (0, graphql_js_1.extractNamedTypeName)(fieldState.type);
if (!outputTypeName) {
continue;
}
const nextType = supergraphState.objectTypes.get(outputTypeName) ??
supergraphState.interfaceTypes.get(outputTypeName);
if (!nextType) {
continue;
}
markRequiresUsageFromSelectionSet(supergraphState, nextType, selection.selectionSet, graphId, overriddenSourceUsage, index);
continue;
}
if (selection.kind !== graphql_1.Kind.INLINE_FRAGMENT) {
continue;
}
const targetType = selection.typeCondition
? (supergraphState.objectTypes.get(selection.typeCondition.name.value) ??
supergraphState.interfaceTypes.get(selection.typeCondition.name.value))
: currentType;
if (!targetType) {
continue;
}
markRequiresUsageFromSelectionSet(supergraphState, targetType, selection.selectionSet, graphId, overriddenSourceUsage, index);
}
}
function markRequiresUsageForCoordinate(coordinate, graphId, overriddenSourceUsage, index) {
let usageByGraph = index.get(coordinate);
if (!usageByGraph) {
usageByGraph = new Map();
index.set(coordinate, usageByGraph);
}
const usageInGraph = usageByGraph.get(graphId) ?? {
isActivelyRequired: false,
isOnlyOverriddenRequired: false,
};
if (overriddenSourceUsage) {
usageInGraph.isOnlyOverriddenRequired = true;
}
else {
usageInGraph.isActivelyRequired = true;
}
usageByGraph.set(graphId, usageInGraph);
}
function getOrCreateObjectType(state, typeName) {
const existing = state.get(typeName);
if (existing) {
return existing;
}
const def = {
kind: "object",
name: typeName,
tags: new Set(),
hasDefinition: false,
isEntity: false,
inaccessible: false,
authenticated: false,
policies: [],
scopes: [],
cost: null,
interfaces: new Set(),
byGraph: new Map(),
fields: new Map(),
ast: {
directives: [],
},
};
state.set(typeName, def);
return def;
}
function getOrCreateField(objectTypeState, fieldName, fieldType) {
const existing = objectTypeState.fields.get(fieldName);
if (existing) {
return existing;
}
const def = {
name: fieldName,
type: fieldType,
isLeaf: false,
tags: new Set(),
inaccessible: false,
authenticated: false,
policies: [],
scopes: [],
cost: null,
listSize: null,
usedAsKey: false,
override: null,
overrideLabel: null,
byGraph: new Map(),
args: new Map(),
ast: {
directives: [],
},
internal: {
seenNonExternal: false,
},
};
objectTypeState.fields.set(fieldName, def);
return def;
}
function getOrCreateArg(fieldState, argName, argType, argKind) {
const existing = fieldState.args.get(argName);
if (existing) {
return existing;
}
const def = {
name: argName,
type: argType,
kind: argKind,
tags: new Set(),
inaccessible: false,
cost: null,
byGraph: new Map(),
ast: {
directives: [],
},
};
fieldState.args.set(argName, def);
return def;
}