Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(graphcache): Mark deferred, uncached results as partial (#3163)

authored by

Phil Pluckthun and committed by
GitHub
b5e50b4f 8e148492

+39 -27
+5
.changeset/rich-taxis-end.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Apply `hasNext: true` and fallthrough logic to cached queries that contain deferred, uncached fields. Deferred query results will now be fetched against the API correctly, even if prior requests have been incomplete.
+1 -1
exchanges/graphcache/e2e-tests/query.spec.tsx
··· 191 191 192 192 cy.get('#first-data').should('have.text', 'Data: title'); 193 193 cy.get('#second-data').should('have.text', 'Data: foo'); 194 - cy.get('#second-stale').should('have.text', 'Stale: true'); 194 + cy.get('#second-stale').should('have.text', 'Stale: false'); 195 195 // TODO: ideally we would be able to keep the error here but... 196 196 // cy.get('#first-error').should('have.text', 'Error: [GraphQL] Test'); 197 197 // cy.get('#second-error').should('have.text', 'Error: [GraphQL] Test');
+15 -12
exchanges/graphcache/src/cacheExchange.test.ts
··· 2411 2411 }); 2412 2412 2413 2413 it('applies deferred results to previous layers', () => { 2414 - let normalData: any; 2415 - let deferredData: any; 2416 - let combinedData: any; 2414 + let normalData: OperationResult | undefined; 2415 + let deferredData: OperationResult | undefined; 2416 + let combinedData: OperationResult | undefined; 2417 2417 2418 2418 const client = createClient({ 2419 2419 url: 'http://0.0.0.0', ··· 2475 2475 tap(result => { 2476 2476 if (result.operation.kind === 'query') { 2477 2477 if (result.operation.key === 1) { 2478 - deferredData = result.data; 2478 + deferredData = result; 2479 2479 } else if (result.operation.key === 42) { 2480 - combinedData = result.data; 2480 + combinedData = result; 2481 2481 } else { 2482 - normalData = result.data; 2482 + normalData = result; 2483 2483 } 2484 2484 } 2485 2485 }), ··· 2530 2530 }, 2531 2531 }); 2532 2532 2533 - expect(normalData).toHaveProperty('node.id', 2); 2534 - expect(combinedData).not.toHaveProperty('deferred'); 2535 - expect(combinedData).toHaveProperty('node.id', 2); 2533 + expect(normalData).toHaveProperty('data.node.id', 2); 2534 + expect(combinedData).not.toHaveProperty('data.deferred'); 2535 + expect(combinedData).toHaveProperty('data.node.id', 2); 2536 2536 2537 2537 nextRes({ 2538 2538 ...queryResponse, ··· 2548 2548 hasNext: true, 2549 2549 }); 2550 2550 2551 - expect(deferredData).toHaveProperty('deferred.id', 1); 2552 - expect(combinedData).toHaveProperty('deferred.id', 1); 2553 - expect(combinedData).toHaveProperty('node.id', 2); 2551 + expect(deferredData).toHaveProperty('hasNext', true); 2552 + expect(deferredData).toHaveProperty('data.deferred.id', 1); 2553 + 2554 + expect(combinedData).toHaveProperty('hasNext', false); 2555 + expect(combinedData).toHaveProperty('data.deferred.id', 1); 2556 + expect(combinedData).toHaveProperty('data.node.id', 2); 2554 2557 }); 2555 2558 });
+11 -13
exchanges/graphcache/src/cacheExchange.ts
··· 30 30 operation: Operation; 31 31 outcome: CacheOutcome; 32 32 dependencies: Dependencies; 33 + hasNext: boolean; 33 34 } 34 35 35 36 type Operations = Set<number>; ··· 197 198 ): OperationResultWithMeta => { 198 199 const result = query(store, operation, results.get(operation.key)); 199 200 const cacheOutcome: CacheOutcome = result.data 200 - ? !result.partial 201 + ? !result.partial && !result.hasNext 201 202 ? 'hit' 202 203 : 'partial' 203 204 : 'miss'; ··· 211 212 operation, 212 213 data: result.data, 213 214 dependencies: result.dependencies, 215 + hasNext: result.hasNext, 214 216 }; 215 217 }; 216 218 ··· 347 349 map((res: OperationResultWithMeta): OperationResult => { 348 350 const { requestPolicy } = res.operation.context; 349 351 350 - // We don't mark cache-only responses as partial, as this would indicate 351 - // that we expect a new result to come from the network, which cannot 352 - // happen 353 - const isPartial = 354 - res.outcome === 'partial' && requestPolicy !== 'cache-only'; 355 - 356 352 // We reexecute requests marked as `cache-and-network`, and partial responses, 357 353 // if we wouldn't cause a request loop 358 354 const shouldReexecute = 359 - requestPolicy === 'cache-and-network' || 360 - (requestPolicy === 'cache-first' && 361 - isPartial && 362 - !reexecutingOperations.has(res.operation.key)); 355 + requestPolicy !== 'cache-only' && 356 + (res.hasNext || 357 + requestPolicy === 'cache-and-network' || 358 + (requestPolicy === 'cache-first' && 359 + res.outcome === 'partial' && 360 + !reexecutingOperations.has(res.operation.key))); 363 361 364 362 const result: OperationResult = { 365 363 operation: addMetadata(res.operation, { ··· 368 366 data: res.data, 369 367 error: res.error, 370 368 extensions: res.extensions, 371 - stale: shouldReexecute || isPartial, 372 - hasNext: false, 369 + stale: shouldReexecute && !res.hasNext, 370 + hasNext: shouldReexecute && res.hasNext, 373 371 }; 374 372 375 373 if (!shouldReexecute) {
+5 -1
exchanges/graphcache/src/operations/query.ts
··· 61 61 export interface QueryResult { 62 62 dependencies: Dependencies; 63 63 partial: boolean; 64 + hasNext: boolean; 64 65 data: null | Data; 65 66 } 66 67 ··· 121 122 return { 122 123 dependencies: getCurrentDependencies(), 123 124 partial: ctx.partial || !data, 125 + hasNext: ctx.hasNext, 124 126 data: data || null, 125 127 }; 126 128 }; ··· 334 336 335 337 let hasFields = false; 336 338 let hasPartials = false; 339 + let hasNext = false; 337 340 let hasChanged = typename !== input.__typename; 338 341 let node: FieldNode | void; 339 342 const output = makeData(input); ··· 455 458 // a partial query result 456 459 if (dataFieldValue === undefined && deferRef.current) { 457 460 // The field is undelivered and uncached, but is included in a deferred fragment 458 - hasFields = true; 461 + hasNext = true; 459 462 } else if ( 460 463 dataFieldValue === undefined && 461 464 ((store.schema && isFieldNullable(store.schema, typename, fieldName)) || ··· 481 484 } 482 485 483 486 ctx.partial = ctx.partial || hasPartials; 487 + ctx.hasNext = ctx.hasNext || hasNext; 484 488 return isQuery && hasPartials && !hasFields 485 489 ? undefined 486 490 : hasChanged
+2
exchanges/graphcache/src/operations/shared.ts
··· 43 43 fieldName: string; 44 44 error: ErrorLike | undefined; 45 45 partial: boolean; 46 + hasNext: boolean; 46 47 optimistic: boolean; 47 48 __internal: { 48 49 path: Array<string | number>; ··· 79 80 fieldName: '', 80 81 error: undefined, 81 82 partial: false, 83 + hasNext: false, 82 84 optimistic: !!optimistic, 83 85 __internal: { 84 86 path: [],