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.

refactor: Clean up Client result source logic and allow multiple mutation results (#3102)

authored by

Phil Pluckthun and committed by
GitHub
849025fd 85fb3ab2

+141 -121
+7
.changeset/soft-glasses-guess.md
··· 1 + --- 2 + '@urql/core': patch 3 + --- 4 + 5 + Refactor `Client` result source construction code and allow multiple mutation 6 + results, if `result.hasNext` on a mutation result is set to `true`, indicating 7 + deferred or streamed results.
+1 -1
package.json
··· 51 51 "react-is": "^17.0.2", 52 52 "styled-components": "^5.2.3", 53 53 "vite": "^3.2.4", 54 - "wonka": "^6.2.6" 54 + "wonka": "^6.3.0" 55 55 } 56 56 }, 57 57 "devDependencies": {
+100 -87
packages/core/src/client.ts
··· 1 1 /* eslint-disable @typescript-eslint/no-use-before-define */ 2 2 3 3 import { 4 + lazy, 4 5 filter, 5 - make, 6 6 makeSubject, 7 7 onEnd, 8 8 onPush, ··· 602 602 const makeResultSource = (operation: Operation) => { 603 603 let result$ = pipe( 604 604 results$, 605 + // Filter by matching key (or _instance if it’s set) 605 606 filter( 606 607 (res: OperationResult) => 607 608 res.operation.kind === operation.kind && 608 609 res.operation.key === operation.key && 609 610 (!res.operation.context._instance || 610 611 res.operation.context._instance === operation.context._instance) 612 + ), 613 + // End the results stream when an active teardown event is sent 614 + takeUntil( 615 + pipe( 616 + operations.source, 617 + filter(op => op.kind === 'teardown' && op.key === operation.key) 618 + ) 611 619 ) 612 620 ); 613 621 614 - // Mask typename properties if the option for it is turned on 615 - if (opts.maskTypename) { 622 + if (operation.kind !== 'query') { 623 + // Interrupt subscriptions and mutations when they have no more results 616 624 result$ = pipe( 617 625 result$, 618 - map(res => ({ ...res, data: maskTypename(res.data, true) })) 626 + takeWhile(result => !!result.hasNext, true) 627 + ); 628 + } else { 629 + result$ = pipe( 630 + result$, 631 + // Add `stale: true` flag when a new operation is sent for queries 632 + switchMap(result => { 633 + const value$ = fromValue(result); 634 + return result.stale 635 + ? value$ 636 + : merge([ 637 + value$, 638 + pipe( 639 + operations.source, 640 + filter( 641 + op => 642 + op.kind === 'query' && 643 + op.key === operation.key && 644 + op.context.requestPolicy !== 'cache-only' 645 + ), 646 + take(1), 647 + map(() => ({ ...result, stale: true })) 648 + ), 649 + ]); 650 + }) 619 651 ); 620 652 } 621 653 622 - if (operation.kind !== 'query') { 654 + if (operation.kind !== 'mutation') { 655 + result$ = pipe( 656 + result$, 657 + // Store replay result 658 + onPush(result => { 659 + dispatched.delete(operation.key); 660 + replays.set(operation.key, result); 661 + }), 662 + // Cleanup active states on end of source 663 + onEnd(() => { 664 + // Delete the active operation handle 665 + dispatched.delete(operation.key); 666 + replays.delete(operation.key); 667 + active.delete(operation.key); 668 + // Interrupt active queue 669 + isOperationBatchActive = false; 670 + // Delete all queued up operations of the same key on end 671 + for (let i = queue.length - 1; i >= 0; i--) 672 + if (queue[i].key === operation.key) queue.splice(i, 1); 673 + // Dispatch a teardown signal for the stopped operation 674 + nextOperation( 675 + makeOperation('teardown', operation, operation.context) 676 + ); 677 + }) 678 + ); 679 + } else { 623 680 result$ = pipe( 624 681 result$, 682 + // Send mutation operation on start 625 683 onStart(() => { 626 684 nextOperation(operation); 627 685 }) 628 686 ); 629 687 } 630 688 631 - // A mutation is always limited to just a single result and is never shared 632 - if (operation.kind === 'mutation') { 633 - return pipe(result$, take(1)); 634 - } 635 - 636 - if (operation.kind === 'subscription') { 689 + // Mask typename properties if the option for it is turned on 690 + if (opts.maskTypename) { 637 691 result$ = pipe( 638 692 result$, 639 - takeWhile(result => !!result.hasNext) 693 + map(res => ({ ...res, data: maskTypename(res.data, true) })) 640 694 ); 641 695 } 642 696 643 - return pipe( 644 - result$, 645 - // End the results stream when an active teardown event is sent 646 - takeUntil( 647 - pipe( 648 - operations.source, 649 - filter(op => op.kind === 'teardown' && op.key === operation.key) 650 - ) 651 - ), 652 - switchMap(result => { 653 - if (operation.kind !== 'query' || result.stale) { 654 - return fromValue(result); 655 - } 656 - 657 - return merge([ 658 - fromValue(result), 659 - // Mark a result as stale when a new operation is sent for it 660 - pipe( 661 - operations.source, 662 - filter( 663 - op => 664 - op.kind === 'query' && 665 - op.key === operation.key && 666 - op.context.requestPolicy !== 'cache-only' 667 - ), 668 - take(1), 669 - map(() => ({ ...result, stale: true })) 670 - ), 671 - ]); 672 - }), 673 - onPush(result => { 674 - dispatched.delete(operation.key); 675 - replays.set(operation.key, result); 676 - }), 677 - onEnd(() => { 678 - // Delete the active operation handle 679 - dispatched.delete(operation.key); 680 - replays.delete(operation.key); 681 - active.delete(operation.key); 682 - // Delete all queued up operations of the same key on end 683 - for (let i = queue.length - 1; i >= 0; i--) 684 - if (queue[i].key === operation.key) queue.splice(i, 1); 685 - // Dispatch a teardown signal for the stopped operation 686 - nextOperation(makeOperation('teardown', operation, operation.context)); 687 - }), 688 - share 689 - ); 697 + return share(result$); 690 698 }; 691 699 692 700 const instance: Client = ··· 736 744 } 737 745 738 746 return withPromise( 739 - make<OperationResult>(observer => { 747 + lazy<OperationResult>(() => { 740 748 let source = active.get(operation.key); 741 749 if (!source) { 742 750 active.set(operation.key, (source = makeResultSource(operation))); 743 751 } 744 752 745 - return pipe( 746 - source, 747 - onStart(() => { 748 - const prevReplay = replays.get(operation.key); 749 - const isNetworkOperation = 750 - operation.context.requestPolicy === 'cache-and-network' || 751 - operation.context.requestPolicy === 'network-only'; 752 - if (operation.kind !== 'query') { 753 - return; 754 - } else if (isNetworkOperation) { 755 - dispatchOperation(operation); 756 - if (prevReplay && !prevReplay.hasNext) prevReplay.stale = true; 757 - } 753 + const isNetworkOperation = 754 + operation.context.requestPolicy === 'cache-and-network' || 755 + operation.context.requestPolicy === 'network-only'; 756 + const replay = replays.get(operation.key); 758 757 759 - if ( 760 - prevReplay != null && 761 - prevReplay === replays.get(operation.key) 762 - ) { 763 - observer.next(prevReplay); 764 - } else if (!isNetworkOperation) { 758 + if (operation.kind !== 'query' || !replay || isNetworkOperation) { 759 + source = pipe( 760 + source, 761 + onStart(() => { 765 762 dispatchOperation(operation); 766 - } 767 - }), 768 - onEnd(() => { 769 - isOperationBatchActive = false; 770 - observer.complete(); 771 - }), 772 - subscribe(observer.next) 773 - ).unsubscribe; 763 + }) 764 + ); 765 + } 766 + 767 + if (operation.kind === 'query' && replay) { 768 + return merge([ 769 + source, 770 + pipe( 771 + fromValue(replay), 772 + filter(replay => { 773 + if (replay === replays.get(operation.key)) { 774 + if (isNetworkOperation && !replay.hasNext) 775 + replay.stale = true; 776 + return true; 777 + } else { 778 + if (!isNetworkOperation) dispatchOperation(operation); 779 + return false; 780 + } 781 + }) 782 + ), 783 + ]); 784 + } else { 785 + return source; 786 + } 774 787 }) 775 788 ); 776 789 },
+33 -33
pnpm-lock.yaml
··· 8 8 react-is: ^17.0.2 9 9 styled-components: ^5.2.3 10 10 vite: ^3.2.4 11 - wonka: ^6.2.6 11 + wonka: ^6.3.0 12 12 13 13 importers: 14 14 ··· 129 129 specifiers: 130 130 '@urql/core': '>=3.2.2' 131 131 graphql: ^16.6.0 132 - wonka: ^6.2.6 132 + wonka: ^6.3.0 133 133 dependencies: 134 134 '@urql/core': link:../../packages/core 135 - wonka: 6.2.6 135 + wonka: 6.3.0 136 136 devDependencies: 137 137 graphql: 16.6.0 138 138 ··· 140 140 specifiers: 141 141 '@urql/core': '>=3.2.2' 142 142 graphql: ^16.6.0 143 - wonka: ^6.2.6 143 + wonka: ^6.3.0 144 144 dependencies: 145 145 '@urql/core': link:../../packages/core 146 - wonka: 6.2.6 146 + wonka: 6.3.0 147 147 devDependencies: 148 148 graphql: 16.6.0 149 149 ··· 151 151 specifiers: 152 152 '@urql/core': '>=3.2.2' 153 153 graphql: ^16.6.0 154 - wonka: ^6.2.6 154 + wonka: ^6.3.0 155 155 dependencies: 156 156 '@urql/core': link:../../packages/core 157 - wonka: 6.2.6 157 + wonka: 6.3.0 158 158 devDependencies: 159 159 graphql: 16.6.0 160 160 ··· 170 170 react: ^17.0.2 171 171 react-dom: ^17.0.2 172 172 urql: workspace:* 173 - wonka: ^6.2.6 173 + wonka: ^6.3.0 174 174 dependencies: 175 175 '@0no-co/graphql.web': 1.0.0_graphql@16.6.0 176 176 '@urql/core': link:../../packages/core 177 - wonka: 6.2.6 177 + wonka: 6.3.0 178 178 devDependencies: 179 179 '@cypress/react': 7.0.2_kxqn2c7raunyx4zfzvxjupflne 180 180 '@urql/exchange-execute': link:../execute ··· 190 190 '@urql/core': '>=3.2.2' 191 191 extract-files: ^11.0.0 192 192 graphql: ^16.6.0 193 - wonka: ^6.2.6 193 + wonka: ^6.3.0 194 194 dependencies: 195 195 '@urql/core': link:../../packages/core 196 196 extract-files: 11.0.0 197 - wonka: 6.2.6 197 + wonka: 6.3.0 198 198 devDependencies: 199 199 graphql: 16.6.0 200 200 ··· 202 202 specifiers: 203 203 '@urql/core': '>=3.2.2' 204 204 graphql: ^16.6.0 205 - wonka: ^6.2.6 205 + wonka: ^6.3.0 206 206 dependencies: 207 207 '@urql/core': link:../../packages/core 208 - wonka: 6.2.6 208 + wonka: 6.3.0 209 209 devDependencies: 210 210 graphql: 16.6.0 211 211 ··· 213 213 specifiers: 214 214 '@urql/core': '>=3.2.2' 215 215 graphql: ^16.6.0 216 - wonka: ^6.2.6 216 + wonka: ^6.3.0 217 217 dependencies: 218 218 '@urql/core': link:../../packages/core 219 - wonka: 6.2.6 219 + wonka: 6.3.0 220 220 devDependencies: 221 221 graphql: 16.6.0 222 222 ··· 225 225 '@types/react': ^17.0.39 226 226 '@urql/core': '>=3.2.2' 227 227 graphql: ^16.6.0 228 - wonka: ^6.2.6 228 + wonka: ^6.3.0 229 229 dependencies: 230 230 '@urql/core': link:../../packages/core 231 - wonka: 6.2.6 231 + wonka: 6.3.0 232 232 devDependencies: 233 233 '@types/react': 17.0.52 234 234 graphql: 16.6.0 ··· 237 237 specifiers: 238 238 '@urql/core': '>=3.2.2' 239 239 graphql: ^16.6.0 240 - wonka: ^6.2.6 240 + wonka: ^6.3.0 241 241 dependencies: 242 242 '@urql/core': link:../../packages/core 243 - wonka: 6.2.6 243 + wonka: 6.3.0 244 244 devDependencies: 245 245 graphql: 16.6.0 246 246 ··· 248 248 specifiers: 249 249 '@urql/core': '>=3.2.2' 250 250 graphql: ^16.6.0 251 - wonka: ^6.2.6 251 + wonka: ^6.3.0 252 252 dependencies: 253 253 '@urql/core': link:../../packages/core 254 - wonka: 6.2.6 254 + wonka: 6.3.0 255 255 devDependencies: 256 256 graphql: 16.6.0 257 257 258 258 packages/core: 259 259 specifiers: 260 260 '@0no-co/graphql.web': ^1.0.0 261 - wonka: ^6.2.6 261 + wonka: ^6.3.0 262 262 dependencies: 263 263 '@0no-co/graphql.web': 1.0.0 264 - wonka: 6.2.6 264 + wonka: 6.3.0 265 265 266 266 packages/introspection: 267 267 specifiers: ··· 310 310 '@urql/core': ^3.2.2 311 311 graphql: ^16.6.0 312 312 preact: ^10.13.0 313 - wonka: ^6.2.6 313 + wonka: ^6.3.0 314 314 dependencies: 315 315 '@urql/core': link:../core 316 - wonka: 6.2.6 316 + wonka: 6.3.0 317 317 devDependencies: 318 318 '@testing-library/preact': 2.0.1_preact@10.13.1 319 319 graphql: 16.6.0 ··· 336 336 react-ssr-prepass: ^1.1.2 337 337 react-test-renderer: ^17.0.1 338 338 vite: ^3.2.4 339 - wonka: ^6.2.6 339 + wonka: ^6.3.0 340 340 dependencies: 341 341 '@urql/core': link:../core 342 - wonka: 6.2.6 342 + wonka: 6.3.0 343 343 devDependencies: 344 344 '@cypress/react': 7.0.2_omnm57pgrvq3mbg7qqmuk7p7le 345 345 '@cypress/vite-dev-server': 5.0.4 ··· 446 446 '@urql/core': ^3.2.2 447 447 graphql: ^16.6.0 448 448 svelte: ^3.20.0 449 - wonka: ^6.2.6 449 + wonka: ^6.3.0 450 450 dependencies: 451 451 '@urql/core': link:../core 452 - wonka: 6.2.6 452 + wonka: 6.3.0 453 453 devDependencies: 454 454 graphql: 16.6.0 455 455 svelte: 3.37.0 ··· 460 460 '@vue/test-utils': ^2.3.0 461 461 graphql: ^16.6.0 462 462 vue: ^3.2.47 463 - wonka: ^6.2.6 463 + wonka: ^6.3.0 464 464 dependencies: 465 465 '@urql/core': link:../core 466 - wonka: 6.2.6 466 + wonka: 6.3.0 467 467 devDependencies: 468 468 '@vue/test-utils': 2.3.0_vue@3.2.47 469 469 graphql: 16.6.0 ··· 15636 15636 execa: 1.0.0 15637 15637 dev: true 15638 15638 15639 - /wonka/6.2.6: 15640 - resolution: {integrity: sha512-ExUBenRwEyf8YswAVOFZDmAdiUMgpnuyDV28G9bF+73o2hnhAG9tLqnn7LmtWgB2KCFQdWywbUfvUW3UgxARew==} 15639 + /wonka/6.3.0: 15640 + resolution: {integrity: sha512-7np+Kj4OnDQeEN0kafYLkPFKj1Qo+k7mNgyMHSgOeg+9AEvJbL8ipTBgSCTQfGcgVo6TPNU4T5+AZ2rAOyVrAw==} 15641 15641 dev: false 15642 15642 15643 15643 /word-wrap/1.2.3: