UNPKG

37 kBJavaScriptView Raw
1"use strict";
2
3const _ = require(`lodash`);
4
5const invariant = require(`invariant`);
6
7const {
8 isSpecifiedScalarType,
9 isIntrospectionType,
10 assertValidName,
11 GraphQLNonNull,
12 GraphQLList,
13 GraphQLObjectType,
14 GraphQLInterfaceType
15} = require(`graphql`);
16
17const {
18 ObjectTypeComposer,
19 InterfaceTypeComposer,
20 UnionTypeComposer,
21 InputTypeComposer,
22 ScalarTypeComposer,
23 EnumTypeComposer,
24 defineFieldMapToConfig
25} = require(`graphql-compose`);
26
27const apiRunner = require(`../utils/api-runner-node`);
28
29const report = require(`gatsby-cli/lib/reporter`);
30
31const {
32 addNodeInterfaceFields
33} = require(`./types/node-interface`);
34
35const {
36 addInferredType,
37 addInferredTypes
38} = require(`./infer`);
39
40const {
41 findOne,
42 findManyPaginated
43} = require(`./resolvers`);
44
45const {
46 processFieldExtensions,
47 internalExtensionNames
48} = require(`./extensions`);
49
50const {
51 getPagination
52} = require(`./types/pagination`);
53
54const {
55 getSortInput,
56 SORTABLE_ENUM
57} = require(`./types/sort`);
58
59const {
60 getFilterInput,
61 SEARCHABLE_ENUM
62} = require(`./types/filter`);
63
64const {
65 isGatsbyType,
66 GatsbyGraphQLTypeKind
67} = require(`./types/type-builders`);
68
69const {
70 isASTDocument,
71 parseTypeDef,
72 reportParsingError
73} = require(`./types/type-defs`);
74
75const {
76 clearDerivedTypes
77} = require(`./types/derived-types`);
78
79const {
80 printTypeDefinitions
81} = require(`./print`);
82
83const buildSchema = async ({
84 schemaComposer,
85 nodeStore,
86 types,
87 typeMapping,
88 fieldExtensions,
89 thirdPartySchemas,
90 printConfig,
91 typeConflictReporter,
92 inferenceMetadata,
93 parentSpan
94}) => {
95 await updateSchemaComposer({
96 schemaComposer,
97 nodeStore,
98 types,
99 typeMapping,
100 fieldExtensions,
101 thirdPartySchemas,
102 printConfig,
103 typeConflictReporter,
104 inferenceMetadata,
105 parentSpan
106 }); // const { printSchema } = require(`graphql`)
107
108 const schema = schemaComposer.buildSchema(); // console.log(printSchema(schema))
109
110 return schema;
111};
112
113const rebuildSchemaWithSitePage = async ({
114 schemaComposer,
115 nodeStore,
116 typeMapping,
117 fieldExtensions,
118 typeConflictReporter,
119 inferenceMetadata,
120 parentSpan
121}) => {
122 const typeComposer = schemaComposer.getOTC(`SitePage`); // Clear derived types and fields
123 // they will be re-created in processTypeComposer later
124
125 clearDerivedTypes({
126 schemaComposer,
127 typeComposer
128 });
129 const shouldInfer = !typeComposer.hasExtension(`infer`) || typeComposer.getExtension(`infer`) !== false;
130
131 if (shouldInfer) {
132 addInferredType({
133 schemaComposer,
134 typeComposer,
135 nodeStore,
136 typeConflictReporter,
137 typeMapping,
138 inferenceMetadata,
139 parentSpan
140 });
141 }
142
143 await processTypeComposer({
144 schemaComposer,
145 typeComposer,
146 fieldExtensions,
147 nodeStore,
148 parentSpan
149 });
150 return schemaComposer.buildSchema();
151};
152
153module.exports = {
154 buildSchema,
155 rebuildSchemaWithSitePage
156};
157
158const updateSchemaComposer = async ({
159 schemaComposer,
160 nodeStore,
161 types,
162 typeMapping,
163 fieldExtensions,
164 thirdPartySchemas,
165 printConfig,
166 typeConflictReporter,
167 inferenceMetadata,
168 parentSpan
169}) => {
170 let activity = report.phantomActivity(`Add explicit types`, {
171 parentSpan: parentSpan
172 });
173 activity.start();
174 await addTypes({
175 schemaComposer,
176 parentSpan: activity.span,
177 types
178 });
179 activity.end();
180 activity = report.phantomActivity(`Add inferred types`, {
181 parentSpan: parentSpan
182 });
183 activity.start();
184 await addInferredTypes({
185 schemaComposer,
186 nodeStore,
187 typeConflictReporter,
188 typeMapping,
189 inferenceMetadata,
190 parentSpan: activity.span
191 });
192 activity.end();
193 activity = report.phantomActivity(`Processing types`, {
194 parentSpan: parentSpan
195 });
196 activity.start();
197 await printTypeDefinitions({
198 config: printConfig,
199 schemaComposer,
200 parentSpan: activity.span
201 });
202 await addSetFieldsOnGraphQLNodeTypeFields({
203 schemaComposer,
204 nodeStore,
205 parentSpan: activity.span
206 });
207 await addConvenienceChildrenFields({
208 schemaComposer,
209 parentSpan: activity.span
210 });
211 await Promise.all(Array.from(new Set(schemaComposer.values())).map(typeComposer => processTypeComposer({
212 schemaComposer,
213 typeComposer,
214 fieldExtensions,
215 nodeStore,
216 parentSpan: activity.span
217 })));
218 checkQueryableInterfaces({
219 schemaComposer,
220 parentSpan: activity.span
221 });
222 await addThirdPartySchemas({
223 schemaComposer,
224 thirdPartySchemas,
225 parentSpan: activity.span
226 });
227 await addCustomResolveFunctions({
228 schemaComposer,
229 parentSpan: activity.span
230 });
231 activity.end();
232};
233
234const processTypeComposer = async ({
235 schemaComposer,
236 typeComposer,
237 fieldExtensions,
238 nodeStore,
239 parentSpan
240}) => {
241 if (typeComposer instanceof ObjectTypeComposer) {
242 await processFieldExtensions({
243 schemaComposer,
244 typeComposer,
245 fieldExtensions,
246 parentSpan
247 });
248
249 if (typeComposer.hasInterface(`Node`)) {
250 await addNodeInterfaceFields({
251 schemaComposer,
252 typeComposer,
253 parentSpan
254 });
255 await addImplicitConvenienceChildrenFields({
256 schemaComposer,
257 typeComposer,
258 nodeStore,
259 parentSpan
260 });
261 }
262
263 await determineSearchableFields({
264 schemaComposer,
265 typeComposer,
266 parentSpan
267 });
268
269 if (typeComposer.hasInterface(`Node`)) {
270 await addTypeToRootQuery({
271 schemaComposer,
272 typeComposer,
273 parentSpan
274 });
275 }
276 } else if (typeComposer instanceof InterfaceTypeComposer) {
277 if (typeComposer.getExtension(`nodeInterface`)) {
278 // We only process field extensions for queryable Node interfaces, so we get
279 // the input args on the root query type, e.g. `formatString` etc. for `dateformat`
280 await processFieldExtensions({
281 schemaComposer,
282 typeComposer,
283 fieldExtensions,
284 parentSpan
285 });
286 await determineSearchableFields({
287 schemaComposer,
288 typeComposer,
289 parentSpan
290 });
291 await addTypeToRootQuery({
292 schemaComposer,
293 typeComposer,
294 parentSpan
295 });
296 }
297 }
298};
299
300const fieldNames = {
301 query: typeName => _.camelCase(typeName),
302 queryAll: typeName => _.camelCase(`all ${typeName}`),
303 convenienceChild: typeName => _.camelCase(`child ${typeName}`),
304 convenienceChildren: typeName => _.camelCase(`children ${typeName}`)
305};
306
307const addTypes = ({
308 schemaComposer,
309 types,
310 parentSpan
311}) => {
312 types.forEach(({
313 typeOrTypeDef,
314 plugin
315 }) => {
316 if (typeof typeOrTypeDef === `string`) {
317 typeOrTypeDef = parseTypeDef(typeOrTypeDef);
318 }
319
320 if (isASTDocument(typeOrTypeDef)) {
321 let parsedTypes;
322 const createdFrom = `sdl`;
323
324 try {
325 parsedTypes = parseTypes({
326 doc: typeOrTypeDef,
327 plugin,
328 createdFrom,
329 schemaComposer,
330 parentSpan
331 });
332 } catch (error) {
333 reportParsingError(error);
334 return;
335 }
336
337 parsedTypes.forEach(type => {
338 processAddedType({
339 schemaComposer,
340 type,
341 parentSpan,
342 createdFrom,
343 plugin
344 });
345 });
346 } else if (isGatsbyType(typeOrTypeDef)) {
347 const type = createTypeComposerFromGatsbyType({
348 schemaComposer,
349 type: typeOrTypeDef,
350 parentSpan
351 });
352
353 if (type) {
354 const typeName = type.getTypeName();
355 const createdFrom = `typeBuilder`;
356 checkIsAllowedTypeName(typeName);
357
358 if (schemaComposer.has(typeName)) {
359 const typeComposer = schemaComposer.get(typeName);
360 mergeTypes({
361 schemaComposer,
362 typeComposer,
363 type,
364 plugin,
365 createdFrom,
366 parentSpan
367 });
368 } else {
369 processAddedType({
370 schemaComposer,
371 type,
372 parentSpan,
373 createdFrom,
374 plugin
375 });
376 }
377 }
378 } else {
379 const typeName = typeOrTypeDef.name;
380 const createdFrom = `graphql-js`;
381 checkIsAllowedTypeName(typeName);
382
383 if (schemaComposer.has(typeName)) {
384 const typeComposer = schemaComposer.get(typeName);
385 mergeTypes({
386 schemaComposer,
387 typeComposer,
388 type: typeOrTypeDef,
389 plugin,
390 createdFrom,
391 parentSpan
392 });
393 } else {
394 processAddedType({
395 schemaComposer,
396 type: typeOrTypeDef,
397 parentSpan,
398 createdFrom,
399 plugin
400 });
401 }
402 }
403 });
404};
405
406const mergeTypes = ({
407 schemaComposer,
408 typeComposer,
409 type,
410 plugin,
411 createdFrom,
412 parentSpan
413}) => {
414 // Only allow user or plugin owning the type to extend already existing type.
415 const typeOwner = typeComposer.getExtension(`plugin`);
416
417 if (!plugin || plugin.name === `default-site-plugin` || plugin.name === typeOwner) {
418 if (type instanceof ObjectTypeComposer) {
419 mergeFields({
420 typeComposer,
421 fields: type.getFields()
422 });
423 type.getInterfaces().forEach(iface => typeComposer.addInterface(iface));
424 } else if (type instanceof InterfaceTypeComposer) {
425 mergeFields({
426 typeComposer,
427 fields: type.getFields()
428 });
429 } else if (type instanceof GraphQLObjectType) {
430 mergeFields({
431 typeComposer,
432 fields: defineFieldMapToConfig(type.getFields())
433 });
434 type.getInterfaces().forEach(iface => typeComposer.addInterface(iface));
435 } else if (type instanceof GraphQLInterfaceType) {
436 mergeFields({
437 typeComposer,
438 fields: defineFieldMapToConfig(type.getFields())
439 });
440 }
441
442 if (isNamedTypeComposer(type)) {
443 typeComposer.extendExtensions(type.getExtensions());
444 }
445
446 addExtensions({
447 schemaComposer,
448 typeComposer,
449 plugin,
450 createdFrom
451 });
452 return true;
453 } else {
454 report.warn(`Plugin \`${plugin.name}\` tried to define the GraphQL type ` + `\`${typeComposer.getTypeName()}\`, which has already been defined ` + `by the plugin \`${typeOwner}\`.`);
455 return false;
456 }
457};
458
459const processAddedType = ({
460 schemaComposer,
461 type,
462 parentSpan,
463 createdFrom,
464 plugin
465}) => {
466 const typeName = schemaComposer.addAsComposer(type);
467 const typeComposer = schemaComposer.get(typeName);
468
469 if (typeComposer instanceof InterfaceTypeComposer || typeComposer instanceof UnionTypeComposer) {
470 if (!typeComposer.getResolveType()) {
471 typeComposer.setResolveType(node => node.internal.type);
472 }
473 }
474
475 schemaComposer.addSchemaMustHaveType(typeComposer);
476 addExtensions({
477 schemaComposer,
478 typeComposer,
479 plugin,
480 createdFrom
481 });
482 return typeComposer;
483};
484
485const addExtensions = ({
486 schemaComposer,
487 typeComposer,
488 plugin,
489 createdFrom
490}) => {
491 typeComposer.setExtension(`createdFrom`, createdFrom);
492 typeComposer.setExtension(`plugin`, plugin ? plugin.name : null);
493
494 if (createdFrom === `sdl`) {
495 const directives = typeComposer.getDirectives();
496 directives.forEach(({
497 name,
498 args
499 }) => {
500 switch (name) {
501 case `infer`:
502 case `dontInfer`:
503 {
504 typeComposer.setExtension(`infer`, name === `infer`);
505
506 if (args.noDefaultResolvers != null) {
507 typeComposer.setExtension(`addDefaultResolvers`, !args.noDefaultResolvers);
508 }
509
510 break;
511 }
512
513 case `mimeTypes`:
514 typeComposer.setExtension(`mimeTypes`, args);
515 break;
516
517 case `childOf`:
518 typeComposer.setExtension(`childOf`, args);
519 break;
520
521 case `nodeInterface`:
522 if (typeComposer instanceof InterfaceTypeComposer) {
523 if (!typeComposer.hasField(`id`) || typeComposer.getFieldType(`id`).toString() !== `ID!`) {
524 report.panic(`Interfaces with the \`nodeInterface\` extension must have a field ` + `\`id\` of type \`ID!\`. Check the type definition of ` + `\`${typeComposer.getTypeName()}\`.`);
525 }
526
527 typeComposer.setExtension(`nodeInterface`, true);
528 }
529
530 break;
531
532 default:
533 }
534 });
535 }
536
537 if (typeComposer instanceof ObjectTypeComposer || typeComposer instanceof InterfaceTypeComposer || typeComposer instanceof InputTypeComposer) {
538 typeComposer.getFieldNames().forEach(fieldName => {
539 typeComposer.setFieldExtension(fieldName, `createdFrom`, createdFrom);
540 typeComposer.setFieldExtension(fieldName, `plugin`, plugin ? plugin.name : null);
541
542 if (createdFrom === `sdl`) {
543 const directives = typeComposer.getFieldDirectives(fieldName);
544 directives.forEach(({
545 name,
546 args
547 }) => {
548 typeComposer.setFieldExtension(fieldName, name, args);
549 });
550 } // Validate field extension args. `graphql-compose` already checks the
551 // type of directive args in `parseDirectives`, but we want to check
552 // extensions provided with type builders as well. Also, we warn if an
553 // extension option was provided which does not exist in the field
554 // extension definition.
555
556
557 const fieldExtensions = typeComposer.getFieldExtensions(fieldName);
558 const typeName = typeComposer.getTypeName();
559 Object.keys(fieldExtensions).filter(name => !internalExtensionNames.includes(name)).forEach(name => {
560 const args = fieldExtensions[name];
561
562 if (!args || typeof args !== `object`) {
563 report.error(`Field extension arguments must be provided as an object. ` + `Received "${args}" on \`${typeName}.${fieldName}\`.`);
564 return;
565 }
566
567 try {
568 const definition = schemaComposer.getDirective(name); // Handle `defaultValue` when not provided as directive
569
570 definition.args.forEach(({
571 name,
572 defaultValue
573 }) => {
574 if (args[name] === undefined && defaultValue !== undefined) {
575 args[name] = defaultValue;
576 }
577 });
578 Object.keys(args).forEach(arg => {
579 const argumentDef = definition.args.find(({
580 name
581 }) => name === arg);
582
583 if (!argumentDef) {
584 report.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `has invalid argument \`${arg}\`.`);
585 return;
586 }
587
588 const value = args[arg];
589
590 try {
591 validate(argumentDef.type, value);
592 } catch (error) {
593 report.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `has argument \`${arg}\` with invalid value "${value}". ` + error.message);
594 }
595 });
596 } catch (error) {
597 report.error(`Field extension \`${name}\` on \`${typeName}.${fieldName}\` ` + `is not available.`);
598 }
599 });
600 });
601 }
602
603 if (typeComposer.hasExtension(`addDefaultResolvers`)) {
604 report.warn(`Deprecation warning - "noDefaultResolvers" is deprecated. In Gatsby 3, ` + `defined fields won't get resolvers, unless explicitly added with a ` + `directive/extension.`);
605 }
606
607 return typeComposer;
608};
609
610const checkIsAllowedTypeName = name => {
611 invariant(name !== `Node`, `The GraphQL type \`Node\` is reserved for internal use.`);
612 invariant(!name.endsWith(`FilterInput`) && !name.endsWith(`SortInput`), `GraphQL type names ending with "FilterInput" or "SortInput" are ` + `reserved for internal use. Please rename \`${name}\`.`);
613 invariant(![`Boolean`, `Date`, `Float`, `ID`, `Int`, `JSON`, `String`].includes(name), `The GraphQL type \`${name}\` is reserved for internal use by ` + `built-in scalar types.`);
614 assertValidName(name);
615};
616
617const createTypeComposerFromGatsbyType = ({
618 schemaComposer,
619 type,
620 parentSpan
621}) => {
622 switch (type.kind) {
623 case GatsbyGraphQLTypeKind.OBJECT:
624 {
625 return ObjectTypeComposer.createTemp(Object.assign({}, type.config, {
626 interfaces: () => {
627 if (type.config.interfaces) {
628 return type.config.interfaces.map(iface => {
629 if (typeof iface === `string`) {
630 return schemaComposer.getIFTC(iface).getType();
631 } else {
632 return iface;
633 }
634 });
635 } else {
636 return [];
637 }
638 }
639 }), schemaComposer);
640 }
641
642 case GatsbyGraphQLTypeKind.INPUT_OBJECT:
643 {
644 return InputTypeComposer.createTemp(type.config, schemaComposer);
645 }
646
647 case GatsbyGraphQLTypeKind.UNION:
648 {
649 return UnionTypeComposer.createTemp(Object.assign({}, type.config, {
650 types: () => {
651 if (type.config.types) {
652 return type.config.types.map(typeName => schemaComposer.getOTC(typeName).getType());
653 } else {
654 return [];
655 }
656 }
657 }), schemaComposer);
658 }
659
660 case GatsbyGraphQLTypeKind.INTERFACE:
661 {
662 return InterfaceTypeComposer.createTemp(type.config, schemaComposer);
663 }
664
665 case GatsbyGraphQLTypeKind.ENUM:
666 {
667 return EnumTypeComposer.createTemp(type.config, schemaComposer);
668 }
669
670 case GatsbyGraphQLTypeKind.SCALAR:
671 {
672 return ScalarTypeComposer.createTemp(type.config, schemaComposer);
673 }
674
675 default:
676 {
677 report.warn(`Illegal type definition: ${JSON.stringify(type.config)}`);
678 return null;
679 }
680 }
681};
682
683const addSetFieldsOnGraphQLNodeTypeFields = ({
684 schemaComposer,
685 nodeStore,
686 parentSpan
687}) => Promise.all(Array.from(schemaComposer.values()).map(async tc => {
688 if (tc instanceof ObjectTypeComposer && tc.hasInterface(`Node`)) {
689 const typeName = tc.getTypeName();
690 const result = await apiRunner(`setFieldsOnGraphQLNodeType`, {
691 type: {
692 name: typeName,
693 nodes: nodeStore.getNodesByType(typeName)
694 },
695 traceId: `initial-setFieldsOnGraphQLNodeType`,
696 parentSpan
697 });
698
699 if (result) {
700 // NOTE: `setFieldsOnGraphQLNodeType` only allows setting
701 // nested fields with a path as property name, i.e.
702 // `{ 'frontmatter.published': 'Boolean' }`, but not in the form
703 // `{ frontmatter: { published: 'Boolean' }}`
704 result.forEach(fields => tc.addNestedFields(fields));
705 }
706 }
707}));
708
709const addThirdPartySchemas = ({
710 schemaComposer,
711 thirdPartySchemas,
712 parentSpan
713}) => {
714 thirdPartySchemas.forEach(schema => {
715 const schemaQueryType = schema.getQueryType();
716 const queryTC = schemaComposer.createTempTC(schemaQueryType);
717 processThirdPartyTypeFields({
718 typeComposer: queryTC,
719 schemaQueryType
720 });
721 schemaComposer.Query.addFields(queryTC.getFields()); // Explicitly add the third-party schema's types, so they can be targeted
722 // in `createResolvers` API.
723
724 const types = schema.getTypeMap();
725 Object.keys(types).forEach(typeName => {
726 const type = types[typeName];
727
728 if (type !== schemaQueryType && !isSpecifiedScalarType(type) && !isIntrospectionType(type) && type.name !== `Date` && type.name !== `JSON`) {
729 const typeComposer = schemaComposer.createTC(type);
730
731 if (typeComposer instanceof ObjectTypeComposer || typeComposer instanceof InterfaceTypeComposer) {
732 processThirdPartyTypeFields({
733 typeComposer,
734 schemaQueryType
735 });
736 }
737
738 typeComposer.setExtension(`createdFrom`, `thirdPartySchema`);
739 schemaComposer.addSchemaMustHaveType(typeComposer);
740 }
741 });
742 });
743};
744
745const resetOverriddenThirdPartyTypeFields = ({
746 typeComposer
747}) => {
748 // The problem: createResolvers API mutates third party schema instance.
749 // For example it can add a new field referencing a type from our main schema
750 // Then if we rebuild the schema this old type instance will sneak into
751 // the new schema and produce the famous error:
752 // "Schema must contain uniquely named types but contains multiple types named X"
753 // This function only affects schema rebuilding pathway.
754 // It cleans up artifacts created by the `createResolvers` API of the previous build
755 // so that we return the third party schema to its initial state (hence can safely re-add)
756 // TODO: the right way to fix this would be not to mutate the third party schema in
757 // the first place. But unfortunately mutation happens in the `graphql-compose`
758 // and we don't have an easy way to avoid it without major rework
759 typeComposer.getFieldNames().forEach(fieldName => {
760 const createdFrom = typeComposer.getFieldExtension(fieldName, `createdFrom`);
761
762 if (createdFrom === `createResolvers`) {
763 typeComposer.removeField(fieldName);
764 return;
765 }
766
767 const config = typeComposer.getFieldExtension(fieldName, `originalFieldConfig`);
768
769 if (config) {
770 typeComposer.removeField(fieldName);
771 typeComposer.addFields({
772 [fieldName]: config
773 });
774 }
775 });
776};
777
778const processThirdPartyTypeFields = ({
779 typeComposer,
780 schemaQueryType
781}) => {
782 resetOverriddenThirdPartyTypeFields({
783 typeComposer
784 }); // Fix for types that refer to Query. Thanks Relay Classic!
785
786 typeComposer.getFieldNames().forEach(fieldName => {
787 // Remove customization that we could have added via `createResolvers`
788 // to make it work with schema rebuilding
789 const field = typeComposer.getField(fieldName);
790 const fieldType = field.type.toString();
791
792 if (fieldType.replace(/[[\]!]/g, ``) === schemaQueryType.name) {
793 typeComposer.extendField(fieldName, {
794 type: fieldType.replace(schemaQueryType.name, `Query`)
795 });
796 }
797 });
798};
799
800const addCustomResolveFunctions = async ({
801 schemaComposer,
802 parentSpan
803}) => {
804 const intermediateSchema = schemaComposer.buildSchema();
805
806 const createResolvers = resolvers => {
807 Object.keys(resolvers).forEach(typeName => {
808 const fields = resolvers[typeName];
809
810 if (schemaComposer.has(typeName)) {
811 const tc = schemaComposer.getOTC(typeName);
812 Object.keys(fields).forEach(fieldName => {
813 const fieldConfig = fields[fieldName];
814
815 if (tc.hasField(fieldName)) {
816 const originalFieldConfig = tc.getFieldConfig(fieldName);
817 const originalTypeName = originalFieldConfig.type.toString();
818 const originalResolver = originalFieldConfig.resolve;
819 let fieldTypeName;
820
821 if (fieldConfig.type) {
822 fieldTypeName = Array.isArray(fieldConfig.type) ? stringifyArray(fieldConfig.type) : fieldConfig.type.toString();
823 }
824
825 if (!fieldTypeName || fieldTypeName.replace(/!/g, ``) === originalTypeName.replace(/!/g, ``) || tc.getExtension(`createdFrom`) === `thirdPartySchema`) {
826 const newConfig = {};
827
828 if (fieldConfig.type) {
829 newConfig.type = fieldConfig.type;
830 }
831
832 if (fieldConfig.args) {
833 newConfig.args = fieldConfig.args;
834 }
835
836 if (fieldConfig.resolve) {
837 newConfig.resolve = (source, args, context, info) => fieldConfig.resolve(source, args, context, Object.assign({}, info, {
838 originalResolver: originalResolver || context.defaultFieldResolver
839 }));
840
841 tc.extendFieldExtensions(fieldName, {
842 needsResolve: true
843 });
844 }
845
846 tc.extendField(fieldName, newConfig); // See resetOverriddenThirdPartyTypeFields for explanation
847
848 if (tc.getExtension(`createdFrom`) === `thirdPartySchema`) {
849 tc.setFieldExtension(fieldName, `originalFieldConfig`, originalFieldConfig);
850 }
851 } else if (fieldTypeName) {
852 report.warn(`\`createResolvers\` passed resolvers for field ` + `\`${typeName}.${fieldName}\` with type \`${fieldTypeName}\`. ` + `Such a field with type \`${originalTypeName}\` already exists ` + `on the type. Use \`createTypes\` to override type fields.`);
853 }
854 } else {
855 tc.addFields({
856 [fieldName]: fieldConfig
857 }); // See resetOverriddenThirdPartyTypeFields for explanation
858
859 tc.setFieldExtension(fieldName, `createdFrom`, `createResolvers`);
860 }
861 });
862 } else {
863 report.warn(`\`createResolvers\` passed resolvers for type \`${typeName}\` that ` + `doesn't exist in the schema. Use \`createTypes\` to add the type ` + `before adding resolvers.`);
864 }
865 });
866 };
867
868 await apiRunner(`createResolvers`, {
869 intermediateSchema,
870 createResolvers,
871 traceId: `initial-createResolvers`,
872 parentSpan
873 });
874};
875
876const determineSearchableFields = ({
877 schemaComposer,
878 typeComposer
879}) => {
880 typeComposer.getFieldNames().forEach(fieldName => {
881 const field = typeComposer.getField(fieldName);
882 const extensions = typeComposer.getFieldExtensions(fieldName);
883
884 if (field.resolve) {
885 if (extensions.dateformat) {
886 typeComposer.extendFieldExtensions(fieldName, {
887 searchable: SEARCHABLE_ENUM.SEARCHABLE,
888 sortable: SORTABLE_ENUM.SORTABLE,
889 needsResolve: extensions.proxy ? true : false
890 });
891 } else if (!_.isEmpty(field.args)) {
892 typeComposer.extendFieldExtensions(fieldName, {
893 searchable: SEARCHABLE_ENUM.DEPRECATED_SEARCHABLE,
894 sortable: SORTABLE_ENUM.DEPRECATED_SORTABLE,
895 needsResolve: true
896 });
897 } else {
898 typeComposer.extendFieldExtensions(fieldName, {
899 searchable: SEARCHABLE_ENUM.SEARCHABLE,
900 sortable: SORTABLE_ENUM.SORTABLE,
901 needsResolve: true
902 });
903 }
904 } else {
905 typeComposer.extendFieldExtensions(fieldName, {
906 searchable: SEARCHABLE_ENUM.SEARCHABLE,
907 sortable: SORTABLE_ENUM.SORTABLE,
908 needsResolve: false
909 });
910 }
911 });
912};
913
914const addConvenienceChildrenFields = ({
915 schemaComposer
916}) => {
917 const parentTypesToChildren = new Map();
918 const mimeTypesToChildren = new Map();
919 const typesHandlingMimeTypes = new Map();
920 schemaComposer.forEach(type => {
921 if ((type instanceof ObjectTypeComposer || type instanceof InterfaceTypeComposer) && type.hasExtension(`mimeTypes`)) {
922 const {
923 types
924 } = type.getExtension(`mimeTypes`);
925 new Set(types).forEach(mimeType => {
926 if (!typesHandlingMimeTypes.has(mimeType)) {
927 typesHandlingMimeTypes.set(mimeType, new Set());
928 }
929
930 typesHandlingMimeTypes.get(mimeType).add(type);
931 });
932 }
933
934 if ((type instanceof ObjectTypeComposer || type instanceof InterfaceTypeComposer) && type.hasExtension(`childOf`)) {
935 if (type instanceof ObjectTypeComposer && !type.hasInterface(`Node`)) {
936 report.error(`The \`childOf\` extension can only be used on types that implement the \`Node\` interface.\n` + `Check the type definition of \`${type.getTypeName()}\`.`);
937 return;
938 }
939
940 if (type instanceof InterfaceTypeComposer && !type.hasExtension(`nodeInterface`)) {
941 report.error(`The \`childOf\` extension can only be used on interface types that ` + `have the \`@nodeInterface\` extension.\n` + `Check the type definition of \`${type.getTypeName()}\`.`);
942 return;
943 }
944
945 const {
946 types,
947 mimeTypes,
948 many
949 } = type.getExtension(`childOf`);
950 new Set(types).forEach(parentType => {
951 if (!parentTypesToChildren.has(parentType)) {
952 parentTypesToChildren.set(parentType, new Map());
953 }
954
955 parentTypesToChildren.get(parentType).set(type, many);
956 });
957 new Set(mimeTypes).forEach(mimeType => {
958 if (!mimeTypesToChildren.has(mimeType)) {
959 mimeTypesToChildren.set(mimeType, new Map());
960 }
961
962 mimeTypesToChildren.get(mimeType).set(type, many);
963 });
964 }
965 });
966 parentTypesToChildren.forEach((children, parent) => {
967 if (!schemaComposer.has(parent)) return;
968 const typeComposer = schemaComposer.getAnyTC(parent);
969
970 if (typeComposer instanceof InterfaceTypeComposer && !typeComposer.hasExtension(`nodeInterface`)) {
971 report.error(`With the \`childOf\` extension, children fields can only be added to ` + `interfaces which have the \`@nodeInterface\` extension.\n` + `Check the type definition of \`${typeComposer.getTypeName()}\`.`);
972 return;
973 }
974
975 children.forEach((many, child) => {
976 if (many) {
977 typeComposer.addFields(createChildrenField(child.getTypeName()));
978 } else {
979 typeComposer.addFields(createChildField(child.getTypeName()));
980 }
981 });
982 });
983 mimeTypesToChildren.forEach((children, mimeType) => {
984 const parentTypes = typesHandlingMimeTypes.get(mimeType);
985
986 if (parentTypes) {
987 parentTypes.forEach(typeComposer => {
988 if (typeComposer instanceof InterfaceTypeComposer && !typeComposer.hasExtension(`nodeInterface`)) {
989 report.error(`With the \`childOf\` extension, children fields can only be added to ` + `interfaces which have the \`@nodeInterface\` extension.\n` + `Check the type definition of \`${typeComposer.getTypeName()}\`.`);
990 return;
991 }
992
993 children.forEach((many, child) => {
994 if (many) {
995 typeComposer.addFields(createChildrenField(child.getTypeName()));
996 } else {
997 typeComposer.addFields(createChildField(child.getTypeName()));
998 }
999 });
1000 });
1001 }
1002 });
1003};
1004
1005const addImplicitConvenienceChildrenFields = ({
1006 schemaComposer,
1007 typeComposer,
1008 nodeStore
1009}) => {
1010 const shouldInfer = typeComposer.getExtension(`infer`); // In Gatsby v3, when `@dontInfer` is set, children fields will not be
1011 // created for parent-child relations set by plugins with
1012 // `createParentChildLink`. With `@dontInfer`, only parent-child
1013 // relations explicitly set with the `childOf` extension will be added.
1014 // if (shouldInfer === false) return
1015
1016 const parentTypeName = typeComposer.getTypeName();
1017 const nodes = nodeStore.getNodesByType(parentTypeName);
1018 const childNodesByType = groupChildNodesByType({
1019 nodeStore,
1020 nodes
1021 });
1022 Object.keys(childNodesByType).forEach(typeName => {
1023 const typeChildren = childNodesByType[typeName];
1024
1025 const maxChildCount = _.maxBy(_.values(_.groupBy(typeChildren, c => c.parent)), g => g.length).length; // Adding children fields to types with the `@dontInfer` extension is deprecated
1026
1027
1028 if (shouldInfer === false) {
1029 const childTypeComposer = schemaComposer.getAnyTC(typeName);
1030 const childOfExtension = childTypeComposer.getExtension(`childOf`);
1031 const many = maxChildCount > 1; // Only warn when the parent-child relation has not been explicitly set with
1032
1033 if (!childOfExtension || !childOfExtension.types.includes(parentTypeName) || !childOfExtension.many === many) {
1034 const fieldName = many ? fieldNames.convenienceChildren(typeName) : fieldNames.convenienceChild(typeName);
1035 report.warn(`The type \`${parentTypeName}\` does not explicitly define ` + `the field \`${fieldName}\`.\n` + `On types with the \`@dontInfer\` directive, or with the \`infer\` ` + `extension set to \`false\`, automatically adding fields for ` + `children types is deprecated.\n` + `In Gatsby v3, only children fields explicitly set with the ` + `\`childOf\` extension will be added.\n`);
1036 }
1037 }
1038
1039 if (maxChildCount > 1) {
1040 typeComposer.addFields(createChildrenField(typeName));
1041 } else {
1042 typeComposer.addFields(createChildField(typeName));
1043 }
1044 });
1045};
1046
1047const createChildrenField = typeName => {
1048 return {
1049 [fieldNames.convenienceChildren(typeName)]: {
1050 type: () => [typeName],
1051
1052 resolve(source, args, context) {
1053 const {
1054 path
1055 } = context;
1056 return context.nodeModel.getNodesByIds({
1057 ids: source.children,
1058 type: typeName
1059 }, {
1060 path
1061 });
1062 }
1063
1064 }
1065 };
1066};
1067
1068const createChildField = typeName => {
1069 return {
1070 [fieldNames.convenienceChild(typeName)]: {
1071 type: () => typeName,
1072
1073 async resolve(source, args, context) {
1074 const {
1075 path
1076 } = context;
1077 const result = await context.nodeModel.getNodesByIds({
1078 ids: source.children,
1079 type: typeName
1080 }, {
1081 path
1082 });
1083
1084 if (result && result.length > 0) {
1085 return result[0];
1086 } else {
1087 return null;
1088 }
1089 }
1090
1091 }
1092 };
1093};
1094
1095const groupChildNodesByType = ({
1096 nodeStore,
1097 nodes
1098}) => _(nodes).flatMap(node => (node.children || []).map(nodeStore.getNode).filter(Boolean)).groupBy(node => node.internal ? node.internal.type : undefined).value();
1099
1100const addTypeToRootQuery = ({
1101 schemaComposer,
1102 typeComposer
1103}) => {
1104 const sortInputTC = getSortInput({
1105 schemaComposer,
1106 typeComposer
1107 });
1108 const filterInputTC = getFilterInput({
1109 schemaComposer,
1110 typeComposer
1111 });
1112 const paginationTC = getPagination({
1113 schemaComposer,
1114 typeComposer
1115 });
1116 const typeName = typeComposer.getTypeName(); // not strictly correctly, result is `npmPackage` and `allNpmPackage` from type `NPMPackage`
1117
1118 const queryName = fieldNames.query(typeName);
1119 const queryNamePlural = fieldNames.queryAll(typeName);
1120 schemaComposer.Query.addFields({
1121 [queryName]: {
1122 type: typeComposer,
1123 args: Object.assign({}, filterInputTC.getFields()),
1124 resolve: findOne(typeName)
1125 },
1126 [queryNamePlural]: {
1127 type: paginationTC,
1128 args: {
1129 filter: filterInputTC,
1130 sort: sortInputTC,
1131 skip: `Int`,
1132 limit: `Int`
1133 },
1134 resolve: findManyPaginated(typeName)
1135 }
1136 }).makeFieldNonNull(queryNamePlural);
1137};
1138
1139const parseTypes = ({
1140 doc,
1141 plugin,
1142 createdFrom,
1143 schemaComposer,
1144 parentSpan
1145}) => {
1146 const types = [];
1147 doc.definitions.forEach(def => {
1148 const name = def.name.value;
1149 checkIsAllowedTypeName(name);
1150
1151 if (schemaComposer.has(name)) {
1152 // We don't check if ast.kind matches composer type, but rely
1153 // that this will throw when something is wrong and get
1154 // reported by `reportParsingError`.
1155 // Keep the original type composer around
1156 const typeComposer = schemaComposer.get(name); // After this, the parsed type composer will be registered as the composer
1157 // handling the type name
1158
1159 const parsedType = schemaComposer.typeMapper.makeSchemaDef(def); // Merge the parsed type with the original
1160
1161 mergeTypes({
1162 schemaComposer,
1163 typeComposer,
1164 type: parsedType,
1165 plugin,
1166 createdFrom,
1167 parentSpan
1168 }); // Set the original type composer (with the merged fields added)
1169 // as the correct composer for the type name
1170
1171 schemaComposer.typeMapper.set(typeComposer.getTypeName(), typeComposer);
1172 } else {
1173 const parsedType = schemaComposer.typeMapper.makeSchemaDef(def);
1174 types.push(parsedType);
1175 }
1176 });
1177 return types;
1178};
1179
1180const stringifyArray = arr => `[${arr.map(item => Array.isArray(item) ? stringifyArray(item) : item.toString())}]`; // TODO: Import this directly from graphql-compose once we update to v7
1181
1182
1183const isNamedTypeComposer = type => type instanceof ObjectTypeComposer || type instanceof InputTypeComposer || type instanceof ScalarTypeComposer || type instanceof EnumTypeComposer || type instanceof InterfaceTypeComposer || type instanceof UnionTypeComposer;
1184
1185const validate = (type, value) => {
1186 if (type instanceof GraphQLNonNull) {
1187 if (value == null) {
1188 throw new Error(`Expected non-null field value.`);
1189 }
1190
1191 return validate(type.ofType, value);
1192 } else if (type instanceof GraphQLList) {
1193 if (!Array.isArray(value)) {
1194 throw new Error(`Expected array field value.`);
1195 }
1196
1197 return value.map(v => validate(type.ofType, v));
1198 } else {
1199 return type.parseValue(value);
1200 }
1201};
1202
1203const checkQueryableInterfaces = ({
1204 schemaComposer
1205}) => {
1206 const queryableInterfaces = new Set();
1207 schemaComposer.forEach(type => {
1208 if (type instanceof InterfaceTypeComposer && type.getExtension(`nodeInterface`)) {
1209 queryableInterfaces.add(type.getTypeName());
1210 }
1211 });
1212 const incorrectTypes = [];
1213 schemaComposer.forEach(type => {
1214 if (type instanceof ObjectTypeComposer) {
1215 const interfaces = type.getInterfaces();
1216
1217 if (interfaces.some(iface => queryableInterfaces.has(iface.name)) && !type.hasInterface(`Node`)) {
1218 incorrectTypes.push(type.getTypeName());
1219 }
1220 }
1221 });
1222
1223 if (incorrectTypes.length) {
1224 report.panic(`Interfaces with the \`nodeInterface\` extension must only be ` + `implemented by types which also implement the \`Node\` ` + `interface. Check the type definition of ` + `${incorrectTypes.map(t => `\`${t}\``).join(`, `)}.`);
1225 }
1226};
1227
1228const mergeFields = ({
1229 typeComposer,
1230 fields
1231}) => Object.entries(fields).forEach(([fieldName, fieldConfig]) => {
1232 if (typeComposer.hasField(fieldName)) {
1233 typeComposer.extendField(fieldName, fieldConfig);
1234 } else {
1235 typeComposer.setField(fieldName, fieldConfig);
1236 }
1237});
1238//# sourceMappingURL=schema.js.map
\No newline at end of file