UNPKG

51.6 kBPlain TextView Raw
1import {
2 GraphQLBoolean,
3 GraphQLEnumType,
4 GraphQLEnumValueConfigMap,
5 GraphQLFieldConfig,
6 GraphQLFieldConfigArgumentMap,
7 GraphQLFieldConfigMap,
8 GraphQLFieldResolver,
9 GraphQLFloat,
10 GraphQLID,
11 GraphQLInputFieldConfig,
12 GraphQLInputFieldConfigMap,
13 GraphQLInputObjectType,
14 GraphQLInputType,
15 GraphQLInt,
16 GraphQLInterfaceType,
17 GraphQLList,
18 GraphQLNamedType,
19 GraphQLNonNull,
20 GraphQLObjectType,
21 GraphQLOutputType,
22 GraphQLScalarType,
23 GraphQLSchema,
24 GraphQLString,
25 GraphQLUnionType,
26 isInputObjectType,
27 isInterfaceType,
28 isLeafType,
29 isNamedType,
30 isObjectType,
31 isOutputType,
32 isScalarType,
33 assertValidName,
34 getNamedType,
35 GraphQLField,
36 defaultFieldResolver,
37 isSchema,
38 GraphQLType,
39 isWrappingType,
40 isUnionType,
41 printSchema,
42} from "graphql";
43import {
44 NexusArgConfig,
45 NexusArgDef,
46 ArgsRecord,
47 arg,
48} from "./definitions/args";
49import {
50 InputDefinitionBlock,
51 NexusInputFieldDef,
52 NexusOutputFieldDef,
53 OutputDefinitionBlock,
54} from "./definitions/definitionBlocks";
55import { EnumTypeConfig } from "./definitions/enumType";
56import {
57 NexusExtendTypeConfig,
58 NexusExtendTypeDef,
59} from "./definitions/extendType";
60import { NexusInputObjectTypeConfig } from "./definitions/inputObjectType";
61import {
62 InterfaceDefinitionBlock,
63 NexusInterfaceTypeConfig,
64 NexusInterfaceTypeDef,
65} from "./definitions/interfaceType";
66import {
67 Implemented,
68 NexusObjectTypeConfig,
69 NexusObjectTypeDef,
70 ObjectDefinitionBlock,
71} from "./definitions/objectType";
72import {
73 NexusScalarTypeConfig,
74 NexusScalarExtensions,
75} from "./definitions/scalarType";
76import {
77 NexusUnionTypeConfig,
78 UnionDefinitionBlock,
79 UnionMembers,
80} from "./definitions/unionType";
81import {
82 AllNexusInputTypeDefs,
83 AllNexusNamedTypeDefs,
84 isNexusEnumTypeDef,
85 isNexusExtendTypeDef,
86 isNexusInputObjectTypeDef,
87 isNexusInterfaceTypeDef,
88 isNexusNamedTypeDef,
89 isNexusObjectTypeDef,
90 isNexusScalarTypeDef,
91 isNexusUnionTypeDef,
92 isNexusExtendInputTypeDef,
93 AllNexusOutputTypeDefs,
94 isNexusDynamicInputMethod,
95 isNexusDynamicOutputMethod,
96 isNexusArgDef,
97 isNexusDynamicOutputProperty,
98 isNexusPlugin,
99} from "./definitions/wrapping";
100import {
101 GraphQLPossibleInputs,
102 GraphQLPossibleOutputs,
103 NonNullConfig,
104 RootTypings,
105 MissingType,
106 NexusGraphQLFieldConfig,
107 NexusGraphQLInterfaceTypeConfig,
108 NexusGraphQLObjectTypeConfig,
109 NexusGraphQLSchema,
110 NexusGraphQLInputObjectTypeConfig,
111} from "./definitions/_types";
112import { TypegenAutoConfigOptions } from "./typegenAutoConfig";
113import { TypegenFormatFn } from "./typegenFormatPrettier";
114import { TypegenMetadata } from "./typegenMetadata";
115import {
116 AbstractTypeResolver,
117 GetGen,
118 AllInputTypes,
119} from "./typegenTypeHelpers";
120import {
121 firstDefined,
122 objValues,
123 isObject,
124 eachObj,
125 resolveTypegenConfig,
126 assertNoMissingTypes,
127 consoleWarn,
128 validateOnInstallHookResult,
129 mapValues,
130 UNKNOWN_TYPE_SCALAR,
131} from "./utils";
132import {
133 NexusExtendInputTypeDef,
134 NexusExtendInputTypeConfig,
135} from "./definitions/extendInputType";
136import { DynamicInputMethodDef, DynamicOutputMethodDef } from "./dynamicMethod";
137import { DynamicOutputPropertyDef } from "./dynamicProperty";
138import {
139 NexusPlugin,
140 composeMiddlewareFns,
141 PluginConfig,
142 CreateFieldResolverInfo,
143 MiddlewareFn,
144} from "./plugin";
145import {
146 NexusFieldExtension,
147 NexusInterfaceTypeExtension,
148 NexusSchemaExtension,
149 NexusObjectTypeExtension,
150 NexusInputObjectTypeExtension,
151} from "./extensions";
152import { fieldAuthorizePlugin } from "./plugins/fieldAuthorizePlugin";
153
154type NexusShapedOutput = {
155 name: string;
156 definition: (t: ObjectDefinitionBlock<string>) => void;
157};
158
159type NexusShapedInput = {
160 name: string;
161 definition: (t: InputDefinitionBlock<string>) => void;
162};
163
164const SCALARS: Record<string, GraphQLScalarType> = {
165 String: GraphQLString,
166 Int: GraphQLInt,
167 Float: GraphQLFloat,
168 ID: GraphQLID,
169 Boolean: GraphQLBoolean,
170};
171
172export interface BuilderConfig {
173 /**
174 * Generated artifact settings. Set to false to disable all.
175 * Set to true to enable all and use default paths. Leave
176 * undefined for default behaviour of each artifact.
177 */
178 outputs?:
179 | boolean
180 | {
181 /**
182 * TypeScript declaration file generation settings. This file
183 * contains types reflected off your source code. It is how
184 * Nexus imbues dynamic code with static guarnatees.
185 *
186 * Defaults to being enabled when `process.env.NODE_ENV !== "production"`.
187 * Set to true to enable and emit into default path (see below).
188 * Set to false to disable. Set to a string to specify absolute path.
189 *
190 * The default path is node_modules/@types/nexus-typegen/index.d.ts.
191 * This is chosen becuase TypeScript will pick it up without
192 * any configuration needed by you. For more details about the @types
193 * system refer to https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#types-typeroots-and-types
194 */
195 typegen?: boolean | string;
196 /**
197 * GraphQL SDL generation settings. This file is not necessary but
198 * may be nice for teams wishing to review SDL in pull-requests or
199 * just generally transitioning from a schema-first workflow.
200 *
201 * Defaults to false (disabled). Set to true to enable and emit into
202 * default path (current working directory). Set to a string to specify
203 * absolute path.
204 */
205 schema?: boolean | string;
206 };
207 /**
208 * Whether the schema & types are generated when the server
209 * starts. Default is !process.env.NODE_ENV || process.env.NODE_ENV === "development"
210 */
211 shouldGenerateArtifacts?: boolean;
212 /**
213 * Automatically configure type resolution for the TypeScript
214 * representations of the associated types.
215 *
216 * Alias for typegenConfig: typegenAutoConfig(options)
217 */
218 typegenAutoConfig?: TypegenAutoConfigOptions;
219 /**
220 * A configuration function for advanced cases where
221 * more control over the `TypegenInfo` is needed.
222 */
223 typegenConfig?: (
224 schema: GraphQLSchema,
225 outputPath: string
226 ) => TypegenInfo | PromiseLike<TypegenInfo>;
227 /**
228 * Either an absolute path to a .prettierrc file, or an object
229 * with relevant Prettier rules to be used on the generated output
230 */
231 prettierConfig?: string | object;
232 /**
233 * Manually apply a formatter to the generated content before saving,
234 * see the `prettierConfig` option if you want to use Prettier.
235 */
236 formatTypegen?: TypegenFormatFn;
237 /**
238 * Configures the default "nonNullDefaults" for the entire schema the type.
239 * Read more about how nexus handles nullability
240 */
241 nonNullDefaults?: NonNullConfig;
242 /**
243 * List of plugins to apply to Nexus, with before/after hooks
244 * executed first to last: before -> resolve -> after
245 */
246 plugins?: NexusPlugin[];
247 /**
248 * Provide if you wish to customize the behavior of the schema printing.
249 * Otherwise, uses `printSchema` from graphql-js
250 */
251 customPrintSchemaFn?: typeof printSchema;
252}
253
254export type SchemaConfig = BuilderConfig & {
255 /**
256 * All of the GraphQL types. This is an any for simplicity of developer experience,
257 * if it's an object we get the values, if it's an array we flatten out the
258 * valid types, ignoring invalid ones.
259 */
260 types: any;
261 /**
262 * Whether we should process.exit after the artifacts are generated.
263 * Useful if you wish to explicitly generate the test artifacts at a certain stage in
264 * a startup or build process.
265 * @default false
266 */
267 shouldExitAfterGenerateArtifacts?: boolean;
268} & NexusGenPluginSchemaConfig;
269
270export interface TypegenInfo {
271 /**
272 * Headers attached to the generate type output
273 */
274 headers: string[];
275 /**
276 * All imports for the backing types / context
277 */
278 imports: string[];
279 /**
280 * A map of all GraphQL types and what TypeScript types they should
281 * be represented by.
282 */
283 backingTypeMap: { [K in GetGen<"objectNames">]?: string };
284 /**
285 * The type of the context for the resolvers
286 */
287 contextType?: string;
288}
289
290export type TypeToWalk =
291 | { type: "named"; value: GraphQLNamedType }
292 | { type: "input"; value: NexusShapedInput }
293 | { type: "object"; value: NexusShapedOutput }
294 | { type: "interface"; value: NexusInterfaceTypeConfig<any> };
295
296export type DynamicInputFields = Record<
297 string,
298 DynamicInputMethodDef<string> | string
299>;
300
301export type DynamicOutputFields = Record<
302 string,
303 DynamicOutputMethodDef<string> | string
304>;
305
306export type DynamicOutputProperties = Record<
307 string,
308 DynamicOutputPropertyDef<string>
309>;
310
311export type TypeDef =
312 | GraphQLNamedType
313 | AllNexusNamedTypeDefs
314 | NexusExtendInputTypeDef<string>
315 | NexusExtendTypeDef<string>;
316
317export type DynamicBlockDef =
318 | DynamicInputMethodDef<string>
319 | DynamicOutputMethodDef<string>
320 | DynamicOutputPropertyDef<string>;
321
322export type NexusAcceptedTypeDef = TypeDef | DynamicBlockDef;
323
324export type PluginBuilderLens = {
325 hasType: SchemaBuilder["hasType"];
326 addType: SchemaBuilder["addType"];
327 setConfigOption: SchemaBuilder["setConfigOption"];
328 hasConfigOption: SchemaBuilder["hasConfigOption"];
329 getConfigOption: SchemaBuilder["getConfigOption"];
330};
331
332/**
333 * Builds all of the types, properly accounts for any using "mix".
334 * Since the enum types are resolved synchronously, these need to guard for
335 * circular references at this step, while fields will guard for it during lazy evaluation.
336 */
337export class SchemaBuilder {
338 /**
339 * Used to check for circular references.
340 */
341 protected buildingTypes = new Set();
342 /**
343 * The "final type" map contains all types as they are built.
344 */
345 protected finalTypeMap: Record<string, GraphQLNamedType> = {};
346 /**
347 * The "defined type" map keeps track of all of the types that were
348 * defined directly as `GraphQL*Type` objects, so we don't accidentally
349 * overwrite any.
350 */
351 protected definedTypeMap: Record<string, GraphQLNamedType> = {};
352 /**
353 * The "pending type" map keeps track of all types that were defined w/
354 * GraphQL Nexus and haven't been processed into concrete types yet.
355 */
356 protected pendingTypeMap: Record<string, AllNexusNamedTypeDefs> = {};
357 /**
358 * All "extensions" to types (adding fields on types from many locations)
359 */
360 protected typeExtendMap: Record<
361 string,
362 NexusExtendTypeConfig<string>[] | null
363 > = {};
364 /**
365 * All "extensions" to input types (adding fields on types from many locations)
366 */
367 protected inputTypeExtendMap: Record<
368 string,
369 NexusExtendInputTypeConfig<string>[] | null
370 > = {};
371 /**
372 * Configures the root-level nonNullDefaults defaults
373 */
374 protected nonNullDefaults: NonNullConfig = {};
375 protected dynamicInputFields: DynamicInputFields = {};
376 protected dynamicOutputFields: DynamicOutputFields = {};
377 protected dynamicOutputProperties: DynamicOutputProperties = {};
378 protected plugins: NexusPlugin[] = [];
379
380 /**
381 * All types that need to be traversed for children types
382 */
383 protected typesToWalk: TypeToWalk[] = [];
384
385 /**
386 * Root type mapping information annotated on the type definitions
387 */
388 protected rootTypings: RootTypings = {};
389
390 /**
391 * Array of missing types
392 */
393 protected missingTypes: Record<string, MissingType> = {};
394
395 /**
396 * Methods we are able to access to read/modify builder state from plugins
397 */
398 protected builderLens: PluginBuilderLens;
399
400 /**
401 * Created just before types are walked, this keeps track of all of the resolvers
402 */
403 protected onMissingTypeFns: Exclude<
404 PluginConfig["onMissingType"],
405 undefined
406 >[] = [];
407
408 /**
409 * Executed just before types are walked
410 */
411 protected onBeforeBuildFns: Exclude<
412 PluginConfig["onBeforeBuild"],
413 undefined
414 >[] = [];
415
416 /**
417 * Executed as the field resolvers are included on the field
418 */
419 protected onCreateResolverFns: Exclude<
420 PluginConfig["onCreateFieldResolver"],
421 undefined
422 >[] = [];
423
424 /**
425 * Executed as the field "subscribe" fields are included on the schema
426 */
427 protected onCreateSubscribeFns: Exclude<
428 PluginConfig["onCreateFieldSubscribe"],
429 undefined
430 >[] = [];
431
432 /**
433 * Executed after the schema is constructed, for any final verification
434 */
435 protected onAfterBuildFns: Exclude<
436 PluginConfig["onAfterBuild"],
437 undefined
438 >[] = [];
439
440 /**
441 * The `schemaExtension` is created just after the types are walked,
442 * but before the fields are materialized.
443 */
444 protected _schemaExtension?: NexusSchemaExtension;
445
446 get schemaExtension() {
447 /* istanbul ignore next */
448 if (!this._schemaExtension) {
449 throw new Error("Cannot reference schemaExtension before it is created");
450 }
451 return this._schemaExtension;
452 }
453
454 constructor(protected config: BuilderConfig) {
455 this.nonNullDefaults = {
456 input: false,
457 output: true,
458 ...config.nonNullDefaults,
459 };
460 this.plugins = config.plugins || [fieldAuthorizePlugin()];
461 this.builderLens = Object.freeze({
462 hasType: this.hasType,
463 addType: this.addType,
464 setConfigOption: this.setConfigOption,
465 hasConfigOption: this.hasConfigOption,
466 getConfigOption: this.getConfigOption,
467 });
468 }
469
470 setConfigOption = <K extends keyof BuilderConfig>(
471 key: K,
472 value: BuilderConfig[K]
473 ) => {
474 this.config = {
475 ...this.config,
476 [key]: value,
477 };
478 };
479
480 hasConfigOption = (key: keyof BuilderConfig): boolean => {
481 return this.config.hasOwnProperty(key);
482 };
483
484 getConfigOption = <K extends keyof BuilderConfig>(
485 key: K
486 ): BuilderConfig[K] => {
487 return this.config[key];
488 };
489
490 hasType = (typeName: string): boolean => {
491 return Boolean(
492 this.pendingTypeMap[typeName] || this.finalTypeMap[typeName]
493 );
494 };
495
496 /**
497 * Add type takes a Nexus type, or a GraphQL type and pulls
498 * it into an internal "type registry". It also does an initial pass
499 * on any types that are referenced on the "types" field and pulls
500 * those in too, so you can define types anonymously, without
501 * exporting them.
502 */
503 addType = (typeDef: TypeDef | DynamicBlockDef) => {
504 if (isNexusDynamicInputMethod(typeDef)) {
505 this.dynamicInputFields[typeDef.name] = typeDef;
506 return;
507 }
508 if (isNexusDynamicOutputMethod(typeDef)) {
509 this.dynamicOutputFields[typeDef.name] = typeDef;
510 return;
511 }
512 if (isNexusDynamicOutputProperty(typeDef)) {
513 this.dynamicOutputProperties[typeDef.name] = typeDef;
514 return;
515 }
516
517 // Don't worry about internal types.
518 if (typeDef.name?.indexOf("__") === 0) {
519 return;
520 }
521
522 const existingType =
523 this.definedTypeMap[typeDef.name] || this.pendingTypeMap[typeDef.name];
524
525 if (isNexusExtendTypeDef(typeDef)) {
526 const typeExtensions = (this.typeExtendMap[typeDef.name] =
527 this.typeExtendMap[typeDef.name] || []);
528 typeExtensions.push(typeDef.value);
529 this.typesToWalk.push({ type: "object", value: typeDef.value });
530 return;
531 }
532
533 if (isNexusExtendInputTypeDef(typeDef)) {
534 const typeExtensions = (this.inputTypeExtendMap[typeDef.name] =
535 this.inputTypeExtendMap[typeDef.name] || []);
536 typeExtensions.push(typeDef.value);
537 this.typesToWalk.push({ type: "input", value: typeDef.value });
538 return;
539 }
540
541 if (existingType) {
542 // Allow importing the same exact type more than once.
543 if (existingType === typeDef) {
544 return;
545 }
546 throw extendError(typeDef.name);
547 }
548
549 if (isNexusScalarTypeDef(typeDef) && typeDef.value.asNexusMethod) {
550 this.dynamicInputFields[typeDef.value.asNexusMethod] = typeDef.name;
551 this.dynamicOutputFields[typeDef.value.asNexusMethod] = typeDef.name;
552 if (typeDef.value.rootTyping) {
553 this.rootTypings[typeDef.name] = typeDef.value.rootTyping;
554 }
555 } else if (isScalarType(typeDef)) {
556 const scalarDef = typeDef as GraphQLScalarType & {
557 extensions?: NexusScalarExtensions;
558 };
559 if (scalarDef.extensions && scalarDef.extensions.nexus) {
560 const { asNexusMethod, rootTyping } = scalarDef.extensions.nexus;
561 if (asNexusMethod) {
562 this.dynamicInputFields[asNexusMethod] = scalarDef.name;
563 this.dynamicOutputFields[asNexusMethod] = typeDef.name;
564 }
565 if (rootTyping) {
566 this.rootTypings[scalarDef.name] = rootTyping;
567 }
568 }
569 }
570
571 if (isNamedType(typeDef)) {
572 let finalTypeDef = typeDef;
573 if (isObjectType(typeDef)) {
574 const config = typeDef.toConfig();
575 finalTypeDef = new GraphQLObjectType({
576 ...config,
577 fields: () => this.rebuildNamedOutputFields(config),
578 interfaces: () =>
579 config.interfaces.map((t) => this.getInterface(t.name)),
580 });
581 } else if (isInterfaceType(typeDef)) {
582 const config = typeDef.toConfig();
583 finalTypeDef = new GraphQLInterfaceType({
584 ...config,
585 fields: () => this.rebuildNamedOutputFields(config),
586 });
587 } else if (isUnionType(typeDef)) {
588 const config = typeDef.toConfig();
589 finalTypeDef = new GraphQLUnionType({
590 ...config,
591 types: () => config.types.map((t) => this.getObjectType(t.name)),
592 });
593 }
594 this.finalTypeMap[typeDef.name] = finalTypeDef;
595 this.definedTypeMap[typeDef.name] = typeDef;
596 this.typesToWalk.push({ type: "named", value: typeDef });
597 } else {
598 this.pendingTypeMap[typeDef.name] = typeDef;
599 }
600 if (isNexusInputObjectTypeDef(typeDef)) {
601 this.typesToWalk.push({ type: "input", value: typeDef.value });
602 }
603 if (isNexusObjectTypeDef(typeDef)) {
604 this.typesToWalk.push({ type: "object", value: typeDef.value });
605 }
606 if (isNexusInterfaceTypeDef(typeDef)) {
607 this.typesToWalk.push({ type: "interface", value: typeDef.value });
608 }
609 };
610
611 addTypes(types: any) {
612 if (!types) {
613 return;
614 }
615 if (isSchema(types)) {
616 this.addTypes(types.getTypeMap());
617 }
618 if (isNexusPlugin(types)) {
619 if (!this.config.plugins?.includes(types)) {
620 throw new Error(
621 `Nexus plugin ${types.config.name} was seen in the "types" config, but should instead be provided to the "plugins" array.`
622 );
623 }
624 return;
625 }
626 if (
627 isNexusNamedTypeDef(types) ||
628 isNexusExtendTypeDef(types) ||
629 isNexusExtendInputTypeDef(types) ||
630 isNamedType(types) ||
631 isNexusDynamicInputMethod(types) ||
632 isNexusDynamicOutputMethod(types) ||
633 isNexusDynamicOutputProperty(types)
634 ) {
635 this.addType(types);
636 } else if (Array.isArray(types)) {
637 types.forEach((typeDef) => this.addTypes(typeDef));
638 } else if (isObject(types)) {
639 Object.keys(types).forEach((key) => this.addTypes(types[key]));
640 }
641 }
642
643 rebuildNamedOutputFields(
644 config: ReturnType<
645 GraphQLObjectType["toConfig"] | GraphQLInterfaceType["toConfig"]
646 >
647 ) {
648 const { fields, ...rest } = config;
649 const fieldsConfig = typeof fields === "function" ? fields() : fields;
650 return mapValues(fieldsConfig, (val, key) => {
651 const { resolve, type, ...fieldConfig } = val;
652 const finalType = this.replaceNamedType(type);
653 return {
654 ...fieldConfig,
655 type: finalType,
656 resolve: this.makeFinalResolver(
657 {
658 builder: this.builderLens,
659 fieldConfig: {
660 ...fieldConfig,
661 type: finalType,
662 name: key,
663 },
664 schemaConfig: this.config,
665 parentTypeConfig: rest,
666 schemaExtension: this.schemaExtension,
667 },
668 resolve
669 ),
670 };
671 });
672 }
673
674 walkTypes() {
675 let obj;
676 while ((obj = this.typesToWalk.shift())) {
677 switch (obj.type) {
678 case "input":
679 this.walkInputType(obj.value);
680 break;
681 case "interface":
682 this.walkInterfaceType(obj.value);
683 break;
684 case "named":
685 this.walkNamedTypes(obj.value);
686 break;
687 case "object":
688 this.walkOutputType(obj.value);
689 break;
690 }
691 }
692 }
693
694 beforeWalkTypes() {
695 this.plugins.forEach((obj, i) => {
696 if (!isNexusPlugin(obj)) {
697 throw new Error(`Expected a plugin in plugins[${i}], saw ${obj}`);
698 }
699 const { config: pluginConfig } = obj;
700 if (pluginConfig.onInstall) {
701 const installResult = pluginConfig.onInstall(this.builderLens);
702 validateOnInstallHookResult(pluginConfig.name, installResult);
703 installResult.types.forEach((t) => this.addType(t));
704 }
705 if (pluginConfig.onCreateFieldResolver) {
706 this.onCreateResolverFns.push(pluginConfig.onCreateFieldResolver);
707 }
708 if (pluginConfig.onCreateFieldSubscribe) {
709 this.onCreateSubscribeFns.push(pluginConfig.onCreateFieldSubscribe);
710 }
711 if (pluginConfig.onBeforeBuild) {
712 this.onBeforeBuildFns.push(pluginConfig.onBeforeBuild);
713 }
714 if (pluginConfig.onMissingType) {
715 this.onMissingTypeFns.push(pluginConfig.onMissingType);
716 }
717 if (pluginConfig.onAfterBuild) {
718 this.onAfterBuildFns.push(pluginConfig.onAfterBuild);
719 }
720 });
721 }
722
723 beforeBuildTypes() {
724 this.onBeforeBuildFns.forEach((fn) => {
725 fn(this.builderLens);
726 if (this.typesToWalk.length > 0) {
727 this.walkTypes();
728 }
729 });
730 }
731
732 buildNexusTypes() {
733 // If Query isn't defined, set it to null so it falls through to "missingType"
734 if (!this.pendingTypeMap.Query) {
735 this.pendingTypeMap.Query = null as any;
736 }
737 Object.keys(this.pendingTypeMap).forEach((key) => {
738 if (this.typesToWalk.length > 0) {
739 this.walkTypes();
740 }
741 // If we've already constructed the type by this point,
742 // via circular dependency resolution don't worry about building it.
743 if (this.finalTypeMap[key]) {
744 return;
745 }
746 if (this.definedTypeMap[key]) {
747 throw extendError(key);
748 }
749 this.finalTypeMap[key] = this.getOrBuildType(key);
750 this.buildingTypes.clear();
751 });
752 Object.keys(this.typeExtendMap).forEach((key) => {
753 // If we haven't defined the type, assume it's an object type
754 if (this.typeExtendMap[key] !== null) {
755 this.buildObjectType({
756 name: key,
757 definition() {},
758 });
759 }
760 });
761 Object.keys(this.inputTypeExtendMap).forEach((key) => {
762 // If we haven't defined the type, assume it's an input object type
763 if (this.inputTypeExtendMap[key] !== null) {
764 this.buildInputObjectType({
765 name: key,
766 definition() {},
767 });
768 }
769 });
770 }
771
772 createSchemaExtension() {
773 this._schemaExtension = new NexusSchemaExtension({
774 ...this.config,
775 dynamicFields: {
776 dynamicInputFields: this.dynamicInputFields,
777 dynamicOutputFields: this.dynamicOutputFields,
778 dynamicOutputProperties: this.dynamicOutputProperties,
779 },
780 rootTypings: this.rootTypings,
781 });
782 }
783
784 getFinalTypeMap(): BuildTypes<any> {
785 this.beforeWalkTypes();
786 this.createSchemaExtension();
787 this.walkTypes();
788 this.beforeBuildTypes();
789 this.buildNexusTypes();
790 return {
791 finalConfig: this.config,
792 typeMap: this.finalTypeMap,
793 schemaExtension: this.schemaExtension!,
794 missingTypes: this.missingTypes,
795 onAfterBuildFns: this.onAfterBuildFns,
796 };
797 }
798
799 buildInputObjectType(
800 config: NexusInputObjectTypeConfig<any>
801 ): GraphQLInputObjectType {
802 const fields: NexusInputFieldDef[] = [];
803 const definitionBlock = new InputDefinitionBlock({
804 typeName: config.name,
805 addField: (field) => fields.push(field),
806 addDynamicInputFields: (block, isList) =>
807 this.addDynamicInputFields(block, isList),
808 warn: consoleWarn,
809 });
810 config.definition(definitionBlock);
811 const extensions = this.inputTypeExtendMap[config.name];
812 if (extensions) {
813 extensions.forEach((extension) => {
814 extension.definition(definitionBlock);
815 });
816 }
817 this.inputTypeExtendMap[config.name] = null;
818 const inputObjectTypeConfig: NexusGraphQLInputObjectTypeConfig = {
819 name: config.name,
820 fields: () => this.buildInputObjectFields(fields, inputObjectTypeConfig),
821 description: config.description,
822 extensions: {
823 nexus: new NexusInputObjectTypeExtension(config),
824 },
825 };
826 return this.finalize(new GraphQLInputObjectType(inputObjectTypeConfig));
827 }
828
829 buildObjectType(config: NexusObjectTypeConfig<any>) {
830 const fields: NexusOutputFieldDef[] = [];
831 const interfaces: Implemented[] = [];
832 const definitionBlock = new ObjectDefinitionBlock({
833 typeName: config.name,
834 addField: (fieldDef) => fields.push(fieldDef),
835 addInterfaces: (interfaceDefs) => interfaces.push(...interfaceDefs),
836 addDynamicOutputMembers: (block, isList) =>
837 this.addDynamicOutputMembers(block, isList, "build"),
838 warn: consoleWarn,
839 });
840 config.definition(definitionBlock);
841 const extensions = this.typeExtendMap[config.name];
842 if (extensions) {
843 extensions.forEach((extension) => {
844 extension.definition(definitionBlock);
845 });
846 }
847 this.typeExtendMap[config.name] = null;
848 if (config.rootTyping) {
849 this.rootTypings[config.name] = config.rootTyping;
850 }
851 const objectTypeConfig: NexusGraphQLObjectTypeConfig = {
852 name: config.name,
853 interfaces: () => interfaces.map((i) => this.getInterface(i)),
854 description: config.description,
855 fields: () => {
856 const allInterfaces = interfaces.map((i) => this.getInterface(i));
857 const interfaceConfigs = allInterfaces.map((i) => i.toConfig());
858 const interfaceFieldsMap: GraphQLFieldConfigMap<any, any> = {};
859 interfaceConfigs.forEach((i) => {
860 Object.keys(i.fields).forEach((iFieldName) => {
861 interfaceFieldsMap[iFieldName] = i.fields[iFieldName];
862 });
863 });
864 return this.buildOutputFields(
865 fields,
866 objectTypeConfig,
867 interfaceFieldsMap
868 );
869 },
870 extensions: {
871 nexus: new NexusObjectTypeExtension(config),
872 },
873 };
874 return this.finalize(new GraphQLObjectType(objectTypeConfig));
875 }
876
877 buildInterfaceType(config: NexusInterfaceTypeConfig<any>) {
878 const { name, description } = config;
879 let resolveType: AbstractTypeResolver<string> | undefined;
880 const fields: NexusOutputFieldDef[] = [];
881 const definitionBlock = new InterfaceDefinitionBlock({
882 typeName: config.name,
883 addField: (field) => fields.push(field),
884 setResolveType: (fn) => (resolveType = fn),
885 addDynamicOutputMembers: (block, isList) =>
886 this.addDynamicOutputMembers(block, isList, "build"),
887 warn: consoleWarn,
888 });
889 config.definition(definitionBlock);
890 const toExtend = this.typeExtendMap[config.name];
891 if (toExtend) {
892 toExtend.forEach((e) => {
893 e.definition(definitionBlock);
894 });
895 }
896 if (!resolveType) {
897 resolveType = this.missingResolveType(config.name, "union");
898 }
899 if (config.rootTyping) {
900 this.rootTypings[config.name] = config.rootTyping;
901 }
902 const interfaceTypeConfig: NexusGraphQLInterfaceTypeConfig = {
903 name,
904 resolveType,
905 description,
906 fields: () => this.buildOutputFields(fields, interfaceTypeConfig, {}),
907 extensions: {
908 nexus: new NexusInterfaceTypeExtension(config),
909 },
910 };
911 return this.finalize(new GraphQLInterfaceType(interfaceTypeConfig));
912 }
913
914 buildEnumType(config: EnumTypeConfig<any>) {
915 const { members } = config;
916 const values: GraphQLEnumValueConfigMap = {};
917 if (Array.isArray(members)) {
918 members.forEach((m) => {
919 if (typeof m === "string") {
920 values[m] = { value: m };
921 } else {
922 values[m.name] = {
923 value: typeof m.value === "undefined" ? m.name : m.value,
924 deprecationReason: m.deprecation,
925 description: m.description,
926 };
927 }
928 });
929 } else {
930 Object.keys(members)
931 // members can potentially be a TypeScript enum.
932 // The compiled version of this enum will be the members object,
933 // numeric enums members also get a reverse mapping from enum values to enum names.
934 // In these cases we have to ensure we don't include these reverse mapping keys.
935 // See: https://www.typescriptlang.org/docs/handbook/enums.html
936 .filter((key) => isNaN(+key))
937 .forEach((key) => {
938 assertValidName(key);
939
940 values[key] = {
941 value: (members as Record<string, string | number | symbol>)[key],
942 };
943 });
944 }
945 if (!Object.keys(values).length) {
946 throw new Error(
947 `GraphQL Nexus: Enum ${config.name} must have at least one member`
948 );
949 }
950 if (config.rootTyping) {
951 this.rootTypings[config.name] = config.rootTyping;
952 }
953 return this.finalize(
954 new GraphQLEnumType({
955 name: config.name,
956 values: values,
957 description: config.description,
958 })
959 );
960 }
961
962 buildUnionType(config: NexusUnionTypeConfig<any>) {
963 let members: UnionMembers | undefined;
964 let resolveType: AbstractTypeResolver<string> | undefined;
965 config.definition(
966 new UnionDefinitionBlock({
967 setResolveType: (fn) => (resolveType = fn),
968 addUnionMembers: (unionMembers) => (members = unionMembers),
969 })
970 );
971 if (!resolveType) {
972 resolveType = this.missingResolveType(config.name, "union");
973 }
974 if (config.rootTyping) {
975 this.rootTypings[config.name] = config.rootTyping;
976 }
977 return this.finalize(
978 new GraphQLUnionType({
979 name: config.name,
980 resolveType,
981 description: config.description,
982 types: () => this.buildUnionMembers(config.name, members),
983 })
984 );
985 }
986
987 buildScalarType(config: NexusScalarTypeConfig<string>): GraphQLScalarType {
988 if (config.rootTyping) {
989 this.rootTypings[config.name] = config.rootTyping;
990 }
991 return this.finalize(new GraphQLScalarType(config));
992 }
993
994 protected finalize<T extends GraphQLNamedType>(type: T): T {
995 this.finalTypeMap[type.name] = type;
996 return type;
997 }
998
999 protected missingType(
1000 typeName: string,
1001 fromObject: boolean = false
1002 ): GraphQLNamedType {
1003 invariantGuard(typeName);
1004 if (this.onMissingTypeFns.length) {
1005 for (let i = 0; i < this.onMissingTypeFns.length; i++) {
1006 const fn = this.onMissingTypeFns[i];
1007 const replacementType = fn(typeName, this.builderLens);
1008 if (replacementType && replacementType.name) {
1009 this.addType(replacementType);
1010 return this.getOrBuildType(replacementType);
1011 }
1012 }
1013 }
1014 if (typeName === "Query") {
1015 return new GraphQLObjectType({
1016 name: "Query",
1017 fields: {
1018 ok: {
1019 type: GraphQLNonNull(GraphQLBoolean),
1020 resolve: () => true,
1021 },
1022 },
1023 });
1024 }
1025
1026 if (!this.missingTypes[typeName]) {
1027 this.missingTypes[typeName] = { fromObject };
1028 }
1029
1030 return UNKNOWN_TYPE_SCALAR;
1031 }
1032
1033 protected buildUnionMembers(
1034 unionName: string,
1035 members: UnionMembers | undefined
1036 ) {
1037 const unionMembers: GraphQLObjectType[] = [];
1038 /* istanbul ignore next */
1039 if (!members) {
1040 throw new Error(
1041 `Missing Union members for ${unionName}.` +
1042 `Make sure to call the t.members(...) method in the union blocks`
1043 );
1044 }
1045 members.forEach((member) => {
1046 unionMembers.push(this.getObjectType(member));
1047 });
1048 /* istanbul ignore next */
1049 if (!unionMembers.length) {
1050 throw new Error(
1051 `GraphQL Nexus: Union ${unionName} must have at least one member type`
1052 );
1053 }
1054 return unionMembers;
1055 }
1056
1057 protected buildOutputFields(
1058 fields: NexusOutputFieldDef[],
1059 typeConfig: NexusGraphQLInterfaceTypeConfig | NexusGraphQLObjectTypeConfig,
1060 intoObject: GraphQLFieldConfigMap<any, any>
1061 ) {
1062 fields.forEach((field) => {
1063 intoObject[field.name] = this.buildOutputField(field, typeConfig);
1064 });
1065 return intoObject;
1066 }
1067
1068 protected buildInputObjectFields(
1069 fields: NexusInputFieldDef[],
1070 typeConfig: NexusGraphQLInputObjectTypeConfig
1071 ): GraphQLInputFieldConfigMap {
1072 const fieldMap: GraphQLInputFieldConfigMap = {};
1073 fields.forEach((field) => {
1074 fieldMap[field.name] = this.buildInputObjectField(field, typeConfig);
1075 });
1076 return fieldMap;
1077 }
1078
1079 protected buildOutputField(
1080 fieldConfig: NexusOutputFieldDef,
1081 typeConfig: NexusGraphQLObjectTypeConfig | NexusGraphQLInterfaceTypeConfig
1082 ): GraphQLFieldConfig<any, any> {
1083 if (!fieldConfig.type) {
1084 /* istanbul ignore next */
1085 throw new Error(
1086 `Missing required "type" field for ${typeConfig.name}.${fieldConfig.name}`
1087 );
1088 }
1089 const fieldExtension = new NexusFieldExtension(fieldConfig);
1090 const builderFieldConfig: Omit<
1091 NexusGraphQLFieldConfig,
1092 "resolve" | "subscribe"
1093 > = {
1094 name: fieldConfig.name,
1095 type: this.decorateType(
1096 this.getOutputType(fieldConfig.type),
1097 fieldConfig.list,
1098 this.outputNonNull(typeConfig, fieldConfig)
1099 ),
1100 args: this.buildArgs(fieldConfig.args || {}, typeConfig),
1101 description: fieldConfig.description,
1102 deprecationReason: fieldConfig.deprecation,
1103 extensions: {
1104 nexus: fieldExtension,
1105 },
1106 };
1107 return {
1108 resolve: this.makeFinalResolver(
1109 {
1110 builder: this.builderLens,
1111 fieldConfig: builderFieldConfig,
1112 parentTypeConfig: typeConfig,
1113 schemaConfig: this.config,
1114 schemaExtension: this.schemaExtension,
1115 },
1116 fieldConfig.resolve
1117 ),
1118 subscribe: fieldConfig.subscribe,
1119 ...builderFieldConfig,
1120 };
1121 }
1122
1123 protected makeFinalResolver(
1124 info: CreateFieldResolverInfo,
1125 resolver?: GraphQLFieldResolver<any, any>
1126 ) {
1127 const resolveFn = resolver || defaultFieldResolver;
1128 if (this.onCreateResolverFns.length) {
1129 const toCompose = this.onCreateResolverFns
1130 .map((fn) => fn(info))
1131 .filter((f) => f) as MiddlewareFn[];
1132 if (toCompose.length) {
1133 return composeMiddlewareFns(toCompose, resolveFn);
1134 }
1135 }
1136 return resolveFn;
1137 }
1138
1139 protected buildInputObjectField(
1140 field: NexusInputFieldDef,
1141 typeConfig: NexusGraphQLInputObjectTypeConfig
1142 ): GraphQLInputFieldConfig {
1143 return {
1144 type: this.decorateType(
1145 this.getInputType(field.type),
1146 field.list,
1147 this.inputNonNull(typeConfig, field)
1148 ),
1149 defaultValue: field.default,
1150 description: field.description,
1151 };
1152 }
1153
1154 protected buildArgs(
1155 args: ArgsRecord,
1156 typeConfig: NexusGraphQLObjectTypeConfig | NexusGraphQLInterfaceTypeConfig
1157 ): GraphQLFieldConfigArgumentMap {
1158 const allArgs: GraphQLFieldConfigArgumentMap = {};
1159 Object.keys(args).forEach((argName) => {
1160 const argDef = normalizeArg(args[argName]).value;
1161 allArgs[argName] = {
1162 type: this.decorateType(
1163 this.getInputType(argDef.type),
1164 argDef.list,
1165 this.inputNonNull(typeConfig, argDef)
1166 ),
1167 description: argDef.description,
1168 defaultValue: argDef.default,
1169 };
1170 });
1171 return allArgs;
1172 }
1173
1174 protected inputNonNull(
1175 typeDef:
1176 | NexusGraphQLObjectTypeConfig
1177 | NexusGraphQLInterfaceTypeConfig
1178 | NexusGraphQLInputObjectTypeConfig,
1179 field: NexusInputFieldDef | NexusArgConfig<any>
1180 ): boolean {
1181 const { nullable, required } = field;
1182 const { name, nonNullDefaults = {} } =
1183 typeDef.extensions?.nexus?.config || {};
1184 if (typeof nullable !== "undefined" && typeof required !== "undefined") {
1185 throw new Error(`Cannot set both nullable & required on ${name}`);
1186 }
1187 if (typeof nullable !== "undefined") {
1188 return !nullable;
1189 }
1190 if (typeof required !== "undefined") {
1191 return required;
1192 }
1193 // Null by default
1194 return firstDefined(
1195 nonNullDefaults.input,
1196 this.nonNullDefaults.input,
1197 false
1198 );
1199 }
1200
1201 protected outputNonNull(
1202 typeDef: NexusGraphQLObjectTypeConfig | NexusGraphQLInterfaceTypeConfig,
1203 field: NexusOutputFieldDef
1204 ): boolean {
1205 const { nullable } = field;
1206 const { nonNullDefaults = {} } = typeDef.extensions?.nexus?.config ?? {};
1207 if (typeof nullable !== "undefined") {
1208 return !nullable;
1209 }
1210 // Non-Null by default
1211 return firstDefined(
1212 nonNullDefaults.output,
1213 this.nonNullDefaults.output,
1214 true
1215 );
1216 }
1217
1218 protected decorateType<T extends GraphQLNamedType>(
1219 type: T,
1220 list: null | undefined | true | boolean[],
1221 isNonNull: boolean
1222 ): T {
1223 if (list) {
1224 type = this.decorateList(type, list);
1225 }
1226 return (isNonNull ? GraphQLNonNull(type) : type) as T;
1227 }
1228
1229 protected decorateList<T extends GraphQLOutputType | GraphQLInputType>(
1230 type: T,
1231 list: true | boolean[]
1232 ): T {
1233 let finalType = type;
1234 if (!Array.isArray(list)) {
1235 return GraphQLList(GraphQLNonNull(type)) as T;
1236 }
1237 if (Array.isArray(list)) {
1238 for (let i = 0; i < list.length; i++) {
1239 const isNull = !list[i];
1240 if (!isNull) {
1241 finalType = GraphQLNonNull(finalType) as T;
1242 }
1243 finalType = GraphQLList(finalType) as T;
1244 }
1245 }
1246 return finalType;
1247 }
1248
1249 protected getInterface(
1250 name: string | NexusInterfaceTypeDef<any>
1251 ): GraphQLInterfaceType {
1252 const type = this.getOrBuildType(name);
1253 if (!isInterfaceType(type)) {
1254 /* istanbul ignore next */
1255 throw new Error(
1256 `Expected ${name} to be an interfaceType, saw ${type.constructor.name}`
1257 );
1258 }
1259 return type;
1260 }
1261
1262 protected getInputType(
1263 name: string | AllNexusInputTypeDefs
1264 ): GraphQLPossibleInputs {
1265 const type = this.getOrBuildType(name);
1266 if (!isInputObjectType(type) && !isLeafType(type)) {
1267 /* istanbul ignore next */
1268 throw new Error(
1269 `Expected ${name} to be a possible input type, saw ${type.constructor.name}`
1270 );
1271 }
1272 return type;
1273 }
1274
1275 protected getOutputType(
1276 name: string | AllNexusOutputTypeDefs
1277 ): GraphQLPossibleOutputs {
1278 const type = this.getOrBuildType(name);
1279 if (!isOutputType(type)) {
1280 /* istanbul ignore next */
1281 throw new Error(
1282 `Expected ${name} to be a valid output type, saw ${type.constructor.name}`
1283 );
1284 }
1285 return type;
1286 }
1287
1288 protected getObjectType(
1289 name: string | NexusObjectTypeDef<string>
1290 ): GraphQLObjectType {
1291 if (isNexusNamedTypeDef(name)) {
1292 return this.getObjectType(name.name);
1293 }
1294 const type = this.getOrBuildType(name);
1295 if (!isObjectType(type)) {
1296 /* istanbul ignore next */
1297 throw new Error(
1298 `Expected ${name} to be a objectType, saw ${type.constructor.name}`
1299 );
1300 }
1301 return type;
1302 }
1303
1304 protected getOrBuildType(
1305 name: string | AllNexusNamedTypeDefs,
1306 fromObject: boolean = false
1307 ): GraphQLNamedType {
1308 invariantGuard(name);
1309 if (isNexusNamedTypeDef(name)) {
1310 return this.getOrBuildType(name.name, true);
1311 }
1312 if (SCALARS[name]) {
1313 return SCALARS[name];
1314 }
1315 if (this.finalTypeMap[name]) {
1316 return this.finalTypeMap[name];
1317 }
1318 if (this.buildingTypes.has(name)) {
1319 /* istanbul ignore next */
1320 throw new Error(
1321 `GraphQL Nexus: Circular dependency detected, while building types ${Array.from(
1322 this.buildingTypes
1323 )}`
1324 );
1325 }
1326 const pendingType = this.pendingTypeMap[name];
1327 if (isNexusNamedTypeDef(pendingType)) {
1328 this.buildingTypes.add(pendingType.name);
1329 if (isNexusObjectTypeDef(pendingType)) {
1330 return this.buildObjectType(pendingType.value);
1331 } else if (isNexusInterfaceTypeDef(pendingType)) {
1332 return this.buildInterfaceType(pendingType.value);
1333 } else if (isNexusEnumTypeDef(pendingType)) {
1334 return this.buildEnumType(pendingType.value);
1335 } else if (isNexusScalarTypeDef(pendingType)) {
1336 return this.buildScalarType(pendingType.value);
1337 } else if (isNexusInputObjectTypeDef(pendingType)) {
1338 return this.buildInputObjectType(pendingType.value);
1339 } else if (isNexusUnionTypeDef(pendingType)) {
1340 return this.buildUnionType(pendingType.value);
1341 }
1342 }
1343 return this.missingType(name, fromObject);
1344 }
1345
1346 missingResolveType(name: string, location: "union" | "interface") {
1347 console.error(
1348 new Error(
1349 `Missing resolveType for the ${name} ${location}. ` +
1350 `Be sure to add one in the definition block for the type, ` +
1351 `or t.resolveType(() => null) if you don't want or need to implement.`
1352 )
1353 );
1354 return (obj: any) => obj?.__typename || null;
1355 }
1356
1357 protected walkInputType<T extends NexusShapedInput>(obj: T) {
1358 const definitionBlock = new InputDefinitionBlock({
1359 typeName: obj.name,
1360 addField: (f) => this.maybeTraverseInputFieldType(f),
1361 addDynamicInputFields: (block, isList) =>
1362 this.addDynamicInputFields(block, isList),
1363 warn: () => {},
1364 });
1365 obj.definition(definitionBlock);
1366 return obj;
1367 }
1368
1369 addDynamicInputFields(block: InputDefinitionBlock<any>, isList: boolean) {
1370 eachObj(this.dynamicInputFields, (val, methodName) => {
1371 if (typeof val === "string") {
1372 return this.addDynamicScalar(methodName, val, block);
1373 }
1374 // @ts-ignore
1375 block[methodName] = (...args: any[]) => {
1376 const config = isList ? [args[0], { list: isList, ...args[1] }] : args;
1377 return val.value.factory({
1378 args: config,
1379 typeDef: block,
1380 builder: this.builderLens,
1381 typeName: block.typeName,
1382 });
1383 };
1384 });
1385 }
1386
1387 addDynamicOutputMembers(
1388 block: OutputDefinitionBlock<any>,
1389 isList: boolean,
1390 stage: "walk" | "build"
1391 ) {
1392 eachObj(this.dynamicOutputFields, (val, methodName) => {
1393 if (typeof val === "string") {
1394 return this.addDynamicScalar(methodName, val, block);
1395 }
1396 // @ts-ignore
1397 block[methodName] = (...args: any[]) => {
1398 const config = isList ? [args[0], { list: isList, ...args[1] }] : args;
1399 return val.value.factory({
1400 args: config,
1401 typeDef: block,
1402 builder: this.builderLens,
1403 typeName: block.typeName,
1404 stage,
1405 });
1406 };
1407 });
1408 eachObj(this.dynamicOutputProperties, (val, propertyName) => {
1409 Object.defineProperty(block, propertyName, {
1410 get() {
1411 return val.value.factory({
1412 typeDef: block,
1413 builder: this.builderLens,
1414 typeName: block.typeName,
1415 stage,
1416 });
1417 },
1418 enumerable: true,
1419 });
1420 });
1421 }
1422
1423 addDynamicScalar(
1424 methodName: string,
1425 typeName: string,
1426 block: OutputDefinitionBlock<any> | InputDefinitionBlock<any>
1427 ) {
1428 // @ts-ignore
1429 block[methodName] = (fieldName: string, opts: any) => {
1430 let fieldConfig = {
1431 type: typeName,
1432 };
1433
1434 if (typeof opts === "function") {
1435 // @ts-ignore
1436 fieldConfig.resolve = opts;
1437 } else {
1438 fieldConfig = { ...fieldConfig, ...opts };
1439 }
1440
1441 // @ts-ignore
1442 block.field(fieldName, fieldConfig);
1443 };
1444 }
1445
1446 protected walkOutputType<T extends NexusShapedOutput>(obj: T) {
1447 const definitionBlock = new ObjectDefinitionBlock({
1448 typeName: obj.name,
1449 addInterfaces: (i) => {
1450 i.forEach((j) => {
1451 if (typeof j !== "string") {
1452 this.addType(j);
1453 }
1454 });
1455 },
1456 addField: (f) => this.maybeTraverseOutputFieldType(f),
1457 addDynamicOutputMembers: (block, isList) =>
1458 this.addDynamicOutputMembers(block, isList, "walk"),
1459 warn: () => {},
1460 });
1461 obj.definition(definitionBlock);
1462 return obj;
1463 }
1464
1465 protected walkInterfaceType(obj: NexusInterfaceTypeConfig<any>) {
1466 const definitionBlock = new InterfaceDefinitionBlock({
1467 typeName: obj.name,
1468 setResolveType: () => {},
1469 addField: (f) => this.maybeTraverseOutputFieldType(f),
1470 addDynamicOutputMembers: (block, isList) =>
1471 this.addDynamicOutputMembers(block, isList, "walk"),
1472 warn: () => {},
1473 });
1474 obj.definition(definitionBlock);
1475 return obj;
1476 }
1477
1478 protected maybeTraverseOutputFieldType(type: NexusOutputFieldDef) {
1479 const { args, type: fieldType } = type;
1480 if (typeof fieldType !== "string") {
1481 this.addType(fieldType);
1482 }
1483 if (args) {
1484 eachObj(args, (val) => {
1485 const t = isNexusArgDef(val) ? val.value.type : val;
1486 if (typeof t !== "string") {
1487 this.addType(t);
1488 }
1489 });
1490 }
1491 }
1492
1493 protected maybeTraverseInputFieldType(type: NexusInputFieldDef) {
1494 const { type: fieldType } = type;
1495 if (typeof fieldType !== "string") {
1496 this.addType(fieldType);
1497 }
1498 }
1499
1500 protected walkNamedTypes(namedType: GraphQLNamedType) {
1501 if (isObjectType(namedType) || isInterfaceType(namedType)) {
1502 eachObj(namedType.getFields(), (val) =>
1503 this.addNamedTypeOutputField(val)
1504 );
1505 }
1506 if (isObjectType(namedType)) {
1507 namedType.getInterfaces().forEach((i) => this.addUnknownTypeInternal(i));
1508 }
1509 if (isInputObjectType(namedType)) {
1510 eachObj(namedType.getFields(), (val) =>
1511 this.addUnknownTypeInternal(getNamedType(val.type))
1512 );
1513 }
1514 if (isUnionType(namedType)) {
1515 namedType.getTypes().forEach((type) => this.addUnknownTypeInternal(type));
1516 }
1517 }
1518
1519 protected addUnknownTypeInternal(t: GraphQLNamedType) {
1520 if (!this.definedTypeMap[t.name]) {
1521 this.addType(t);
1522 }
1523 }
1524
1525 protected addNamedTypeOutputField(obj: GraphQLField<any, any>) {
1526 this.addUnknownTypeInternal(getNamedType(obj.type));
1527 if (obj.args) {
1528 obj.args.forEach((val) => this.addType(getNamedType(val.type)));
1529 }
1530 }
1531
1532 protected replaceNamedType(type: GraphQLType) {
1533 let wrappingTypes: any[] = [];
1534 let finalType = type;
1535 while (isWrappingType(finalType)) {
1536 wrappingTypes.unshift(finalType.constructor);
1537 finalType = finalType.ofType;
1538 }
1539 if (
1540 this.finalTypeMap[finalType.name] === this.definedTypeMap[finalType.name]
1541 ) {
1542 return type;
1543 }
1544 return wrappingTypes.reduce((result, Wrapper) => {
1545 return new Wrapper(result);
1546 }, this.finalTypeMap[finalType.name]);
1547 }
1548}
1549
1550function extendError(name: string) {
1551 return new Error(
1552 `${name} was already defined and imported as a type, check the docs for extending types`
1553 );
1554}
1555
1556export type DynamicFieldDefs = {
1557 dynamicInputFields: DynamicInputFields;
1558 dynamicOutputFields: DynamicOutputFields;
1559 dynamicOutputProperties: DynamicOutputProperties;
1560};
1561
1562export interface BuildTypes<
1563 TypeMapDefs extends Record<string, GraphQLNamedType>
1564> {
1565 finalConfig: BuilderConfig;
1566 typeMap: TypeMapDefs;
1567 missingTypes: Record<string, MissingType>;
1568 schemaExtension: NexusSchemaExtension;
1569 onAfterBuildFns: SchemaBuilder["onAfterBuildFns"];
1570}
1571
1572/**
1573 * Builds the schema, we may return more than just the schema
1574 * from this one day.
1575 */
1576export function makeSchemaInternal(config: SchemaConfig) {
1577 const builder = new SchemaBuilder(config);
1578 builder.addTypes(config.types);
1579 const {
1580 finalConfig,
1581 typeMap,
1582 missingTypes,
1583 schemaExtension,
1584 onAfterBuildFns,
1585 } = builder.getFinalTypeMap();
1586 const { Query, Mutation, Subscription } = typeMap;
1587
1588 /* istanbul ignore next */
1589 if (!isObjectType(Query)) {
1590 throw new Error(
1591 `Expected Query to be a objectType, saw ${Query.constructor.name}`
1592 );
1593 }
1594 /* istanbul ignore next */
1595 if (Mutation && !isObjectType(Mutation)) {
1596 throw new Error(
1597 `Expected Mutation to be a objectType, saw ${Mutation.constructor.name}`
1598 );
1599 }
1600 /* istanbul ignore next */
1601 if (Subscription && !isObjectType(Subscription)) {
1602 throw new Error(
1603 `Expected Subscription to be a objectType, saw ${Subscription.constructor.name}`
1604 );
1605 }
1606
1607 const schema = new GraphQLSchema({
1608 query: Query,
1609 mutation: Mutation,
1610 subscription: Subscription,
1611 types: objValues(typeMap),
1612 extensions: {
1613 nexus: schemaExtension,
1614 },
1615 }) as NexusGraphQLSchema;
1616
1617 onAfterBuildFns.forEach((fn) => fn(schema));
1618
1619 return { schema, missingTypes, finalConfig };
1620}
1621
1622/**
1623 * Defines the GraphQL schema, by combining the GraphQL types defined
1624 * by the GraphQL Nexus layer or any manually defined GraphQLType objects.
1625 *
1626 * Requires at least one type be named "Query", which will be used as the
1627 * root query type.
1628 */
1629export function makeSchema(config: SchemaConfig): NexusGraphQLSchema {
1630 const { schema, missingTypes, finalConfig } = makeSchemaInternal(config);
1631 const typegenConfig = resolveTypegenConfig(finalConfig);
1632
1633 if (typegenConfig.outputs.schema || typegenConfig.outputs.typegen) {
1634 // Generating in the next tick allows us to use the schema
1635 // in the optional thunk for the typegen config
1636 const typegenPromise = new TypegenMetadata(typegenConfig).generateArtifacts(
1637 schema
1638 );
1639 if (config.shouldExitAfterGenerateArtifacts) {
1640 typegenPromise
1641 .then(() => {
1642 console.log(`Generated Artifacts:
1643 TypeScript Types ==> ${typegenConfig.outputs.typegen ||
1644 "(not enabled)"}
1645 GraphQL Schema ==> ${typegenConfig.outputs.schema ||
1646 "(not enabled)"}`);
1647 process.exit(0);
1648 })
1649 .catch((e) => {
1650 console.error(e);
1651 process.exit(1);
1652 });
1653 } else {
1654 typegenPromise.catch((e) => {
1655 console.error(e);
1656 });
1657 }
1658 }
1659 assertNoMissingTypes(schema, missingTypes);
1660 return schema;
1661}
1662
1663/**
1664 * Like makeSchema except that typegen is always run
1665 * and waited upon.
1666 */
1667export async function generateSchema(
1668 config: SchemaConfig
1669): Promise<NexusGraphQLSchema> {
1670 const { schema, missingTypes, finalConfig } = makeSchemaInternal(config);
1671 const typegenConfig = resolveTypegenConfig(finalConfig);
1672 assertNoMissingTypes(schema, missingTypes);
1673 await new TypegenMetadata(typegenConfig).generateArtifacts(schema);
1674 return schema;
1675}
1676
1677/**
1678 * Mainly useful for testing, generates the schema and returns the artifacts
1679 * that would have been otherwise written to the filesystem.
1680 */
1681generateSchema.withArtifacts = async (
1682 config: SchemaConfig,
1683 typeFilePath: string | false
1684): Promise<{
1685 schema: NexusGraphQLSchema;
1686 schemaTypes: string;
1687 tsTypes: string;
1688}> => {
1689 const { schema, missingTypes, finalConfig } = makeSchemaInternal(config);
1690 const typegenConfig = resolveTypegenConfig(finalConfig);
1691 assertNoMissingTypes(schema, missingTypes);
1692 const { schemaTypes, tsTypes } = await new TypegenMetadata(
1693 typegenConfig
1694 ).generateArtifactContents(schema, typeFilePath);
1695 return { schema, schemaTypes, tsTypes };
1696};
1697
1698/**
1699 * Assertion utility with nexus-aware feedback for users.
1700 */
1701function invariantGuard(val: any) {
1702 /* istanbul ignore next */
1703 if (Boolean(val) === false) {
1704 throw new Error(
1705 "Nexus Error: This should never happen, " +
1706 "please check your code or if you think this is a bug open a GitHub issue https://github.com/prisma-labs/nexus/issues/new."
1707 );
1708 }
1709}
1710
1711function normalizeArg(
1712 argVal:
1713 | NexusArgDef<AllInputTypes>
1714 | AllInputTypes
1715 | AllNexusInputTypeDefs<string>
1716): NexusArgDef<AllInputTypes> {
1717 if (isNexusArgDef(argVal)) {
1718 return argVal;
1719 }
1720 return arg({ type: argVal } as any);
1721}