From dfc5310ab476bed6deaefc608f311ff368722f7e Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Wed, 15 May 2024 19:46:51 +1000 Subject: [PATCH] [typescript-resolvers] Add meta field to typescript-resolvers plugin's output (#9961) * Make typescript-resolvers generate meta of generated typenames * Add changeset for @graphql-codegen/plugin-helpers * Add changeset for typescript-resolvers meta field * Use minor instead of patch for typescript-resolvers --- .changeset/curvy-lobsters-kneel.md | 5 + .changeset/new-radios-flash.md | 6 ++ .../src/base-resolvers-visitor.ts | 42 ++++++-- .../plugins/typescript/resolvers/src/index.ts | 16 +-- .../resolvers/tests/ts-resolvers.spec.ts | 97 +++++++++++++++++++ packages/utils/plugins-helpers/src/types.ts | 7 +- 6 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 .changeset/curvy-lobsters-kneel.md create mode 100644 .changeset/new-radios-flash.md diff --git a/.changeset/curvy-lobsters-kneel.md b/.changeset/curvy-lobsters-kneel.md new file mode 100644 index 00000000000..de05c36d7af --- /dev/null +++ b/.changeset/curvy-lobsters-kneel.md @@ -0,0 +1,5 @@ +--- +'@graphql-codegen/plugin-helpers': patch +--- + +Update plugin output type to allow option `meta` field diff --git a/.changeset/new-radios-flash.md b/.changeset/new-radios-flash.md new file mode 100644 index 00000000000..a9aee029c65 --- /dev/null +++ b/.changeset/new-radios-flash.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/visitor-plugin-common': minor +'@graphql-codegen/typescript-resolvers': minor +--- + +Update typescript-resolvers to report generated resolver types in the run to meta field in the output 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 3774a953dfd..69956220a84 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 @@ -634,7 +634,7 @@ export class BaseResolversVisitor< > extends BaseVisitor { protected _parsedConfig: TPluginConfig; protected _declarationBlockConfig: DeclarationBlockConfig = {}; - protected _collectedResolvers: { [key: string]: string } = {}; + protected _collectedResolvers: { [key: string]: { typename: string; baseGeneratedTypename?: string } } = {}; protected _collectedDirectiveResolvers: { [key: string]: string } = {}; protected _variablesTransformer: OperationVariablesToObject; protected _usedMappers: { [key: string]: boolean } = {}; @@ -1264,12 +1264,13 @@ export class BaseResolversVisitor< return this._hasFederation; } - public getRootResolver(): string { + public getRootResolver(): { content: string; generatedResolverTypes: Record } { const name = this.convertName(this.config.allResolversTypeName); const declarationKind = 'type'; const contextType = ``; - return [ + const generatedResolverTypes: Record = {}; + const content = [ new DeclarationBlock(this._declarationBlockConfig) .export() .asKind(declarationKind) @@ -1279,11 +1280,20 @@ export class BaseResolversVisitor< .map(schemaTypeName => { const resolverType = this._collectedResolvers[schemaTypeName]; - return indent(this.formatRootResolver(schemaTypeName, resolverType, declarationKind)); + if (resolverType.baseGeneratedTypename) { + generatedResolverTypes[schemaTypeName] = { name: resolverType.baseGeneratedTypename }; + } + + return indent(this.formatRootResolver(schemaTypeName, resolverType.typename, declarationKind)); }) .join('\n') ).string, ].join('\n'); + + return { + content, + generatedResolverTypes, + }; } protected formatRootResolver(schemaTypeName: string, resolverType: string, declarationKind: DeclarationKind): string { @@ -1536,7 +1546,10 @@ export class BaseResolversVisitor< .withName(name, ``) .withBlock(fieldsContent.join('\n')); - this._collectedResolvers[node.name as any] = name + ''; + this._collectedResolvers[node.name as any] = { + typename: name + '', + baseGeneratedTypename: name, + }; return block.string; } @@ -1552,7 +1565,10 @@ export class BaseResolversVisitor< .map(f => `'${f}'`) .join(' | '); - this._collectedResolvers[node.name as any] = name + ''; + this._collectedResolvers[node.name as any] = { + typename: name + '', + baseGeneratedTypename: name, + }; const parentType = this.getParentTypeToUse(node.name as any as string); return new DeclarationBlock(this._declarationBlockConfig) @@ -1577,7 +1593,9 @@ export class BaseResolversVisitor< } this._hasScalars = true; - this._collectedResolvers[node.name as any] = 'GraphQLScalarType'; + this._collectedResolvers[node.name as any] = { + typename: 'GraphQLScalarType', + }; return new DeclarationBlock({ ...this._declarationBlockConfig, @@ -1667,7 +1685,10 @@ export class BaseResolversVisitor< } const name = this.convertName(node, { suffix: this.config.resolverTypeSuffix }); - this._collectedResolvers[rawTypeName] = name; + this._collectedResolvers[rawTypeName] = { + typename: name, + baseGeneratedTypename: name, + }; const hasExplicitValues = this.config.enumValues[rawTypeName]?.mappedValues; return new DeclarationBlock(this._declarationBlockConfig) @@ -1689,7 +1710,10 @@ export class BaseResolversVisitor< const allTypesMap = this._schema.getTypeMap(); const implementingTypes: string[] = []; - this._collectedResolvers[node.name as any] = name + ''; + this._collectedResolvers[node.name as any] = { + typename: name + '', + baseGeneratedTypename: name, + }; for (const graphqlType of Object.values(allTypesMap)) { if (graphqlType instanceof GraphQLObjectType) { diff --git a/packages/plugins/typescript/resolvers/src/index.ts b/packages/plugins/typescript/resolvers/src/index.ts index 758d97c8133..70dabc18423 100644 --- a/packages/plugins/typescript/resolvers/src/index.ts +++ b/packages/plugins/typescript/resolvers/src/index.ts @@ -12,11 +12,10 @@ import { TypeScriptResolversVisitor } from './visitor.js'; const capitalize = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1); -export const plugin: PluginFunction = ( - schema: GraphQLSchema, - documents: Types.DocumentFile[], - config: TypeScriptResolversPluginConfig -) => { +export const plugin: PluginFunction< + TypeScriptResolversPluginConfig, + Types.ComplexPluginOutput<{ generatedResolverTypes: Record }> +> = (schema: GraphQLSchema, documents: Types.DocumentFile[], config: TypeScriptResolversPluginConfig) => { const imports = []; if (!config.customResolveInfo) { imports.push('GraphQLResolveInfo'); @@ -280,6 +279,8 @@ export type DirectiveResolverFn typeof d === 'string'), - getRootResolver(), + rootResolver.content, getAllDirectiveResolvers(), ].join('\n'), + meta: { + generatedResolverTypes: rootResolver.generatedResolverTypes, + }, }; }; diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts index 9cb97e5fd33..bbc4ce32677 100644 --- a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts @@ -3039,4 +3039,101 @@ export type ResolverFn = ( }; `); }); + + it('generates meta correctly', async () => { + const result = await plugin( + buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + post(id: ID!): Post + } + + type Mutation { + createUser(name: String!): CreateUserPayload! + } + + interface Node { + id: ID! + } + type Post implements Node { + id: ID! + author: User + } + type User implements Node { + id: ID! + name: String + } + + type CreateUserOk { + user: User! + } + + type CreateUserError { + error: ErrorType! + } + + union CreateUserPayload = CreateUserOk | CreateUserError + + enum ErrorType { + FORBIDDEN_ERROR + INTERNAL_ERROR + } + `), + [], + { + namingConvention: 'change-case-all#snakeCase', + enumValues: { + ErrorType: { + FORBIDDEN_ERROR: '403', + INTERNAL_ERROR: '500', + }, + }, + }, + { outputFile: '' } + ); + + expect(result.content).toContain(`export type create_user_error_resolvers`); + expect(result.content).toContain(`export type create_user_ok_resolvers`); + expect(result.content).toContain(`export type create_user_payload_resolvers`); + expect(result.content).toContain(`export type error_type_resolvers`); + expect(result.content).toContain(`export type mutation_resolvers`); + expect(result.content).toContain(`export type node_resolvers`); + expect(result.content).toContain(`export type post_resolvers`); + expect(result.content).toContain(`export type query_resolvers`); + expect(result.content).toContain(`export type user_resolvers`); + + expect(result.meta).toMatchInlineSnapshot(` + Object { + "generatedResolverTypes": Object { + "CreateUserError": Object { + "name": "create_user_error_resolvers", + }, + "CreateUserOk": Object { + "name": "create_user_ok_resolvers", + }, + "CreateUserPayload": Object { + "name": "create_user_payload_resolvers", + }, + "ErrorType": Object { + "name": "error_type_resolvers", + }, + "Mutation": Object { + "name": "mutation_resolvers", + }, + "Node": Object { + "name": "node_resolvers", + }, + "Post": Object { + "name": "post_resolvers", + }, + "Query": Object { + "name": "query_resolvers", + }, + "User": Object { + "name": "user_resolvers", + }, + }, + } + `); + }); }); diff --git a/packages/utils/plugins-helpers/src/types.ts b/packages/utils/plugins-helpers/src/types.ts index 3f1f0804a94..46c7f043a90 100644 --- a/packages/utils/plugins-helpers/src/types.ts +++ b/packages/utils/plugins-helpers/src/types.ts @@ -549,7 +549,12 @@ export namespace Types { noSilentErrors?: boolean; } - export type ComplexPluginOutput = { content: string; prepend?: string[]; append?: string[] }; + export type ComplexPluginOutput> = { + content: string; + prepend?: string[]; + append?: string[]; + meta?: M; + }; export type PluginOutput = string | ComplexPluginOutput; export type HookFunction = (...args: any[]) => void | Promise; export type HookAlterFunction = (...args: any[]) => void | string | Promise;