@graphql-mesh/transform-federation
Version:
267 lines (266 loc) • 12.9 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const dset_1 = require("dset");
const graphql_1 = require("graphql");
const types_js_1 = require("@apollo/subgraph/dist/types.js");
const string_interpolation_1 = require("@graphql-mesh/string-interpolation");
const utils_1 = require("@graphql-mesh/utils");
const utils_2 = require("@graphql-tools/utils");
const federationDirectives = [
'link',
'key',
'interfaceObject',
'extends',
'shareable',
'inaccessible',
'override',
'external',
'provides',
'requires',
'tag',
'composeDirective',
];
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) {
rawSource.merge = {};
if (this.config?.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 (type.config?.key) {
typeDirectivesObj.key = type.config.key;
}
if (type.config?.shareable) {
typeDirectivesObj.shareable = type.config.shareable;
}
if (type.config?.extends) {
typeDirectivesObj.extends = type.config.extends;
}
const typeFieldObjs = typeObj.getFields();
if (type.config?.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 (type.config?.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 (type.config?.resolveReference) {
const resolveReferenceConfig = type.config.resolveReference;
if (typeof resolveReferenceConfig === 'string') {
const fn$ = (0, utils_1.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 = string_interpolation_1.stringInterpolator.parse(resolveReferenceConfig.args[argName], {
root,
args,
context,
info,
env: process.env,
});
if (argVal) {
(0, dset_1.dset)(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 ((0, graphql_1.isObjectType)(type)) {
entityTypes.push(type);
}
(0, dset_1.dset)(type, 'extensions.apollo.subgraph.resolveReference', rawSource.merge[typeName].resolve);
}
const schemaWithFederationQueryType = (0, utils_2.mapSchema)(schema, {
[utils_2.MapperKind.QUERY]: type => {
const config = type.toConfig();
return new graphql_1.GraphQLObjectType({
...config,
fields: {
...config.fields,
_entities: types_js_1.entitiesField,
_service: {
...types_js_1.serviceField,
resolve: (root, args, context, info) => ({
sdl: (0, utils_2.printSchemaWithDirectives)(info.schema),
}),
},
},
});
},
});
const schemaWithUnionType = (0, utils_2.mapSchema)(schemaWithFederationQueryType, {
[utils_2.MapperKind.UNION_TYPE]: type => {
if (type.name === types_js_1.EntityType.name) {
return new graphql_1.GraphQLUnionType({
...types_js_1.EntityType.toConfig(),
types: entityTypes,
});
}
return type;
},
});
schemaWithUnionType.extensions = schemaWithUnionType.extensions || {};
const directivesObj = (schemaWithUnionType.extensions.directives =
schemaWithUnionType.extensions.directives || {});
const existingDirectives = schemaWithUnionType.getDirectives();
const filteredDirectives = existingDirectives.filter(directive => federationDirectives.includes(directive.name));
directivesObj.link = {
url: 'https://specs.apollo.dev/federation/' + (this.config.version || 'v2.0'),
import: filteredDirectives
.filter(({ name }) => name !== 'link')
.map(dirName => `@${dirName.name}`),
};
if (existingDirectives.length === filteredDirectives.length) {
return schemaWithUnionType;
}
return (0, utils_2.mapSchema)(schemaWithUnionType, {
[utils_2.MapperKind.DIRECTIVE]: directive => {
if (federationDirectives.includes(directive.name)) {
return directive;
}
return null;
},
[utils_2.MapperKind.OBJECT_TYPE]: type => {
return new graphql_1.GraphQLObjectType({
...type.toConfig(),
astNode: type.astNode && {
...type.astNode,
directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)),
},
extensions: {
...type.extensions,
directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))),
},
});
},
[utils_2.MapperKind.INTERFACE_TYPE]: type => {
return new graphql_1.GraphQLInterfaceType({
...type.toConfig(),
astNode: type.astNode && {
...type.astNode,
directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)),
},
extensions: {
...type.extensions,
directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))),
},
});
},
[utils_2.MapperKind.COMPOSITE_FIELD]: fieldConfig => {
return {
...fieldConfig,
astNode: fieldConfig.astNode && {
...fieldConfig.astNode,
directives: fieldConfig.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)),
},
extensions: {
...fieldConfig.extensions,
directives: Object.fromEntries(Object.entries(fieldConfig.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))),
},
};
},
[utils_2.MapperKind.SCALAR_TYPE]: type => {
if ((0, graphql_1.isSpecifiedScalarType)(type)) {
return type;
}
return new graphql_1.GraphQLScalarType({
...type.toConfig(),
astNode: type.astNode && {
...type.astNode,
directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)),
},
extensions: {
...type.extensions,
directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))),
},
});
},
[utils_2.MapperKind.ENUM_TYPE]: type => new graphql_1.GraphQLEnumType({
...type.toConfig(),
astNode: type.astNode && {
...type.astNode,
directives: type.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)),
},
extensions: {
...type.extensions,
directives: Object.fromEntries(Object.entries(type.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))),
},
}),
[utils_2.MapperKind.ENUM_VALUE]: enumValueConfig => ({
...enumValueConfig,
astNode: enumValueConfig.astNode && {
...enumValueConfig.astNode,
directives: enumValueConfig.astNode.directives?.filter(directive => federationDirectives.includes(directive.name.value)),
},
extensions: {
...enumValueConfig.extensions,
directives: Object.fromEntries(Object.entries(enumValueConfig.extensions?.directives || {}).filter(([key]) => federationDirectives.includes(key))),
},
}),
});
}
}
exports.default = FederationTransform;
;