From 31d161a5d0ddf8c390bb0876c51e1bab223ed22e Mon Sep 17 00:00:00 2001 From: Duc Nghiem Xuan Date: Tue, 10 Sep 2024 00:34:54 +0900 Subject: [PATCH] fix(graphcache): mutation would cause dependent operations and reexecuting operations become the same set (#3665) --- .changeset/tidy-spies-turn.md | 5 + .../graphcache/src/cacheExchange.test.ts | 142 ++++++++++++++++++ exchanges/graphcache/src/cacheExchange.ts | 3 +- 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 .changeset/tidy-spies-turn.md diff --git a/.changeset/tidy-spies-turn.md b/.changeset/tidy-spies-turn.md new file mode 100644 index 0000000000..0eea5fda75 --- /dev/null +++ b/.changeset/tidy-spies-turn.md @@ -0,0 +1,5 @@ +--- +'@urql/exchange-graphcache': patch +--- + +fix bug that mutation would cause dependent operations and reexecuting operations to become the same set diff --git a/exchanges/graphcache/src/cacheExchange.test.ts b/exchanges/graphcache/src/cacheExchange.test.ts index fb7b6c4167..832d32f638 100644 --- a/exchanges/graphcache/src/cacheExchange.test.ts +++ b/exchanges/graphcache/src/cacheExchange.test.ts @@ -913,6 +913,148 @@ describe('data dependencies', () => { expect(reexecuteOperation).toHaveBeenCalledTimes(0); expect(result.mock.calls[0][0]).toHaveProperty('data.author', null); }); + + it('mutation does not change number of reexecute request after a query', () => { + const client = createClient({ + url: 'http://0.0.0.0', + exchanges: [], + }); + + const { source: ops$, next: nextOp } = makeSubject(); + + const reexec = vi + .spyOn(client, 'reexecuteOperation') + .mockImplementation(nextOp); + + const mutation = gql` + mutation { + updateNode { + __typename + id + } + } + `; + + const normalQuery = gql` + { + __typename + item { + __typename + id + } + } + `; + + const extendedQuery = gql` + { + __typename + item { + __typename + extended: id + extra @_optional + } + } + `; + + const mutationOp = client.createRequestOperation('mutation', { + key: 0, + query: mutation, + variables: undefined, + }); + + const normalOp = client.createRequestOperation( + 'query', + { + key: 1, + query: normalQuery, + variables: undefined, + }, + { + requestPolicy: 'cache-and-network', + } + ); + + const extendedOp = client.createRequestOperation( + 'query', + { + key: 2, + query: extendedQuery, + variables: undefined, + }, + { + requestPolicy: 'cache-only', + } + ); + + const response = vi.fn((forwardOp: Operation): OperationResult => { + if (forwardOp.key === 0) { + return { + operation: mutationOp, + data: { + __typename: 'Mutation', + updateNode: { + __typename: 'Node', + id: 'id', + }, + }, + stale: false, + hasNext: false, + }; + } else if (forwardOp.key === 1) { + return { + operation: normalOp, + data: { + __typename: 'Query', + item: { + __typename: 'Node', + id: 'id', + }, + }, + stale: false, + hasNext: false, + }; + } else if (forwardOp.key === 2) { + return { + operation: extendedOp, + data: { + __typename: 'Query', + item: { + __typename: 'Node', + extended: 'id', + extra: 'extra', + }, + }, + stale: false, + hasNext: false, + }; + } + + return undefined as any; + }); + + const forward = (ops$: Source): Source => + pipe(ops$, map(response), share); + + pipe(cacheExchange()({ forward, client, dispatchDebug })(ops$), publish); + + nextOp(normalOp); + expect(reexec).toHaveBeenCalledTimes(0); + + nextOp(extendedOp); + expect(reexec).toHaveBeenCalledTimes(0); + + // re-execute first operation + reexec.mockClear(); + nextOp(normalOp); + expect(reexec).toHaveBeenCalledTimes(4); + + nextOp(mutationOp); + + // re-execute first operation after mutation + reexec.mockClear(); + nextOp(normalOp); + expect(reexec).toHaveBeenCalledTimes(4); + }); }); describe('directives', () => { diff --git a/exchanges/graphcache/src/cacheExchange.ts b/exchanges/graphcache/src/cacheExchange.ts index 260ca9db47..0b14caee68 100644 --- a/exchanges/graphcache/src/cacheExchange.ts +++ b/exchanges/graphcache/src/cacheExchange.ts @@ -136,8 +136,9 @@ export const cacheExchange = // Upon completion, all dependent operations become reexecuting operations, preventing // them from reexecuting prior operations again, causing infinite loops const _reexecutingOperations = reexecutingOperations; + reexecutingOperations = dependentOperations; if (operation.kind === 'query') { - (reexecutingOperations = dependentOperations).add(operation.key); + reexecutingOperations.add(operation.key); } (dependentOperations = _reexecutingOperations).clear(); }