diff --git a/.changeset/proud-jobs-decide.md b/.changeset/proud-jobs-decide.md new file mode 100644 index 00000000000..36c3a298ac4 --- /dev/null +++ b/.changeset/proud-jobs-decide.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/visitor-plugin-common': minor +'@graphql-codegen/typescript-operations': minor +--- + +Add generatesOperationTypes to typescript-operations to allow omitting operation types such as Variables, Query/Mutation/Subscription selection set, and Fragment types diff --git a/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts index 394804884e3..8ef3b117e88 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts @@ -41,6 +41,7 @@ export interface ParsedDocumentsConfig extends ParsedTypesConfig { experimentalFragmentVariables: boolean; mergeFragmentTypes: boolean; customDirectives: CustomDirectivesConfig; + generatesOperationTypes: boolean; } export interface RawDocumentsConfig extends RawTypesConfig { @@ -175,6 +176,29 @@ export interface RawDocumentsConfig extends RawTypesConfig { * ``` */ customDirectives?: CustomDirectivesConfig; + + /** + * @description Whether to generate operation types such as Variables, Query/Mutation/Subscription selection set, and Fragment types + * @default true + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript-operations'], + * config: { + * generatesOperationTypes: false, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + generatesOperationTypes?: boolean; } export class BaseDocumentsVisitor< @@ -207,6 +231,7 @@ export class BaseDocumentsVisitor< operationResultSuffix: getConfigValue(rawConfig.operationResultSuffix, ''), scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars), customDirectives: getConfigValue(rawConfig.customDirectives, { apolloUnmask: false }), + generatesOperationTypes: getConfigValue(rawConfig.generatesOperationTypes, true), ...((additionalConfig || {}) as any), }); @@ -261,6 +286,10 @@ export class BaseDocumentsVisitor< } FragmentDefinition(node: FragmentDefinitionNode): string { + if (!this.config.generatesOperationTypes) { + return null; + } + const fragmentRootType = this._schema.getType(node.typeCondition.name.value); const selectionSet = this._selectionSetToObject.createNext(fragmentRootType, node.selectionSet); const fragmentSuffix = this.getFragmentSuffix(node); @@ -289,7 +318,11 @@ export class BaseDocumentsVisitor< return variablesBlock; } - OperationDefinition(node: OperationDefinitionNode): string { + OperationDefinition(node: OperationDefinitionNode): string | null { + if (!this.config.generatesOperationTypes) { + return null; + } + const name = this.handleAnonymousOperation(node); const operationRootType = getRootType(node.operation, this._schema); diff --git a/packages/plugins/typescript/operations/src/index.ts b/packages/plugins/typescript/operations/src/index.ts index 3e1497bc8f2..107e330309c 100644 --- a/packages/plugins/typescript/operations/src/index.ts +++ b/packages/plugins/typescript/operations/src/index.ts @@ -65,7 +65,7 @@ export const plugin: PluginFunction = { [K in keyof T]: T[K] };', + visitor.getExactUtilityType(), ], content: content.join('\n'), }; diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 406cf788237..408687875d1 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -254,4 +254,12 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< useTypeImports: this.config.useTypeImports, }); } + + getExactUtilityType(): string | null { + if (!this.config.generatesOperationTypes) { + return null; + } + + return 'type Exact = { [K in keyof T]: T[K] };'; + } } diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts index f3106cf72a1..2f1cb9a5485 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -1,5 +1,5 @@ import { mergeOutputs } from '@graphql-codegen/plugin-helpers'; -// import { validateTs } from '@graphql-codegen/testing'; +import { validateTs } from '@graphql-codegen/testing'; import { buildSchema, parse } from 'graphql'; import { plugin } from '../src/index.js'; @@ -171,4 +171,132 @@ describe('TypeScript Operations Plugin - Standalone', () => { " `); }); + + it('does not generate Variables, Result or Fragments when generatesOperationTypes is false', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + user(id: ID!): User + users(input: UsersInput!): UsersResponse! + } + + type Mutation { + makeUserAdmin(id: ID!): User! + } + + type Subscription { + userChanges(id: ID!): User! + } + + type ResponseError { + error: ResponseErrorType! + } + + enum ResponseErrorType { + NOT_FOUND + INPUT_VALIDATION_ERROR + FORBIDDEN_ERROR + UNEXPECTED_ERROR + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + "UserRole Description" + enum UserRole { + "UserRole ADMIN" + ADMIN + "UserRole CUSTOMER" + CUSTOMER + } + + "UsersInput Description" + input UsersInput { + "UsersInput from" + from: DateTime + "UsersInput to" + to: DateTime + role: UserRole + } + + type UsersResponseOk { + result: [User!]! + } + union UsersResponse = UsersResponseOk | ResponseError + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query User($id: ID!) { + user(id: $id) { + id + name + role + createdAt + } + } + + query Users($input: UsersInput!) { + users(input: $input) { + ... on UsersResponseOk { + result { + ...UserFragment + } + } + ... on ResponseError { + error + } + } + } + + query UsersWithScalarInput($from: DateTime!, $to: DateTime, $role: UserRole) { + users(input: { from: $from, to: $to, role: $role }) { + ... on UsersResponseOk { + result { + __typename + } + } + ... on ResponseError { + __typename + } + } + } + + mutation MakeAdmin { + makeUserAdmin(id: "100") { + ...UserFragment + } + } + + subscription UserChanges { + makeUserAdmin(id: "100") { + ...UserFragment + } + } + + fragment UserFragment on User { + id + role + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { generatesOperationTypes: false })]); + + expect(result).toMatchInlineSnapshot(` + " + /** UserRole Description */ + export type UserRole = + /** UserRole ADMIN */ + | 'ADMIN' + /** UserRole CUSTOMER */ + | 'CUSTOMER'; + + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); });