@graphql-mesh/transform-federation
Version:
169 lines (168 loc) • 8.16 kB
JavaScript
import { GraphQLObjectType, GraphQLUnionType, isObjectType } from 'graphql';
import { loadFromModuleExportExpression } from '@graphql-mesh/utils';
import { entitiesField, EntityType, serviceField } from '@apollo/subgraph/dist/types.js';
import { mapSchema, MapperKind, printSchemaWithDirectives } from '@graphql-tools/utils';
import set from 'lodash.set';
import { stringInterpolator } from '@graphql-mesh/string-interpolation';
export default class FederationTransform {
constructor({ apiName, baseDir, config, importFn, }) {
this.noWrap = true;
this.apiName = apiName;
this.config = config;
this.baseDir = baseDir;
this.importFn = importFn;
}
transformSchema(schema, rawSource) {
var _a, _b, _c, _d, _e, _f, _g;
rawSource.merge = {};
if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.types) {
const queryType = schema.getQueryType();
const queryTypeFields = queryType.getFields();
for (const type of this.config.types) {
rawSource.merge[type.name] = {};
const typeObj = schema.getType(type.name);
typeObj.extensions = typeObj.extensions || {};
const typeDirectivesObj = (typeObj.extensions.directives =
typeObj.extensions.directives || {});
if ((_b = type.config) === null || _b === void 0 ? void 0 : _b.key) {
typeDirectivesObj.key = type.config.key;
}
if ((_c = type.config) === null || _c === void 0 ? void 0 : _c.shareable) {
typeDirectivesObj.shareable = type.config.shareable;
}
if ((_d = type.config) === null || _d === void 0 ? void 0 : _d.extends) {
typeDirectivesObj.extends = type.config.extends;
}
const typeFieldObjs = typeObj.getFields();
if ((_e = type.config) === null || _e === void 0 ? void 0 : _e.fields) {
for (const field of type.config.fields) {
const typeField = typeFieldObjs[field.name];
if (typeField) {
typeField.extensions = typeField.extensions || {};
const directivesObj = (typeField.extensions.directives =
typeField.extensions.directives || {});
Object.assign(directivesObj, field.config);
}
rawSource.merge[type.name].fields = rawSource.merge[type.name].fields || {};
rawSource.merge[type.name].fields[field.name] =
rawSource.merge[type.name].fields[field.name] || {};
if (field.config.requires) {
rawSource.merge[type.name].fields[field.name].computed = true;
rawSource.merge[type.name].fields[field.name].selectionSet = `{ ${field.config.requires} }`;
}
}
}
// If a field is a key field, it should be GraphQLID
if ((_f = type.config) === null || _f === void 0 ? void 0 : _f.key) {
let selectionSetContent = '';
for (const keyField of type.config.key) {
selectionSetContent += '\n';
selectionSetContent += keyField.fields || '';
}
if (selectionSetContent) {
rawSource.merge[type.name].selectionSet = `{ ${selectionSetContent} }`;
}
}
let resolveReference;
if ((_g = type.config) === null || _g === void 0 ? void 0 : _g.resolveReference) {
const resolveReferenceConfig = type.config.resolveReference;
if (typeof resolveReferenceConfig === 'string') {
const fn$ = loadFromModuleExportExpression(resolveReferenceConfig, {
cwd: this.baseDir,
defaultExportName: 'default',
importFn: this.importFn,
});
resolveReference = (...args) => fn$.then(fn => fn(...args));
}
else if (typeof resolveReferenceConfig === 'function') {
resolveReference = resolveReferenceConfig;
}
else {
const queryField = queryTypeFields[resolveReferenceConfig.queryFieldName];
resolveReference = async (root, context, info) => {
const args = {};
for (const argName in resolveReferenceConfig.args) {
const argVal = stringInterpolator.parse(resolveReferenceConfig.args[argName], {
root,
args,
context,
info,
env: process.env,
});
if (argVal) {
set(args, argName, argVal);
}
}
const result = await context[this.apiName].Query[queryField.name]({
root,
args,
context,
info,
});
return {
...root,
...result,
};
};
}
rawSource.merge[type.name].resolve = resolveReference;
}
}
}
const entityTypes = [];
for (const typeName in rawSource.merge || {}) {
const type = schema.getType(typeName);
if (isObjectType(type)) {
entityTypes.push(type);
}
set(type, 'extensions.apollo.subgraph.resolveReference', rawSource.merge[typeName].resolve);
}
const schemaWithFederationQueryType = mapSchema(schema, {
[MapperKind.QUERY]: type => {
const config = type.toConfig();
return new GraphQLObjectType({
...config,
fields: {
...config.fields,
_entities: entitiesField,
_service: {
...serviceField,
resolve: (root, args, context, info) => ({
sdl: printSchemaWithDirectives(info.schema),
}),
},
},
});
},
});
const schemaWithUnionType = mapSchema(schemaWithFederationQueryType, {
[MapperKind.UNION_TYPE]: type => {
if (type.name === EntityType.name) {
return new GraphQLUnionType({
...EntityType.toConfig(),
types: entityTypes,
});
}
return type;
},
});
schemaWithUnionType.extensions = schemaWithUnionType.extensions || {};
const directivesObj = (schemaWithUnionType.extensions.directives =
schemaWithUnionType.extensions.directives || {});
directivesObj.link = {
url: 'https://specs.apollo.dev/federation/v2.0',
import: [
'@extends',
'@external',
'@inaccessible',
'@key',
'@override',
'@provides',
'@requires',
'@shareable',
'@tag',
],
};
return schemaWithUnionType;
}
}