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.

(core) - Reemit results as stale if they're refetched in the background (#1375)

* Reemit results as stale on active refetches

* Add test for stale = true on reexecute behaviour

* Add exceptions to stale = true behaviour

* Add changeset

* Apply forced stale-flag to cached results

authored by

Phil Pluckthun and committed by
GitHub
1de153b6 9a1f1239

+229 -1
+5
.changeset/strong-emus-confess.md
··· 1 + --- 2 + '@urql/core': minor 3 + --- 4 + 5 + Reemit an `OperationResult` as `stale: true` if it's being reexecuted as `network-only` operation to give bindings immediate feedback on background refetches.
+202 -1
packages/core/src/client.test.ts
··· 2 2 3 3 /** NOTE: Testing in this file is designed to test both the client and its interaction with default Exchanges */ 4 4 5 - import { Source, map, pipe, subscribe, filter, toArray, tap } from 'wonka'; 5 + import { 6 + Source, 7 + delay, 8 + map, 9 + pipe, 10 + subscribe, 11 + filter, 12 + toArray, 13 + tap, 14 + } from 'wonka'; 6 15 import { gql } from './gql'; 7 16 import { Exchange, Operation, OperationResult } from './types'; 17 + import { makeOperation } from './utils'; 8 18 import { createClient } from './client'; 9 19 import { queryOperation } from './test-utils'; 10 20 ··· 289 299 }); 290 300 291 301 describe('queuing behavior', () => { 302 + beforeEach(() => { 303 + jest.useFakeTimers(); 304 + }); 305 + 306 + afterEach(() => { 307 + jest.useRealTimers(); 308 + }); 309 + 292 310 it('queues reexecuteOperation, which dispatchOperation consumes', () => { 293 311 const output: Array<Operation | OperationResult> = []; 294 312 ··· 358 376 359 377 expect(output[1]).toBe(results[0]); 360 378 expect(output[3]).toBe(results[1]); 379 + }); 380 + 381 + it('reemits previous results as stale if the operation is reexecuted as network-only', async () => { 382 + const output: OperationResult[] = []; 383 + 384 + const exchange: Exchange = () => { 385 + let countRes = 0; 386 + return ops$ => { 387 + return pipe( 388 + ops$, 389 + filter(op => op.kind !== 'teardown'), 390 + map(op => ({ 391 + data: ++countRes, 392 + operation: op, 393 + })), 394 + delay(1) 395 + ); 396 + }; 397 + }; 398 + 399 + const client = createClient({ 400 + url: 'test', 401 + exchanges: [exchange], 402 + }); 403 + 404 + const { unsubscribe } = pipe( 405 + client.executeRequestOperation(queryOperation), 406 + subscribe(result => { 407 + output.push(result); 408 + }) 409 + ); 410 + 411 + jest.advanceTimersByTime(1); 412 + 413 + expect(output.length).toBe(1); 414 + expect(output[0]).toHaveProperty('data', 1); 415 + expect(output[0]).toHaveProperty('operation.key', queryOperation.key); 416 + expect(output[0]).toHaveProperty( 417 + 'operation.context.requestPolicy', 418 + 'cache-first' 419 + ); 420 + 421 + client.reexecuteOperation( 422 + makeOperation(queryOperation.kind, queryOperation, { 423 + ...queryOperation.context, 424 + requestPolicy: 'network-only', 425 + }) 426 + ); 427 + 428 + await Promise.resolve(); 429 + 430 + expect(output.length).toBe(2); 431 + expect(output[1]).toHaveProperty('data', 1); 432 + expect(output[1]).toHaveProperty('stale', true); 433 + expect(output[1]).toHaveProperty('operation.key', queryOperation.key); 434 + expect(output[1]).toHaveProperty( 435 + 'operation.context.requestPolicy', 436 + 'cache-first' 437 + ); 438 + 439 + jest.advanceTimersByTime(1); 440 + 441 + expect(output.length).toBe(3); 442 + expect(output[2]).toHaveProperty('data', 2); 443 + expect(output[2]).toHaveProperty('stale', undefined); 444 + expect(output[2]).toHaveProperty('operation.key', queryOperation.key); 445 + expect(output[2]).toHaveProperty( 446 + 'operation.context.requestPolicy', 447 + 'network-only' 448 + ); 449 + 450 + unsubscribe(); 451 + }); 452 + 453 + it('does not reemit previous results as stale if it was marked as stale already', async () => { 454 + const output: OperationResult[] = []; 455 + const exchange: Exchange = () => ops$ => { 456 + return pipe( 457 + ops$, 458 + filter(op => op.kind !== 'teardown'), 459 + map(op => ({ 460 + data: 1, 461 + operation: op, 462 + stale: true, 463 + })), 464 + delay(1) 465 + ); 466 + }; 467 + 468 + const client = createClient({ 469 + url: 'test', 470 + exchanges: [exchange], 471 + }); 472 + 473 + const { unsubscribe } = pipe( 474 + client.executeRequestOperation(queryOperation), 475 + subscribe(result => { 476 + output.push(result); 477 + }) 478 + ); 479 + 480 + jest.advanceTimersByTime(1); 481 + 482 + expect(output.length).toBe(1); 483 + expect(output[0]).toHaveProperty('operation.key', queryOperation.key); 484 + expect(output[0]).toHaveProperty( 485 + 'operation.context.requestPolicy', 486 + 'cache-first' 487 + ); 488 + 489 + client.reexecuteOperation( 490 + makeOperation(queryOperation.kind, queryOperation, { 491 + ...queryOperation.context, 492 + requestPolicy: 'network-only', 493 + }) 494 + ); 495 + 496 + await Promise.resolve(); 497 + jest.advanceTimersByTime(1); 498 + 499 + expect(output.length).toBe(2); 500 + expect(output[1]).toHaveProperty('stale', true); 501 + expect(output[1]).toHaveProperty('operation.key', queryOperation.key); 502 + expect(output[1]).toHaveProperty( 503 + 'operation.context.requestPolicy', 504 + 'network-only' 505 + ); 506 + 507 + unsubscribe(); 508 + }); 509 + 510 + it.skip('does not reemit previous results as stale if cache emits first', async () => { 511 + const output: OperationResult[] = []; 512 + 513 + const exchange: Exchange = () => ops$ => { 514 + return pipe( 515 + ops$, 516 + filter(op => op.kind !== 'teardown'), 517 + map(op => ({ 518 + data: 1, 519 + operation: op, 520 + stale: true, 521 + })) 522 + ); 523 + }; 524 + 525 + const client = createClient({ 526 + url: 'test', 527 + exchanges: [exchange], 528 + }); 529 + 530 + const { unsubscribe } = pipe( 531 + client.executeRequestOperation(queryOperation), 532 + subscribe(result => { 533 + output.push(result); 534 + }) 535 + ); 536 + 537 + expect(output.length).toBe(1); 538 + expect(output[0]).toHaveProperty('operation.key', queryOperation.key); 539 + expect(output[0]).toHaveProperty( 540 + 'operation.context.requestPolicy', 541 + 'cache-first' 542 + ); 543 + 544 + client.reexecuteOperation( 545 + makeOperation(queryOperation.kind, queryOperation, { 546 + ...queryOperation.context, 547 + requestPolicy: 'cache-and-network', 548 + }) 549 + ); 550 + 551 + await Promise.resolve(); 552 + 553 + expect(output.length).toBe(2); 554 + expect(output[1]).toHaveProperty('stale', false); 555 + expect(output[1]).toHaveProperty('operation.key', queryOperation.key); 556 + expect(output[1]).toHaveProperty( 557 + 'operation.context.requestPolicy', 558 + 'cache-and-network' 559 + ); 560 + 561 + unsubscribe(); 361 562 }); 362 563 });
+22
packages/core/src/client.ts
··· 254 254 ) 255 255 ); 256 256 257 + const refetch$ = pipe( 258 + this.operations$, 259 + filter( 260 + (op: Operation) => 261 + op.kind === operation.kind && 262 + op.key === operation.key && 263 + op.context.requestPolicy !== 'cache-only' 264 + ) 265 + ); 266 + 257 267 const result$ = pipe( 258 268 operationResults$, 259 269 takeUntil(teardown$), 270 + switchMap(result => { 271 + if (result.stale) return fromValue(result); 272 + 273 + return merge([ 274 + fromValue(result), 275 + pipe( 276 + refetch$, 277 + take(1), 278 + map(() => ({ ...result, stale: true })) 279 + ), 280 + ]); 281 + }), 260 282 onStart<OperationResult>(() => { 261 283 this.onOperationStart(operation); 262 284 }),