Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 6.2 kB view raw
1import type { 2 AnyVariables, 3 GraphQLRequestParams, 4 Client, 5 OperationContext, 6} from '@urql/core'; 7import { createRequest } from '@urql/core'; 8 9import type { Source } from 'wonka'; 10import { 11 pipe, 12 map, 13 fromValue, 14 switchMap, 15 subscribe, 16 concat, 17 scan, 18 never, 19} from 'wonka'; 20 21import { derived, writable } from 'svelte/store'; 22 23import type { 24 OperationResultState, 25 OperationResultStore, 26 Pausable, 27} from './common'; 28import { initialResult, createPausable, fromStore } from './common'; 29 30/** Combines previous data with an incoming subscription result’s data. 31 * 32 * @remarks 33 * A `SubscriptionHandler` may be passed to {@link subscriptionStore} to 34 * aggregate subscription results into a combined `data` value on the 35 * {@link OperationResultStore}. 36 * 37 * This is useful when a subscription event delivers a single item, while 38 * you’d like to display a list of events. 39 * 40 * @example 41 * ```ts 42 * const NotificationsSubscription = gql` 43 * subscription { newNotification { id, text } } 44 * `; 45 * 46 * subscriptionStore( 47 * { query: NotificationsSubscription }, 48 * function combineNotifications(notifications = [], data) { 49 * return [...notifications, data.newNotification]; 50 * }, 51 * ); 52 * ``` 53 */ 54export type SubscriptionHandler<T, R> = (prev: R | undefined, data: T) => R; 55 56/** Input arguments for the {@link subscriptionStore} function. 57 * 58 * @param query - The GraphQL subscription that the `subscriptionStore` executes. 59 * @param variables - The variables for the GraphQL subscription that `subscriptionStore` executes. 60 */ 61export type SubscriptionArgs< 62 Data = any, 63 Variables extends AnyVariables = AnyVariables, 64> = { 65 /** The {@link Client} using which the subscription will be started. 66 * 67 * @remarks 68 * If you’ve previously provided a {@link Client} on Svelte’s context 69 * this can be set to {@link getContextClient}’s return value. 70 */ 71 client: Client; 72 /** Updates the {@link OperationContext} for the GraphQL subscription operation. 73 * 74 * @remarks 75 * `context` may be passed to {@link subscriptionStore}, to update the 76 * {@link OperationContext} of a subscription operation. This may be used to update 77 * the `context` that exchanges will receive for a single hook. 78 * 79 * @example 80 * ```ts 81 * subscriptionStore({ 82 * query, 83 * context: { 84 * additionalTypenames: ['Item'], 85 * }, 86 * }); 87 * ``` 88 */ 89 context?: Partial<OperationContext>; 90 /** Prevents the {@link subscriptionStore} from automatically starting the GraphQL subscription. 91 * 92 * @remarks 93 * `pause` may be set to `true` to stop the {@link subscriptionStore} from starting 94 * its subscription automatically. The store won't execute the subscription operation, 95 * until either it’s set to `false` or {@link Pausable.resume} is called. 96 */ 97 pause?: boolean; 98} & GraphQLRequestParams<Data, Variables>; 99 100/** Function to create a `subscriptionStore` that starts a GraphQL subscription. 101 * 102 * @param args - a {@link QueryArgs} object, to pass a `query`, `variables`, and options. 103 * @param handler - optionally, a {@link SubscriptionHandler} function to combine multiple subscription results. 104 * @returns a {@link OperationResultStore} of subscription results, which implements {@link Pausable}. 105 * 106 * @remarks 107 * `subscriptionStore` allows GraphQL subscriptions to be defined as Svelte stores. 108 * Given {@link SubscriptionArgs.query}, it executes the GraphQL subsription on the 109 * {@link SubscriptionArgs.client}. 110 * 111 * The returned store updates with {@link OperationResult} values when 112 * the `Client` has new results for the subscription. 113 * 114 * @see {@link https://urql.dev/goto/docs/advanced/subscriptions#svelte} for 115 * `subscriptionStore` docs. 116 * 117 * @example 118 * ```ts 119 * import { subscriptionStore, gql, getContextClient } from '@urql/svelte'; 120 * 121 * const todos = subscriptionStore({ 122 * client: getContextClient(), 123 * query: gql` 124 * subscription { 125 * newNotification { id, text } 126 * } 127 * `, 128 * }, 129 * function combineNotifications(notifications = [], data) { 130 * return [...notifications, data.newNotification]; 131 * }, 132 * ); 133 * ``` 134 */ 135export function subscriptionStore< 136 Data, 137 Result = Data, 138 Variables extends AnyVariables = AnyVariables, 139>( 140 args: SubscriptionArgs<Data, Variables>, 141 handler?: SubscriptionHandler<Data, Result> 142): OperationResultStore<Result, Variables> & Pausable { 143 const request = createRequest(args.query, args.variables as Variables); 144 145 const operation = args.client.createRequestOperation( 146 'subscription', 147 request, 148 args.context 149 ); 150 const initialState: OperationResultState<Result, Variables> = { 151 ...initialResult, 152 operation, 153 fetching: true, 154 }; 155 156 const isPaused$ = writable(!!args.pause); 157 158 const result$ = writable(initialState, () => { 159 return pipe( 160 fromStore(isPaused$), 161 switchMap( 162 (isPaused): Source<Partial<OperationResultState<Data, Variables>>> => { 163 if (isPaused) { 164 return never as any; 165 } 166 167 return concat<Partial<OperationResultState<Data, Variables>>>([ 168 fromValue({ fetching: true, stale: false }), 169 pipe( 170 args.client.executeRequestOperation(operation), 171 map(({ stale, data, error, extensions, operation }) => ({ 172 fetching: true, 173 stale: !!stale, 174 data, 175 error, 176 operation, 177 extensions, 178 })) 179 ), 180 fromValue({ fetching: false }), 181 ]); 182 } 183 ), 184 scan((result: OperationResultState<Result, Variables>, partial) => { 185 const data = 186 partial.data != null 187 ? typeof handler === 'function' 188 ? handler(result.data, partial.data) 189 : partial.data 190 : result.data; 191 return { 192 ...result, 193 ...partial, 194 data, 195 } as OperationResultState<Result, Variables>; 196 }, initialState), 197 subscribe(result => { 198 result$.set(result); 199 }) 200 ).unsubscribe; 201 }); 202 203 return { 204 ...derived(result$, (result, set) => { 205 set(result); 206 }), 207 ...createPausable(isPaused$), 208 }; 209}