@graphql-mesh/fusion-composition
Version:
Basic composition utility for Fusion spec
136 lines (135 loc) • 6.16 kB
JavaScript
import { DirectiveLocation, GraphQLDirective, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString, } from 'graphql';
import { getDirectiveExtensions } from '@graphql-tools/utils';
import { importFederationDirectives } from '../federation-utils.js';
import { addInaccessibleDirective } from './filter-schema.js';
const OPERATION_TYPE_SUFFIX_MAP = {
query: 'Query',
mutation: 'Mutation',
subscription: 'Subscription',
};
const DEFAULT_APPLY_TO = {
query: true,
mutation: true,
subscription: true,
};
export function createEncapsulateTransform(opts = {}) {
return function encapsulateTransform(schema, subgraphConfig) {
const groupName = opts.name || subgraphConfig.name;
const applyToMap = {
...DEFAULT_APPLY_TO,
...(opts.applyTo || {}),
};
const newRootTypes = {};
let inaccessibleDirectiveAdded = false;
for (const opTypeString in applyToMap) {
const operationType = opTypeString;
const originalType = schema.getRootType(operationType);
if (originalType && applyToMap[operationType]) {
const originalTypeConfig = originalType.toConfig();
const wrappedTypeName = `${groupName}${OPERATION_TYPE_SUFFIX_MAP[operationType]}`;
const originalFieldMapWithHidden = {};
const wrappedFieldMap = {};
for (const fieldName in originalTypeConfig.fields) {
const originalFieldConfig = originalTypeConfig.fields[fieldName];
// Generate sourceArgs to forward all arguments
const sourceArgs = {};
if (originalFieldConfig.args) {
for (const argName in originalFieldConfig.args) {
sourceArgs[argName] = `{args.${argName}}`;
}
}
const wrappedFieldName = `_encapsulated_${groupName}_${fieldName}`;
wrappedFieldMap[fieldName] = {
...originalFieldConfig,
extensions: {
directives: {
resolveTo: [
{
sourceName: subgraphConfig.name,
sourceTypeName: originalType.name,
sourceFieldName: wrappedFieldName,
...(Object.keys(sourceArgs).length > 0 && { sourceArgs }),
},
],
},
},
};
const newOriginalFieldConfig = {
...originalFieldConfig,
astNode: undefined,
};
addInaccessibleDirective(newOriginalFieldConfig);
inaccessibleDirectiveAdded = true;
originalFieldMapWithHidden[wrappedFieldName] = newOriginalFieldConfig;
}
const wrappedType = new GraphQLObjectType({
name: wrappedTypeName,
fields: wrappedFieldMap,
});
newRootTypes[operationType] = new GraphQLObjectType({
...originalTypeConfig,
fields: {
...originalFieldMapWithHidden,
[groupName]: {
type: new GraphQLNonNull(wrappedType),
extensions: {
directives: {
resolveTo: [
{
sourceName: subgraphConfig.name,
sourceTypeName: originalType.name,
sourceFieldName: '__typename',
},
],
},
},
},
},
});
}
else {
newRootTypes[operationType] = originalType;
}
}
const schemaConfig = schema.toConfig();
const newDirectives = [...schemaConfig.directives];
if (!newDirectives.some(directive => directive.name === 'resolveTo')) {
newDirectives.push(resolveToDirective);
}
const newSchema = new GraphQLSchema({
...schemaConfig,
types: undefined,
directives: newDirectives,
...newRootTypes,
});
const schemaLevelDirectives = getDirectiveExtensions(newSchema);
const importStatement = schemaLevelDirectives?.link?.find(linkDirectiveArgs => linkDirectiveArgs.url?.startsWith('https://specs.apollo.dev/federation/') &&
linkDirectiveArgs.import);
if (importStatement && inaccessibleDirectiveAdded) {
return importFederationDirectives(newSchema, ['@inaccessible']);
}
return newSchema;
};
}
export const resolveToSourceArgsScalar = new GraphQLScalarType({
name: 'ResolveToSourceArgs',
});
export const resolveToDirective = new GraphQLDirective({
name: 'resolveTo',
locations: [DirectiveLocation.FIELD_DEFINITION],
args: {
additionalArgs: { type: resolveToSourceArgsScalar },
filterBy: { type: GraphQLString },
keyField: { type: GraphQLString },
keysArg: { type: GraphQLString },
pubsubTopic: { type: GraphQLString },
requiredSelectionSet: { type: GraphQLString },
result: { type: GraphQLString },
resultType: { type: GraphQLString },
sourceArgs: { type: resolveToSourceArgsScalar },
sourceFieldName: { type: GraphQLString },
sourceName: { type: GraphQLString },
sourceSelectionSet: { type: GraphQLString },
sourceTypeName: { type: GraphQLString },
},
});