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 b5ba06acef..e9d12b013b 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 { @@ -244,7 +247,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 e16fd0535a..a20ea7cf8d 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` @@ -45,6 +46,7 @@ it(`calls getContext`, () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { return { + ...queryResponse, operation: forwardOp, data: queryOneData, }; @@ -80,6 +82,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 9666b6e080..c1cdb9b6c8 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` @@ -77,7 +79,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 }; } ); @@ -127,7 +129,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 }; } ); @@ -196,9 +198,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; @@ -332,11 +338,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; @@ -431,9 +441,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; @@ -487,7 +501,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; @@ -544,7 +562,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; @@ -602,6 +624,7 @@ describe('data dependencies', () => { }); const queryResult: OperationResult = { + ...queryResponse, operation, data: { __typename: 'Query', @@ -702,9 +725,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; @@ -801,11 +828,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; @@ -898,7 +933,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; @@ -1009,9 +1044,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 }, @@ -1082,7 +1118,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; @@ -1161,9 +1197,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; @@ -1304,9 +1344,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; @@ -1458,9 +1506,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; @@ -1592,9 +1648,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; @@ -1672,6 +1736,7 @@ describe('commutativity', () => { const result = (operation: Operation): Source => pipe( fromValue({ + ...queryResponse, operation, data: { __typename: 'Query', @@ -1816,6 +1881,7 @@ describe('commutativity', () => { nextOp(queryOpA); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -1906,6 +1972,7 @@ describe('commutativity', () => { nextOp(queryOpB); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -1920,6 +1987,7 @@ describe('commutativity', () => { expect(data).toHaveProperty('node.name', 'query a'); nextRes({ + ...queryResponse, operation: mutationOp, data: { __typename: 'Mutation', @@ -1935,6 +2003,7 @@ describe('commutativity', () => { expect(data).toHaveProperty('node.name', 'mutation'); nextRes({ + ...queryResponse, operation: queryOpB, data: { __typename: 'Query', @@ -2021,6 +2090,7 @@ describe('commutativity', () => { nextOp(mutationOp); nextRes({ + ...queryResponse, operation: queryOp, data: { __typename: 'Query', @@ -2035,6 +2105,7 @@ describe('commutativity', () => { expect(data).toHaveProperty('node.name', 'optimistic'); nextRes({ + ...queryResponse, operation: mutationOp, data: { __typename: 'Query', @@ -2119,6 +2190,7 @@ describe('commutativity', () => { nextOp(subscriptionOp); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -2131,6 +2203,7 @@ describe('commutativity', () => { }); nextRes({ + ...queryResponse, operation: subscriptionOp, data: { node: { @@ -2229,6 +2302,7 @@ describe('commutativity', () => { nextOp(subscriptionOp); nextRes({ + ...queryResponse, operation: queryOpA, data: { __typename: 'Query', @@ -2243,6 +2317,7 @@ describe('commutativity', () => { nextOp(mutationOp); nextRes({ + ...queryResponse, operation: mutationOp, data: { node: { @@ -2254,6 +2329,7 @@ describe('commutativity', () => { }); nextRes({ + ...queryResponse, operation: subscriptionOp, data: { node: { @@ -2265,6 +2341,7 @@ describe('commutativity', () => { }); nextRes({ + ...queryResponse, operation: subscriptionOp, data: { node: { @@ -2370,6 +2447,7 @@ describe('commutativity', () => { nextOp(normalOp); nextRes({ + ...queryResponse, operation: deferredOp, data: { __typename: 'Query', @@ -2380,6 +2458,7 @@ describe('commutativity', () => { expect(deferredData).not.toHaveProperty('deferred'); nextRes({ + ...queryResponse, operation: normalOp, data: { __typename: 'Query', @@ -2396,6 +2475,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 384eec3922..aae28d5264 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 178af829f9..6595744af0 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(); @@ -46,6 +47,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 16518ed5b6..66f1ec5c3f 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(); @@ -50,6 +51,7 @@ it(`upgrades to cache-and-network`, async () => { const response = vi.fn( (forwardOp: Operation): OperationResult => { return { + ...queryResponse, operation: forwardOp, data: queryOneData, }; @@ -96,6 +98,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 5680efd6b3..45be55fe19 100755 --- a/packages/core/src/client.test.ts +++ b/packages/core/src/client.test.ts @@ -372,6 +372,8 @@ describe('queuing behavior', () => { } }), map(op => ({ + stale: false, + hasNext: false, data: op.key, operation: op, })) @@ -427,6 +429,8 @@ describe('queuing behavior', () => { ops$, filter(op => op.kind !== 'teardown'), map(op => ({ + hasNext: false, + stale: false, data: ++countRes, operation: op, })), @@ -479,7 +483,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', @@ -499,6 +503,7 @@ describe('queuing behavior', () => { data: 1, operation: op, stale: true, + hasNext: false, })), delay(1) ); @@ -562,6 +567,8 @@ describe('shared sources behavior', () => { return pipe( ops$, map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -587,6 +594,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation: queryOperation, + stale: false, + hasNext: false, }); pipe(client.executeRequestOperation(queryOperation), subscribe(resultTwo)); @@ -594,6 +603,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 1, operation: queryOperation, + stale: false, + hasNext: false, }); vi.advanceTimersByTime(1); @@ -608,6 +619,8 @@ describe('shared sources behavior', () => { return pipe( ops$, map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -641,6 +654,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation: operationOne, + hasNext: false, + stale: false, }); pipe(client.executeRequestOperation(operationTwo), subscribe(resultTwo)); @@ -649,6 +664,7 @@ describe('shared sources behavior', () => { data: 1, operation: operationOne, stale: true, + hasNext: false, }); vi.advanceTimersByTime(1); @@ -656,6 +672,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 2, operation: operationTwo, + stale: false, + hasNext: false, }); }); @@ -665,6 +683,8 @@ describe('shared sources behavior', () => { return pipe( ops$, map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -695,6 +715,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation, + stale: false, + hasNext: false, }); pipe(client.executeRequestOperation(operation), subscribe(resultTwo)); @@ -703,6 +725,7 @@ describe('shared sources behavior', () => { data: 1, operation, stale: true, + hasNext: false, }); vi.advanceTimersByTime(1); @@ -713,6 +736,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 2, operation, + stale: false, + hasNext: false, }); }); @@ -723,6 +748,8 @@ describe('shared sources behavior', () => { ops$, filter(op => op.kind !== 'teardown'), map(op => ({ + hasNext: false, + stale: false, data: ++i, operation: op, })), @@ -748,6 +775,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation: queryOperation, + hasNext: false, + stale: false, }); subscription.unsubscribe(); @@ -760,6 +789,8 @@ describe('shared sources behavior', () => { expect(resultTwo).toHaveBeenCalledWith({ data: 2, operation: queryOperation, + stale: false, + hasNext: false, }); }); @@ -771,6 +802,8 @@ describe('shared sources behavior', () => { map(op => ({ data: ++i, operation: op, + hasNext: false, + stale: false, })), take(1) ); @@ -797,6 +830,7 @@ describe('shared sources behavior', () => { data: 1, operation, stale: true, + hasNext: false, }); }); @@ -804,7 +838,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) ); }; @@ -830,7 +864,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 })) ); }; @@ -852,6 +886,8 @@ describe('shared sources behavior', () => { expect(resultOne).toHaveBeenCalledWith({ data: 1, operation, + hasNext: false, + stale: false, }); pipe(client.executeRequestOperation(operation), subscribe(resultTwo)); @@ -859,11 +895,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, }); }); @@ -871,7 +911,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) ); }; @@ -890,6 +930,7 @@ describe('shared sources behavior', () => { data: 1, operation: queryOperation, stale: true, + hasNext: false, }); pipe(client.executeRequestOperation(queryOperation), subscribe(resultTwo)); @@ -898,6 +939,7 @@ describe('shared sources behavior', () => { data: 1, operation: queryOperation, stale: true, + hasNext: false, }); }); @@ -938,7 +980,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 })) ); }; @@ -957,6 +999,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 {