Skip to content

Commit c39ae26

Browse files
authored
Add generatesOperationTypes config option to generate shared types only (#10529)
1 parent ad00ac8 commit c39ae26

File tree

5 files changed

+178
-3
lines changed

5 files changed

+178
-3
lines changed

.changeset/proud-jobs-decide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphql-codegen/visitor-plugin-common': minor
3+
'@graphql-codegen/typescript-operations': minor
4+
---
5+
6+
Add generatesOperationTypes to typescript-operations to allow omitting operation types such as Variables, Query/Mutation/Subscription selection set, and Fragment types

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface ParsedDocumentsConfig extends ParsedTypesConfig {
4141
experimentalFragmentVariables: boolean;
4242
mergeFragmentTypes: boolean;
4343
customDirectives: CustomDirectivesConfig;
44+
generatesOperationTypes: boolean;
4445
}
4546

4647
export interface RawDocumentsConfig extends RawTypesConfig {
@@ -175,6 +176,29 @@ export interface RawDocumentsConfig extends RawTypesConfig {
175176
* ```
176177
*/
177178
customDirectives?: CustomDirectivesConfig;
179+
180+
/**
181+
* @description Whether to generate operation types such as Variables, Query/Mutation/Subscription selection set, and Fragment types
182+
* @default true
183+
* @exampleMarkdown
184+
* ```ts filename="codegen.ts"
185+
* import type { CodegenConfig } from '@graphql-codegen/cli';
186+
*
187+
* const config: CodegenConfig = {
188+
* // ...
189+
* generates: {
190+
* 'path/to/file.ts': {
191+
* plugins: ['typescript-operations'],
192+
* config: {
193+
* generatesOperationTypes: false,
194+
* },
195+
* },
196+
* },
197+
* };
198+
* export default config;
199+
* ```
200+
*/
201+
generatesOperationTypes?: boolean;
178202
}
179203

180204
export class BaseDocumentsVisitor<
@@ -207,6 +231,7 @@ export class BaseDocumentsVisitor<
207231
operationResultSuffix: getConfigValue(rawConfig.operationResultSuffix, ''),
208232
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
209233
customDirectives: getConfigValue(rawConfig.customDirectives, { apolloUnmask: false }),
234+
generatesOperationTypes: getConfigValue(rawConfig.generatesOperationTypes, true),
210235
...((additionalConfig || {}) as any),
211236
});
212237

@@ -261,6 +286,10 @@ export class BaseDocumentsVisitor<
261286
}
262287

263288
FragmentDefinition(node: FragmentDefinitionNode): string {
289+
if (!this.config.generatesOperationTypes) {
290+
return null;
291+
}
292+
264293
const fragmentRootType = this._schema.getType(node.typeCondition.name.value);
265294
const selectionSet = this._selectionSetToObject.createNext(fragmentRootType, node.selectionSet);
266295
const fragmentSuffix = this.getFragmentSuffix(node);
@@ -289,7 +318,11 @@ export class BaseDocumentsVisitor<
289318
return variablesBlock;
290319
}
291320

292-
OperationDefinition(node: OperationDefinitionNode): string {
321+
OperationDefinition(node: OperationDefinitionNode): string | null {
322+
if (!this.config.generatesOperationTypes) {
323+
return null;
324+
}
325+
293326
const name = this.handleAnonymousOperation(node);
294327
const operationRootType = getRootType(node.operation, this._schema);
295328

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const plugin: PluginFunction<TypeScriptDocumentsPluginConfig, Types.Compl
6565
...visitor.getImports(),
6666
...visitor.getEnumsImports(),
6767
...visitor.getGlobalDeclarations(visitor.config.noExport),
68-
'type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };',
68+
visitor.getExactUtilityType(),
6969
],
7070
content: content.join('\n'),
7171
};

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,12 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor<
254254
useTypeImports: this.config.useTypeImports,
255255
});
256256
}
257+
258+
getExactUtilityType(): string | null {
259+
if (!this.config.generatesOperationTypes) {
260+
return null;
261+
}
262+
263+
return 'type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };';
264+
}
257265
}

packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { mergeOutputs } from '@graphql-codegen/plugin-helpers';
2-
// import { validateTs } from '@graphql-codegen/testing';
2+
import { validateTs } from '@graphql-codegen/testing';
33
import { buildSchema, parse } from 'graphql';
44
import { plugin } from '../src/index.js';
55

@@ -171,4 +171,132 @@ describe('TypeScript Operations Plugin - Standalone', () => {
171171
"
172172
`);
173173
});
174+
175+
it('does not generate Variables, Result or Fragments when generatesOperationTypes is false', async () => {
176+
const schema = buildSchema(/* GraphQL */ `
177+
type Query {
178+
user(id: ID!): User
179+
users(input: UsersInput!): UsersResponse!
180+
}
181+
182+
type Mutation {
183+
makeUserAdmin(id: ID!): User!
184+
}
185+
186+
type Subscription {
187+
userChanges(id: ID!): User!
188+
}
189+
190+
type ResponseError {
191+
error: ResponseErrorType!
192+
}
193+
194+
enum ResponseErrorType {
195+
NOT_FOUND
196+
INPUT_VALIDATION_ERROR
197+
FORBIDDEN_ERROR
198+
UNEXPECTED_ERROR
199+
}
200+
201+
type User {
202+
id: ID!
203+
name: String!
204+
role: UserRole!
205+
createdAt: DateTime!
206+
}
207+
208+
"UserRole Description"
209+
enum UserRole {
210+
"UserRole ADMIN"
211+
ADMIN
212+
"UserRole CUSTOMER"
213+
CUSTOMER
214+
}
215+
216+
"UsersInput Description"
217+
input UsersInput {
218+
"UsersInput from"
219+
from: DateTime
220+
"UsersInput to"
221+
to: DateTime
222+
role: UserRole
223+
}
224+
225+
type UsersResponseOk {
226+
result: [User!]!
227+
}
228+
union UsersResponse = UsersResponseOk | ResponseError
229+
230+
scalar DateTime
231+
`);
232+
const document = parse(/* GraphQL */ `
233+
query User($id: ID!) {
234+
user(id: $id) {
235+
id
236+
name
237+
role
238+
createdAt
239+
}
240+
}
241+
242+
query Users($input: UsersInput!) {
243+
users(input: $input) {
244+
... on UsersResponseOk {
245+
result {
246+
...UserFragment
247+
}
248+
}
249+
... on ResponseError {
250+
error
251+
}
252+
}
253+
}
254+
255+
query UsersWithScalarInput($from: DateTime!, $to: DateTime, $role: UserRole) {
256+
users(input: { from: $from, to: $to, role: $role }) {
257+
... on UsersResponseOk {
258+
result {
259+
__typename
260+
}
261+
}
262+
... on ResponseError {
263+
__typename
264+
}
265+
}
266+
}
267+
268+
mutation MakeAdmin {
269+
makeUserAdmin(id: "100") {
270+
...UserFragment
271+
}
272+
}
273+
274+
subscription UserChanges {
275+
makeUserAdmin(id: "100") {
276+
...UserFragment
277+
}
278+
}
279+
280+
fragment UserFragment on User {
281+
id
282+
role
283+
}
284+
`);
285+
286+
const result = mergeOutputs([await plugin(schema, [{ document }], { generatesOperationTypes: false })]);
287+
288+
expect(result).toMatchInlineSnapshot(`
289+
"
290+
/** UserRole Description */
291+
export type UserRole =
292+
/** UserRole ADMIN */
293+
| 'ADMIN'
294+
/** UserRole CUSTOMER */
295+
| 'CUSTOMER';
296+
297+
"
298+
`);
299+
300+
validateTs(result, undefined, undefined, undefined, undefined, true);
301+
});
174302
});

0 commit comments

Comments
 (0)