@graphql-tools/resolvers-composition
Version:
Common package containing utils and types for GraphQL tools
88 lines (87 loc) • 3.42 kB
JavaScript
import _ from 'lodash';
import micromatch from 'micromatch';
import { asArray } from '@graphql-tools/utils';
import { chainFunctions } from './chain-functions.js';
function isScalarTypeConfiguration(config) {
return config && 'serialize' in config && 'parseLiteral' in config;
}
function resolveRelevantMappings(resolvers, path) {
if (!resolvers) {
return [];
}
const [typeNameOrGlob, fieldNameOrGlob] = path.split('.');
const isTypeMatch = micromatch.matcher(typeNameOrGlob);
let fixedFieldGlob = fieldNameOrGlob;
// convert single value OR `{singleField}` to `singleField` as matching will fail otherwise
if (fixedFieldGlob.includes('{') && !fixedFieldGlob.includes(',')) {
fixedFieldGlob = fieldNameOrGlob.replace('{', '').replace('}', '');
}
fixedFieldGlob = fixedFieldGlob.replace(', ', ',').trim();
const isFieldMatch = micromatch.matcher(fixedFieldGlob);
const mappings = [];
for (const typeName in resolvers) {
if (!isTypeMatch(typeName)) {
continue;
}
if (isScalarTypeConfiguration(resolvers[typeName])) {
continue;
}
const fieldMap = resolvers[typeName];
if (!fieldMap) {
return [];
}
for (const field in fieldMap) {
if (!isFieldMatch(field)) {
continue;
}
const resolvedPath = `${typeName}.${field}`;
if (resolvers[typeName] && resolvers[typeName][field]) {
if (resolvers[typeName][field].subscribe) {
mappings.push(resolvedPath + '.subscribe');
}
if (resolvers[typeName][field].resolve) {
mappings.push(resolvedPath + '.resolve');
}
if (typeof resolvers[typeName][field] === 'function') {
mappings.push(resolvedPath);
}
}
}
}
return mappings;
}
/**
* Wraps the resolvers object with the resolvers composition objects.
* Implemented as a simple and basic middleware mechanism.
*
* @param resolvers - resolvers object
* @param mapping - resolvers composition mapping
* @hidden
*/
export function composeResolvers(resolvers, mapping = {}) {
const mappingResult = {};
for (const resolverPath in mapping) {
const resolverPathMapping = mapping[resolverPath];
if (resolverPathMapping instanceof Array || typeof resolverPathMapping === 'function') {
const composeFns = resolverPathMapping;
const relevantFields = resolveRelevantMappings(resolvers, resolverPath);
for (const path of relevantFields) {
mappingResult[path] = asArray(composeFns);
}
}
else if (resolverPathMapping) {
for (const fieldName in resolverPathMapping) {
const composeFns = resolverPathMapping[fieldName];
const relevantFields = resolveRelevantMappings(resolvers, resolverPath + '.' + fieldName);
for (const path of relevantFields) {
mappingResult[path] = asArray(composeFns);
}
}
}
}
for (const path in mappingResult) {
const fns = chainFunctions([...asArray(mappingResult[path]), () => _.get(resolvers, path)]);
_.set(resolvers, path, fns());
}
return resolvers;
}