Skip to content
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

Excluded the __typename from the root node of subscriptions when using addTypenameSelectionDocumentTransform with documentTransforms #9889

Merged
merged 5 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/light-clocks-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/client-preset': minor
---

The \_\_typename should not be added to the root node of a subscription when using addTypenameSelectionDocumentTransform with documentTransforms since a single root node is expected and the code generator fails because of that (refer to https://spec.graphql.org/draft/#sec-Single-root-field)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Kind, visit } from 'graphql';
import { ASTNode, OperationDefinitionNode, Kind, visit } from 'graphql';
import { Types } from '@graphql-codegen/plugin-helpers';

/**
* Automatically adds `__typename` selections to every object type in your GraphQL document.
* Automatically adds `__typename` selections to every object type in your GraphQL document except the root node in subscriptions since a single root field is expected (https://spec.graphql.org/draft/#sec-Single-root-field).
* This is useful for GraphQL Clients such as Apollo Client or urql that need typename information for their cache to function.
*/
export const addTypenameSelectionDocumentTransform: Types.DocumentTransformObject = {
Expand All @@ -11,8 +11,13 @@ export const addTypenameSelectionDocumentTransform: Types.DocumentTransformObjec
...document,
document: document.document
? visit(document.document, {
SelectionSet(node) {
SelectionSet(node, _, parent) {
const isSubscriptionRoot =
typeof (parent as ASTNode)?.kind === 'string' &&
(parent as ASTNode).kind === 'OperationDefinition' &&
(parent as OperationDefinitionNode).operation === 'subscription';
if (
!isSubscriptionRoot &&
!node.selections.find(selection => selection.kind === 'Field' && selection.name.value === '__typename')
) {
return {
Expand Down
120 changes: 119 additions & 1 deletion packages/presets/client/tests/client-preset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';
import { executeCodegen } from '@graphql-codegen/cli';
import { mergeOutputs } from '@graphql-codegen/plugin-helpers';
import { validateTs } from '@graphql-codegen/testing';
import { preset } from '../src/index.js';
import { addTypenameSelectionDocumentTransform, preset } from '../src/index.js';
import { print } from 'graphql';

describe('client-preset', () => {
Expand Down Expand Up @@ -2513,5 +2513,123 @@ export * from "./gql.js";`);
}\`) as unknown as TypedDocumentString<VideoQuery, VideoQueryVariables>;
`);
});

it('correctly skips the typename addition for the root node for subscriptions', async () => {
const result = await executeCodegen({
schema: [
/* GraphQL */ `
schema {
query: Query
mutation: Mutation
subscription: Subscription
}

type Region {
regionId: Int!
regionDescription: String!
}

type Subscription {
onRegionCreated: Region!
}

type Query {
regions: [Region]
}

type Mutation {
createRegion(regionDescription: String!): Region
}
`,
],
documents: path.join(__dirname, 'fixtures/subscription-root-node.ts'),
generates: {
'out1/': {
preset,
config: {
documentMode: 'string',
},
documentTransforms: [addTypenameSelectionDocumentTransform],
},
},
});

const graphqlFile = result.find(file => file.filename === 'out1/graphql.ts');
expect(graphqlFile.content).toBeSimilarStringTo(`
/* eslint-disable */
import { DocumentTypeDecoration } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
};

export type Mutation = {
__typename?: 'Mutation';
createRegion?: Maybe<Region>;
};


export type MutationCreateRegionArgs = {
regionDescription: Scalars['String']['input'];
};

export type Query = {
__typename?: 'Query';
regions?: Maybe<Array<Maybe<Region>>>;
};

export type Region = {
__typename?: 'Region';
regionDescription: Scalars['String']['output'];
regionId: Scalars['Int']['output'];
};

export type Subscription = {
__typename?: 'Subscription';
onRegionCreated: Region;
};

export type OnRegionCreatedSubscriptionVariables = Exact<{ [key: string]: never; }>;


export type OnRegionCreatedSubscription = { __typename?: 'Subscription', onRegionCreated: { __typename: 'Region', regionId: number, regionDescription: string } };

export class TypedDocumentString<TResult, TVariables>
extends String
implements DocumentTypeDecoration<TResult, TVariables>
{
__apiType?: DocumentTypeDecoration<TResult, TVariables>['__apiType'];

constructor(private value: string, public __meta__?: Record<string, any>) {
super(value);
}

toString(): string & DocumentTypeDecoration<TResult, TVariables> {
return this.value;
}
}

export const OnRegionCreatedDocument = new TypedDocumentString(\`
subscription onRegionCreated {
onRegionCreated {
__typename
regionId
regionDescription
}
}
\`) as unknown as TypedDocumentString<OnRegionCreatedSubscription, OnRegionCreatedSubscriptionVariables>;
`);
});
});
});
12 changes: 12 additions & 0 deletions packages/presets/client/tests/fixtures/subscription-root-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
//@ts-ignore
import gql from 'gql-tag';

gql(`
subscription onRegionCreated {
onRegionCreated{
regionId
regionDescription
}
}
`);