diff --git a/.changeset/tricky-eyes-sneeze.md b/.changeset/tricky-eyes-sneeze.md new file mode 100644 index 0000000000000..7e9b9b5b99be7 --- /dev/null +++ b/.changeset/tricky-eyes-sneeze.md @@ -0,0 +1,5 @@ +--- +'@graphql-mesh/transform-cache': patch +--- + +Ensure cache transform respects subfield arguments. diff --git a/packages/transforms/cache/src/compute-cache-key.ts b/packages/transforms/cache/src/compute-cache-key.ts index 4eb9013a6e460..7525885f14ea0 100644 --- a/packages/transforms/cache/src/compute-cache-key.ts +++ b/packages/transforms/cache/src/compute-cache-key.ts @@ -9,7 +9,7 @@ export function computeCacheKey(options: { info: GraphQLResolveInfo; }): string { const argsHash = options.args ? objectHash(options.args, { ignoreUnknown: true }) : ''; - const fieldsObj = graphqlFields(options.info); + const fieldsObj = graphqlFields(options.info, {}, { processArguments: true }); const fieldNamesHash = objectHash(fieldsObj, { ignoreUnknown: true }); if (!options.keyStr) { diff --git a/packages/transforms/cache/test/cache.spec.ts b/packages/transforms/cache/test/cache.spec.ts index 14b468997d45c..7b71e404094fb 100644 --- a/packages/transforms/cache/test/cache.spec.ts +++ b/packages/transforms/cache/test/cache.spec.ts @@ -100,6 +100,11 @@ const spies = { }); }), }, + User: { + friend: jest.fn().mockImplementation((_, { id }) => { + return MOCK_DATA.find(u => u.id.toString() === id.toString()); + }), + }, }; describe('cache', () => { @@ -132,6 +137,7 @@ describe('cache', () => { username: String! email: String! profile: Profile! + friend(id: ID!): User } type Profile { @@ -476,5 +482,58 @@ describe('cache', () => { expect(await cache.get(expectedCacheKey)).toBeDefined(); expect(spies.Query.user.mock.calls.length).toBe(2); }); + + describe('Subfields', () => { + it('Should cache queries including subfield arguments', async () => { + const transform = new CacheTransform({ + config: [{ field: 'Query.user' }], + cache, + pubsub, + baseDir, + }); + const schemaWithCache = transform.transformSchema(schema); + + // First query should call resolver and fill cache + const executeOptions1 = { + schema: schemaWithCache, + document: parse(/* GraphQL */ ` + query { + user(id: 1) { + friend(id: 2) { + id + } + } + } + `), + }; + const { data: actual1 } = await execute(executeOptions1); + expect(spies.Query.user.mock.calls.length).toBe(1); + expect(actual1.user.friend.id).toBe('2'); + + // Second query should call resolver and also fill cache + const executeOptions2 = { + schema: schemaWithCache, + document: parse(/* GraphQL */ ` + query { + user(id: 1) { + friend(id: 3) { + id + } + } + } + `), + }; + const { data: actual2 } = await execute(executeOptions2); + expect(spies.Query.user.mock.calls.length).toBe(2); + expect(actual2.user.friend.id).toBe('3'); + + // Repeat both queries, no new calls for resolver + const { data: repeat1 } = await execute(executeOptions1); + const { data: repeat2 } = await execute(executeOptions2); + expect(spies.Query.user.mock.calls.length).toBe(2); + expect(repeat1.user.friend.id).toBe('2'); + expect(repeat2.user.friend.id).toBe('3'); + }); + }); }); });