From 2fc98429db9428155d2b2d3859b3f29fd8c554c1 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Thu, 16 Mar 2023 14:12:35 +0000 Subject: [PATCH] refactor(core): Refactor hasNext/stale on OperationResult to be required (#3061) --- .changeset/four-boxes-impress.md | 7 + exchanges/auth/src/authExchange.test.ts | 9 +- exchanges/context/src/context.test.ts | 3 + exchanges/execute/src/execute.test.ts | 1 + .../graphcache/src/cacheExchange.test.ts | 134 ++++++++++++++---- exchanges/graphcache/src/cacheExchange.ts | 5 +- .../graphcache/src/offlineExchange.test.ts | 10 +- .../multipartFetchExchange.test.ts.snap | 7 + exchanges/refocus/src/refocusExchange.test.ts | 2 + .../src/requestPolicyExchange.test.ts | 3 + packages/core/src/client.test.ts | 53 ++++++- .../__snapshots__/fetch.test.ts.snap | 5 + .../__snapshots__/subscription.test.ts.snap | 1 + packages/core/src/exchanges/cache.ts | 15 +- packages/core/src/exchanges/map.test.ts | 4 +- packages/core/src/exchanges/ssr.test.ts | 17 ++- .../__snapshots__/fetchSource.test.ts.snap | 8 ++ packages/core/src/test-utils/samples.ts | 6 + packages/core/src/types.ts | 4 +- packages/core/src/utils/result.test.ts | 16 +++ packages/core/src/utils/result.ts | 4 + .../react-urql/src/test-utils/ssr.test.tsx | 2 + packages/svelte-urql/src/common.ts | 6 +- 23 files changed, 266 insertions(+), 56 deletions(-) create mode 100644 .changeset/four-boxes-impress.md diff --git a/.changeset/four-boxes-impress.md b/.changeset/four-boxes-impress.md new file mode 100644 index 0000000000..79814c48d3 --- /dev/null +++ b/.changeset/four-boxes-impress.md @@ -0,0 +1,7 @@ +--- +'@urql/exchange-graphcache': major +'@urql/svelte': major +'@urql/core': major +--- + +Update `OperationResult.hasNext` and `OperationResult.stale` to be required fields. If you have a custom exchange creating results, you'll have to add these fields or use the `makeResult`, `mergeResultPatch`, or `makeErrorResult` helpers. diff --git a/exchanges/auth/src/authExchange.test.ts b/exchanges/auth/src/authExchange.test.ts index 37d648615b..1229142fe4 100644 --- a/exchanges/auth/src/authExchange.test.ts +++ b/exchanges/auth/src/authExchange.test.ts @@ -21,13 +21,16 @@ import { import { vi, expect, it } from 'vitest'; import { print } from 'graphql'; -import { queryOperation } from '../../../packages/core/src/test-utils'; +import { + queryResponse, + queryOperation, +} from '../../../packages/core/src/test-utils'; import { authExchange } from './authExchange'; const makeExchangeArgs = () => { const operations: Operation[] = []; const result = vi.fn( - (operation: Operation): OperationResult => ({ operation }) + (operation: Operation): OperationResult => ({ ...queryResponse, operation }) ); return { @@ -247,7 +250,9 @@ it('triggers authentication when an operation did error', async () => { await new Promise(resolve => setTimeout(resolve)); result.mockReturnValueOnce({ + ...queryResponse, operation: queryOperation, + data: undefined, error: new CombinedError({ graphQLErrors: [{ message: 'Oops' }], }), diff --git a/exchanges/context/src/context.test.ts b/exchanges/context/src/context.test.ts index 9702a8f3ad..001f7146d5 100644 --- a/exchanges/context/src/context.test.ts +++ b/exchanges/context/src/context.test.ts @@ -9,6 +9,7 @@ import { ExchangeIO, } from '@urql/core'; +import { queryResponse } from '../../../packages/core/src/test-utils'; import { contextExchange } from './context'; const queryOne = gql` @@ -48,6 +49,7 @@ it(`calls getContext`, () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { return { + ...queryResponse, operation: forwardOp, data: queryOneData, }; @@ -83,6 +85,7 @@ it(`calls getContext async`, async () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { return { + ...queryResponse, operation: forwardOp, data: queryOneData, }; diff --git a/exchanges/execute/src/execute.test.ts b/exchanges/execute/src/execute.test.ts index c4070fd165..3185d9a993 100644 --- a/exchanges/execute/src/execute.test.ts +++ b/exchanges/execute/src/execute.test.ts @@ -272,6 +272,7 @@ describe('on success response', () => { operation: queryOperation, data: mockHttpResponseData, hasNext: false, + stale: false, }); }); }); diff --git a/exchanges/graphcache/src/cacheExchange.test.ts b/exchanges/graphcache/src/cacheExchange.test.ts index 6e1c425d17..49f79f1ad4 100644 --- a/exchanges/graphcache/src/cacheExchange.test.ts +++ b/exchanges/graphcache/src/cacheExchange.test.ts @@ -6,6 +6,7 @@ import { OperationResult, CombinedError, } from '@urql/core'; + import { vi, expect, it, describe } from 'vitest'; import { @@ -23,6 +24,7 @@ import { } from 'wonka'; import { minifyIntrospectionQuery } from '@urql/introspection'; +import { queryResponse } from '../../../packages/core/src/test-utils'; import { cacheExchange } from './cacheExchange'; const queryOne = gql` @@ -80,7 +82,7 @@ describe('data dependencies', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { expect(forwardOp.key).toBe(op.key); - return { operation: forwardOp, data: expected }; + return { ...queryResponse, operation: forwardOp, data: expected }; } ); @@ -133,7 +135,7 @@ describe('data dependencies', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { expect(forwardOp.key).toBe(op.key); - return { operation: forwardOp, data: queryOneData }; + return { ...queryResponse, operation: forwardOp, data: queryOneData }; } ); @@ -205,9 +207,13 @@ describe('data dependencies', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryOneData }; + return { ...queryResponse, operation: opOne, data: queryOneData }; } else if (forwardOp.key === 2) { - return { operation: opMultiple, data: queryMultipleData }; + return { + ...queryResponse, + operation: opMultiple, + data: queryMultipleData, + }; } return undefined as any; @@ -344,11 +350,15 @@ describe('data dependencies', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryByIdDataA }; + return { ...queryResponse, operation: opOne, data: queryByIdDataA }; } else if (forwardOp.key === 2) { - return { operation: opTwo, data: queryByIdDataB }; + return { ...queryResponse, operation: opTwo, data: queryByIdDataB }; } else if (forwardOp.key === 3) { - return { operation: opMutation, data: mutationData }; + return { + ...queryResponse, + operation: opMutation, + data: mutationData, + }; } return undefined as any; @@ -446,9 +456,13 @@ describe('data dependencies', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryOneData }; + return { ...queryResponse, operation: opOne, data: queryOneData }; } else if (forwardOp.key === 2) { - return { operation: opUnrelated, data: queryUnrelatedData }; + return { + ...queryResponse, + operation: opUnrelated, + data: queryUnrelatedData, + }; } return undefined as any; @@ -505,7 +519,11 @@ describe('data dependencies', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opMutation, data: mutationData }; + return { + ...queryResponse, + operation: opMutation, + data: mutationData, + }; } return undefined as any; @@ -565,7 +583,11 @@ describe('data dependencies', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opMutation, data: mutationData }; + return { + ...queryResponse, + operation: opMutation, + data: mutationData, + }; } return undefined as any; @@ -626,6 +648,7 @@ describe('data dependencies', () => { }); const queryResult: OperationResult = { + ...queryResponse, operation, data: { __typename: 'Query', @@ -729,9 +752,13 @@ describe('optimistic updates', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryOneData }; + return { ...queryResponse, operation: opOne, data: queryOneData }; } else if (forwardOp.key === 2) { - return { operation: opMutation, data: mutationData }; + return { + ...queryResponse, + operation: opMutation, + data: mutationData, + }; } return undefined as any; @@ -831,11 +858,19 @@ describe('optimistic updates', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryOneData }; + return { ...queryResponse, operation: opOne, data: queryOneData }; } else if (forwardOp.key === 2) { - return { operation: opMutationOne, data: mutationData }; + return { + ...queryResponse, + operation: opMutationOne, + data: mutationData, + }; } else if (forwardOp.key === 3) { - return { operation: opMutationTwo, data: mutationData }; + return { + ...queryResponse, + operation: opMutationTwo, + data: mutationData, + }; } return undefined as any; @@ -931,7 +966,7 @@ describe('optimistic updates', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryOneData }; + return { ...queryResponse, operation: opOne, data: queryOneData }; } return undefined as any; @@ -1045,9 +1080,10 @@ describe('optimistic updates', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: authorsQueryData }; + return { ...queryResponse, operation: opOne, data: authorsQueryData }; } else if (forwardOp.key === 2) { return { + ...queryResponse, operation: opMutation, error: 'error' as any, data: { __typename: 'Mutation', addAuthor: null }, @@ -1121,7 +1157,7 @@ describe('custom resolvers', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryOneData }; + return { ...queryResponse, operation: opOne, data: queryOneData }; } return undefined as any; @@ -1203,9 +1239,13 @@ describe('custom resolvers', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: opOne, data: queryOneData }; + return { ...queryResponse, operation: opOne, data: queryOneData }; } else if (forwardOp.key === 2) { - return { operation: opMutation, data: mutationData }; + return { + ...queryResponse, + operation: opMutation, + data: mutationData, + }; } return undefined as any; @@ -1349,9 +1389,17 @@ describe('custom resolvers', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: queryOperation, data: queryData }; + return { + ...queryResponse, + operation: queryOperation, + data: queryData, + }; } else if (forwardOp.key === 2) { - return { operation: mutationOperation, data: mutationData }; + return { + ...queryResponse, + operation: mutationOperation, + data: mutationData, + }; } return undefined as any; @@ -1506,9 +1554,17 @@ describe('schema awareness', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: initialQueryOperation, data: queryData }; + return { + ...queryResponse, + operation: initialQueryOperation, + data: queryData, + }; } else if (forwardOp.key === 2) { - return { operation: queryOperation, data: queryData }; + return { + ...queryResponse, + operation: queryOperation, + data: queryData, + }; } return undefined as any; @@ -1643,9 +1699,17 @@ describe('schema awareness', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { if (forwardOp.key === 1) { - return { operation: initialQueryOperation, data: queryData }; + return { + ...queryResponse, + operation: initialQueryOperation, + data: queryData, + }; } else if (forwardOp.key === 2) { - return { operation: queryOperation, data: queryData }; + return { + ...queryResponse, + operation: queryOperation, + data: queryData, + }; } return undefined as any; @@ -1724,6 +1788,7 @@ describe('commutativity', () => { const result = (operation: Operation): Source => pipe( fromValue({ + ...queryResponse, operation, data: { __typename: 'Query', @@ -1871,6 +1936,7 @@ describe('commutativity', () => { nextOp(queryOpA); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -1964,6 +2030,7 @@ describe('commutativity', () => { nextOp(queryOpB); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -1978,6 +2045,7 @@ describe('commutativity', () => { expect(data).toHaveProperty('node.name', 'query a'); nextRes({ + ...queryResponse, operation: mutationOp, data: { __typename: 'Mutation', @@ -1993,6 +2061,7 @@ describe('commutativity', () => { expect(data).toHaveProperty('node.name', 'mutation'); nextRes({ + ...queryResponse, operation: queryOpB, data: { __typename: 'Query', @@ -2082,6 +2151,7 @@ describe('commutativity', () => { nextOp(mutationOp); nextRes({ + ...queryResponse, operation: queryOp, data: { __typename: 'Query', @@ -2096,6 +2166,7 @@ describe('commutativity', () => { expect(data).toHaveProperty('node.name', 'optimistic'); nextRes({ + ...queryResponse, operation: mutationOp, data: { __typename: 'Query', @@ -2183,6 +2254,7 @@ describe('commutativity', () => { nextOp(subscriptionOp); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -2195,6 +2267,7 @@ describe('commutativity', () => { }); nextRes({ + ...queryResponse, operation: subscriptionOp, data: { node: { @@ -2296,6 +2369,7 @@ describe('commutativity', () => { nextOp(subscriptionOp); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -2310,6 +2384,7 @@ describe('commutativity', () => { nextOp(mutationOp); nextRes({ + ...queryResponse, operation: mutationOp, data: { node: { @@ -2321,6 +2396,7 @@ describe('commutativity', () => { }); nextRes({ + ...queryResponse, operation: subscriptionOp, data: { node: { @@ -2332,6 +2408,7 @@ describe('commutativity', () => { }); nextRes({ + ...queryResponse, operation: subscriptionOp, data: { node: { @@ -2440,6 +2517,7 @@ describe('commutativity', () => { nextOp(normalOp); nextRes({ + ...queryResponse, operation: deferredOp, data: { __typename: 'Query', @@ -2450,6 +2528,7 @@ describe('commutativity', () => { expect(deferredData).not.toHaveProperty('deferred'); nextRes({ + ...queryResponse, operation: normalOp, data: { __typename: 'Query', @@ -2466,6 +2545,7 @@ describe('commutativity', () => { expect(combinedData).toHaveProperty('node.id', 2); nextRes({ + ...queryResponse, operation: deferredOp, data: { __typename: 'Query', diff --git a/exchanges/graphcache/src/cacheExchange.ts b/exchanges/graphcache/src/cacheExchange.ts index 8eb26d50c2..c8ff6e00f1 100644 --- a/exchanges/graphcache/src/cacheExchange.ts +++ b/exchanges/graphcache/src/cacheExchange.ts @@ -26,10 +26,11 @@ import { filterVariables, getMainOperation } from './ast'; import { Store, noopDataState, hydrateData, reserveLayer } from './store'; import { Data, Dependencies, CacheExchangeOpts } from './types'; -type OperationResultWithMeta = OperationResult & { +interface OperationResultWithMeta extends Partial { + operation: Operation; outcome: CacheOutcome; dependencies: Dependencies; -}; +} type Operations = Set; type OperationMap = Map; diff --git a/exchanges/graphcache/src/offlineExchange.test.ts b/exchanges/graphcache/src/offlineExchange.test.ts index 92042bf026..ff87421b8d 100644 --- a/exchanges/graphcache/src/offlineExchange.test.ts +++ b/exchanges/graphcache/src/offlineExchange.test.ts @@ -9,6 +9,7 @@ import { print } from 'graphql'; import { vi, expect, it, describe } from 'vitest'; import { pipe, map, makeSubject, tap, publish } from 'wonka'; +import { queryResponse } from '../../../packages/core/src/test-utils'; import { offlineExchange } from './offlineExchange'; const mutationOne = gql` @@ -79,7 +80,11 @@ describe('storage', () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { expect(forwardOp.key).toBe(op.key); - return { operation: forwardOp, data: mutationOneData }; + return { + ...queryResponse, + operation: forwardOp, + data: mutationOneData, + }; } ); @@ -125,10 +130,11 @@ describe('offline', () => { (forwardOp: Operation): OperationResult => { if (forwardOp.key === queryOp.key) { onlineSpy.mockReturnValueOnce(true); - return { operation: forwardOp, data: queryOneData }; + return { ...queryResponse, operation: forwardOp, data: queryOneData }; } else { onlineSpy.mockReturnValueOnce(false); return { + ...queryResponse, operation: forwardOp, // @ts-ignore error: { networkError: new Error('failed to fetch') }, diff --git a/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap b/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap index 7ea815072c..10bd223bac 100644 --- a/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap +++ b/exchanges/multipart-fetch/src/__snapshots__/multipartFetchExchange.test.ts.snap @@ -5,6 +5,7 @@ exports[`on error > returns error data 1`] = ` "data": undefined, "error": [CombinedError: [Network] No Content], "extensions": undefined, + "hasNext": false, "operation": { "context": { "fetchOptions": { @@ -141,6 +142,7 @@ exports[`on error > returns error data 1`] = ` "name": "Clara", }, }, + "stale": false, } `; @@ -149,6 +151,7 @@ exports[`on error > returns error data with status 400 and manual redirect mode "data": undefined, "error": [CombinedError: [Network] No Content], "extensions": undefined, + "hasNext": false, "operation": { "context": { "fetchOptions": [MockFunction spy] { @@ -295,6 +298,7 @@ exports[`on error > returns error data with status 400 and manual redirect mode "name": "Clara", }, }, + "stale": false, } `; @@ -452,6 +456,7 @@ exports[`on success > returns response data 1`] = ` "name": "Clara", }, }, + "stale": false, } `; @@ -587,6 +592,7 @@ exports[`on success > uses a file when given 1`] = ` "picture": File {}, }, }, + "stale": false, } `; @@ -728,6 +734,7 @@ exports[`on success > uses multiple files when given 1`] = ` ], }, }, + "stale": false, } `; diff --git a/exchanges/refocus/src/refocusExchange.test.ts b/exchanges/refocus/src/refocusExchange.test.ts index 605e613eb1..4c89432a1f 100644 --- a/exchanges/refocus/src/refocusExchange.test.ts +++ b/exchanges/refocus/src/refocusExchange.test.ts @@ -9,6 +9,7 @@ import { ExchangeIO, } from '@urql/core'; +import { queryResponse } from '../../../packages/core/src/test-utils'; import { refocusExchange } from './refocusExchange'; const dispatchDebug = vi.fn(); @@ -49,6 +50,7 @@ it(`attaches a listener and redispatches queries on call`, () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { return { + ...queryResponse, operation: forwardOp, data: queryOneData, }; diff --git a/exchanges/request-policy/src/requestPolicyExchange.test.ts b/exchanges/request-policy/src/requestPolicyExchange.test.ts index 5a44a9f907..98de97e469 100644 --- a/exchanges/request-policy/src/requestPolicyExchange.test.ts +++ b/exchanges/request-policy/src/requestPolicyExchange.test.ts @@ -9,6 +9,7 @@ import { ExchangeIO, } from '@urql/core'; +import { queryResponse } from '../../../packages/core/src/test-utils'; import { requestPolicyExchange } from './requestPolicyExchange'; const dispatchDebug = vi.fn(); @@ -53,6 +54,7 @@ it(`upgrades to cache-and-network`, async () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { return { + ...queryResponse, operation: forwardOp, data: queryOneData, }; @@ -99,6 +101,7 @@ it(`doesn't upgrade when shouldUpgrade returns false`, async () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { return { + ...queryResponse, operation: forwardOp, data: queryOneData, }; diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts index 417ce7abd8..a11302651c 100755 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -375,6 +375,8 @@ describe('queuing behavior', () => { } }), map(op => ({ + stale: false, + hasNext: false, data: op.key, operation: op, })) @@ -430,6 +432,8 @@ describe('queuing behavior', () => { ops$, filter(op => op.kind !== 'teardown'), map(op => ({ + hasNext: false, + stale: false, data: ++countRes, operation: op, })), @@ -482,7 +486,7 @@ describe('queuing behavior', () => { expect(output.length).toBe(3); expect(output[2]).toHaveProperty('data', 2); - expect(output[2]).not.toHaveProperty('stale'); + expect(output[2]).toHaveProperty('stale', false); expect(output[2]).toHaveProperty('operation.key', queryOperation.key); expect(output[2]).toHaveProperty( 'operation.context.requestPolicy', @@ -502,6 +506,7 @@ describe('queuing behavior', () => { data: 1, operation: op, stale: true, + hasNext: false, })), delay(1) ); @@ -565,6 +570,8 @@ describe('shared sources behavior', () => { return pipe( ops$, map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -590,6 +597,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation: queryOperation, + stale: false, + hasNext: false, }); pipe(client.executeRequestOperation(queryOperation), subscribe(resultTwo)); @@ -597,6 +606,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 1, operation: queryOperation, + stale: false, + hasNext: false, }); vi.advanceTimersByTime(1); @@ -611,6 +622,8 @@ describe('shared sources behavior', () => { return pipe( ops$, map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -644,6 +657,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation: operationOne, + hasNext: false, + stale: false, }); pipe(client.executeRequestOperation(operationTwo), subscribe(resultTwo)); @@ -652,6 +667,7 @@ describe('shared sources behavior', () => { data: 1, operation: operationOne, stale: true, + hasNext: false, }); vi.advanceTimersByTime(1); @@ -659,6 +675,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 2, operation: operationTwo, + stale: false, + hasNext: false, }); }); @@ -668,6 +686,8 @@ describe('shared sources behavior', () => { return pipe( ops$, map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -698,6 +718,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation, + stale: false, + hasNext: false, }); pipe(client.executeRequestOperation(operation), subscribe(resultTwo)); @@ -706,6 +728,7 @@ describe('shared sources behavior', () => { data: 1, operation, stale: true, + hasNext: false, }); vi.advanceTimersByTime(1); @@ -716,6 +739,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 2, operation, + stale: false, + hasNext: false, }); }); @@ -726,6 +751,8 @@ describe('shared sources behavior', () => { ops$, filter(op => op.kind !== 'teardown'), map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -751,6 +778,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation: queryOperation, + hasNext: false, + stale: false, }); subscription.unsubscribe(); @@ -763,6 +792,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 2, operation: queryOperation, + stale: false, + hasNext: false, }); }); @@ -774,6 +805,8 @@ describe('shared sources behavior', () => { map(op => ({ data: ++i, operation: op, + hasNext: false, + stale: false, })), take(1) ); @@ -800,6 +833,7 @@ describe('shared sources behavior', () => { data: 1, operation, stale: true, + hasNext: false, }); }); @@ -807,7 +841,7 @@ describe('shared sources behavior', () => { const exchange: Exchange = () => ops$ => { return pipe( ops$, - map(op => ({ data: 1, operation: op })), + map(op => ({ hasNext: false, stale: false, data: 1, operation: op })), filter(() => false) ); }; @@ -833,7 +867,7 @@ describe('shared sources behavior', () => { let i = 0; return pipe( ops$, - map(op => ({ data: ++i, operation: op })) + map(op => ({ hasNext: false, stale: false, data: ++i, operation: op })) ); }; @@ -855,6 +889,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation, + hasNext: false, + stale: false, }); pipe(client.executeRequestOperation(operation), subscribe(resultTwo)); @@ -862,11 +898,15 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 2, operation, + hasNext: false, + stale: false, }); expect(resultOne).toHaveBeenCalledWith({ data: 2, operation, + hasNext: false, + stale: false, }); }); @@ -874,7 +914,7 @@ describe('shared sources behavior', () => { const exchange: Exchange = () => ops$ => { return pipe( ops$, - map(op => ({ stale: true, data: 1, operation: op })), + map(op => ({ hasNext: false, stale: true, data: 1, operation: op })), take(1) ); }; @@ -893,6 +933,7 @@ describe('shared sources behavior', () => { data: 1, operation: queryOperation, stale: true, + hasNext: false, }); pipe(client.executeRequestOperation(queryOperation), subscribe(resultTwo)); @@ -901,6 +942,7 @@ describe('shared sources behavior', () => { data: 1, operation: queryOperation, stale: true, + hasNext: false, }); }); @@ -941,7 +983,7 @@ describe('shared sources behavior', () => { const exchange: Exchange = () => ops$ => { return pipe( ops$, - map(op => ({ stale: true, data: 1, operation: op })) + map(op => ({ hasNext: false, stale: true, data: 1, operation: op })) ); }; @@ -960,6 +1002,7 @@ describe('shared sources behavior', () => { data: 1, operation: queryOperation, stale: true, + hasNext: false, }); }); }); diff --git a/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap b/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap index 59cc89f258..f7953616e3 100644 --- a/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap +++ b/packages/core/src/exchanges/__snapshots__/fetch.test.ts.snap @@ -5,6 +5,7 @@ exports[`on error > returns error data 1`] = ` "data": undefined, "error": [CombinedError: [Network] No Content], "extensions": undefined, + "hasNext": false, "operation": { "context": { "fetchOptions": { @@ -141,6 +142,7 @@ exports[`on error > returns error data 1`] = ` "name": "Clara", }, }, + "stale": false, } `; @@ -149,6 +151,7 @@ exports[`on error > returns error data with status 400 and manual redirect mode "data": undefined, "error": [CombinedError: [Network] No Content], "extensions": undefined, + "hasNext": false, "operation": { "context": { "fetchOptions": [MockFunction spy] { @@ -295,6 +298,7 @@ exports[`on error > returns error data with status 400 and manual redirect mode "name": "Clara", }, }, + "stale": false, } `; @@ -452,6 +456,7 @@ exports[`on success > returns response data 1`] = ` "name": "Clara", }, }, + "stale": false, } `; diff --git a/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap b/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap index 0497d395d2..8de48e4710 100644 --- a/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap +++ b/packages/core/src/exchanges/__snapshots__/subscription.test.ts.snap @@ -118,5 +118,6 @@ exports[`should return response data from forwardSubscription observable 1`] = ` "user": "colin", }, }, + "stale": false, } `; diff --git a/packages/core/src/exchanges/cache.ts b/packages/core/src/exchanges/cache.ts index cbe67e32fd..f43c313fe9 100755 --- a/packages/core/src/exchanges/cache.ts +++ b/packages/core/src/exchanges/cache.ts @@ -76,12 +76,15 @@ export const cacheExchange: Exchange = ({ forward, client, dispatchDebug }) => { }), }); - const result: OperationResult = { - ...cachedResult, - operation: addMetadata(operation, { - cacheOutcome: cachedResult ? 'hit' : 'miss', - }), - }; + let result: OperationResult = cachedResult!; + if (process.env.NODE_ENV !== 'production') { + result = { + ...result, + operation: addMetadata(operation, { + cacheOutcome: cachedResult ? 'hit' : 'miss', + }), + }; + } if (operation.context.requestPolicy === 'cache-and-network') { result.stale = true; diff --git a/packages/core/src/exchanges/map.test.ts b/packages/core/src/exchanges/map.test.ts index 2a6ec2f0bf..3df71c930e 100644 --- a/packages/core/src/exchanges/map.test.ts +++ b/packages/core/src/exchanges/map.test.ts @@ -2,7 +2,7 @@ import { map, tap, pipe, fromValue, toArray, toPromise } from 'wonka'; import { vi, expect, describe, it } from 'vitest'; import { Client } from '../client'; -import { queryOperation } from '../test-utils'; +import { queryResponse, queryOperation } from '../test-utils'; import { Operation } from '../types'; import { mapExchange } from './map'; @@ -210,7 +210,7 @@ describe('onError', () => { forward: op$ => pipe( op$, - map((operation: Operation) => ({ operation })) + map((operation: Operation) => ({ ...queryResponse, operation })) ), client: {} as Client, dispatchDebug: () => null, diff --git a/packages/core/src/exchanges/ssr.test.ts b/packages/core/src/exchanges/ssr.test.ts index fe3d62b22e..866a2bd586 100644 --- a/packages/core/src/exchanges/ssr.test.ts +++ b/packages/core/src/exchanges/ssr.test.ts @@ -47,12 +47,14 @@ it('caches query results correctly', () => { [queryOperation.key]: { data: serializedQueryResponse.data, error: undefined, + hasNext: false, }, }); }); it('serializes query results quickly', () => { - const queryResponse: OperationResult = { + const result: OperationResult = { + ...queryResponse, operation: queryOperation, data: { user: { @@ -62,11 +64,11 @@ it('serializes query results quickly', () => { }; const serializedQueryResponse = { - ...queryResponse, - data: JSON.stringify(queryResponse.data), + ...result, + data: JSON.stringify(result.data), }; - output.mockReturnValueOnce(queryResponse); + output.mockReturnValueOnce(result); const ssr = ssrExchange(); const { source: ops$, next } = input; @@ -74,7 +76,7 @@ it('serializes query results quickly', () => { publish(exchange); next(queryOperation); - queryResponse.data.user.name = 'Not Clive'; + result.data.user.name = 'Not Clive'; const data = ssr.extractData(); expect(Object.keys(data)).toEqual(['' + queryOperation.key]); @@ -83,6 +85,7 @@ it('serializes query results quickly', () => { [queryOperation.key]: { data: serializedQueryResponse.data, error: undefined, + hasNext: false, }, }); }); @@ -119,6 +122,7 @@ it('caches errored query results correctly', () => { ], networkError: undefined, }, + hasNext: false, }, }); }); @@ -147,6 +151,7 @@ it('caches extensions when includeExtensions=true', () => { [queryOperation.key]: { data: '{"user":{"name":"Clive"}}', extensions: '{"foo":"bar"}', + hasNext: false, }, }); }); @@ -219,8 +224,8 @@ it('resolves deferred, cached query results correctly', () => { isClient: true, initialState: { [queryOperation.key]: { - hasNext: true, ...(serializedQueryResponse as any), + hasNext: true, }, }, }); diff --git a/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap b/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap index 408344e90a..8d0418c115 100644 --- a/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap +++ b/packages/core/src/internal/__snapshots__/fetchSource.test.ts.snap @@ -5,6 +5,7 @@ exports[`on error > ignores the error when a result is available 1`] = ` "data": undefined, "error": [CombinedError: [Network] Forbidden], "extensions": undefined, + "hasNext": false, "operation": { "context": { "fetchOptions": { @@ -141,6 +142,7 @@ exports[`on error > ignores the error when a result is available 1`] = ` "name": "Clara", }, }, + "stale": false, } `; @@ -149,6 +151,7 @@ exports[`on error > returns error data 1`] = ` "data": undefined, "error": [CombinedError: [Network] Forbidden], "extensions": undefined, + "hasNext": false, "operation": { "context": { "fetchOptions": { @@ -285,6 +288,7 @@ exports[`on error > returns error data 1`] = ` "name": "Clara", }, }, + "stale": false, } `; @@ -293,6 +297,7 @@ exports[`on error > returns error data with status 400 and manual redirect mode "data": undefined, "error": [CombinedError: [Network] Forbidden], "extensions": undefined, + "hasNext": false, "operation": { "context": { "fetchOptions": { @@ -429,6 +434,7 @@ exports[`on error > returns error data with status 400 and manual redirect mode "name": "Clara", }, }, + "stale": false, } `; @@ -578,6 +584,7 @@ exports[`on success > returns response data 1`] = ` "name": "Clara", }, }, + "stale": false, } `; @@ -759,5 +766,6 @@ exports[`on success > uses the mock fetch if given 1`] = ` "name": "Clara", }, }, + "stale": false, } `; diff --git a/packages/core/src/test-utils/samples.ts b/packages/core/src/test-utils/samples.ts index 599c65857f..18873e3c53 100644 --- a/packages/core/src/test-utils/samples.ts +++ b/packages/core/src/test-utils/samples.ts @@ -103,6 +103,8 @@ export const subscriptionOperation: Operation = makeOperation( export const undefinedQueryResponse: OperationResult = { operation: queryOperation, + stale: false, + hasNext: false, }; export const queryResponse: OperationResult = { @@ -112,11 +114,15 @@ export const queryResponse: OperationResult = { name: 'Clive', }, }, + stale: false, + hasNext: false, }; export const mutationResponse: OperationResult = { operation: mutationOperation, data: {}, + stale: false, + hasNext: false, }; export const subscriptionResult: ExecutionResult = { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a1e6bcf464..2e6ba39e19 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -565,7 +565,7 @@ export interface OperationResult< * Most commonly, this flag is set for a cached result when the operation is executed using the * `cache-and-network` {@link RequestPolicy}. */ - stale?: boolean; + stale: boolean; /** Indicates that the GraphQL response is streamed and updated results will follow. * * @remarks @@ -575,7 +575,7 @@ export interface OperationResult< * * For GraphQL subscriptions, this flag will always be set to `true`. */ - hasNext?: boolean; + hasNext: boolean; } /** The input parameters a `Client` passes to an `Exchange` when it's created. diff --git a/packages/core/src/utils/result.test.ts b/packages/core/src/utils/result.test.ts index 86346b6467..49697480cb 100644 --- a/packages/core/src/utils/result.test.ts +++ b/packages/core/src/utils/result.test.ts @@ -38,6 +38,8 @@ describe('mergeResultPatch', () => { }, ], }, + stale: false, + hasNext: true, }; const merged = mergeResultPatch(prevResult, { @@ -77,6 +79,8 @@ describe('mergeResultPatch', () => { }, ], }, + stale: false, + hasNext: true, }; const patch = { __typename: 'Child' }; @@ -111,6 +115,8 @@ describe('mergeResultPatch', () => { __typename: 'Query', item: undefined, }, + stale: false, + hasNext: true, }; const merged = mergeResultPatch(prevResult, { @@ -133,6 +139,8 @@ describe('mergeResultPatch', () => { __typename: 'Query', items: [{ __typename: 'Item' }], }, + stale: false, + hasNext: true, }; const patch = { __typename: 'Item' }; @@ -162,6 +170,8 @@ describe('mergeResultPatch', () => { __typename: 'Query', items: [{ __typename: 'Item' }], }, + stale: false, + hasNext: true, }; const merged = mergeResultPatch(prevResult, { @@ -190,6 +200,8 @@ describe('mergeResultPatch', () => { extensions: { base: true, }, + stale: false, + hasNext: true, }; const merged = mergeResultPatch(prevResult, { @@ -240,6 +252,8 @@ describe('mergeResultPatch', () => { extensions: { base: true, }, + stale: false, + hasNext: true, }; const merged = mergeResultPatch(prevResult, { @@ -264,6 +278,8 @@ describe('mergeResultPatch', () => { }, ], }, + stale: false, + hasNext: true, }; const patch = { __typename: 'Child' }; diff --git a/packages/core/src/utils/result.ts b/packages/core/src/utils/result.ts index 16e7cb18d8..349775a738 100644 --- a/packages/core/src/utils/result.ts +++ b/packages/core/src/utils/result.ts @@ -44,6 +44,7 @@ export const makeResult = ( : undefined, extensions: result.extensions ? { ...result.extensions } : undefined, hasNext: result.hasNext == null ? defaultHasNext : result.hasNext, + stale: false, }; }; @@ -126,6 +127,7 @@ export const mergeResultPatch = ( : undefined, extensions: hasExtensions ? extensions : undefined, hasNext: !!nextResult.hasNext, + stale: false, }; }; @@ -154,4 +156,6 @@ export const makeErrorResult = ( response, }), extensions: undefined, + hasNext: false, + stale: false, }); diff --git a/packages/react-urql/src/test-utils/ssr.test.tsx b/packages/react-urql/src/test-utils/ssr.test.tsx index 0e9d6f45ef..75e9cd4cd7 100644 --- a/packages/react-urql/src/test-utils/ssr.test.tsx +++ b/packages/react-urql/src/test-utils/ssr.test.tsx @@ -72,6 +72,8 @@ const queryResponse: OperationResult = { name: 'Clive', }, }, + stale: false, + hasNext: false, }; const url = 'https://hostname.com'; diff --git a/packages/svelte-urql/src/common.ts b/packages/svelte-urql/src/common.ts index 621b5dfbe5..23ac6648c4 100644 --- a/packages/svelte-urql/src/common.ts +++ b/packages/svelte-urql/src/common.ts @@ -19,11 +19,13 @@ export const fromStore = (store$: Readable): Source => make(observer => store$.subscribe(observer.next)); export const initialResult = { + operation: undefined, fetching: false, - stale: false, - error: undefined, data: undefined, + error: undefined, extensions: undefined, + hasNext: false, + stale: false, }; export interface Pausable {