@graphql-mesh/fusion-composition
Version:
Basic composition utility for Fusion spec
313 lines (312 loc) • 17.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.composeSubgraphs = void 0;
const tslib_1 = require("tslib");
const graphql_1 = require("graphql");
const pascal_case_1 = require("pascal-case");
const pluralize_1 = tslib_1.__importDefault(require("pluralize"));
const snake_case_1 = require("snake-case");
const schema_1 = require("@graphql-tools/schema");
const utils_1 = require("@graphql-tools/utils");
const getDirectiveExtensions_js_1 = require("./getDirectiveExtensions.js");
const defaultRootTypeNames = {
query: 'Query',
mutation: 'Mutation',
subscription: 'Subscription',
};
function composeSubgraphs(subgraphs, options) {
const annotatedSubgraphs = [];
const transformedSubgraphMap = new Map();
for (const subgraphConfig of subgraphs) {
const { name: subgraphName, schema, transforms } = subgraphConfig;
const rootTypeMap = (0, utils_1.getRootTypeMap)(schema);
const typeToOperationType = new Map();
for (const [operationType, rootType] of rootTypeMap) {
typeToOperationType.set(rootType.name, operationType);
}
const queryType = schema.getQueryType();
const queryFields = queryType?.getFields();
const annotatedSubgraph = (0, utils_1.mapSchema)(schema, {
[utils_1.MapperKind.TYPE]: type => {
if ((0, graphql_1.isSpecifiedScalarType)(type)) {
return type;
}
const operationType = typeToOperationType.get(type.name);
if (operationType) {
return new (Object.getPrototypeOf(type).constructor)({
...type.toConfig(),
name: defaultRootTypeNames[operationType],
});
}
const directives = {
...(0, getDirectiveExtensions_js_1.getDirectiveExtensions)(type),
source: {
subgraph: subgraphName,
name: type.name,
},
};
// Automatic type merging configuration based on ById and ByIds naming conventions before transforms
addAnnotationsForSemanticConventions({
type,
queryFields,
subgraphName,
directives,
subgraphs,
transformedSubgraphMap,
});
return new (Object.getPrototypeOf(type).constructor)({
...type.toConfig(),
extensions: {
...type.extensions,
directives,
},
});
},
[utils_1.MapperKind.FIELD]: (fieldConfig, fieldName) => ({
...fieldConfig,
extensions: {
...fieldConfig.extensions,
directives: {
...(0, getDirectiveExtensions_js_1.getDirectiveExtensions)(fieldConfig),
source: {
subgraph: subgraphName,
name: fieldName,
type: fieldConfig.type.toString(),
},
},
},
}),
[utils_1.MapperKind.ENUM_VALUE]: (valueConfig, _typeName, _schema, externalValue) => ({
...valueConfig,
extensions: {
...valueConfig.extensions,
directives: {
...(0, getDirectiveExtensions_js_1.getDirectiveExtensions)(valueConfig),
source: {
subgraph: subgraphName,
name: externalValue,
},
},
},
}),
[utils_1.MapperKind.ROOT_FIELD]: (fieldConfig, fieldName, typeName) => {
const operationType = typeToOperationType.get(typeName);
const operationName = operationType === 'query' ? fieldName : `${operationType}${fieldName}`;
const variableDefinitions = [];
const rootFieldArgs = [];
if (fieldConfig.args) {
for (const argName in fieldConfig.args) {
const arg = fieldConfig.args[argName];
let variableDefinitionStr = `$${argName}: ${arg.type}`;
if (arg.defaultValue) {
variableDefinitionStr += ` = ${typeof arg.defaultValue === 'string'
? JSON.stringify(arg.defaultValue)
: arg.defaultValue}`;
}
variableDefinitions.push(variableDefinitionStr);
rootFieldArgs.push(`${argName}: $${argName}`);
}
}
const variableDefinitionsString = variableDefinitions.length
? `(${variableDefinitions.join(', ')})`
: '';
const rootFieldArgsString = rootFieldArgs.length ? `(${rootFieldArgs.join(', ')})` : '';
const operationString = `${operationType} ${operationName}${variableDefinitionsString} { ${fieldName}${rootFieldArgsString} }`;
return {
...fieldConfig,
extensions: {
...fieldConfig.extensions,
directives: {
...(0, getDirectiveExtensions_js_1.getDirectiveExtensions)(fieldConfig),
resolver: {
subgraph: subgraphName,
operation: operationString,
},
source: {
subgraph: subgraphName,
name: fieldName,
type: fieldConfig.type.toString(),
},
},
},
};
},
});
let transformedSubgraph = annotatedSubgraph;
transformedSubgraphMap.set(subgraphName, transformedSubgraph);
if (transforms?.length) {
for (const transform of transforms) {
transformedSubgraph = transform(transformedSubgraph, subgraphConfig);
transformedSubgraphMap.set(subgraphName, transformedSubgraph);
}
// Semantic conventions
const transformedQueryType = transformedSubgraph.getQueryType();
const transformedQueryFields = transformedQueryType?.getFields();
const rootTypes = (0, utils_1.getRootTypes)(transformedSubgraph);
transformedSubgraph = (0, utils_1.mapSchema)(transformedSubgraph, {
[utils_1.MapperKind.TYPE]: type => {
if ((0, graphql_1.isSpecifiedScalarType)(type) || rootTypes.has(type)) {
return type;
}
const directives = (0, getDirectiveExtensions_js_1.getDirectiveExtensions)(type);
// Automatic type merging configuration based on ById and ByIds naming conventions after transforms
addAnnotationsForSemanticConventions({
type,
queryFields: transformedQueryFields,
subgraphName,
directives,
subgraphs,
transformedSubgraphMap,
});
return new (Object.getPrototypeOf(type).constructor)({
...type.toConfig(),
extensions: {
...type.extensions,
directives,
},
});
},
});
}
annotatedSubgraphs.push(transformedSubgraph);
transformedSubgraphMap.set(subgraphName, transformedSubgraph);
}
return (0, schema_1.mergeSchemas)({
schemas: annotatedSubgraphs,
assumeValidSDL: true,
assumeValid: true,
...options,
});
}
exports.composeSubgraphs = composeSubgraphs;
function addAnnotationsForSemanticConventions({ type, queryFields, subgraphName, directives, subgraphs, transformedSubgraphMap, }) {
if (queryFields && (0, graphql_1.isObjectType)(type)) {
const fieldMap = type.getFields();
for (const queryFieldName in queryFields) {
for (const fieldName in fieldMap) {
const objectField = fieldMap[fieldName];
const queryField = queryFields[queryFieldName];
const objectFieldType = (0, graphql_1.getNamedType)(objectField.type);
const arg = queryField.args.find(arg => (0, graphql_1.getNamedType)(arg.type) === objectFieldType);
const queryFieldTypeName = (0, graphql_1.getNamedType)(queryField.type).name;
const queryFieldNameSnakeCase = (0, snake_case_1.snakeCase)(queryFieldName);
const varName = `${type.name}_${fieldName}`;
if (queryFieldTypeName === type.name) {
// eslint-disable-next-line no-inner-declarations
function addVariablesForOtherSubgraphs() {
directives.variable ||= [];
for (const otherSubgraphConfig of subgraphs) {
const otherType = otherSubgraphConfig.schema.getType(type.name);
const otherTransformedType = transformedSubgraphMap
.get(otherSubgraphConfig.name)
?.getType(type.name);
const otherTypeFieldNames = [];
if ((0, graphql_1.isObjectType)(otherType)) {
otherTypeFieldNames.push(...Object.keys(otherType.getFields()));
}
if ((0, graphql_1.isObjectType)(otherTransformedType)) {
otherTypeFieldNames.push(...Object.keys(otherTransformedType.getFields()));
}
if (otherTypeFieldNames.includes(fieldName)) {
directives.variable ||= [];
if (!directives.variable.some((v) => v.subgraph === otherSubgraphConfig.name && v.name === varName)) {
directives.variable.push({
subgraph: otherSubgraphConfig.name,
name: varName,
select: fieldName,
});
}
}
}
}
const pluralTypeName = (0, pluralize_1.default)(type.name);
if (arg) {
switch (queryFieldNameSnakeCase) {
case (0, snake_case_1.snakeCase)(type.name):
case (0, snake_case_1.snakeCase)(`get_${type.name}_by_${fieldName}`):
case (0, snake_case_1.snakeCase)(`${type.name}_by_${fieldName}`): {
const operationName = (0, pascal_case_1.pascalCase)(`${type.name}_by_${fieldName}`);
const originalFieldName = getOriginalFieldNameForSubgraph(queryField, subgraphName);
const resolverAnnotation = {
subgraph: subgraphName,
operation: `query ${operationName}($${varName}: ${arg.type}) { ${originalFieldName}(${arg.name}: $${varName}) }`,
kind: 'FETCH',
};
directives.resolver ||= [];
directives.resolver.push(resolverAnnotation);
addVariablesForOtherSubgraphs();
break;
}
case (0, snake_case_1.snakeCase)(pluralTypeName):
case (0, snake_case_1.snakeCase)(`get_${pluralTypeName}_by_${fieldName}`):
case (0, snake_case_1.snakeCase)(`${pluralTypeName}_by_${fieldName}`):
case (0, snake_case_1.snakeCase)(`get_${pluralTypeName}_by_${fieldName}s`):
case (0, snake_case_1.snakeCase)(`${pluralTypeName}_by_${fieldName}s`): {
const operationName = (0, pascal_case_1.pascalCase)(`${pluralTypeName}_by_${fieldName}s`);
const originalFieldName = getOriginalFieldNameForSubgraph(queryField, subgraphName) || queryFieldName;
const resolverAnnotation = {
subgraph: subgraphName,
operation: `query ${operationName}($${varName}: ${arg.type}) { ${originalFieldName}(${arg.name}: $${varName}) }`,
kind: 'BATCH',
};
directives.resolver ||= [];
directives.resolver.push(resolverAnnotation);
directives.variable ||= [];
addVariablesForOtherSubgraphs();
break;
}
}
}
if (fieldName === 'id') {
/** For the schemas with filter in `where` argument */
const whereArg = queryField.args.find(arg => arg.name === 'where');
const whereArgType = whereArg && (0, graphql_1.getNamedType)(whereArg.type);
const whereArgTypeFields = (0, graphql_1.isInputObjectType)(whereArgType) && whereArgType.getFields();
const regularFieldInWhereArg = whereArgTypeFields?.[fieldName];
const regularFieldTypeName = regularFieldInWhereArg && (0, graphql_1.getNamedType)(regularFieldInWhereArg.type)?.name;
const batchFieldInWhereArg = whereArgTypeFields?.[`${fieldName}_in`];
const batchFieldTypeName = batchFieldInWhereArg && (0, graphql_1.getNamedType)(batchFieldInWhereArg.type)?.name;
const objectFieldTypeName = objectFieldType.name;
if (regularFieldTypeName === objectFieldTypeName) {
const operationName = (0, pascal_case_1.pascalCase)(`get_${queryFieldTypeName}_by_${fieldName}`);
const originalFieldName = getOriginalFieldNameForSubgraph(queryField, subgraphName);
const resolverAnnotation = {
subgraph: subgraphName,
operation: `query ${operationName}($${varName}: ${objectFieldTypeName}!) { ${originalFieldName}(where: { ${fieldName}: $${varName}) } }`,
kind: 'FETCH',
};
directives.resolver ||= [];
directives.resolver.push(resolverAnnotation);
directives.variable ||= [];
addVariablesForOtherSubgraphs();
}
if (batchFieldTypeName === objectFieldTypeName) {
const pluralFieldName = (0, pluralize_1.default)(fieldName);
const operationName = (0, pascal_case_1.pascalCase)(`get_${pluralTypeName}_by_${pluralFieldName}`);
const originalFieldName = getOriginalFieldNameForSubgraph(queryField, subgraphName);
const resolverAnnotation = {
subgraph: subgraphName,
operation: `query ${operationName}($${varName}: [${objectFieldTypeName}!]!) { ${originalFieldName}(where: { ${fieldName}_in: $${varName} }) }`,
kind: 'BATCH',
};
directives.resolver ||= [];
directives.resolver.push(resolverAnnotation);
directives.variable ||= [];
addVariablesForOtherSubgraphs();
}
}
}
}
}
}
}
function getOriginalFieldNameForSubgraph(field, subgraph) {
if (field.extensions?.directives) {
const sourceDirectives = (0, utils_1.asArray)(field.extensions.directives.source);
const sourceDirective = sourceDirectives.find((d) => d.subgraph === subgraph);
if (sourceDirective) {
return sourceDirective.name;
}
}
return field.name;
}