UNPKG

@graphql-mesh/fusion-composition

Version:

Basic composition utility for Fusion spec

108 lines (107 loc) 5.58 kB
import { DirectiveLocation, getNamedType, GraphQLDirective, GraphQLScalarType, GraphQLSchema, GraphQLString, isInterfaceType, isObjectType, } from 'graphql'; import { MapperKind, mapSchema } from '@graphql-tools/utils'; import { TransformValidationError } from './utils.js'; function assertTypeWithFields(type) { if (!checkTypeWithFields(type)) { throw new TransformValidationError(`Type ${type.name} is not an object or interface type, so you cannot apply hoisting for this type`); } } function checkTypeWithFields(type) { return isObjectType(type) || isInterfaceType(type); } export const hoistDirective = new GraphQLDirective({ name: 'hoist', locations: [DirectiveLocation.FIELD_DEFINITION], args: { subgraph: { type: GraphQLString, }, pathConfig: { type: new GraphQLScalarType({ name: '_HoistConfig', }), }, }, }); export function createHoistFieldTransform(opts) { return function hoistFieldTransform(schema, subgraphConfig) { const mappedSchema = mapSchema(schema, { [MapperKind.TYPE](type) { if (opts.mapping) { if (checkTypeWithFields(type)) { let changed = false; const fields = type.getFields(); const typeConfig = type.toConfig(); const newFieldConfigMap = typeConfig.fields; for (const mapping of opts.mapping) { if (type.name === mapping.typeName) { const pathConfig = mapping.pathConfig[0]; let fieldName = typeof pathConfig === 'string' ? pathConfig : pathConfig.fieldName; let field = fields[fieldName]; const pathConfigArr = [...mapping.pathConfig.slice(1)]; let filteredArgs = typeof pathConfig === 'object' ? pathConfig.filterArgs : undefined; const argsConfig = {}; while (pathConfigArr.length > 0) { if (!field) { throw new TransformValidationError(`Field ${fieldName} not found for the hoisting of ${type.name}, so you cannot apply hoisting`); } for (const arg of field.args) { if (filteredArgs) { if (filteredArgs.includes(arg.name)) { continue; } } else if (mapping.filterArgsInPath) { continue; } argsConfig[arg.name] = arg; } const fieldType = getNamedType(field.type); assertTypeWithFields(fieldType); const subFields = fieldType.getFields(); const pathPart = pathConfigArr.shift(); filteredArgs = typeof pathPart === 'string' ? [] : pathPart.filterArgs || []; fieldName = typeof pathPart === 'string' ? pathPart : pathPart.fieldName; field = subFields[fieldName]; } changed = true; const existingFieldConfig = newFieldConfigMap[mapping.newFieldName]; newFieldConfigMap[mapping.newFieldName] = { ...(existingFieldConfig || {}), args: argsConfig, type: field.type, extensions: { ...existingFieldConfig?.extensions, directives: { ...existingFieldConfig?.extensions?.directives, hoist: [ { subgraph: subgraphConfig.name, pathConfig: mapping.pathConfig, }, ], }, }, }; } } if (changed) { return new (Object.getPrototypeOf(type).constructor)({ ...typeConfig, fields: newFieldConfigMap, }); } } } return type; }, }); if (mappedSchema.getDirective('hoist') == null) { return new GraphQLSchema({ ...mappedSchema.toConfig(), directives: [...mappedSchema.toConfig().directives, hoistDirective], }); } return mappedSchema; }; }