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.

(svelte) - Expand Readables with factory and PromiseLike methods (#862)

* Reimplement query/mutate/subscription store factories

`query` and `subscription` are now Readables as well as recursive Store factories.
`mutate` is now curried but also returns Readable.

* Add rule for overwriting `pause` with `false` by default

* Add PromiseLike interface to mutate and query

* Keep shared state active on subscription/query

* Allow calling query/subscription store factory without args

* Fix pause/execute behaviour

* Add changeset

authored by

Phil Plückthun and committed by
GitHub
3c1fb7cb 147e6281

+245 -58
+39
.changeset/strange-donuts-listen.md
··· 1 + --- 2 + '@urql/svelte': minor 3 + --- 4 + 5 + Refactor all operations to allow for more use-cases which preserve state and allow all modes of Svelte to be applied to urql. 6 + 7 + ``` 8 + // Standard Usage: 9 + mutate({ query, variables })() 10 + 11 + // Subscribable Usage: 12 + $: result = mutate({ query, variables }); 13 + 14 + // Curried Usage 15 + const executeMutation = mutate({ query, variables }); 16 + const onClick = () => executeMutation(); 17 + 18 + // Curried Usage with overrides 19 + const executeMutation = mutate({ query }); 20 + const onClick = () => await executeMutation({ variables }); 21 + 22 + // Subscribable Usage (as before): 23 + $: result = query({ query: TestQuery, variables }); 24 + 25 + // Subscribable Usage which preserves state over time: 26 + const testQuery = query({ query: TestQuery }); 27 + // - this preserves the state even when the variables change! 28 + $: result = testQuery({ variables }); 29 + 30 + // Promise-based callback usage: 31 + const testQuery = query({ query: TestQuery }); 32 + const doQuery = async () => await testQuery; 33 + 34 + // Promise-based usage updates the subscribables! 35 + const testQuery = query({ query: TestQuery }); 36 + const doQuery = async () => await testQuery; 37 + // - doQuery will also update this result 38 + $: result = query({ query: TestQuery, variables }); 39 + ```
+7 -4
packages/svelte-urql/example/src/App.svelte
··· 5 5 6 6 let i = 0; 7 7 8 - $: todos = query({ 8 + const todosQuery = query({ 9 9 query: ` 10 10 query { 11 11 todos { ··· 15 15 } 16 16 } 17 17 `, 18 - variables: { i }, 19 18 }); 20 19 21 - const increment = () => i = i + 1; 20 + $: todos = todosQuery({ pause: true }); 21 + 22 + const execute = () => todosQuery().then(); 22 23 </script> 23 24 24 25 {#if $todos.fetching} 25 26 Loading... 26 27 {:else if $todos.error} 27 28 Oh no! {$todos.error.message} 29 + {:else if !$todos.data} 30 + No data 28 31 {:else} 29 32 <ul> 30 33 {#each $todos.data.todos as todo} ··· 33 36 </ul> 34 37 {/if} 35 38 36 - <button on:click={increment}>Increment {i}</button> 39 + <button on:click={execute}>Execute</button>
+36 -4
packages/svelte-urql/src/operations/mutate.ts
··· 1 + import { pipe, subscribe } from 'wonka'; 1 2 import { OperationResult, OperationContext } from '@urql/core'; 3 + import { Readable } from 'svelte/store'; 2 4 import { DocumentNode } from 'graphql'; 3 5 4 6 import { getClient } from '../context'; ··· 9 11 context?: Partial<OperationContext>; 10 12 } 11 13 14 + export interface MutationStore<T = any, V = object> 15 + extends Readable<OperationResult<T>>, 16 + PromiseLike<OperationResult<T>> { 17 + (additionalArgs?: Partial<MutationArguments<V>>): Promise<OperationResult<T>>; 18 + } 19 + 12 20 export const mutate = <T = any, V = object>( 13 21 args: MutationArguments<V> 14 - ): Promise<OperationResult<T>> => { 15 - return getClient() 16 - .mutation(args.query, args.variables as any, args.context) 17 - .toPromise(); 22 + ): MutationStore<T, V> => { 23 + const client = getClient(); 24 + 25 + function mutate$(additionalArgs?: Partial<MutationArguments<V>>) { 26 + const mergedArgs = { ...args, ...additionalArgs }; 27 + return client 28 + .mutation( 29 + mergedArgs.query, 30 + mergedArgs.variables as any, 31 + mergedArgs.context 32 + ) 33 + .toPromise(); 34 + } 35 + 36 + mutate$.subscribe = (onValue: (result: OperationResult<T>) => void) => { 37 + return pipe( 38 + client.mutation(args.query, args.variables as any, args.context), 39 + subscribe(onValue) 40 + ).unsubscribe; 41 + }; 42 + 43 + mutate$.then = ( 44 + onValue: (result: OperationResult<T>) => any 45 + ): Promise<any> => { 46 + return mutate$().then(onValue); 47 + }; 48 + 49 + return mutate$ as any; 18 50 };
+84 -25
packages/svelte-urql/src/operations/query.ts
··· 1 + import { 2 + pipe, 3 + makeSubject, 4 + fromValue, 5 + switchMap, 6 + onStart, 7 + concat, 8 + scan, 9 + map, 10 + take, 11 + share, 12 + subscribe, 13 + publish, 14 + toPromise, 15 + } from 'wonka'; 16 + 1 17 import { RequestPolicy, OperationContext, CombinedError } from '@urql/core'; 2 - import { pipe, fromValue, concat, scan, map, subscribe } from 'wonka'; 18 + 3 19 import { Readable } from 'svelte/store'; 4 20 import { DocumentNode } from 'graphql'; 5 21 ··· 11 27 variables?: V; 12 28 requestPolicy?: RequestPolicy; 13 29 pollInterval?: number; 30 + pause?: boolean; 14 31 context?: Partial<OperationContext>; 15 32 } 16 33 ··· 22 39 extensions?: Record<string, any>; 23 40 } 24 41 42 + export interface QueryStore<T = any, V = object> 43 + extends Readable<QueryResult<T>>, 44 + PromiseLike<QueryResult<T>> { 45 + (args?: Partial<QueryArguments<V>>): QueryStore<T>; 46 + } 47 + 25 48 export const query = <T = any, V = object>( 26 49 args: QueryArguments<V> 27 - ): Readable<QueryResult<T>> => { 50 + ): QueryStore<T, V> => { 28 51 const client = getClient(); 52 + const { source: args$, next: nextArgs } = makeSubject<QueryArguments<V>>(); 29 53 30 54 const queryResult$ = pipe( 31 - concat([ 32 - fromValue({ fetching: true, stale: false }), 33 - pipe( 34 - client.query<T>(args.query, args.variables, { 35 - requestPolicy: args.requestPolicy, 36 - pollInterval: args.pollInterval, 37 - ...args.context, 38 - }), 39 - map(({ stale, data, error, extensions }) => ({ 40 - fetching: false, 41 - stale: !!stale, 42 - data, 43 - error, 44 - extensions, 45 - })) 46 - ), 47 - fromValue({ fetching: false, stale: false }), 48 - ]), 55 + args$, 56 + switchMap(args => { 57 + if (args.pause) { 58 + return fromValue({ fetching: false, stale: false }); 59 + } 60 + 61 + return concat([ 62 + // Initially set fetching to true 63 + fromValue({ fetching: true, stale: false }), 64 + pipe( 65 + client.query<T>(args.query, args.variables, { 66 + requestPolicy: args.requestPolicy, 67 + pollInterval: args.pollInterval, 68 + ...args.context, 69 + }), 70 + map(({ stale, data, error, extensions }) => ({ 71 + fetching: false, 72 + stale: !!stale, 73 + data, 74 + error, 75 + extensions, 76 + })) 77 + ), 78 + // When the source proactively closes, fetching is set to false 79 + fromValue({ fetching: false, stale: false }), 80 + ]); 81 + }), 82 + // The individual partial results are merged into each previous result 49 83 scan( 50 84 (result, partial) => ({ 51 85 ...result, 52 86 ...partial, 53 87 }), 54 88 initialState 55 - ) 89 + ), 90 + share 56 91 ); 57 92 58 - return { 59 - subscribe(onValue) { 60 - return pipe(queryResult$, subscribe(onValue)).unsubscribe; 61 - }, 93 + publish(queryResult$); 94 + 95 + const queryStore = (baseArgs: QueryArguments<V>): QueryStore<T, V> => { 96 + const result$ = pipe( 97 + queryResult$, 98 + onStart(() => { 99 + nextArgs({ ...baseArgs, ...args }); 100 + }) 101 + ); 102 + 103 + function query$(args?: Partial<QueryArguments<V>>) { 104 + return queryStore({ 105 + ...baseArgs, 106 + ...args, 107 + }); 108 + } 109 + 110 + query$.subscribe = (onValue: (result: QueryResult<T>) => void) => { 111 + return pipe(result$, subscribe(onValue)).unsubscribe; 112 + }; 113 + 114 + query$.then = (onValue: (result: QueryResult<T>) => any): Promise<any> => { 115 + return pipe(result$, take(1), toPromise).then(onValue); 116 + }; 117 + 118 + return query$ as any; 62 119 }; 120 + 121 + return queryStore(args); 63 122 };
+79 -25
packages/svelte-urql/src/operations/subscription.ts
··· 1 - import { OperationContext, CombinedError, createRequest } from '@urql/core'; 2 - import { pipe, fromValue, concat, scan, map, subscribe } from 'wonka'; 1 + import { 2 + pipe, 3 + makeSubject, 4 + fromValue, 5 + switchMap, 6 + onStart, 7 + concat, 8 + scan, 9 + map, 10 + share, 11 + subscribe, 12 + publish, 13 + } from 'wonka'; 14 + 15 + import { OperationContext, CombinedError } from '@urql/core'; 3 16 import { Readable } from 'svelte/store'; 4 17 import { DocumentNode } from 'graphql'; 5 18 ··· 9 22 export interface SubscriptionArguments<V> { 10 23 query: string | DocumentNode; 11 24 variables?: V; 25 + pause?: boolean; 12 26 context?: Partial<OperationContext>; 13 27 } 14 28 ··· 22 36 extensions?: Record<string, any>; 23 37 } 24 38 39 + export interface SubscriptionStore<T = any, R = T, V = object> 40 + extends Readable<SubscriptionResult<T>> { 41 + (args?: Partial<SubscriptionArguments<V>>): SubscriptionStore<T, R, V>; 42 + } 43 + 25 44 export const subscription = <T = any, R = T, V = object>( 26 45 args: SubscriptionArguments<V>, 27 46 handler?: SubscriptionHandler<T, R> 28 - ): Readable<SubscriptionResult<T>> => { 47 + ): SubscriptionStore<T, R, V> => { 29 48 const client = getClient(); 30 - const request = createRequest(args.query, args.variables as any); 49 + const { source: args$, next: nextArgs } = makeSubject< 50 + SubscriptionArguments<V> 51 + >(); 31 52 32 - const queryResult$ = pipe( 33 - concat([ 34 - fromValue({ fetching: true, stale: false }), 35 - pipe( 36 - client.executeSubscription(request, args.context), 37 - map(({ stale, data, error, extensions }) => ({ 38 - fetching: false, 39 - stale: !!stale, 40 - data, 41 - error, 42 - extensions, 43 - })) 44 - ), 45 - fromValue({ fetching: false, stale: false }), 46 - ]), 53 + const subscriptionResult$ = pipe( 54 + args$, 55 + switchMap(args => { 56 + if (args.pause) { 57 + return fromValue({ fetching: false, stale: false }); 58 + } 59 + 60 + return concat([ 61 + // Initially set fetching to true 62 + fromValue({ fetching: true, stale: false }), 63 + pipe( 64 + client.subscription<T>(args.query, args.variables, args.context), 65 + map(({ stale, data, error, extensions }) => ({ 66 + fetching: false, 67 + stale: !!stale, 68 + data, 69 + error, 70 + extensions, 71 + })) 72 + ), 73 + // When the source proactively closes, fetching is set to false 74 + fromValue({ fetching: false, stale: false }), 75 + ]); 76 + }), 77 + // The individual partial results are merged into each previous result 47 78 scan((result, partial: any) => { 48 79 const data = 49 80 partial.data !== undefined ··· 52 83 : partial.data 53 84 : result.data; 54 85 return { ...result, ...partial, data }; 55 - }, initialState) 86 + }, initialState), 87 + share 56 88 ); 57 89 58 - return { 59 - subscribe(onValue) { 60 - const { unsubscribe } = pipe(queryResult$, subscribe(onValue)); 61 - return unsubscribe as () => void; 62 - }, 90 + publish(subscriptionResult$); 91 + 92 + const subscriptionStore = ( 93 + baseArgs: SubscriptionArguments<V> 94 + ): SubscriptionStore<T, R, V> => { 95 + function subscription$(args?: Partial<SubscriptionArguments<V>>) { 96 + return subscriptionStore({ 97 + ...baseArgs, 98 + ...args, 99 + }); 100 + } 101 + 102 + subscription$.subscribe = ( 103 + onValue: (result: SubscriptionResult<T>) => void 104 + ) => { 105 + return pipe( 106 + subscriptionResult$, 107 + onStart(() => { 108 + nextArgs({ ...baseArgs, ...args }); 109 + }), 110 + subscribe(onValue) 111 + ).unsubscribe; 112 + }; 113 + 114 + return subscription$ as any; 63 115 }; 116 + 117 + return subscriptionStore(args); 64 118 };