UNPKG

16.6 kBJavaScriptView Raw
1import { GraphQLEnumType, GraphQLInterfaceType, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType, isEnumType, isInterfaceType, isObjectType, isScalarType, isSpecifiedScalarType, isUnionType, } from 'graphql';
2import { forEachDefaultValue, forEachField, healSchema, MapperKind, mapSchema, parseInputValue, serializeInputValue, } from '@graphql-tools/utils';
3import { checkForResolveTypeResolver } from './checkForResolveTypeResolver.js';
4import { extendResolversFromInterfaces } from './extendResolversFromInterfaces.js';
5export 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 // allow -- without recommending -- overriding of specified scalar types
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 // Field present in resolver but not in schema
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 // Field present in both the resolver and schema
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}
106function 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 // this is for isTypeOf and resolveType and all the other stuff.
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 // for convenience. Allows shorter syntax in resolver definition file
186 field.resolve = fieldResolve.bind(resolverValue);
187 }
188 else {
189 setFieldProperties(field, fieldResolve);
190 }
191 }
192 }
193 }
194 }
195 // serialize all default values prior to healing fields with new scalar/enum types.
196 forEachDefaultValue(schema, serializeInputValue);
197 // schema may have new scalar/enum types that require healing
198 healSchema(schema);
199 // reparse all default values with new parsing functions.
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}
210function 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 // for convenience. Allows shorter syntax in resolver definition file
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}
333function setFieldProperties(field, propertiesObj) {
334 for (const propertyName in propertiesObj) {
335 field[propertyName] = propertiesObj[propertyName];
336 }
337}