@graphql-mesh/fusion-composition
Version:
Basic composition utility for Fusion spec
256 lines (255 loc) • 13.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFederationTransform = createFederationTransform;
const graphql_1 = require("graphql");
const utils_1 = require("@graphql-tools/utils");
const federation_utils_js_1 = require("../federation-utils.js");
const utils_js_1 = require("./utils.js");
const federationDirectiveNames = [
'key',
'interfaceObject',
'extends',
'shareable',
'inaccessible',
'override',
'authenticated',
'requiresScopes',
'policy',
'external',
'provides',
'requires',
];
function createFederationTransform(config) {
return function (subgraphSchema, subgraphConfig) {
const configurationByType = new Map();
const configurationByField = new Map();
for (const coordinate in config) {
const [typeName, fieldName] = coordinate.split('.');
const typeInSchema = subgraphSchema.getType(typeName);
if (!typeInSchema) {
throw new utils_js_1.TransformValidationError(`Federation Transform: Type ${typeName} not found in schema for the coordinate: ${coordinate}`);
}
if (fieldName) {
if (!('getFields' in typeInSchema)) {
throw new utils_js_1.TransformValidationError(`Federation Transform: Type ${typeName} is not an object type in schema for the coordinate: ${coordinate}`);
}
const fieldsInType = typeInSchema.getFields();
if (!fieldsInType[fieldName]) {
throw new utils_js_1.TransformValidationError(`Federation Transform: Field ${fieldName} not found in type ${typeName} for the coordinate: ${coordinate}`);
}
const fieldConfig = config[coordinate];
let fieldMap = configurationByField.get(typeName);
if (!fieldMap) {
fieldMap = new Map();
configurationByField.set(typeName, fieldMap);
}
fieldMap.set(fieldName, fieldConfig);
}
else {
configurationByType.set(typeName, config[coordinate]);
}
}
const mergeDirectiveConfigMap = new Map();
const rootTypeNameOperationMap = new Map([...(0, utils_1.getRootTypeMap)(subgraphSchema)].map(([operationType, type]) => [
type.name,
operationType,
]));
const usedFederationDirectives = new Set();
let mergeDirectiveUsed = false;
subgraphSchema = (0, utils_1.mapSchema)(subgraphSchema, {
[utils_1.MapperKind.TYPE]: type => {
const configByType = configurationByType.get(type.name);
if (configByType) {
const directiveExtensions = (0, utils_1.getDirectiveExtensions)(type) || {};
for (const directiveName in configByType) {
const directiveConfigs = (0, utils_1.asArray)(configByType[directiveName]);
for (const directiveConfig of directiveConfigs) {
const specificDirectiveExtensions = (directiveExtensions[directiveName] ||= []);
switch (directiveName) {
case 'key': {
const keyConfig = directiveConfig;
specificDirectiveExtensions.push({
fields: keyConfig.fields,
});
if (keyConfig.resolveReference) {
const operation = keyConfig.resolveReference.operation || graphql_1.OperationTypeNode.QUERY;
let operationMergeDirectiveConfig = mergeDirectiveConfigMap.get(operation);
if (!operationMergeDirectiveConfig) {
operationMergeDirectiveConfig = new Map();
mergeDirectiveConfigMap.set(operation, operationMergeDirectiveConfig);
}
operationMergeDirectiveConfig.set(keyConfig.resolveReference.fieldName, {
keyField: keyConfig.fields,
...keyConfig.resolveReference,
});
}
break;
}
default: {
if (directiveConfig) {
specificDirectiveExtensions.push(directiveConfig === true ? {} : directiveConfig);
}
break;
}
}
usedFederationDirectives.add(`@${directiveName}`);
}
}
for (const directiveName of federationDirectiveNames) {
if (directiveExtensions[directiveName]?.length &&
federationDirectiveNames.includes(directiveName)) {
usedFederationDirectives.add(`@${directiveName}`);
}
}
// Existing directives
return new (Object.getPrototypeOf(type).constructor)({
...type.toConfig(),
astNode: undefined,
extensions: {
...(type.extensions || {}),
directives: directiveExtensions,
},
});
}
},
[utils_1.MapperKind.ROOT_FIELD]: (fieldConfig, fieldName, typeName) => {
const operation = rootTypeNameOperationMap.get(typeName);
if (!operation) {
throw new Error(`Unexpected root field ${fieldName} in type ${typeName}`);
}
const mergeDirectiveConfigForOperation = mergeDirectiveConfigMap.get(operation);
const mergeDirectiveConfig = mergeDirectiveConfigForOperation?.get(fieldName);
const fieldDirectives = (0, utils_1.getDirectiveExtensions)(fieldConfig) || {};
const fieldTransformConfig = configurationByField.get(typeName)?.get(fieldName);
if (fieldTransformConfig) {
for (const directiveName in fieldTransformConfig) {
const directiveConfigs = (0, utils_1.asArray)(fieldTransformConfig[directiveName]);
const specificDirectiveExtensions = (fieldDirectives[directiveName] ||= []);
for (let directiveConfig of directiveConfigs) {
if (typeof directiveConfig === 'boolean') {
directiveConfig = {};
}
specificDirectiveExtensions.push(directiveConfig);
usedFederationDirectives.add(`@${directiveName}`);
}
}
}
if (mergeDirectiveConfig) {
const mergeDirectiveExtensions = (fieldDirectives.merge ||= []);
let argsExpr = mergeDirectiveConfig.argsExpr;
if (!argsExpr && fieldConfig.args && !mergeDirectiveConfig.keyArg) {
const argsExprElems = [];
const returnNamedType = (0, graphql_1.getNamedType)(fieldConfig.type);
if ('getFields' in returnNamedType) {
const returnFields = returnNamedType.getFields();
for (const argName in fieldConfig.args) {
const arg = fieldConfig.args[argName];
const argType = (0, graphql_1.getNamedType)(arg.type);
const returnField = returnFields[argName];
if (returnField) {
const returnFieldType = (0, graphql_1.getNamedType)(returnField.type);
if (argType.name === returnFieldType.name) {
argsExprElems.push(`${argName}: $key.${argName}`);
}
}
}
argsExpr = argsExprElems.join(', ');
}
}
mergeDirectiveExtensions.push({
subgraph: subgraphConfig.name,
key: mergeDirectiveConfig.key,
keyField: mergeDirectiveConfig.keyField,
keyArg: mergeDirectiveConfig.keyArg,
argsExpr,
});
mergeDirectiveUsed = true;
}
for (const directiveName of federationDirectiveNames) {
if (fieldDirectives[directiveName]?.length &&
federationDirectiveNames.includes(directiveName)) {
usedFederationDirectives.add(`@${directiveName}`);
}
}
if (fieldTransformConfig || mergeDirectiveConfig) {
return {
...fieldConfig,
astNode: undefined,
extensions: {
...(fieldConfig.extensions || {}),
directives: fieldDirectives,
},
};
}
},
[utils_1.MapperKind.FIELD]: (fieldConfig, fieldName, typeName) => {
const fieldTransformConfig = configurationByField.get(typeName)?.get(fieldName);
if (fieldTransformConfig) {
const fieldDirectives = (0, utils_1.getDirectiveExtensions)(fieldConfig) || {};
for (const directiveName in fieldTransformConfig) {
const directiveConfigs = (0, utils_1.asArray)(fieldTransformConfig[directiveName]);
const specificDirectiveExtensions = (fieldDirectives[directiveName] ||= []);
for (let directiveConfig of directiveConfigs) {
if (typeof directiveConfig === 'boolean') {
directiveConfig = {};
}
specificDirectiveExtensions.push(directiveConfig);
usedFederationDirectives.add(`@${directiveName}`);
}
}
for (const directiveName of federationDirectiveNames) {
if (fieldDirectives[directiveName]?.length &&
federationDirectiveNames.includes(directiveName)) {
usedFederationDirectives.add(`@${directiveName}`);
}
}
return {
...fieldConfig,
astNode: undefined,
extensions: {
...(fieldConfig.extensions || {}),
directives: fieldDirectives,
},
};
}
},
});
subgraphSchema = (0, federation_utils_js_1.addFederation2DirectivesToSubgraph)(subgraphSchema);
subgraphSchema = (0, federation_utils_js_1.importFederationDirectives)(subgraphSchema, [...usedFederationDirectives]);
if (mergeDirectiveUsed) {
subgraphSchema = (0, federation_utils_js_1.importMeshDirectives)(subgraphSchema, ['@merge']);
const schemaConfig = subgraphSchema.toConfig();
subgraphSchema = new graphql_1.GraphQLSchema({
...schemaConfig,
directives: [
...schemaConfig.directives,
new graphql_1.GraphQLDirective({
name: 'merge',
locations: [graphql_1.DirectiveLocation.FIELD_DEFINITION],
args: {
subgraph: {
type: new graphql_1.GraphQLNonNull(graphql_1.GraphQLString),
},
argsExpr: {
type: graphql_1.GraphQLString,
},
keyArg: {
type: graphql_1.GraphQLString,
},
keyField: {
type: graphql_1.GraphQLString,
},
key: {
type: new graphql_1.GraphQLList(graphql_1.GraphQLString),
},
additionalArgs: {
type: graphql_1.GraphQLString,
},
},
}),
],
});
}
return subgraphSchema;
};
}