-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add root level resolvers support to avoidOptionals
#10077
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
'@graphql-codegen/visitor-plugin-common': minor | ||
'@graphql-codegen/typescript-operations': minor | ||
'@graphql-codegen/typescript': minor | ||
'@graphql-codegen/typescript-resolvers': minor | ||
--- | ||
|
||
Extend `config.avoidOptions` to support query, mutation and subscription | ||
|
||
Previously, `config.avoidOptions.resolvers` was being used to make query, mutation and subscription fields non-optional. | ||
Now, `config.avoidOptions.query`, `config.avoidOptions.mutation` and `config.avoidOptions.subscription` can be used to target the respective types. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ import { | |
ConvertOptions, | ||
DeclarationKind, | ||
EnumValuesMap, | ||
NormalizedAvoidOptionalsConfig, | ||
NormalizedScalarsMap, | ||
ParsedEnumValuesMap, | ||
ResolversNonOptionalTypenameConfig, | ||
|
@@ -50,6 +51,7 @@ import { | |
wrapTypeWithModifiers, | ||
} from './utils.js'; | ||
import { OperationVariablesToObject } from './variables-to-object.js'; | ||
import { normalizeAvoidOptionals } from './avoid-optionals.js'; | ||
|
||
export interface ParsedResolversConfig extends ParsedConfig { | ||
contextType: ParsedMapper; | ||
|
@@ -60,7 +62,7 @@ export interface ParsedResolversConfig extends ParsedConfig { | |
[typeName: string]: ParsedMapper; | ||
}; | ||
defaultMapper: ParsedMapper | null; | ||
avoidOptionals: AvoidOptionalsConfig | boolean; | ||
avoidOptionals: NormalizedAvoidOptionalsConfig; | ||
addUnderscoreToArgsType: boolean; | ||
enumValues: ParsedEnumValuesMap; | ||
resolverTypeWrapperSignature: string; | ||
|
@@ -78,6 +80,8 @@ export interface ParsedResolversConfig extends ParsedConfig { | |
resolversNonOptionalTypename: ResolversNonOptionalTypenameConfig; | ||
} | ||
|
||
type FieldDefinitionPrintFn = (parentName: string, avoidResolverOptionals: boolean) => string | null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was REALLY confused why/how the field's name is a function. Found out it's because we do custom logic in |
||
|
||
export interface RawResolversConfig extends RawConfig { | ||
/** | ||
* @description Adds `_` to generated `Args` types in order to avoid duplicate identifiers. | ||
|
@@ -429,6 +433,9 @@ export interface RawResolversConfig extends RawConfig { | |
* inputValue: true, | ||
* object: true, | ||
* defaultValue: true, | ||
* query: true, | ||
* mutation: true, | ||
* subscription: true, | ||
* } | ||
* }, | ||
* }, | ||
|
@@ -682,7 +689,7 @@ export class BaseResolversVisitor< | |
allResolversTypeName: getConfigValue(rawConfig.allResolversTypeName, 'Resolvers'), | ||
rootValueType: parseMapper(rawConfig.rootValueType || '{}', 'RootValueType'), | ||
namespacedImportName: getConfigValue(rawConfig.namespacedImportName, ''), | ||
avoidOptionals: getConfigValue(rawConfig.avoidOptionals, false), | ||
avoidOptionals: normalizeAvoidOptionals(rawConfig.avoidOptionals), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This plugin seems to be the only one that's not using |
||
defaultMapper: rawConfig.defaultMapper | ||
? parseMapper(rawConfig.defaultMapper || 'any', 'DefaultMapperType') | ||
: null, | ||
|
@@ -1307,7 +1314,7 @@ export class BaseResolversVisitor< | |
} | ||
|
||
protected formatRootResolver(schemaTypeName: string, resolverType: string, declarationKind: DeclarationKind): string { | ||
return `${schemaTypeName}${this.config.avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation( | ||
return `${schemaTypeName}${this.config.avoidOptionals.resolvers ? '' : '?'}: ${resolverType}${this.getPunctuation( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old implementation can cause a scenario where an empty object is passed in, but it'd be treated as truthy so it'd treat it as non-optional. In this new implementation, we use |
||
declarationKind | ||
)}`; | ||
} | ||
|
@@ -1394,11 +1401,11 @@ export class BaseResolversVisitor< | |
return `ParentType extends ${parentType} = ${parentType}`; | ||
} | ||
|
||
FieldDefinition(node: FieldDefinitionNode, key: string | number, parent: any): (parentName: string) => string | null { | ||
FieldDefinition(node: FieldDefinitionNode, key: string | number, parent: any): FieldDefinitionPrintFn { | ||
const hasArguments = node.arguments && node.arguments.length > 0; | ||
const declarationKind = 'type'; | ||
|
||
return (parentName: string) => { | ||
return (parentName, avoidResolverOptionals) => { | ||
const original: FieldDefinitionNode = parent[key]; | ||
const baseType = getBaseTypeNode(original.type); | ||
const realType = baseType.name.value; | ||
|
@@ -1431,10 +1438,7 @@ export class BaseResolversVisitor< | |
) | ||
: null; | ||
|
||
const avoidInputsOptionals = | ||
typeof this.config.avoidOptionals === 'object' | ||
? this.config.avoidOptionals?.inputValue | ||
: this.config.avoidOptionals === true; | ||
const avoidInputsOptionals = this.config.avoidOptionals.inputValue; | ||
|
||
if (argsType !== null) { | ||
const argsToForceRequire = original.arguments.filter( | ||
|
@@ -1463,10 +1467,6 @@ export class BaseResolversVisitor< | |
|
||
const resolverType = isSubscriptionType ? 'SubscriptionResolver' : directiveMappings[0] ?? 'Resolver'; | ||
|
||
const avoidResolverOptionals = | ||
typeof this.config.avoidOptionals === 'object' | ||
? this.config.avoidOptionals?.resolvers | ||
: this.config.avoidOptionals === true; | ||
Comment on lines
-1466
to
-1469
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We now pass this from caller. I found that this function is called in Interface fields and Object type fields |
||
const signature: { | ||
name: string; | ||
modifier: string; | ||
|
@@ -1532,15 +1532,31 @@ export class BaseResolversVisitor< | |
}); | ||
const typeName = node.name as any as string; | ||
const parentType = this.getParentTypeToUse(typeName); | ||
const isRootType = [ | ||
this.schema.getQueryType()?.name, | ||
this.schema.getMutationType()?.name, | ||
this.schema.getSubscriptionType()?.name, | ||
].includes(typeName); | ||
|
||
const fieldsContent = node.fields.map((f: any) => f(node.name)); | ||
const rootType = ((): false | 'query' | 'mutation' | 'subscription' => { | ||
if (this.schema.getQueryType()?.name === typeName) { | ||
return 'query'; | ||
} | ||
if (this.schema.getMutationType()?.name === typeName) { | ||
return 'mutation'; | ||
} | ||
if (this.schema.getSubscriptionType()?.name === typeName) { | ||
return 'subscription'; | ||
} | ||
return false; | ||
})(); | ||
|
||
const fieldsContent = (node.fields as unknown as FieldDefinitionPrintFn[]).map(f => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have to inline |
||
return f( | ||
typeName, | ||
(rootType === 'query' && this.config.avoidOptionals.query) || | ||
(rootType === 'mutation' && this.config.avoidOptionals.mutation) || | ||
(rootType === 'subscription' && this.config.avoidOptionals.subscription) || | ||
(rootType === false && this.config.avoidOptionals.resolvers) | ||
); | ||
}); | ||
|
||
if (!isRootType) { | ||
if (!rootType) { | ||
fieldsContent.push( | ||
indent( | ||
`${ | ||
|
@@ -1720,21 +1736,23 @@ export class BaseResolversVisitor< | |
const allTypesMap = this._schema.getTypeMap(); | ||
const implementingTypes: string[] = []; | ||
|
||
this._collectedResolvers[node.name as any] = { | ||
const typeName = node.name as any as string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cleaning this up by limiting where we do |
||
|
||
this._collectedResolvers[typeName] = { | ||
typename: name + '<ContextType>', | ||
baseGeneratedTypename: name, | ||
}; | ||
|
||
for (const graphqlType of Object.values(allTypesMap)) { | ||
if (graphqlType instanceof GraphQLObjectType) { | ||
const allInterfaces = graphqlType.getInterfaces(); | ||
if (allInterfaces.find(int => int.name === (node.name as any as string))) { | ||
if (allInterfaces.find(int => int.name === typeName)) { | ||
implementingTypes.push(graphqlType.name); | ||
} | ||
} | ||
} | ||
|
||
const parentType = this.getParentTypeToUse(node.name as any as string); | ||
const parentType = this.getParentTypeToUse(typeName); | ||
const possibleTypes = implementingTypes.map(name => `'${name}'`).join(' | ') || 'null'; | ||
const fields = this.config.onlyResolveTypeForInterfaces ? [] : node.fields || []; | ||
|
||
|
@@ -1749,7 +1767,9 @@ export class BaseResolversVisitor< | |
this.config.optionalResolveType ? '?' : '' | ||
}: TypeResolveFn<${possibleTypes}, ParentType, ContextType>${this.getPunctuation(declarationKind)}` | ||
), | ||
...fields.map((f: any) => f(node.name)), | ||
...(fields as unknown as FieldDefinitionPrintFn[]).map(f => | ||
f(typeName, this.config.avoidOptionals.resolvers) | ||
), | ||
].join('\n') | ||
).string; | ||
} | ||
|
@@ -1809,7 +1829,7 @@ export class BaseResolversVisitor< | |
return null; | ||
} | ||
|
||
const addOptionalSign = !this.config.avoidOptionals && !isNonNullType(field.type); | ||
const addOptionalSign = !this.config.avoidOptionals.resolvers && !isNonNullType(field.type); | ||
|
||
return { | ||
addOptionalSign, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NormalizedAvoidOptionalsConfig
is similar toAvoidOptionalsConfig
but all the fields are non-optional, making it much easier to code with