diff --git a/packages/plugins/response-cache/src/plugin.ts b/packages/plugins/response-cache/src/plugin.ts index dc16179f0b..6e321893be 100644 --- a/packages/plugins/response-cache/src/plugin.ts +++ b/packages/plugins/response-cache/src/plugin.ts @@ -114,6 +114,11 @@ export type UseResponseCacheParameter * Defaults to `["id"]` */ idFields?: Array; + /** + * List of SchemaCoordinates in format {ObjectType}.{FieldName} which are ignored during scan for entities. + * Defaults to `[]` + */ + ignoreIdFieldsBySchemaCoordinate?: Array; /** * Whether the mutation execution result should be used for invalidating resources. * Defaults to `true` @@ -294,6 +299,7 @@ export function useResponseCache = {}> ttlPerSchemaCoordinate = {}, scopePerSchemaCoordinate = {}, idFields = ['id'], + ignoreIdFieldsBySchemaCoordinate = [], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, @@ -361,7 +367,11 @@ export function useResponseCache = {}> const resultTypeNames = unwrapTypenames(fieldConfig.type); typePerSchemaCoordinateMap.set(schemaCoordinates, resultTypeNames); - if (idFields.includes(fieldName) && !idFieldByTypeName.has(typeName)) { + if ( + idFields.includes(fieldName) && + !idFieldByTypeName.has(typeName) && + !ignoreIdFieldsBySchemaCoordinate?.includes(schemaCoordinates) + ) { idFieldByTypeName.set(typeName, fieldName); } diff --git a/packages/plugins/response-cache/test/response-cache.spec.ts b/packages/plugins/response-cache/test/response-cache.spec.ts index 61abd5ef35..48df9f6200 100644 --- a/packages/plugins/response-cache/test/response-cache.spec.ts +++ b/packages/plugins/response-cache/test/response-cache.spec.ts @@ -4031,3 +4031,129 @@ it('calls enabled fn after context building', async () => { }, }); }); + +it('id field in body is returned as is and not overwritten', async () => { + expect.assertions(1); + const queryResult = { + id: 'idString', + header: { id: { fieldA: 'Tick', fieldB: 'Trick', fieldC: 'Track' }, someField: 'someData' }, + }; + const schema = makeExecutableSchema({ + typeDefs: /* GraphQL */ ` + type Query { + subgraphObject: SubgraphObject + } + type SubgraphObject { + id: String + header: Header + } + type Header { + id: IdObject + someField: String + } + type IdObject { + fieldA: String + fieldB: String + fieldC: String + } + `, + resolvers: { Query: { subgraphObject: () => queryResult } }, + }); + const testkit = createTestkit( + [ + useEngine({ ...GraphQLJS, execute: normalizedExecutor, subscribe: normalizedExecutor }), + useResponseCache({ + session: () => null, + ttl: 0, + ignoreIdFieldsBySchemaCoordinate: ['Header.id'], + }), + ], + schema, + ); + + const document = /* GraphQL */ ` + query { + subgraphObject { + id + header { + id { + fieldA + fieldB + fieldC + } + someField + } + } + } + `; + + const result = await testkit.execute(document); + assertSingleExecutionValue(result); + expect(result).toMatchObject({ + data: { + subgraphObject: queryResult, + }, + }); +}); + +it('id field in body overwritten and request fails', async () => { + expect.assertions(1); + const queryResult = { + id: 'idString', + header: { id: { fieldA: 'Tick', fieldB: 'Trick', fieldC: 'Track' }, someField: 'someData' }, + }; + const schema = makeExecutableSchema({ + typeDefs: /* GraphQL */ ` + type Query { + subgraphObject: SubgraphObject + } + type SubgraphObject { + id: String + header: Header! + } + type Header { + id: IdObject! + someField: String + } + type IdObject { + fieldA: String! + fieldB: String! + fieldC: String! + } + `, + resolvers: { Query: { subgraphObject: () => queryResult } }, + }); + const testkit = createTestkit( + [ + useEngine({ ...GraphQLJS, execute: normalizedExecutor, subscribe: normalizedExecutor }), + useResponseCache({ + enabled(context: any): boolean { + return true; + }, + session(context: any): string | undefined | null { + return 'sessionString'; + }, + ttl: 0, + }), + ], + schema, + ); + + const document = /* GraphQL */ ` + query { + subgraphObject { + id + header { + id { + fieldA + fieldB + fieldC + } + someField + } + } + } + `; + + await expect(testkit.execute(document)).rejects.toThrow(TypeError); +});