diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/client-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/client-generator.test.ts.snap index a088b8860..41c85c7c3 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/client-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/client-generator.test.ts.snap @@ -159,7 +159,7 @@ export class FetchAdapter implements GraphQLAdapter { fetchFn?: typeof globalThis.fetch, ) { this.headers = headers ?? {}; - this.fetchFn = fetchFn ?? createFetch(); + this.fetchFn = (fetchFn ?? createFetch()).bind(globalThis); } async execute( diff --git a/graphql/codegen/src/__tests__/codegen/client-generator.test.ts b/graphql/codegen/src/__tests__/codegen/client-generator.test.ts index a2526c8b9..2badd5af4 100644 --- a/graphql/codegen/src/__tests__/codegen/client-generator.test.ts +++ b/graphql/codegen/src/__tests__/codegen/client-generator.test.ts @@ -76,6 +76,34 @@ describe('client-generator', () => { ); expect(result.content).toContain('createFetch()'); }); + + it('binds fetchFn to globalThis to avoid Illegal invocation in browsers', () => { + const result = generateOrmClientFile(); + + // The generated FetchAdapter must .bind(globalThis) to prevent + // "Illegal invocation" when native window.fetch is stored as + // an instance property (which detaches it from its original this). + expect(result.content).toContain('.bind(globalThis)'); + }); + + it('bind(globalThis) prevents Illegal invocation for this-sensitive fetch', () => { + // Simulate a native browser fetch that requires this === globalThis + // (calling window.fetch with any other this throws TypeError) + function thisSensitiveFetch(this: unknown): Promise { + if (this !== globalThis) { + throw new TypeError('Illegal invocation'); + } + return Promise.resolve(new Response(JSON.stringify({ data: null }))); + } + + // Without bind: storing on an object and calling detaches this + const broken = { fetchFn: thisSensitiveFetch }; + expect(() => broken.fetchFn()).toThrow('Illegal invocation'); + + // With bind: the pattern used in FetchAdapter keeps correct this + const fixed = { fetchFn: thisSensitiveFetch.bind(globalThis) }; + expect(() => fixed.fetchFn()).not.toThrow(); + }); }); describe('generateQueryBuilderFile', () => { diff --git a/graphql/codegen/src/core/codegen/templates/orm-client.ts b/graphql/codegen/src/core/codegen/templates/orm-client.ts index ab9c99ced..d7c5c578f 100644 --- a/graphql/codegen/src/core/codegen/templates/orm-client.ts +++ b/graphql/codegen/src/core/codegen/templates/orm-client.ts @@ -63,7 +63,7 @@ export class FetchAdapter implements GraphQLAdapter { fetchFn?: typeof globalThis.fetch, ) { this.headers = headers ?? {}; - this.fetchFn = fetchFn ?? createFetch(); + this.fetchFn = (fetchFn ?? createFetch()).bind(globalThis); } async execute(