From b1782e7470c16a0690d6ec7f80652b0d1495ff81 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Tue, 23 Sep 2025 23:24:42 +1000 Subject: [PATCH 1/4] Handle __isTypeOf correctly in graphql-modules-preset --- .../presets/graphql-modules/src/builder.ts | 19 +++- .../graphql-modules/tests/builder.spec.ts | 87 +++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/packages/presets/graphql-modules/src/builder.ts b/packages/presets/graphql-modules/src/builder.ts index bcd907e1300..faa3addafa6 100644 --- a/packages/presets/graphql-modules/src/builder.ts +++ b/packages/presets/graphql-modules/src/builder.ts @@ -34,6 +34,7 @@ type RegistryKeys = 'objects' | 'inputs' | 'interfaces' | 'scalars' | 'unions' | type Registry = Record; const registryKeys: RegistryKeys[] = ['objects', 'inputs', 'interfaces', 'scalars', 'unions', 'enums']; const resolverKeys: Array> = ['scalars', 'objects', 'enums']; +const withIsTypeOfKeys: Array<'objects'> = ['objects']; export function buildModule( name: string, @@ -65,6 +66,7 @@ export function buildModule( const picks: Record> = createObject(registryKeys, () => ({})); const defined: Registry = createObject(registryKeys, () => []); const extended: Registry = createObject(registryKeys, () => []); + const withIsTypeOf: { objects: string[] } = createObject(withIsTypeOfKeys, () => []); // List of types used in objects, fields, arguments etc const usedTypes = collectUsedTypes(doc); @@ -216,7 +218,9 @@ export function buildModule( 'DefinedFields', // In case of enabled `requireRootResolvers` flag, the preset has to produce a non-optional properties. requireRootResolvers && rootTypes.includes(name), - !rootTypes.includes(name) && defined.objects.includes(name) ? ` | '__isTypeOf'` : '' + !rootTypes.includes(name) && defined.objects.includes(name) && withIsTypeOf.objects.includes(name) + ? ` | '__isTypeOf'` + : '' ) ) .join('\n'), @@ -405,6 +409,11 @@ export function buildModule( case Kind.OBJECT_TYPE_DEFINITION: { defined.objects.push(name); collectFields(node, picks.objects); + + if (node.interfaces?.length > 0) { + withIsTypeOf.objects.push(name); + } + break; } @@ -433,6 +442,10 @@ export function buildModule( case Kind.UNION_TYPE_DEFINITION: { defined.unions.push(name); + + for (const namedType of node.types || []) { + pushUnique(withIsTypeOf.objects, namedType.name.value); + } break; } } @@ -453,6 +466,10 @@ export function buildModule( pushUnique(extended.objects, name); + if (node.interfaces?.length > 0) { + pushUnique(withIsTypeOf.objects, name); + } + break; } diff --git a/packages/presets/graphql-modules/tests/builder.spec.ts b/packages/presets/graphql-modules/tests/builder.spec.ts index 6286e531762..b42b32cce4a 100644 --- a/packages/presets/graphql-modules/tests/builder.spec.ts +++ b/packages/presets/graphql-modules/tests/builder.spec.ts @@ -479,3 +479,90 @@ test('should generate a signature for ResolveMiddleware (with widlcards)', () => }; `); }); + +test('only picks __isTypeOf from implementing types (of Interfaces) and union members', () => { + const output = buildModule( + 'test', + parse(/* GraphQL */ ` + type Query { + me: User + pet: Pet + offer: Offer + } + + type User { + id: ID! + username: String! + } + + interface Pet { + id: ID! + name: String! + } + type Cat implements Pet { + id: ID! + name: String! + canScratch: Boolean! + } + type Dog implements Pet { + id: ID! + name: String! + canBark: Boolean! + } + type Elephant { + id: ID! + } + extend type Elephant implements Pet { + name: String! + hasTrunk: Boolean! + } + + union Offer = Discount | Coupon + type Discount { + id: ID! + name: String! + } + type Coupon { + id: ID! + name: String! + } + `), + { + importPath: '../types', + importNamespace: 'core', + encapsulate: 'none', + requireRootResolvers: false, + shouldDeclare: false, + rootTypes: ROOT_TYPES, + baseVisitor, + useGraphQLModules: true, + } + ); + + // User does not pick `__isTypeOf` because it is not a union member, or implementing types + expect(output).toBeSimilarStringTo(` + export type UserResolvers = Pick; + `); + + // Cat picks `__isTypeOf` because it is an implementing type of Pet + expect(output).toBeSimilarStringTo(` + export type CatResolvers = Pick; + `); + // Dog picks `__isTypeOf` because it is an implementing type of Pet + expect(output).toBeSimilarStringTo(` + export type DogResolvers = Pick; + `); + // Elephant picks `__isTypeOf` because it is an implementing type of Pet, via `extend type ` + expect(output).toBeSimilarStringTo(` + export type ElephantResolvers = Pick; + `); + + // Discount picks `__isTypeOf` because it is a union member + expect(output).toBeSimilarStringTo(` + export type DiscountResolvers = Pick; + `); + // Coupon picks `__isTypeOf` because it is a union member + expect(output).toBeSimilarStringTo(` + export type CouponResolvers = Pick; + `); +}); From 5b0d040aca3a2c6010e25d814892e72e6926f3b7 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Tue, 23 Sep 2025 23:25:36 +1000 Subject: [PATCH 2/4] Add changeset --- .changeset/lemon-insects-attend.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lemon-insects-attend.md diff --git a/.changeset/lemon-insects-attend.md b/.changeset/lemon-insects-attend.md new file mode 100644 index 00000000000..4b6658cc71a --- /dev/null +++ b/.changeset/lemon-insects-attend.md @@ -0,0 +1,5 @@ +--- +'@graphql-codegen/graphql-modules-preset': patch +--- + +Fix \_\_isTypeOf wrongly picked on objects that are not implementing types or union members From 108061a8bee9ef4d55a91c18062eee33f27e4126 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Tue, 23 Sep 2025 23:37:10 +1000 Subject: [PATCH 3/4] Fix dev-tests --- dev-test/modules/blog/generated.ts | 2 +- dev-test/modules/dotanions/generated.ts | 2 +- dev-test/modules/users/generated.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-test/modules/blog/generated.ts b/dev-test/modules/blog/generated.ts index 20ee2adb586..265f7de1438 100644 --- a/dev-test/modules/blog/generated.ts +++ b/dev-test/modules/blog/generated.ts @@ -10,7 +10,7 @@ export namespace BlogModule { export type User = Types.User; export type Query = Pick; - export type ArticleResolvers = Pick; + export type ArticleResolvers = Pick; export type QueryResolvers = Pick; export interface Resolvers { diff --git a/dev-test/modules/dotanions/generated.ts b/dev-test/modules/dotanions/generated.ts index 387e7a1fe9c..526718e11b5 100644 --- a/dev-test/modules/dotanions/generated.ts +++ b/dev-test/modules/dotanions/generated.ts @@ -23,7 +23,7 @@ export namespace DotanionsModule { export type PaypalResolvers = Pick; export type CreditCardResolvers = Pick; - export type DonationResolvers = Pick; + export type DonationResolvers = Pick; export type MutationResolvers = Pick; export type UserResolvers = Pick; diff --git a/dev-test/modules/users/generated.ts b/dev-test/modules/users/generated.ts index 46d4631d22d..b3f468dab5d 100644 --- a/dev-test/modules/users/generated.ts +++ b/dev-test/modules/users/generated.ts @@ -9,7 +9,7 @@ export namespace UsersModule { export type User = Pick; export type Query = Pick; - export type UserResolvers = Pick; + export type UserResolvers = Pick; export type QueryResolvers = Pick; export interface Resolvers { From d3256d9fcce8c6b3042fddbb280ab439609d2992 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Tue, 23 Sep 2025 23:40:29 +1000 Subject: [PATCH 4/4] Update unit tests --- .../tests/__snapshots__/integration.spec.ts.snap | 2 +- packages/presets/graphql-modules/tests/integration.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/presets/graphql-modules/tests/__snapshots__/integration.spec.ts.snap b/packages/presets/graphql-modules/tests/__snapshots__/integration.spec.ts.snap index fc8c0fbeb7b..6496f5e52ec 100644 --- a/packages/presets/graphql-modules/tests/__snapshots__/integration.spec.ts.snap +++ b/packages/presets/graphql-modules/tests/__snapshots__/integration.spec.ts.snap @@ -25,7 +25,7 @@ export type Mutation = Pick; export type PaypalResolvers = Pick; export type CreditCardResolvers = Pick; -export type DonationResolvers = Pick; +export type DonationResolvers = Pick; export type MutationResolvers = Pick; export type UserResolvers = Pick; diff --git a/packages/presets/graphql-modules/tests/integration.spec.ts b/packages/presets/graphql-modules/tests/integration.spec.ts index 2626da08a14..0bdff01efe9 100644 --- a/packages/presets/graphql-modules/tests/integration.spec.ts +++ b/packages/presets/graphql-modules/tests/integration.spec.ts @@ -34,7 +34,7 @@ describe('Integration', () => { test('should not duplicate type even if type and extend type are in the same module', async () => { const { result } = await executeCodegen(options); - const userResolversStr = `export type UserResolvers = Pick;`; + const userResolversStr = `export type UserResolvers = Pick;`; const nbOfTimeUserResolverFound = result[4].content.split(userResolversStr).length - 1; expect(nbOfTimeUserResolverFound).toBe(1); @@ -176,7 +176,7 @@ describe('Integration', () => { // Only Query related properties should be required expect(usersModuleOutput.content).toBeSimilarStringTo(` - export type UserResolvers = Pick; + export type UserResolvers = Pick; export type QueryResolvers = Required>; `); expect(usersModuleOutput.content).toBeSimilarStringTo(`