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.

feat(core): Provide OperationResultSource from Client methods (#3060)

authored by

Phil Pluckthun and committed by
GitHub
c2c1328e 2fc98429

+108 -73
+5
.changeset/curly-bees-rhyme.md
··· 1 + --- 2 + '@urql/core': minor 3 + --- 4 + 5 + Return a new `OperationResultSource` from all `Client` methods (which replaces `PromisifiedSource` on shortcut methods). This allows not only `toPromise()` to be called, but it can also be used as an awaitable `PromiseLike` and has a `.subscribe(onResult)` method aliasing the subscribe utility from `wonka`.
+49 -52
packages/core/src/client.ts
··· 37 37 OperationInstance, 38 38 OperationContext, 39 39 OperationResult, 40 + OperationResultSource, 40 41 OperationType, 41 42 RequestPolicy, 42 - PromisifiedSource, 43 43 DebugEvent, 44 44 } from './types'; 45 45 ··· 313 313 Variables extends AnyVariables = AnyVariables 314 314 >( 315 315 operation: Operation<Data, Variables> 316 - ): Source<OperationResult<Data, Variables>>; 316 + ): OperationResultSource<OperationResult<Data, Variables>>; 317 317 318 318 /** Creates a `Source` that executes the GraphQL query operation created from the passed parameters. 319 319 * 320 320 * @param query - a GraphQL document containing the query operation that will be executed. 321 321 * @param variables - the variables used to execute the operation. 322 322 * @param opts - {@link OperationContext} options that'll override and be merged with options from the {@link ClientOptions}. 323 - * @returns A {@link PromisifiedSource} issuing the {@link OperationResult | OperationResults} for the GraphQL operation. 323 + * @returns A {@link OperationResultSource} issuing the {@link OperationResult | OperationResults} for the GraphQL operation. 324 324 * 325 325 * @remarks 326 326 * The `Client.query` method is useful to programmatically create and issue a GraphQL query operation. 327 327 * It automatically calls {@link createRequest}, {@link client.createRequestOperation}, and 328 328 * {@link client.executeRequestOperation} for you, and is a convenience method. 329 329 * 330 - * Since it returns a {@link PromisifiedSource} it may be chained with a `toPromise()` call to only 330 + * Since it returns a {@link OperationResultSource} it may be chained with a `toPromise()` call to only 331 331 * await a single result in an async function. 332 332 * 333 333 * Hint: This is the recommended way to create queries programmatically when not using the bindings, ··· 361 361 query: DocumentNode | TypedDocumentNode<Data, Variables> | string, 362 362 variables: Variables, 363 363 context?: Partial<OperationContext> 364 - ): PromisifiedSource<OperationResult<Data, Variables>>; 364 + ): OperationResultSource<OperationResult<Data, Variables>>; 365 365 366 366 /** Returns the first synchronous result a `Client` provides for a given operation. 367 367 * ··· 405 405 executeQuery<Data = any, Variables extends AnyVariables = AnyVariables>( 406 406 query: GraphQLRequest<Data, Variables>, 407 407 opts?: Partial<OperationContext> | undefined 408 - ): Source<OperationResult<Data, Variables>>; 408 + ): OperationResultSource<OperationResult<Data, Variables>>; 409 409 410 410 /** Creates a `Source` that executes the GraphQL subscription operation created from the passed parameters. 411 411 * ··· 453 453 query: DocumentNode | TypedDocumentNode<Data, Variables> | string, 454 454 variables: Variables, 455 455 context?: Partial<OperationContext> 456 - ): Source<OperationResult<Data, Variables>>; 456 + ): OperationResultSource<OperationResult<Data, Variables>>; 457 457 458 458 /** Creates a `Source` that executes the GraphQL subscription operation for the passed `GraphQLRequest`. 459 459 * ··· 474 474 >( 475 475 query: GraphQLRequest<Data, Variables>, 476 476 opts?: Partial<OperationContext> | undefined 477 - ): Source<OperationResult<Data, Variables>>; 477 + ): OperationResultSource<OperationResult<Data, Variables>>; 478 478 479 479 /** Creates a `Source` that executes the GraphQL mutation operation created from the passed parameters. 480 480 * ··· 522 522 query: DocumentNode | TypedDocumentNode<Data, Variables> | string, 523 523 variables: Variables, 524 524 context?: Partial<OperationContext> 525 - ): PromisifiedSource<OperationResult<Data, Variables>>; 525 + ): OperationResultSource<OperationResult<Data, Variables>>; 526 526 527 527 /** Creates a `Source` that executes the GraphQL mutation operation for the passed `GraphQLRequest`. 528 528 * ··· 540 540 executeMutation<Data = any, Variables extends AnyVariables = AnyVariables>( 541 541 query: GraphQLRequest<Data, Variables>, 542 542 opts?: Partial<OperationContext> | undefined 543 - ): Source<OperationResult<Data, Variables>>; 543 + ): OperationResultSource<OperationResult<Data, Variables>>; 544 544 } 545 545 546 546 export const Client: new (opts: ClientOptions) => Client = function Client( ··· 721 721 722 722 executeRequestOperation(operation) { 723 723 if (operation.kind === 'mutation') { 724 - return makeResultSource(operation); 724 + return withPromise(makeResultSource(operation)); 725 725 } 726 726 727 - return make<OperationResult>(observer => { 728 - let source = active.get(operation.key); 729 - if (!source) { 730 - active.set(operation.key, (source = makeResultSource(operation))); 731 - } 727 + return withPromise( 728 + make<OperationResult>(observer => { 729 + let source = active.get(operation.key); 730 + if (!source) { 731 + active.set(operation.key, (source = makeResultSource(operation))); 732 + } 732 733 733 - return pipe( 734 - source, 735 - onStart(() => { 736 - const prevReplay = replays.get(operation.key); 737 - const isNetworkOperation = 738 - operation.context.requestPolicy === 'cache-and-network' || 739 - operation.context.requestPolicy === 'network-only'; 740 - if (operation.kind !== 'query') { 741 - return; 742 - } else if (isNetworkOperation) { 743 - dispatchOperation(operation); 744 - if (prevReplay && !prevReplay.hasNext) prevReplay.stale = true; 745 - } 734 + return pipe( 735 + source, 736 + onStart(() => { 737 + const prevReplay = replays.get(operation.key); 738 + const isNetworkOperation = 739 + operation.context.requestPolicy === 'cache-and-network' || 740 + operation.context.requestPolicy === 'network-only'; 741 + if (operation.kind !== 'query') { 742 + return; 743 + } else if (isNetworkOperation) { 744 + dispatchOperation(operation); 745 + if (prevReplay && !prevReplay.hasNext) prevReplay.stale = true; 746 + } 746 747 747 - if ( 748 - prevReplay != null && 749 - prevReplay === replays.get(operation.key) 750 - ) { 751 - observer.next(prevReplay); 752 - } else if (!isNetworkOperation) { 753 - dispatchOperation(operation); 754 - } 755 - }), 756 - onEnd(() => { 757 - isOperationBatchActive = false; 758 - observer.complete(); 759 - }), 760 - subscribe(observer.next) 761 - ).unsubscribe; 762 - }); 748 + if ( 749 + prevReplay != null && 750 + prevReplay === replays.get(operation.key) 751 + ) { 752 + observer.next(prevReplay); 753 + } else if (!isNetworkOperation) { 754 + dispatchOperation(operation); 755 + } 756 + }), 757 + onEnd(() => { 758 + isOperationBatchActive = false; 759 + observer.complete(); 760 + }), 761 + subscribe(observer.next) 762 + ).unsubscribe; 763 + }) 764 + ); 763 765 }, 764 766 765 767 executeQuery(query, opts) { ··· 785 787 if (!context || typeof context.suspense !== 'boolean') { 786 788 context = { ...context, suspense: false }; 787 789 } 788 - 789 - return withPromise( 790 - client.executeQuery(createRequest(query, variables), context) 791 - ); 790 + return client.executeQuery(createRequest(query, variables), context); 792 791 }, 793 792 794 793 readQuery(query, variables, context) { ··· 812 811 }, 813 812 814 813 mutation(query, variables, context) { 815 - return withPromise( 816 - client.executeMutation(createRequest(query, variables), context) 817 - ); 814 + return client.executeMutation(createRequest(query, variables), context); 818 815 }, 819 816 } as Client); 820 817
+22 -8
packages/core/src/types.ts
··· 1 1 import type { GraphQLError, DocumentNode } from 'graphql'; 2 - import { Source } from 'wonka'; 2 + import { Subscription, Source } from 'wonka'; 3 3 import { Client } from './client'; 4 4 import { CombinedError } from './utils/error'; 5 5 ··· 127 127 hasNext?: boolean; 128 128 } 129 129 130 - /** A `Source` with a `PromisifiedSource.toPromise` helper method, to promisify a single result. 130 + /** A source of {@link OperationResult | OperationResults}, convertable to a promise, subscribable, or Wonka Source. 131 131 * 132 132 * @remarks 133 - * The {@link Client} will often return a `PromisifiedSource` to provide the `toPromise` method. When called, this returns 134 - * a promise of the source that resolves on the first {@link OperationResult} of the `Source` that doesn't have `stale: true` 135 - * nor `hasNext: true` set, meaning, it'll resolve to the first result that is stable and complete. 133 + * The {@link Client} will often return a `OperationResultSource` to provide a more flexible Wonka {@link Source}. 134 + * 135 + * While a {@link Source} may require you to import helpers to convert it to a `Promise` for a single result, or 136 + * to subscribe to it, the `OperationResultSource` is a `PromiseLike` and has methods to convert it to a promise, 137 + * or to subscribe to it with a single method call. 136 138 */ 137 - export type PromisifiedSource<T = any> = Source<T> & { 138 - toPromise: () => Promise<T>; 139 - }; 139 + export type OperationResultSource<T extends OperationResult> = Source<T> & 140 + PromiseLike<T> & { 141 + /** Returns the first non-stale, settled results of the source. 142 + * @remarks 143 + * The `toPromise` method gives you the first result of an `OperationResultSource` 144 + * that has `hasNext: false` and `stale: false` set as a `Promise`. 145 + * 146 + * Hint: If you're trying to get updates for your results, this won't work. 147 + * This gives you only a single, promisified result, so it won't receive 148 + * cache or other updates. 149 + */ 150 + toPromise(): Promise<T>; 151 + /** Alias for Wonka's `subscribe` and calls `onResult` when subscribed to for each new `OperationResult`. */ 152 + subscribe(onResult: (value: T) => void): Subscription; 153 + }; 140 154 141 155 /** A type of Operation, either a GraphQL `query`, `mutation`, or `subscription`; or a `teardown` signal. 142 156 *
+11 -7
packages/core/src/utils/streamUtils.ts
··· 1 - import { Source, take, filter, toPromise, pipe } from 'wonka'; 2 - import { OperationResult, PromisifiedSource } from '../types'; 1 + import { Sink, Source, subscribe, take, filter, toPromise, pipe } from 'wonka'; 2 + import { OperationResult, OperationResultSource } from '../types'; 3 3 4 4 /** Patches a `toPromise` method onto the `Source` passed to it. 5 5 * @param source$ - the Wonka {@link Source} to patch. ··· 7 7 * @internal 8 8 */ 9 9 export function withPromise<T extends OperationResult>( 10 - source$: Source<T> 11 - ): PromisifiedSource<T> { 12 - (source$ as PromisifiedSource<T>).toPromise = () => 10 + _source$: Source<T> 11 + ): OperationResultSource<T> { 12 + const source$ = ((sink: Sink<T>) => 13 + _source$(sink)) as OperationResultSource<T>; 14 + source$.toPromise = () => 13 15 pipe( 14 16 source$, 15 17 filter(result => !result.stale && !result.hasNext), 16 18 take(1), 17 19 toPromise 18 20 ); 19 - 20 - return source$ as PromisifiedSource<T>; 21 + source$.then = (onResolve, onReject) => 22 + source$.toPromise().then(onResolve, onReject); 23 + source$.subscribe = onResult => subscribe(onResult)(source$); 24 + return source$; 21 25 }
+4 -1
packages/vue-urql/src/useMutation.test.ts
··· 1 + import { OperationResult, OperationResultSource } from '@urql/core'; 1 2 import { reactive } from 'vue'; 2 3 import { vi, expect, it, beforeEach, describe } from 'vitest'; 3 4 ··· 25 26 const subject = makeSubject<any>(); 26 27 const clientMutation = vi 27 28 .spyOn(client, 'executeMutation') 28 - .mockImplementation(() => subject.source); 29 + .mockImplementation( 30 + () => subject.source as OperationResultSource<OperationResult> 31 + ); 29 32 30 33 const mutation = reactive( 31 34 useMutation(
+7 -2
packages/vue-urql/src/useQuery.test.ts
··· 1 + import { OperationResult, OperationResultSource } from '@urql/core'; 1 2 import { nextTick, reactive, ref } from 'vue'; 2 3 import { vi, expect, it, describe } from 'vitest'; 3 4 ··· 18 19 const subject = makeSubject<any>(); 19 20 const executeQuery = vi 20 21 .spyOn(client, 'executeQuery') 21 - .mockImplementation(() => subject.source); 22 + .mockImplementation( 23 + () => subject.source as OperationResultSource<OperationResult> 24 + ); 22 25 23 26 const _query = useQuery({ 24 27 query: `{ test }`, ··· 109 112 const subject = makeSubject<any>(); 110 113 const executeQuery = vi 111 114 .spyOn(client, 'executeQuery') 112 - .mockImplementation(() => subject.source); 115 + .mockImplementation( 116 + () => subject.source as OperationResultSource<OperationResult> 117 + ); 113 118 114 119 const _query = useQuery({ 115 120 query: `{ test }`,
+10 -3
packages/vue-urql/src/useSubscription.test.ts
··· 1 + import { OperationResult, OperationResultSource } from '@urql/core'; 1 2 import { nextTick, reactive, ref } from 'vue'; 2 3 import { vi, expect, it, describe } from 'vitest'; 3 4 ··· 18 19 const subject = makeSubject<any>(); 19 20 const executeQuery = vi 20 21 .spyOn(client, 'executeSubscription') 21 - .mockImplementation(() => subject.source); 22 + .mockImplementation( 23 + () => subject.source as OperationResultSource<OperationResult> 24 + ); 22 25 23 26 const sub = reactive( 24 27 useSubscription({ ··· 60 63 const subject = makeSubject<any>(); 61 64 const executeSubscription = vi 62 65 .spyOn(client, 'executeSubscription') 63 - .mockImplementation(() => subject.source); 66 + .mockImplementation( 67 + () => subject.source as OperationResultSource<OperationResult> 68 + ); 64 69 65 70 const variables = ref({}); 66 71 const sub = reactive( ··· 101 106 const subject = makeSubject<any>(); 102 107 const executeSubscription = vi 103 108 .spyOn(client, 'executeSubscription') 104 - .mockImplementation(() => subject.source); 109 + .mockImplementation( 110 + () => subject.source as OperationResultSource<OperationResult> 111 + ); 105 112 106 113 const scanHandler = (currentState: any, nextState: any) => ({ 107 114 counter: (currentState ? currentState.counter : 0) + nextState.counter,