1 | import { GraphQLEnumType, GraphQLInterfaceType, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType, isEnumType, isInterfaceType, isObjectType, isScalarType, isSpecifiedScalarType, isUnionType, } from 'graphql';
|
2 | import { forEachDefaultValue, forEachField, healSchema, MapperKind, mapSchema, parseInputValue, serializeInputValue, } from '@graphql-tools/utils';
|
3 | import { checkForResolveTypeResolver } from './checkForResolveTypeResolver.js';
|
4 | import { extendResolversFromInterfaces } from './extendResolversFromInterfaces.js';
|
5 | export function addResolversToSchema({ schema, resolvers: inputResolvers, defaultFieldResolver, resolverValidationOptions = {}, inheritResolversFromInterfaces = false, updateResolversInPlace = false, }) {
|
6 | const { requireResolversToMatchSchema = 'error', requireResolversForResolveType } = resolverValidationOptions;
|
7 | const resolvers = inheritResolversFromInterfaces
|
8 | ? extendResolversFromInterfaces(schema, inputResolvers)
|
9 | : inputResolvers;
|
10 | for (const typeName in resolvers) {
|
11 | const resolverValue = resolvers[typeName];
|
12 | const resolverType = typeof resolverValue;
|
13 | if (resolverType !== 'object') {
|
14 | throw new Error(`"${typeName}" defined in resolvers, but has invalid value "${resolverValue}". The resolver's value must be of type object.`);
|
15 | }
|
16 | const type = schema.getType(typeName);
|
17 | if (type == null) {
|
18 | const msg = `"${typeName}" defined in resolvers, but not in schema`;
|
19 | if (requireResolversToMatchSchema && requireResolversToMatchSchema !== 'error') {
|
20 | if (requireResolversToMatchSchema === 'warn') {
|
21 | console.warn(msg);
|
22 | }
|
23 | continue;
|
24 | }
|
25 | throw new Error(msg);
|
26 | }
|
27 | else if (isSpecifiedScalarType(type)) {
|
28 |
|
29 | for (const fieldName in resolverValue) {
|
30 | if (fieldName.startsWith('__')) {
|
31 | type[fieldName.substring(2)] = resolverValue[fieldName];
|
32 | }
|
33 | else {
|
34 | type[fieldName] = resolverValue[fieldName];
|
35 | }
|
36 | }
|
37 | }
|
38 | else if (isEnumType(type)) {
|
39 | const values = type.getValues();
|
40 | for (const fieldName in resolverValue) {
|
41 | if (!fieldName.startsWith('__') &&
|
42 | !values.some(value => value.name === fieldName) &&
|
43 | requireResolversToMatchSchema &&
|
44 | requireResolversToMatchSchema !== 'ignore') {
|
45 | const msg = `${type.name}.${fieldName} was defined in resolvers, but not present within ${type.name}`;
|
46 | if (requireResolversToMatchSchema === 'error') {
|
47 | throw new Error(msg);
|
48 | }
|
49 | else {
|
50 | console.warn(msg);
|
51 | }
|
52 | }
|
53 | }
|
54 | }
|
55 | else if (isUnionType(type)) {
|
56 | for (const fieldName in resolverValue) {
|
57 | if (!fieldName.startsWith('__') &&
|
58 | requireResolversToMatchSchema &&
|
59 | requireResolversToMatchSchema !== 'ignore') {
|
60 | const msg = `${type.name}.${fieldName} was defined in resolvers, but ${type.name} is not an object or interface type`;
|
61 | if (requireResolversToMatchSchema === 'error') {
|
62 | throw new Error(msg);
|
63 | }
|
64 | else {
|
65 | console.warn(msg);
|
66 | }
|
67 | }
|
68 | }
|
69 | }
|
70 | else if (isObjectType(type) || isInterfaceType(type)) {
|
71 | for (const fieldName in resolverValue) {
|
72 | if (!fieldName.startsWith('__')) {
|
73 | const fields = type.getFields();
|
74 | const field = fields[fieldName];
|
75 | if (field == null) {
|
76 |
|
77 | if (requireResolversToMatchSchema && requireResolversToMatchSchema !== 'ignore') {
|
78 | const msg = `${typeName}.${fieldName} defined in resolvers, but not in schema`;
|
79 | if (requireResolversToMatchSchema === 'error') {
|
80 | throw new Error(msg);
|
81 | }
|
82 | else {
|
83 | console.error(msg);
|
84 | }
|
85 | }
|
86 | }
|
87 | else {
|
88 |
|
89 | const fieldResolve = resolverValue[fieldName];
|
90 | if (typeof fieldResolve !== 'function' && typeof fieldResolve !== 'object') {
|
91 | throw new Error(`Resolver ${typeName}.${fieldName} must be object or function`);
|
92 | }
|
93 | }
|
94 | }
|
95 | }
|
96 | }
|
97 | }
|
98 | schema = updateResolversInPlace
|
99 | ? addResolversToExistingSchema(schema, resolvers, defaultFieldResolver)
|
100 | : createNewSchemaWithResolvers(schema, resolvers, defaultFieldResolver);
|
101 | if (requireResolversForResolveType && requireResolversForResolveType !== 'ignore') {
|
102 | checkForResolveTypeResolver(schema, requireResolversForResolveType);
|
103 | }
|
104 | return schema;
|
105 | }
|
106 | function addResolversToExistingSchema(schema, resolvers, defaultFieldResolver) {
|
107 | const typeMap = schema.getTypeMap();
|
108 | for (const typeName in resolvers) {
|
109 | const type = schema.getType(typeName);
|
110 | const resolverValue = resolvers[typeName];
|
111 | if (isScalarType(type)) {
|
112 | for (const fieldName in resolverValue) {
|
113 | if (fieldName.startsWith('__')) {
|
114 | type[fieldName.substring(2)] = resolverValue[fieldName];
|
115 | }
|
116 | else if (fieldName === 'astNode' && type.astNode != null) {
|
117 | type.astNode = {
|
118 | ...type.astNode,
|
119 | description: resolverValue?.astNode?.description ??
|
120 | type.astNode.description,
|
121 | directives: (type.astNode.directives ?? []).concat(resolverValue?.astNode?.directives ?? []),
|
122 | };
|
123 | }
|
124 | else if (fieldName === 'extensionASTNodes' && type.extensionASTNodes != null) {
|
125 | type.extensionASTNodes = type.extensionASTNodes.concat(resolverValue?.extensionASTNodes ?? []);
|
126 | }
|
127 | else if (fieldName === 'extensions' &&
|
128 | type.extensions != null &&
|
129 | resolverValue.extensions != null) {
|
130 | type.extensions = Object.assign(Object.create(null), type.extensions, resolverValue.extensions);
|
131 | }
|
132 | else {
|
133 | type[fieldName] = resolverValue[fieldName];
|
134 | }
|
135 | }
|
136 | }
|
137 | else if (isEnumType(type)) {
|
138 | const config = type.toConfig();
|
139 | const enumValueConfigMap = config.values;
|
140 | for (const fieldName in resolverValue) {
|
141 | if (fieldName.startsWith('__')) {
|
142 | config[fieldName.substring(2)] = resolverValue[fieldName];
|
143 | }
|
144 | else if (fieldName === 'astNode' && config.astNode != null) {
|
145 | config.astNode = {
|
146 | ...config.astNode,
|
147 | description: resolverValue?.astNode?.description ??
|
148 | config.astNode.description,
|
149 | directives: (config.astNode.directives ?? []).concat(resolverValue?.astNode?.directives ?? []),
|
150 | };
|
151 | }
|
152 | else if (fieldName === 'extensionASTNodes' && config.extensionASTNodes != null) {
|
153 | config.extensionASTNodes = config.extensionASTNodes.concat(resolverValue?.extensionASTNodes ?? []);
|
154 | }
|
155 | else if (fieldName === 'extensions' &&
|
156 | type.extensions != null &&
|
157 | resolverValue.extensions != null) {
|
158 | type.extensions = Object.assign(Object.create(null), type.extensions, resolverValue.extensions);
|
159 | }
|
160 | else if (enumValueConfigMap[fieldName]) {
|
161 | enumValueConfigMap[fieldName].value = resolverValue[fieldName];
|
162 | }
|
163 | }
|
164 | typeMap[typeName] = new GraphQLEnumType(config);
|
165 | }
|
166 | else if (isUnionType(type)) {
|
167 | for (const fieldName in resolverValue) {
|
168 | if (fieldName.startsWith('__')) {
|
169 | type[fieldName.substring(2)] = resolverValue[fieldName];
|
170 | }
|
171 | }
|
172 | }
|
173 | else if (isObjectType(type) || isInterfaceType(type)) {
|
174 | for (const fieldName in resolverValue) {
|
175 | if (fieldName.startsWith('__')) {
|
176 |
|
177 | type[fieldName.substring(2)] = resolverValue[fieldName];
|
178 | continue;
|
179 | }
|
180 | const fields = type.getFields();
|
181 | const field = fields[fieldName];
|
182 | if (field != null) {
|
183 | const fieldResolve = resolverValue[fieldName];
|
184 | if (typeof fieldResolve === 'function') {
|
185 |
|
186 | field.resolve = fieldResolve.bind(resolverValue);
|
187 | }
|
188 | else {
|
189 | setFieldProperties(field, fieldResolve);
|
190 | }
|
191 | }
|
192 | }
|
193 | }
|
194 | }
|
195 |
|
196 | forEachDefaultValue(schema, serializeInputValue);
|
197 |
|
198 | healSchema(schema);
|
199 |
|
200 | forEachDefaultValue(schema, parseInputValue);
|
201 | if (defaultFieldResolver != null) {
|
202 | forEachField(schema, field => {
|
203 | if (!field.resolve) {
|
204 | field.resolve = defaultFieldResolver;
|
205 | }
|
206 | });
|
207 | }
|
208 | return schema;
|
209 | }
|
210 | function createNewSchemaWithResolvers(schema, resolvers, defaultFieldResolver) {
|
211 | schema = mapSchema(schema, {
|
212 | [MapperKind.SCALAR_TYPE]: type => {
|
213 | const config = type.toConfig();
|
214 | const resolverValue = resolvers[type.name];
|
215 | if (!isSpecifiedScalarType(type) && resolverValue != null) {
|
216 | for (const fieldName in resolverValue) {
|
217 | if (fieldName.startsWith('__')) {
|
218 | config[fieldName.substring(2)] = resolverValue[fieldName];
|
219 | }
|
220 | else if (fieldName === 'astNode' && config.astNode != null) {
|
221 | config.astNode = {
|
222 | ...config.astNode,
|
223 | description: resolverValue?.astNode?.description ??
|
224 | config.astNode.description,
|
225 | directives: (config.astNode.directives ?? []).concat(resolverValue?.astNode?.directives ?? []),
|
226 | };
|
227 | }
|
228 | else if (fieldName === 'extensionASTNodes' && config.extensionASTNodes != null) {
|
229 | config.extensionASTNodes = config.extensionASTNodes.concat(resolverValue?.extensionASTNodes ?? []);
|
230 | }
|
231 | else if (fieldName === 'extensions' &&
|
232 | config.extensions != null &&
|
233 | resolverValue.extensions != null) {
|
234 | config.extensions = Object.assign(Object.create(null), type.extensions, resolverValue.extensions);
|
235 | }
|
236 | else {
|
237 | config[fieldName] = resolverValue[fieldName];
|
238 | }
|
239 | }
|
240 | return new GraphQLScalarType(config);
|
241 | }
|
242 | },
|
243 | [MapperKind.ENUM_TYPE]: type => {
|
244 | const resolverValue = resolvers[type.name];
|
245 | const config = type.toConfig();
|
246 | const enumValueConfigMap = config.values;
|
247 | if (resolverValue != null) {
|
248 | for (const fieldName in resolverValue) {
|
249 | if (fieldName.startsWith('__')) {
|
250 | config[fieldName.substring(2)] = resolverValue[fieldName];
|
251 | }
|
252 | else if (fieldName === 'astNode' && config.astNode != null) {
|
253 | config.astNode = {
|
254 | ...config.astNode,
|
255 | description: resolverValue?.astNode?.description ??
|
256 | config.astNode.description,
|
257 | directives: (config.astNode.directives ?? []).concat(resolverValue?.astNode?.directives ?? []),
|
258 | };
|
259 | }
|
260 | else if (fieldName === 'extensionASTNodes' && config.extensionASTNodes != null) {
|
261 | config.extensionASTNodes = config.extensionASTNodes.concat(resolverValue?.extensionASTNodes ?? []);
|
262 | }
|
263 | else if (fieldName === 'extensions' &&
|
264 | config.extensions != null &&
|
265 | resolverValue.extensions != null) {
|
266 | config.extensions = Object.assign(Object.create(null), type.extensions, resolverValue.extensions);
|
267 | }
|
268 | else if (enumValueConfigMap[fieldName]) {
|
269 | enumValueConfigMap[fieldName].value = resolverValue[fieldName];
|
270 | }
|
271 | }
|
272 | return new GraphQLEnumType(config);
|
273 | }
|
274 | },
|
275 | [MapperKind.UNION_TYPE]: type => {
|
276 | const resolverValue = resolvers[type.name];
|
277 | if (resolverValue != null) {
|
278 | const config = type.toConfig();
|
279 | if (resolverValue['__resolveType']) {
|
280 | config.resolveType = resolverValue['__resolveType'];
|
281 | }
|
282 | return new GraphQLUnionType(config);
|
283 | }
|
284 | },
|
285 | [MapperKind.OBJECT_TYPE]: type => {
|
286 | const resolverValue = resolvers[type.name];
|
287 | if (resolverValue != null) {
|
288 | const config = type.toConfig();
|
289 | if (resolverValue['__isTypeOf']) {
|
290 | config.isTypeOf = resolverValue['__isTypeOf'];
|
291 | }
|
292 | return new GraphQLObjectType(config);
|
293 | }
|
294 | },
|
295 | [MapperKind.INTERFACE_TYPE]: type => {
|
296 | const resolverValue = resolvers[type.name];
|
297 | if (resolverValue != null) {
|
298 | const config = type.toConfig();
|
299 | if (resolverValue['__resolveType']) {
|
300 | config.resolveType = resolverValue['__resolveType'];
|
301 | }
|
302 | return new GraphQLInterfaceType(config);
|
303 | }
|
304 | },
|
305 | [MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName, typeName) => {
|
306 | const resolverValue = resolvers[typeName];
|
307 | if (resolverValue != null) {
|
308 | const fieldResolve = resolverValue[fieldName];
|
309 | if (fieldResolve != null) {
|
310 | const newFieldConfig = { ...fieldConfig };
|
311 | if (typeof fieldResolve === 'function') {
|
312 |
|
313 | newFieldConfig.resolve = fieldResolve.bind(resolverValue);
|
314 | }
|
315 | else {
|
316 | setFieldProperties(newFieldConfig, fieldResolve);
|
317 | }
|
318 | return newFieldConfig;
|
319 | }
|
320 | }
|
321 | },
|
322 | });
|
323 | if (defaultFieldResolver != null) {
|
324 | schema = mapSchema(schema, {
|
325 | [MapperKind.OBJECT_FIELD]: fieldConfig => ({
|
326 | ...fieldConfig,
|
327 | resolve: fieldConfig.resolve != null ? fieldConfig.resolve : defaultFieldResolver,
|
328 | }),
|
329 | });
|
330 | }
|
331 | return schema;
|
332 | }
|
333 | function setFieldProperties(field, propertiesObj) {
|
334 | for (const propertyName in propertiesObj) {
|
335 | field[propertyName] = propertiesObj[propertyName];
|
336 | }
|
337 | }
|