From a5f73a0b20128e10693af6111cca506e66cdae40 Mon Sep 17 00:00:00 2001 From: "patrick.rehn" Date: Tue, 4 Jun 2024 14:33:05 +0200 Subject: [PATCH 1/5] ignore schmemaCoordinates in the mapschema step for id fields not part of a subgraph --- packages/plugins/response-cache/src/plugin.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/plugins/response-cache/src/plugin.ts b/packages/plugins/response-cache/src/plugin.ts index dc16179f0b..a17b97dbbc 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 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,7 @@ 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); } From dbd45933a74bce9ae48f2cc1d3c2e4425b12ea9b Mon Sep 17 00:00:00 2001 From: "patrick.rehn" Date: Wed, 5 Jun 2024 15:14:59 +0200 Subject: [PATCH 2/5] added unittest to reprocude Issue --- .../test/response-cache.spec.ts | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/packages/plugins/response-cache/test/response-cache.spec.ts b/packages/plugins/response-cache/test/response-cache.spec.ts index 61abd5ef35..fe9a31151f 100644 --- a/packages/plugins/response-cache/test/response-cache.spec.ts +++ b/packages/plugins/response-cache/test/response-cache.spec.ts @@ -4031,3 +4031,128 @@ 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({ + session: () => null, + ttl:0 + }), + ], + 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, + }, + }); +}); \ No newline at end of file From 5d92250c77ffe41374c45df1cfde70ca9b91531e Mon Sep 17 00:00:00 2001 From: "patrick.rehn" Date: Thu, 6 Jun 2024 13:20:25 +0200 Subject: [PATCH 3/5] fixed last test to test for failing --- .../test/response-cache.spec.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/plugins/response-cache/test/response-cache.spec.ts b/packages/plugins/response-cache/test/response-cache.spec.ts index fe9a31151f..f77cd9ee5f 100644 --- a/packages/plugins/response-cache/test/response-cache.spec.ts +++ b/packages/plugins/response-cache/test/response-cache.spec.ts @@ -4107,16 +4107,16 @@ it('id field in body overwritten and request fails', async () => { }, type SubgraphObject { id: String, - header: Header, + header: Header!, }, type Header { - id: IdObject, + id: IdObject!, someField: String }, type IdObject { - fieldA: String, - fieldB: String, - fieldC: String, + fieldA: String!, + fieldB: String!, + fieldC: String!, } `, resolvers: { Query: { subgraphObject: () => queryResult } }, @@ -4125,7 +4125,11 @@ it('id field in body overwritten and request fails', async () => { [ useEngine({ ...GraphQLJS, execute: normalizedExecutor, subscribe: normalizedExecutor }), useResponseCache({ - session: () => null, + enabled(context: any): boolean { + return true; + }, session(context: any): string | undefined | null { + return "sessionString"; + }, ttl:0 }), ], @@ -4148,11 +4152,6 @@ it('id field in body overwritten and request fails', async () => { } `; - const result = await testkit.execute(document); - assertSingleExecutionValue(result); - expect(result).toMatchObject({ - data: { - subgraphObject: queryResult, - }, - }); + await expect( testkit.execute(document)).rejects.toThrow(TypeError); + }); \ No newline at end of file From bf60cedf027e27c09c4f1aedc2b30c832eb655eb Mon Sep 17 00:00:00 2001 From: "patrick.rehn" Date: Thu, 6 Jun 2024 13:23:26 +0200 Subject: [PATCH 4/5] documentation --- packages/plugins/response-cache/src/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/response-cache/src/plugin.ts b/packages/plugins/response-cache/src/plugin.ts index a17b97dbbc..a6501e55c0 100644 --- a/packages/plugins/response-cache/src/plugin.ts +++ b/packages/plugins/response-cache/src/plugin.ts @@ -115,7 +115,7 @@ export type UseResponseCacheParameter */ idFields?: Array; /** - * List of SchemaCoordinates which are ignored during scan for entities. + * List of SchemaCoordinates in format {ObjectType}.{FieldName} which are ignored during scan for entities. * Defaults to `[]` */ ignoreIdFieldsBySchemaCoordinate?: Array From 45271af22e7b8f0eab2b4f9fde7d60f1220a22ae Mon Sep 17 00:00:00 2001 From: "patrick.rehn" Date: Thu, 6 Jun 2024 13:58:34 +0200 Subject: [PATCH 5/5] run prettier --- packages/plugins/response-cache/src/plugin.ts | 8 +- .../test/response-cache.spec.ts | 94 ++++++++++--------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/packages/plugins/response-cache/src/plugin.ts b/packages/plugins/response-cache/src/plugin.ts index a6501e55c0..6e321893be 100644 --- a/packages/plugins/response-cache/src/plugin.ts +++ b/packages/plugins/response-cache/src/plugin.ts @@ -118,7 +118,7 @@ export type UseResponseCacheParameter * List of SchemaCoordinates in format {ObjectType}.{FieldName} which are ignored during scan for entities. * Defaults to `[]` */ - ignoreIdFieldsBySchemaCoordinate?: Array + ignoreIdFieldsBySchemaCoordinate?: Array; /** * Whether the mutation execution result should be used for invalidating resources. * Defaults to `true` @@ -367,7 +367,11 @@ export function useResponseCache = {}> const resultTypeNames = unwrapTypenames(fieldConfig.type); typePerSchemaCoordinateMap.set(schemaCoordinates, resultTypeNames); - if (idFields.includes(fieldName) && !idFieldByTypeName.has(typeName) && !ignoreIdFieldsBySchemaCoordinate?.includes(schemaCoordinates)) { + 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 f77cd9ee5f..48df9f6200 100644 --- a/packages/plugins/response-cache/test/response-cache.spec.ts +++ b/packages/plugins/response-cache/test/response-cache.spec.ts @@ -4035,39 +4035,40 @@ 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'} - } + 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, - }, + id: String + header: Header + } type Header { - id: IdObject, + id: IdObject someField: String - }, + } type IdObject { - fieldA: String, - fieldB: String, - fieldC: String, + 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, + [ + useEngine({ ...GraphQLJS, execute: normalizedExecutor, subscribe: normalizedExecutor }), + useResponseCache({ + session: () => null, + ttl: 0, + ignoreIdFieldsBySchemaCoordinate: ['Header.id'], + }), + ], + schema, ); const document = /* GraphQL */ ` @@ -4098,42 +4099,44 @@ it('id field in body is returned as is and not overwritten', async () => { 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'} - } + 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!, - }, + id: String + header: Header! + } type Header { - id: IdObject!, + id: IdObject! someField: String - }, + } type IdObject { - fieldA: String!, - fieldB: String!, - fieldC: String!, + 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, + [ + 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 */ ` @@ -4152,6 +4155,5 @@ it('id field in body overwritten and request fails', async () => { } `; - await expect( testkit.execute(document)).rejects.toThrow(TypeError); - -}); \ No newline at end of file + await expect(testkit.execute(document)).rejects.toThrow(TypeError); +});