Skip to content

Commit 3f4f546

Browse files
authored
Add root level resolvers support to avoidOptionals (#10077)
* Add NormalizedAvoidOptionalsConfig, use avoidOptionals.resolvers for general resolvers things * Add avoidOptionals.(query|mutation|subscription) options * Add changeset * Update doc
1 parent 2f5ae64 commit 3f4f546

File tree

10 files changed

+397
-237
lines changed

10 files changed

+397
-237
lines changed

.changeset/short-mirrors-fail.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': minor
3+
'@graphql-codegen/typescript-operations': minor
4+
'@graphql-codegen/typescript': minor
5+
'@graphql-codegen/typescript-resolvers': minor
6+
---
7+
8+
Extend `config.avoidOptions` to support query, mutation and subscription
9+
10+
Previously, `config.avoidOptions.resolvers` was being used to make query, mutation and subscription fields non-optional.
11+
Now, `config.avoidOptions.query`, `config.avoidOptions.mutation` and `config.avoidOptions.subscription` can be used to target the respective types.

packages/plugins/other/visitor-plugin-common/src/avoid-optionals.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
1-
import { AvoidOptionalsConfig } from './types.js';
1+
import { AvoidOptionalsConfig, NormalizedAvoidOptionalsConfig } from './types.js';
22

3-
export const DEFAULT_AVOID_OPTIONALS: AvoidOptionalsConfig = {
3+
export const DEFAULT_AVOID_OPTIONALS: NormalizedAvoidOptionalsConfig = {
44
object: false,
55
inputValue: false,
66
field: false,
77
defaultValue: false,
88
resolvers: false,
9+
query: false,
10+
mutation: false,
11+
subscription: false,
912
};
1013

11-
export function normalizeAvoidOptionals(avoidOptionals?: boolean | AvoidOptionalsConfig): AvoidOptionalsConfig {
14+
export function normalizeAvoidOptionals(
15+
avoidOptionals?: boolean | AvoidOptionalsConfig
16+
): NormalizedAvoidOptionalsConfig {
1217
if (typeof avoidOptionals === 'boolean') {
1318
return {
1419
object: avoidOptionals,
1520
inputValue: avoidOptionals,
1621
field: avoidOptionals,
1722
defaultValue: avoidOptionals,
1823
resolvers: avoidOptionals,
24+
query: avoidOptionals,
25+
mutation: avoidOptionals,
26+
subscription: avoidOptionals,
1927
};
2028
}
2129

packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts

+45-25
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
ConvertOptions,
3434
DeclarationKind,
3535
EnumValuesMap,
36+
NormalizedAvoidOptionalsConfig,
3637
NormalizedScalarsMap,
3738
ParsedEnumValuesMap,
3839
ResolversNonOptionalTypenameConfig,
@@ -50,6 +51,7 @@ import {
5051
wrapTypeWithModifiers,
5152
} from './utils.js';
5253
import { OperationVariablesToObject } from './variables-to-object.js';
54+
import { normalizeAvoidOptionals } from './avoid-optionals.js';
5355

5456
export interface ParsedResolversConfig extends ParsedConfig {
5557
contextType: ParsedMapper;
@@ -60,7 +62,7 @@ export interface ParsedResolversConfig extends ParsedConfig {
6062
[typeName: string]: ParsedMapper;
6163
};
6264
defaultMapper: ParsedMapper | null;
63-
avoidOptionals: AvoidOptionalsConfig | boolean;
65+
avoidOptionals: NormalizedAvoidOptionalsConfig;
6466
addUnderscoreToArgsType: boolean;
6567
enumValues: ParsedEnumValuesMap;
6668
resolverTypeWrapperSignature: string;
@@ -78,6 +80,8 @@ export interface ParsedResolversConfig extends ParsedConfig {
7880
resolversNonOptionalTypename: ResolversNonOptionalTypenameConfig;
7981
}
8082

83+
type FieldDefinitionPrintFn = (parentName: string, avoidResolverOptionals: boolean) => string | null;
84+
8185
export interface RawResolversConfig extends RawConfig {
8286
/**
8387
* @description Adds `_` to generated `Args` types in order to avoid duplicate identifiers.
@@ -429,6 +433,9 @@ export interface RawResolversConfig extends RawConfig {
429433
* inputValue: true,
430434
* object: true,
431435
* defaultValue: true,
436+
* query: true,
437+
* mutation: true,
438+
* subscription: true,
432439
* }
433440
* },
434441
* },
@@ -682,7 +689,7 @@ export class BaseResolversVisitor<
682689
allResolversTypeName: getConfigValue(rawConfig.allResolversTypeName, 'Resolvers'),
683690
rootValueType: parseMapper(rawConfig.rootValueType || '{}', 'RootValueType'),
684691
namespacedImportName: getConfigValue(rawConfig.namespacedImportName, ''),
685-
avoidOptionals: getConfigValue(rawConfig.avoidOptionals, false),
692+
avoidOptionals: normalizeAvoidOptionals(rawConfig.avoidOptionals),
686693
defaultMapper: rawConfig.defaultMapper
687694
? parseMapper(rawConfig.defaultMapper || 'any', 'DefaultMapperType')
688695
: null,
@@ -1307,7 +1314,7 @@ export class BaseResolversVisitor<
13071314
}
13081315

13091316
protected formatRootResolver(schemaTypeName: string, resolverType: string, declarationKind: DeclarationKind): string {
1310-
return `${schemaTypeName}${this.config.avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation(
1317+
return `${schemaTypeName}${this.config.avoidOptionals.resolvers ? '' : '?'}: ${resolverType}${this.getPunctuation(
13111318
declarationKind
13121319
)}`;
13131320
}
@@ -1394,11 +1401,11 @@ export class BaseResolversVisitor<
13941401
return `ParentType extends ${parentType} = ${parentType}`;
13951402
}
13961403

1397-
FieldDefinition(node: FieldDefinitionNode, key: string | number, parent: any): (parentName: string) => string | null {
1404+
FieldDefinition(node: FieldDefinitionNode, key: string | number, parent: any): FieldDefinitionPrintFn {
13981405
const hasArguments = node.arguments && node.arguments.length > 0;
13991406
const declarationKind = 'type';
14001407

1401-
return (parentName: string) => {
1408+
return (parentName, avoidResolverOptionals) => {
14021409
const original: FieldDefinitionNode = parent[key];
14031410
const baseType = getBaseTypeNode(original.type);
14041411
const realType = baseType.name.value;
@@ -1431,10 +1438,7 @@ export class BaseResolversVisitor<
14311438
)
14321439
: null;
14331440

1434-
const avoidInputsOptionals =
1435-
typeof this.config.avoidOptionals === 'object'
1436-
? this.config.avoidOptionals?.inputValue
1437-
: this.config.avoidOptionals === true;
1441+
const avoidInputsOptionals = this.config.avoidOptionals.inputValue;
14381442

14391443
if (argsType !== null) {
14401444
const argsToForceRequire = original.arguments.filter(
@@ -1463,10 +1467,6 @@ export class BaseResolversVisitor<
14631467

14641468
const resolverType = isSubscriptionType ? 'SubscriptionResolver' : directiveMappings[0] ?? 'Resolver';
14651469

1466-
const avoidResolverOptionals =
1467-
typeof this.config.avoidOptionals === 'object'
1468-
? this.config.avoidOptionals?.resolvers
1469-
: this.config.avoidOptionals === true;
14701470
const signature: {
14711471
name: string;
14721472
modifier: string;
@@ -1532,15 +1532,31 @@ export class BaseResolversVisitor<
15321532
});
15331533
const typeName = node.name as any as string;
15341534
const parentType = this.getParentTypeToUse(typeName);
1535-
const isRootType = [
1536-
this.schema.getQueryType()?.name,
1537-
this.schema.getMutationType()?.name,
1538-
this.schema.getSubscriptionType()?.name,
1539-
].includes(typeName);
15401535

1541-
const fieldsContent = node.fields.map((f: any) => f(node.name));
1536+
const rootType = ((): false | 'query' | 'mutation' | 'subscription' => {
1537+
if (this.schema.getQueryType()?.name === typeName) {
1538+
return 'query';
1539+
}
1540+
if (this.schema.getMutationType()?.name === typeName) {
1541+
return 'mutation';
1542+
}
1543+
if (this.schema.getSubscriptionType()?.name === typeName) {
1544+
return 'subscription';
1545+
}
1546+
return false;
1547+
})();
1548+
1549+
const fieldsContent = (node.fields as unknown as FieldDefinitionPrintFn[]).map(f => {
1550+
return f(
1551+
typeName,
1552+
(rootType === 'query' && this.config.avoidOptionals.query) ||
1553+
(rootType === 'mutation' && this.config.avoidOptionals.mutation) ||
1554+
(rootType === 'subscription' && this.config.avoidOptionals.subscription) ||
1555+
(rootType === false && this.config.avoidOptionals.resolvers)
1556+
);
1557+
});
15421558

1543-
if (!isRootType) {
1559+
if (!rootType) {
15441560
fieldsContent.push(
15451561
indent(
15461562
`${
@@ -1720,21 +1736,23 @@ export class BaseResolversVisitor<
17201736
const allTypesMap = this._schema.getTypeMap();
17211737
const implementingTypes: string[] = [];
17221738

1723-
this._collectedResolvers[node.name as any] = {
1739+
const typeName = node.name as any as string;
1740+
1741+
this._collectedResolvers[typeName] = {
17241742
typename: name + '<ContextType>',
17251743
baseGeneratedTypename: name,
17261744
};
17271745

17281746
for (const graphqlType of Object.values(allTypesMap)) {
17291747
if (graphqlType instanceof GraphQLObjectType) {
17301748
const allInterfaces = graphqlType.getInterfaces();
1731-
if (allInterfaces.find(int => int.name === (node.name as any as string))) {
1749+
if (allInterfaces.find(int => int.name === typeName)) {
17321750
implementingTypes.push(graphqlType.name);
17331751
}
17341752
}
17351753
}
17361754

1737-
const parentType = this.getParentTypeToUse(node.name as any as string);
1755+
const parentType = this.getParentTypeToUse(typeName);
17381756
const possibleTypes = implementingTypes.map(name => `'${name}'`).join(' | ') || 'null';
17391757
const fields = this.config.onlyResolveTypeForInterfaces ? [] : node.fields || [];
17401758

@@ -1749,7 +1767,9 @@ export class BaseResolversVisitor<
17491767
this.config.optionalResolveType ? '?' : ''
17501768
}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}`
17511769
),
1752-
...fields.map((f: any) => f(node.name)),
1770+
...(fields as unknown as FieldDefinitionPrintFn[]).map(f =>
1771+
f(typeName, this.config.avoidOptionals.resolvers)
1772+
),
17531773
].join('\n')
17541774
).string;
17551775
}
@@ -1809,7 +1829,7 @@ export class BaseResolversVisitor<
18091829
return null;
18101830
}
18111831

1812-
const addOptionalSign = !this.config.avoidOptionals && !isNonNullType(field.type);
1832+
const addOptionalSign = !this.config.avoidOptionals.resolvers && !isNonNullType(field.type);
18131833

18141834
return {
18151835
addOptionalSign,

packages/plugins/other/visitor-plugin-common/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,13 @@ export interface AvoidOptionalsConfig {
106106
inputValue?: boolean;
107107
defaultValue?: boolean;
108108
resolvers?: boolean;
109+
query?: boolean;
110+
mutation?: boolean;
111+
subscription?: boolean;
109112
}
110113

114+
export type NormalizedAvoidOptionalsConfig = Required<AvoidOptionalsConfig>;
115+
111116
export interface ParsedImport {
112117
moduleName: string | null;
113118
propName: string;

packages/plugins/typescript/operations/src/visitor.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
2-
AvoidOptionalsConfig,
32
BaseDocumentsVisitor,
43
DeclarationKind,
54
generateFragmentImportStatement,
65
getConfigValue,
76
LoadedFragment,
87
normalizeAvoidOptionals,
8+
NormalizedAvoidOptionalsConfig,
99
ParsedDocumentsConfig,
1010
PreResolveTypesProcessor,
1111
SelectionSetProcessorConfig,
@@ -20,7 +20,7 @@ import { TypeScriptSelectionSetProcessor } from './ts-selection-set-processor.js
2020

2121
export interface TypeScriptDocumentsParsedConfig extends ParsedDocumentsConfig {
2222
arrayInputCoercion: boolean;
23-
avoidOptionals: AvoidOptionalsConfig;
23+
avoidOptionals: NormalizedAvoidOptionalsConfig;
2424
immutableTypes: boolean;
2525
noExport: boolean;
2626
maybeValue: string;
@@ -108,7 +108,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
108108
new TypeScriptOperationVariablesToObject(
109109
this.scalars,
110110
this.convertName.bind(this),
111-
this.config.avoidOptionals.object,
111+
this.config.avoidOptionals,
112112
this.config.immutableTypes,
113113
this.config.namespacedImportName,
114114
enumsNames,

packages/plugins/typescript/resolvers/src/visitor.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
BaseResolversVisitor,
44
DeclarationKind,
55
getConfigValue,
6+
normalizeAvoidOptionals,
67
ParsedResolversConfig,
78
} from '@graphql-codegen/visitor-plugin-common';
89
import autoBind from 'auto-bind';
@@ -34,7 +35,7 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor<
3435
super(
3536
pluginConfig,
3637
{
37-
avoidOptionals: getConfigValue(pluginConfig.avoidOptionals, false),
38+
avoidOptionals: normalizeAvoidOptionals(pluginConfig.avoidOptionals),
3839
useIndexSignature: getConfigValue(pluginConfig.useIndexSignature, false),
3940
wrapFieldDefinitions: getConfigValue(pluginConfig.wrapFieldDefinitions, false),
4041
allowParentTypeOverride: getConfigValue(pluginConfig.allowParentTypeOverride, false),
@@ -75,10 +76,7 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor<
7576
}
7677

7778
protected formatRootResolver(schemaTypeName: string, resolverType: string, declarationKind: DeclarationKind): string {
78-
const avoidOptionals =
79-
typeof this.config.avoidOptionals === 'object'
80-
? this.config.avoidOptionals?.resolvers
81-
: !!this.config.avoidOptionals === true;
79+
const avoidOptionals = this.config.avoidOptionals.resolvers;
8280
return `${schemaTypeName}${avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation(declarationKind)}`;
8381
}
8482

@@ -121,7 +119,7 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor<
121119

122120
protected buildEnumResolverContentBlock(node: EnumTypeDefinitionNode, mappedEnumType: string): string {
123121
const valuesMap = `{ ${(node.values || [])
124-
.map(v => `${v.name as any as string}${this.config.avoidOptionals ? '' : '?'}: any`)
122+
.map(v => `${v.name as any as string}${this.config.avoidOptionals.resolvers ? '' : '?'}: any`)
125123
.join(', ')} }`;
126124

127125
this._globalDeclarations.add(ENUM_RESOLVERS_SIGNATURE);

0 commit comments

Comments
 (0)