UNPKG

@graphql-mesh/fusion-composition

Version:

Basic composition utility for Fusion spec

313 lines (312 loc) • 17.3 kB
"use strict"; 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; }