@graphql-mesh/fusion-runtime
Version:
Runtime for GraphQL Mesh Fusion Supergraph
219 lines (218 loc) • 11.6 kB
JavaScript
import { isSpecifiedScalarType, } from 'graphql';
import { getDefDirectives } from '@graphql-mesh/utils';
import { stitchingDirectives } from '@graphql-tools/stitching-directives';
import { getRootTypeNames, MapperKind, mapSchema } from '@graphql-tools/utils';
import { RenameInputObjectFields, RenameInterfaceFields, RenameObjectFields, RenameTypes, TransformEnumValues, } from '@graphql-tools/wrap';
export function extractSubgraphsFromFusiongraph(fusiongraph) {
const subgraphNames = new Set();
const subschemaMap = new Map();
const transportEntryMap = {};
const schemaDirectives = getDefDirectives(fusiongraph, fusiongraph);
const transportDirectives = schemaDirectives.filter(directive => directive.name === 'transport');
const { stitchingDirectivesTransformer } = stitchingDirectives();
for (const transportDirective of transportDirectives) {
const subgraph = transportDirective.args.subgraph;
if (typeof subgraph === 'string') {
subgraphNames.add(subgraph);
transportEntryMap[subgraph] = transportDirective.args;
}
}
const rootTypeNames = getRootTypeNames(fusiongraph);
const additionalResolversFromTypeDefs = [];
const additionalTypeDefs = new Set();
for (const subgraph of subgraphNames) {
const renameTypeNames = {};
const renameTypeNamesReversed = {};
const renameFieldByObjectTypeNames = {};
const renameFieldByInputTypeNames = {};
const renameFieldByInterfaceTypeNames = {};
const renameEnumValueByEnumTypeNames = {};
const subgraphSchema = mapSchema(fusiongraph, {
[MapperKind.TYPE]: type => {
const typeDirectives = getDefDirectives(fusiongraph, type, subgraph);
const sourceDirectives = typeDirectives.filter(directive => directive.name === 'source');
const sourceDirective = sourceDirectives.find(directive => directive.args.subgraph === subgraph);
if (sourceDirective != null) {
const realName = sourceDirective.args.name ?? type.name;
if (type.name !== realName) {
renameTypeNames[realName] = type.name;
renameTypeNamesReversed[type.name] = realName;
return new (Object.getPrototypeOf(type).constructor)({
...type.toConfig(),
name: realName,
});
}
return type;
}
if (rootTypeNames.has(type.name) || isSpecifiedScalarType(type)) {
return type;
}
return null;
},
[MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => {
const fieldDirectives = getDefDirectives(fusiongraph, fieldConfig);
const resolveToDirectives = fieldDirectives.filter(directive => directive.name === 'resolveTo');
if (resolveToDirectives.length > 0) {
for (const resolveToDirective of resolveToDirectives) {
additionalResolversFromTypeDefs.push({
targetTypeName: typeName,
targetFieldName: fieldName,
...resolveToDirective.args,
});
}
}
const sourceDirectives = fieldDirectives.filter(directive => directive.name === 'source');
if (!sourceDirectives.length) {
const argEntries = Object.entries(fieldConfig.args ?? {});
additionalTypeDefs.add(`extend type ${typeName} {
${fieldName}${argEntries.length > 0
? `
(${argEntries.map(([argName, argConfig]) => `${argName}: ${argConfig.type}`).join('\n,')})
`
: ''}: ${fieldConfig.type}
}
`);
}
const sourceDirective = sourceDirectives.find(directive => directive.args.subgraph === subgraph);
if (sourceDirective != null) {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realName = sourceDirective.args.name ?? fieldName;
if (fieldName !== realName) {
if (!renameFieldByObjectTypeNames[realTypeName]) {
renameFieldByObjectTypeNames[realTypeName] = {};
}
renameFieldByObjectTypeNames[realTypeName][realName] = fieldName;
}
const directivesObj = {};
for (const fieldDirective of fieldDirectives) {
if (fieldDirective?.args?.subgraph && fieldDirective.args.subgraph !== subgraph) {
continue;
}
directivesObj[fieldDirective.name] ||= [];
directivesObj[fieldDirective.name].push(fieldDirective.args);
}
return [
realName,
{
...fieldConfig,
astNode: undefined,
extensions: {
...fieldConfig.extensions,
directives: directivesObj,
},
},
];
}
return null;
},
[MapperKind.INPUT_OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => {
const fieldDirectives = getDefDirectives(fusiongraph, fieldConfig, subgraph);
const [sourceDirective] = fieldDirectives.filter(directive => directive.name === 'source' && directive.args.subgraph === subgraph);
if (sourceDirective != null) {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
const realName = sourceDirective.args.name ?? fieldName;
if (fieldName !== realName) {
if (!renameFieldByInputTypeNames[realTypeName]) {
renameFieldByInputTypeNames[realTypeName] = {};
}
renameFieldByInputTypeNames[realTypeName][realName] = fieldName;
}
return [realName, fieldConfig];
}
return null;
},
[MapperKind.INTERFACE_FIELD]: (fieldConfig, fieldName, typeName) => {
const fieldDirectives = getDefDirectives(fusiongraph, fieldConfig, subgraph);
const [sourceDirective] = fieldDirectives.filter(directive => directive.name === 'source' && directive.args.subgraph === subgraph);
if (sourceDirective != null) {
const realName = sourceDirective.args.name ?? fieldName;
if (fieldName !== realName) {
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
if (!renameFieldByInterfaceTypeNames[realTypeName]) {
renameFieldByInterfaceTypeNames[realTypeName] = {};
}
renameFieldByInterfaceTypeNames[realTypeName][realName] = fieldName;
}
return [realName, fieldConfig];
}
return null;
},
[MapperKind.ENUM_VALUE]: (enumValueConfig, typeName, _schema, externalValue) => {
const enumValueDirectives = getDefDirectives(fusiongraph, enumValueConfig, subgraph);
const [sourceDirective] = enumValueDirectives.filter(directive => directive.name === 'source' && directive.args.subgraph === subgraph);
if (sourceDirective != null) {
const realValue = sourceDirective.args.name ?? externalValue;
const realTypeName = renameTypeNamesReversed[typeName] ?? typeName;
if (externalValue !== realValue) {
if (!renameEnumValueByEnumTypeNames[realTypeName]) {
renameEnumValueByEnumTypeNames[realTypeName] = {};
}
renameEnumValueByEnumTypeNames[realTypeName][realValue] = externalValue;
}
return [realValue, enumValueConfig];
}
return null;
},
});
const transforms = [];
if (Object.keys(renameTypeNames).length > 0) {
transforms.push(new RenameTypes(typeName => renameTypeNames[typeName] || typeName));
}
if (Object.keys(renameFieldByObjectTypeNames).length > 0) {
transforms.push(new RenameObjectFields((typeName, fieldName, _fieldConfig) => {
return renameFieldByObjectTypeNames[typeName]?.[fieldName] ?? fieldName;
}));
}
if (Object.keys(renameFieldByInputTypeNames).length > 0) {
transforms.push(new RenameInputObjectFields((typeName, fieldName, _fieldConfig) => {
return renameFieldByInputTypeNames[typeName]?.[fieldName] ?? fieldName;
}));
}
if (Object.keys(renameFieldByInterfaceTypeNames).length > 0) {
transforms.push(new RenameInterfaceFields((typeName, fieldName, _fieldConfig) => {
return renameFieldByInterfaceTypeNames[typeName]?.[fieldName] ?? fieldName;
}));
}
if (Object.keys(renameEnumValueByEnumTypeNames).length > 0) {
transforms.push(new TransformEnumValues((typeName, externalValue, enumValueConfig) => {
return [
renameEnumValueByEnumTypeNames[typeName]?.[externalValue] ?? externalValue,
enumValueConfig,
];
}));
}
let subschema = {
schema: subgraphSchema,
transforms,
};
subschema = stitchingDirectivesTransformer(subschema);
const queryType = subgraphSchema.getQueryType();
// Transformer doesn't respect transforms
if (transforms.length && subschema.merge) {
const mergeConfig = {};
for (const realTypeName in subschema.merge) {
const renamedTypeName = renameTypeNames[realTypeName] ?? realTypeName;
mergeConfig[renamedTypeName] = subschema.merge[realTypeName];
const realQueryFieldName = mergeConfig[renamedTypeName].fieldName;
if (realQueryFieldName) {
mergeConfig[renamedTypeName].fieldName =
renameFieldByObjectTypeNames[queryType.name]?.[realQueryFieldName] ??
realQueryFieldName;
}
mergeConfig[renamedTypeName].entryPoints = subschema.merge[realTypeName].entryPoints?.map(entryPoint => ({
...entryPoint,
fieldName: renameFieldByObjectTypeNames[queryType.name]?.[entryPoint.fieldName] ??
entryPoint.fieldName,
}));
}
subschema.merge = mergeConfig;
}
subschemaMap.set(subgraph, subschema);
}
return {
subschemaMap,
transportEntryMap,
additionalTypeDefs,
additionalResolversFromTypeDefs,
};
}