From 3f4f5466ff168ad822b9a00d83d3779078e6d8c4 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Sat, 3 Aug 2024 14:27:49 +1000 Subject: [PATCH] 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 --- .changeset/short-mirrors-fail.md | 11 + .../src/avoid-optionals.ts | 14 +- .../src/base-resolvers-visitor.ts | 70 ++-- .../other/visitor-plugin-common/src/types.ts | 5 + .../typescript/operations/src/visitor.ts | 6 +- .../typescript/resolvers/src/visitor.ts | 10 +- ...ts-resolvers.config.avoidOptionals.spec.ts | 313 ++++++++++++++++++ .../resolvers/tests/ts-resolvers.spec.ts | 194 ----------- .../src/typescript-variables-to-object.ts | 7 +- .../typescript/typescript/src/visitor.ts | 4 +- 10 files changed, 397 insertions(+), 237 deletions(-) create mode 100644 .changeset/short-mirrors-fail.md create mode 100644 packages/plugins/typescript/resolvers/tests/ts-resolvers.config.avoidOptionals.spec.ts diff --git a/.changeset/short-mirrors-fail.md b/.changeset/short-mirrors-fail.md new file mode 100644 index 00000000000..1c1326c9bb4 --- /dev/null +++ b/.changeset/short-mirrors-fail.md @@ -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. diff --git a/packages/plugins/other/visitor-plugin-common/src/avoid-optionals.ts b/packages/plugins/other/visitor-plugin-common/src/avoid-optionals.ts index 55faa46cedc..38d728ea719 100644 --- a/packages/plugins/other/visitor-plugin-common/src/avoid-optionals.ts +++ b/packages/plugins/other/visitor-plugin-common/src/avoid-optionals.ts @@ -1,14 +1,19 @@ -import { AvoidOptionalsConfig } from './types.js'; +import { AvoidOptionalsConfig, NormalizedAvoidOptionalsConfig } from './types.js'; -export const DEFAULT_AVOID_OPTIONALS: AvoidOptionalsConfig = { +export const DEFAULT_AVOID_OPTIONALS: NormalizedAvoidOptionalsConfig = { object: false, inputValue: false, field: false, defaultValue: false, resolvers: false, + query: false, + mutation: false, + subscription: false, }; -export function normalizeAvoidOptionals(avoidOptionals?: boolean | AvoidOptionalsConfig): AvoidOptionalsConfig { +export function normalizeAvoidOptionals( + avoidOptionals?: boolean | AvoidOptionalsConfig +): NormalizedAvoidOptionalsConfig { if (typeof avoidOptionals === 'boolean') { return { object: avoidOptionals, @@ -16,6 +21,9 @@ export function normalizeAvoidOptionals(avoidOptionals?: boolean | AvoidOptional field: avoidOptionals, defaultValue: avoidOptionals, resolvers: avoidOptionals, + query: avoidOptionals, + mutation: avoidOptionals, + subscription: avoidOptionals, }; } diff --git a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts index a08472afa8a..08b7c572b0b 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts @@ -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; + 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), 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( 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; 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 => { + 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,7 +1736,9 @@ 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; + + this._collectedResolvers[typeName] = { typename: name + '', baseGeneratedTypename: name, }; @@ -1728,13 +1746,13 @@ export class BaseResolversVisitor< 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, diff --git a/packages/plugins/other/visitor-plugin-common/src/types.ts b/packages/plugins/other/visitor-plugin-common/src/types.ts index 2e1e52d7259..f275fa47af7 100644 --- a/packages/plugins/other/visitor-plugin-common/src/types.ts +++ b/packages/plugins/other/visitor-plugin-common/src/types.ts @@ -106,8 +106,13 @@ export interface AvoidOptionalsConfig { inputValue?: boolean; defaultValue?: boolean; resolvers?: boolean; + query?: boolean; + mutation?: boolean; + subscription?: boolean; } +export type NormalizedAvoidOptionalsConfig = Required; + export interface ParsedImport { moduleName: string | null; propName: string; diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index f604a2308d7..f6121c0ce00 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -1,11 +1,11 @@ import { - AvoidOptionalsConfig, BaseDocumentsVisitor, DeclarationKind, generateFragmentImportStatement, getConfigValue, LoadedFragment, normalizeAvoidOptionals, + NormalizedAvoidOptionalsConfig, ParsedDocumentsConfig, PreResolveTypesProcessor, SelectionSetProcessorConfig, @@ -20,7 +20,7 @@ import { TypeScriptSelectionSetProcessor } from './ts-selection-set-processor.js export interface TypeScriptDocumentsParsedConfig extends ParsedDocumentsConfig { arrayInputCoercion: boolean; - avoidOptionals: AvoidOptionalsConfig; + avoidOptionals: NormalizedAvoidOptionalsConfig; immutableTypes: boolean; noExport: boolean; maybeValue: string; @@ -108,7 +108,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< new TypeScriptOperationVariablesToObject( this.scalars, this.convertName.bind(this), - this.config.avoidOptionals.object, + this.config.avoidOptionals, this.config.immutableTypes, this.config.namespacedImportName, enumsNames, diff --git a/packages/plugins/typescript/resolvers/src/visitor.ts b/packages/plugins/typescript/resolvers/src/visitor.ts index 8ab973be081..a587e67b550 100644 --- a/packages/plugins/typescript/resolvers/src/visitor.ts +++ b/packages/plugins/typescript/resolvers/src/visitor.ts @@ -3,6 +3,7 @@ import { BaseResolversVisitor, DeclarationKind, getConfigValue, + normalizeAvoidOptionals, ParsedResolversConfig, } from '@graphql-codegen/visitor-plugin-common'; import autoBind from 'auto-bind'; @@ -34,7 +35,7 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor< super( pluginConfig, { - avoidOptionals: getConfigValue(pluginConfig.avoidOptionals, false), + avoidOptionals: normalizeAvoidOptionals(pluginConfig.avoidOptionals), useIndexSignature: getConfigValue(pluginConfig.useIndexSignature, false), wrapFieldDefinitions: getConfigValue(pluginConfig.wrapFieldDefinitions, false), allowParentTypeOverride: getConfigValue(pluginConfig.allowParentTypeOverride, false), @@ -75,10 +76,7 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor< } protected formatRootResolver(schemaTypeName: string, resolverType: string, declarationKind: DeclarationKind): string { - const avoidOptionals = - typeof this.config.avoidOptionals === 'object' - ? this.config.avoidOptionals?.resolvers - : !!this.config.avoidOptionals === true; + const avoidOptionals = this.config.avoidOptionals.resolvers; return `${schemaTypeName}${avoidOptionals ? '' : '?'}: ${resolverType}${this.getPunctuation(declarationKind)}`; } @@ -121,7 +119,7 @@ export class TypeScriptResolversVisitor extends BaseResolversVisitor< protected buildEnumResolverContentBlock(node: EnumTypeDefinitionNode, mappedEnumType: string): string { const valuesMap = `{ ${(node.values || []) - .map(v => `${v.name as any as string}${this.config.avoidOptionals ? '' : '?'}: any`) + .map(v => `${v.name as any as string}${this.config.avoidOptionals.resolvers ? '' : '?'}: any`) .join(', ')} }`; this._globalDeclarations.add(ENUM_RESOLVERS_SIGNATURE); diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.config.avoidOptionals.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.config.avoidOptionals.spec.ts new file mode 100644 index 00000000000..ff7b7730a36 --- /dev/null +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.config.avoidOptionals.spec.ts @@ -0,0 +1,313 @@ +import { resolversTestingSchema, resolversTestingValidate } from '@graphql-codegen/testing'; +import { buildSchema } from 'graphql'; +import type { Types } from '@graphql-codegen/plugin-helpers'; +import { plugin } from '../src/index.js'; + +describe('TypeScript Resolvers Plugin - config.avoidOptionals', () => { + it('should generate basic type resolvers if config.avoidOptionals = true', async () => { + const result = (await plugin( + resolversTestingSchema, + [], + { avoidOptionals: true }, + { outputFile: '' } + )) as Types.ComplexPluginOutput; + + expect(result.content).toBeSimilarStringTo(` + export type MyDirectiveDirectiveArgs = { + arg: Scalars['Int']['input']; + arg2: Scalars['String']['input']; + arg3: Scalars['Boolean']['input']; + };`); + + expect(result.content).toBeSimilarStringTo(` + export type MyDirectiveDirectiveResolver = DirectiveResolverFn;`); + + expect(result.content).toBeSimilarStringTo(` + export type MyOtherTypeResolvers = { + bar: Resolver; + __isTypeOf?: IsTypeOfResolverFn; + }; + `); + + expect(result.content) + .toBeSimilarStringTo(`export interface MyScalarScalarConfig extends GraphQLScalarTypeConfig { + name: 'MyScalar'; + }`); + + expect(result.content).toBeSimilarStringTo(` + export type MyTypeResolvers = { + foo: Resolver; + otherType: Resolver, ParentType, ContextType>; + withArgs: Resolver, ParentType, ContextType, RequireFields>; + unionChild: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type MyUnionResolvers = { + __resolveType: TypeResolveFn<'MyType' | 'MyOtherType', ParentType, ContextType>; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type NodeResolvers = { + __resolveType: TypeResolveFn<'SomeNode', ParentType, ContextType>; + id: Resolver; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type QueryResolvers = { + something: Resolver; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type SomeNodeResolvers = { + id: Resolver; + __isTypeOf?: IsTypeOfResolverFn; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type SubscriptionResolvers = { + somethingChanged: SubscriptionResolver, "somethingChanged", ParentType, ContextType>; + }; + `); + + await resolversTestingValidate(result); + }); + + it('#7005 - avoidOptionals should preserve optional resolvers', async () => { + const testSchema = buildSchema(/* GraphQL */ ` + type Query { + users(filter: UserFilterInput = {}): [User!]! + ping: String! + } + + input UserFilterInput { + status: String = "ACTIVE" + } + + type User { + id: ID! + } + `); + + const output = (await plugin( + testSchema, + [], + { + avoidOptionals: { + defaultValue: true, + field: true, + inputValue: true, + object: true, + resolvers: false, + }, + } as any, + { outputFile: 'graphql.ts' } + )) as Types.ComplexPluginOutput; + + expect(output.content).toBeSimilarStringTo(` + export type QueryResolvers = { + users?: Resolver, ParentType, ContextType, RequireFields>; + ping?: Resolver; + }; + `); + }); + + it('#9438 - avoidOptionals should not wrap arguments with partial', async () => { + const testSchema = buildSchema(/* GraphQL */ ` + type Query { + users(filter: UserFilterInput): [User!]! + } + + input UserFilterInput { + status: String = "ACTIVE" + } + + type User { + id: ID! + } + `); + + const output = (await plugin( + testSchema, + [], + { + avoidOptionals: { + defaultValue: true, + field: true, + inputValue: true, + object: true, + resolvers: false, + }, + } as any, + { outputFile: 'graphql.ts' } + )) as Types.ComplexPluginOutput; + + expect(output.content).toBeSimilarStringTo(` + export type QueryResolvers = { + users?: Resolver, ParentType, ContextType, QueryUsersArgs>; + }; + `); + }); + + it('should keep non-optional arguments non-optional - issue #2323', async () => { + const testSchema = buildSchema(/* GraphQL */ ` + enum OrderBy { + name + id + } + + input Filter { + contain: String + } + + type Node { + id: ID! + name: String! + } + + type Connection { + nodes: [Node] + } + + type Query { + list(after: String, orderBy: OrderBy = name, filter: Filter!): Connection! + } + `); + + const output = (await plugin( + testSchema, + [], + { + avoidOptionals: false, + maybeValue: 'T | undefined', + } as any, + { outputFile: 'graphql.ts' } + )) as Types.ComplexPluginOutput; + + // filter should be non-optional + expect(output.content).toBeSimilarStringTo(` + export type QueryResolvers = { + list?: Resolver>; + }; + `); + }); +}); + +describe('TypeScript Resolvers Plugin - config.avoidOptionals - query, mutation, subscription', () => { + const testSchema = buildSchema(/* GraphQL */ ` + type Query { + user: User + currentAppVersion: String! + } + + type Mutation { + updateUser: User! + flagUser: User! + } + + type Subscription { + userUpdates: User! + appVersionUpdates: String! + } + + type User { + id: ID! + } + `); + + it('avoids non-optional Query fields if config.avoidOptionals.query = true', async () => { + const output = (await plugin( + testSchema, + [], + { avoidOptionals: { query: true } }, + { outputFile: 'graphql.ts' } + )) as Types.ComplexPluginOutput; + + expect(output.content).toBeSimilarStringTo(` + export type QueryResolvers = { + user: Resolver, ParentType, ContextType>; + currentAppVersion: Resolver; + }; + `); + + expect(output.content).toBeSimilarStringTo(` + export type MutationResolvers = { + updateUser?: Resolver; + flagUser?: Resolver; + }; + `); + + expect(output.content).toBeSimilarStringTo(` + export type SubscriptionResolvers = { + userUpdates?: SubscriptionResolver; + appVersionUpdates?: SubscriptionResolver; + }; + `); + }); + + it('avoids non-optional Mutation fields if config.avoidOptionals.mutation = true', async () => { + const output = (await plugin( + testSchema, + [], + { avoidOptionals: { mutation: true } }, + { outputFile: 'graphql.ts' } + )) as Types.ComplexPluginOutput; + + expect(output.content).toBeSimilarStringTo(` + export type QueryResolvers = { + user?: Resolver, ParentType, ContextType>; + currentAppVersion?: Resolver; + }; + `); + + expect(output.content).toBeSimilarStringTo(` + export type MutationResolvers = { + updateUser: Resolver; + flagUser: Resolver; + }; + `); + + expect(output.content).toBeSimilarStringTo(` + export type SubscriptionResolvers = { + userUpdates?: SubscriptionResolver; + appVersionUpdates?: SubscriptionResolver; + }; + `); + }); + + it('avoids non-optional Subscription fields if config.avoidOptionals.subscription = true', async () => { + const output = (await plugin( + testSchema, + [], + { avoidOptionals: { subscription: true } }, + { outputFile: 'graphql.ts' } + )) as Types.ComplexPluginOutput; + + expect(output.content).toBeSimilarStringTo(` + export type QueryResolvers = { + user?: Resolver, ParentType, ContextType>; + currentAppVersion?: Resolver; + }; + `); + + expect(output.content).toBeSimilarStringTo(` + export type MutationResolvers = { + updateUser?: Resolver; + flagUser?: Resolver; + }; + `); + + expect(output.content).toBeSimilarStringTo(` + export type SubscriptionResolvers = { + userUpdates: SubscriptionResolver; + appVersionUpdates: SubscriptionResolver; + }; + `); + }); +}); diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts index f67889c55f6..595a548d0f3 100644 --- a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts @@ -720,81 +720,6 @@ __isTypeOf?: IsTypeOfResolverFn; await resolversTestingValidate(result); }); - it('Should generate basic type resolvers with avoidOptionals', async () => { - const result = (await plugin( - resolversTestingSchema, - [], - { avoidOptionals: true }, - { outputFile: '' } - )) as Types.ComplexPluginOutput; - - expect(result.content).toBeSimilarStringTo(` - export type MyDirectiveDirectiveArgs = { - arg: Scalars['Int']['input']; - arg2: Scalars['String']['input']; - arg3: Scalars['Boolean']['input']; - };`); - - expect(result.content).toBeSimilarStringTo(` - export type MyDirectiveDirectiveResolver = DirectiveResolverFn;`); - - expect(result.content).toBeSimilarStringTo(` - export type MyOtherTypeResolvers = { - bar: Resolver; - __isTypeOf?: IsTypeOfResolverFn; - }; - `); - - expect(result.content) - .toBeSimilarStringTo(`export interface MyScalarScalarConfig extends GraphQLScalarTypeConfig { - name: 'MyScalar'; - }`); - - expect(result.content).toBeSimilarStringTo(` - export type MyTypeResolvers = { - foo: Resolver; - otherType: Resolver, ParentType, ContextType>; - withArgs: Resolver, ParentType, ContextType, RequireFields>; - unionChild: Resolver, ParentType, ContextType>; - __isTypeOf?: IsTypeOfResolverFn; - }; - `); - - expect(result.content).toBeSimilarStringTo(` - export type MyUnionResolvers = { - __resolveType: TypeResolveFn<'MyType' | 'MyOtherType', ParentType, ContextType>; - }; - `); - - expect(result.content).toBeSimilarStringTo(` - export type NodeResolvers = { - __resolveType: TypeResolveFn<'SomeNode', ParentType, ContextType>; - id: Resolver; - }; - `); - - expect(result.content).toBeSimilarStringTo(` - export type QueryResolvers = { - something: Resolver; - }; - `); - - expect(result.content).toBeSimilarStringTo(` - export type SomeNodeResolvers = { - id: Resolver; - __isTypeOf?: IsTypeOfResolverFn; - }; - `); - - expect(result.content).toBeSimilarStringTo(` - export type SubscriptionResolvers = { - somethingChanged: SubscriptionResolver, "somethingChanged", ParentType, ContextType>; - }; - `); - - await resolversTestingValidate(result); - }); - it('Should allow to override context with simple identifier', async () => { const result = (await plugin( resolversTestingSchema, @@ -2305,49 +2230,6 @@ export type ResolverFn = ( };`); }); - it('should keep non-optional arguments non-optional - issue #2323', async () => { - const testSchema = buildSchema(/* GraphQL */ ` - enum OrderBy { - name - id - } - - input Filter { - contain: String - } - - type Node { - id: ID! - name: String! - } - - type Connection { - nodes: [Node] - } - - type Query { - list(after: String, orderBy: OrderBy = name, filter: Filter!): Connection! - } - `); - - const output = (await plugin( - testSchema, - [], - { - avoidOptionals: false, - maybeValue: 'T | undefined', - } as any, - { outputFile: 'graphql.ts' } - )) as Types.ComplexPluginOutput; - - // filter should be non-optional - expect(output.content).toBeSimilarStringTo(` - export type QueryResolvers = { - list?: Resolver>; - }; - `); - }); - it('#3257 - should not import mapper when its already imported because of enumValues', async () => { const testSchema = buildSchema(/* GraphQL */ ` schema { @@ -2470,80 +2352,4 @@ export type ResolverFn = ( await resolversTestingValidate(result); }); - - it('#7005 - avoidOptionals should preserve optional resolvers', async () => { - const testSchema = buildSchema(/* GraphQL */ ` - type Query { - users(filter: UserFilterInput = {}): [User!]! - ping: String! - } - - input UserFilterInput { - status: String = "ACTIVE" - } - - type User { - id: ID! - } - `); - - const output = (await plugin( - testSchema, - [], - { - avoidOptionals: { - defaultValue: true, - field: true, - inputValue: true, - object: true, - resolvers: false, - }, - } as any, - { outputFile: 'graphql.ts' } - )) as Types.ComplexPluginOutput; - - expect(output.content).toBeSimilarStringTo(` - export type QueryResolvers = { - users?: Resolver, ParentType, ContextType, RequireFields>; - ping?: Resolver; - }; - `); - }); - - it('#9438 - avoidOptionals should not wrap arguments with partial', async () => { - const testSchema = buildSchema(/* GraphQL */ ` - type Query { - users(filter: UserFilterInput): [User!]! - } - - input UserFilterInput { - status: String = "ACTIVE" - } - - type User { - id: ID! - } - `); - - const output = (await plugin( - testSchema, - [], - { - avoidOptionals: { - defaultValue: true, - field: true, - inputValue: true, - object: true, - resolvers: false, - }, - } as any, - { outputFile: 'graphql.ts' } - )) as Types.ComplexPluginOutput; - - expect(output.content).toBeSimilarStringTo(` - export type QueryResolvers = { - users?: Resolver, ParentType, ContextType, QueryUsersArgs>; - }; - `); - }); }); diff --git a/packages/plugins/typescript/typescript/src/typescript-variables-to-object.ts b/packages/plugins/typescript/typescript/src/typescript-variables-to-object.ts index 6e1c422a1ef..2c8d42ce25a 100644 --- a/packages/plugins/typescript/typescript/src/typescript-variables-to-object.ts +++ b/packages/plugins/typescript/typescript/src/typescript-variables-to-object.ts @@ -1,7 +1,6 @@ import { - AvoidOptionalsConfig, ConvertNameFn, - normalizeAvoidOptionals, + NormalizedAvoidOptionalsConfig, NormalizedScalarsMap, OperationVariablesToObject, ParsedDirectiveArgumentAndInputFieldMappings, @@ -13,7 +12,7 @@ export class TypeScriptOperationVariablesToObject extends OperationVariablesToOb constructor( _scalars: NormalizedScalarsMap, _convertName: ConvertNameFn, - private _avoidOptionals: boolean | AvoidOptionalsConfig, + private _avoidOptionals: NormalizedAvoidOptionalsConfig, private _immutableTypes: boolean, _namespacedImportName: string | null = null, _enumNames: string[] = [], @@ -83,7 +82,7 @@ export class TypeScriptOperationVariablesToObject extends OperationVariablesToOb } protected getAvoidOption(isNonNullType: boolean, hasDefaultValue: boolean) { - const options = normalizeAvoidOptionals(this._avoidOptionals); + const options = this._avoidOptionals; return ((options.object || !options.defaultValue) && hasDefaultValue) || (!options.object && !isNonNullType); } diff --git a/packages/plugins/typescript/typescript/src/visitor.ts b/packages/plugins/typescript/typescript/src/visitor.ts index 16c8ca78bbb..55c4ebd33c9 100644 --- a/packages/plugins/typescript/typescript/src/visitor.ts +++ b/packages/plugins/typescript/typescript/src/visitor.ts @@ -1,5 +1,4 @@ import { - AvoidOptionalsConfig, BaseTypesVisitor, DeclarationBlock, DeclarationKind, @@ -7,6 +6,7 @@ import { indent, isOneOfInputObjectType, normalizeAvoidOptionals, + NormalizedAvoidOptionalsConfig, ParsedTypesConfig, transformComment, wrapWithSingleQuotes, @@ -30,7 +30,7 @@ import { TypeScriptPluginConfig } from './config.js'; import { TypeScriptOperationVariablesToObject } from './typescript-variables-to-object.js'; export interface TypeScriptPluginParsedConfig extends ParsedTypesConfig { - avoidOptionals: AvoidOptionalsConfig; + avoidOptionals: NormalizedAvoidOptionalsConfig; constEnums: boolean; enumsAsTypes: boolean; futureProofEnums: boolean;