-
-
Notifications
You must be signed in to change notification settings - Fork 797
/
resolvers-composition.ts
135 lines (112 loc) · 4.2 KB
/
resolvers-composition.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { GraphQLFieldResolver, GraphQLScalarTypeConfig } from 'graphql';
import _ from 'lodash';
import micromatch from 'micromatch';
import { asArray } from '@graphql-tools/utils';
import { chainFunctions } from './chain-functions.js';
export type ResolversComposition<
Resolver extends GraphQLFieldResolver<any, any, any> = GraphQLFieldResolver<any, any>,
> = (next: Resolver) => Resolver;
export type ResolversComposerMapping<Resolvers extends Record<string, any> = Record<string, any>> =
| {
[TypeName in keyof Resolvers]?: {
[FieldName in keyof Resolvers[TypeName]]: Resolvers[TypeName][FieldName] extends GraphQLFieldResolver<
any,
any
>
?
| ResolversComposition<Resolvers[TypeName][FieldName]>
| Array<ResolversComposition<Resolvers[TypeName][FieldName]>>
: ResolversComposition | ResolversComposition[];
};
}
| {
[path: string]: ResolversComposition | ResolversComposition[];
};
function isScalarTypeConfiguration(config: any): config is GraphQLScalarTypeConfig<any, any> {
return config && 'serialize' in config && 'parseLiteral' in config;
}
function resolveRelevantMappings<Resolvers extends Record<string, any> = Record<string, any>>(
resolvers: Resolvers,
path: string,
): string[] {
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: string[] = [];
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 extends Record<string, any>>(
resolvers: Resolvers,
mapping: ResolversComposerMapping<Resolvers> = {},
): Resolvers {
const mappingResult: { [path: string]: ((...args: any[]) => any)[] } = {};
for (const resolverPath in mapping) {
const resolverPathMapping = mapping[resolverPath];
if (resolverPathMapping instanceof Array || typeof resolverPathMapping === 'function') {
const composeFns = resolverPathMapping as ResolversComposition | ResolversComposition[];
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;
}