@theguild/federation-composition
Version:
Open Source Composition library for Apollo Federation
381 lines (380 loc) • 17.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.objectTypeBuilder = exports.isRealExtension = void 0;
const ast_js_1 = require("./ast.js");
const common_js_1 = require("./common.js");
function isRealExtension(meta, version) {
return meta.extension
? meta.extensionType !== '@extends' && version === 'v1.0'
? false
: true
: false;
}
exports.isRealExtension = isRealExtension;
function objectTypeBuilder() {
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;
}
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));
objectTypeState.byGraph.set(graph.id, {
extension: type.extension,
extensionType: type.extensionType,
external: type.external,
keys: type.keys,
inaccessible: type.inaccessible,
shareable: type.shareable,
interfaces: type.interfaces,
});
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 && isRealExtension(type, graph.version)
: field.external;
const shouldForceType = !usedAsKey && !isExternal && !fieldState.internal.seenNonExternal;
const shouldChangeType = shouldForceType ||
(!isExternal &&
!field.type.endsWith('!') &&
fieldState.type.endsWith('!'));
if (shouldChangeType) {
fieldState.type = field.type;
}
if (!fieldState.internal.seenNonExternal && !isExternal) {
fieldState.internal.seenNonExternal = true;
}
if (field.inaccessible) {
fieldState.inaccessible = true;
}
if (field.override) {
fieldState.override = field.override;
}
if (field.description && !fieldState.description) {
fieldState.description = field.description;
}
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,
override: field.override,
provides: field.provides,
requires: field.requires,
provided: field.provided,
required: field.required,
shareable: field.shareable,
used: field.used,
usedAsKey,
});
for (const arg of field.args.values()) {
const argState = getOrCreateArg(fieldState, arg.name, arg.type);
arg.tags.forEach(tag => argState.tags.add(tag));
if (arg.type.endsWith('!')) {
argState.type = arg.type;
}
if (arg.inaccessible) {
argState.inaccessible = true;
}
if (!field.external) {
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 === 'string') {
argState.defaultValue = arg.defaultValue;
}
argState.byGraph.set(graph.id, {
type: arg.type,
inaccessible: arg.inaccessible,
defaultValue: arg.defaultValue,
});
}
}
},
composeSupergraphNode(objectType, graphs) {
const isQuery = objectType.name === 'Query';
return (0, ast_js_1.createObjectTypeNode)({
name: objectType.name,
ast: {
directives: (0, common_js_1.convertToConst)(objectType.ast.directives),
},
description: objectType.description,
fields: Array.from(objectType.fields.values()).map(field => {
const fieldInGraphs = Array.from(field.byGraph.entries());
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,
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.provides !== null) {
differencesBetweenGraphs.provides = true;
}
if (meta.requires !== null) {
differencesBetweenGraphs.requires = true;
}
if (meta.type !== field.type) {
differencesBetweenGraphs.type = true;
}
}
if (isQuery) {
if (differencesBetweenGraphs.override) {
const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null);
joinFields = graphsWithOverride.map(([graphId, meta]) => ({
graph: graphId,
override: meta.override ?? undefined,
type: differencesBetweenGraphs.type ? meta.type : undefined,
external: meta.external ?? undefined,
provides: meta.provides ?? undefined,
requires: meta.requires ?? undefined,
}));
}
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 graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null);
joinFields = graphsWithOverride.map(([graphId, meta]) => ({
graph: graphId,
override: meta.override ?? undefined,
type: differencesBetweenGraphs.type ? meta.type : undefined,
external: meta.external ?? undefined,
provides: meta.provides ?? undefined,
requires: meta.requires ?? undefined,
}));
}
else if (hasDifferencesBetweenGraphs) {
joinFields = fieldInGraphs.map(([graphId, meta]) => ({
graph: graphId,
type: differencesBetweenGraphs.type ? meta.type : undefined,
override: meta.override ?? undefined,
external: meta.external
?
field.usedAsKey && objectType.byGraph.get(graphId).extension === true
? false
: true
: undefined,
provides: meta.provides ?? undefined,
requires: meta.requires ?? undefined,
}));
}
}
else {
if (differencesBetweenGraphs.override) {
const graphsWithOverride = fieldInGraphs.filter(([_, meta]) => meta.override !== null);
joinFields = graphsWithOverride.map(([graphId, meta]) => ({
graph: graphId,
override: meta.override ?? undefined,
type: differencesBetweenGraphs.type ? meta.type : undefined,
external: meta.external ?? undefined,
provides: meta.provides ?? undefined,
requires: meta.requires ?? undefined,
}));
}
else {
joinFields = fieldInGraphs.map(([graphId, meta]) => ({
graph: graphId,
type: hasDifferentOutputType ? meta.type : undefined,
override: meta.override ?? undefined,
external: meta.external ?? undefined,
provides: meta.provides ?? undefined,
requires: meta.requires ?? undefined,
}));
}
}
return {
name: field.name,
type: field.type,
inaccessible: field.inaccessible,
tags: Array.from(field.tags),
description: field.description,
deprecated: field.deprecated,
ast: {
directives: (0, common_js_1.convertToConst)(field.ast.directives),
},
join: {
field: 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,
inaccessible: arg.inaccessible,
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,
join: {
type: isQuery
?
Array.from(graphs.values()).map(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: isRealExtension(meta, graphs.get(graphId).version),
resolvable: key.resolvable,
}));
}
return [
{
graph: graphId,
},
];
})
.flat(1),
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)
: [],
},
});
},
};
}
exports.objectTypeBuilder = objectTypeBuilder;
function getOrCreateObjectType(state, typeName) {
const existing = state.get(typeName);
if (existing) {
return existing;
}
const def = {
name: typeName,
tags: new Set(),
hasDefinition: false,
inaccessible: false,
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,
tags: new Set(),
inaccessible: false,
usedAsKey: false,
override: null,
byGraph: new Map(),
args: new Map(),
ast: {
directives: [],
},
internal: {
seenNonExternal: false,
},
};
objectTypeState.fields.set(fieldName, def);
return def;
}
function getOrCreateArg(fieldState, argName, argType) {
const existing = fieldState.args.get(argName);
if (existing) {
return existing;
}
const def = {
name: argName,
type: argType,
tags: new Set(),
inaccessible: false,
byGraph: new Map(),
ast: {
directives: [],
},
};
fieldState.args.set(argName, def);
return def;
}