1import type {
2 Client,
3 GraphQLRequestParams,
4 AnyVariables,
5 OperationContext,
6 RequestPolicy,
7} from '@urql/core';
8import { createRequest } from '@urql/core';
9
10import type { Source } from 'wonka';
11import {
12 pipe,
13 map,
14 fromValue,
15 switchMap,
16 subscribe,
17 concat,
18 scan,
19 never,
20} from 'wonka';
21
22import { derived, writable } from 'svelte/store';
23
24import type {
25 OperationResultState,
26 OperationResultStore,
27 Pausable,
28} from './common';
29import { initialResult, createPausable, fromStore } from './common';
30
31/** Input arguments for the {@link queryStore} function.
32 *
33 * @param query - The GraphQL query that the `queryStore` executes.
34 * @param variables - The variables for the GraphQL query that `queryStore` executes.
35 */
36export type QueryArgs<
37 Data = any,
38 Variables extends AnyVariables = AnyVariables,
39> = {
40 /** The {@link Client} using which the query will be executed.
41 *
42 * @remarks
43 * If you’ve previously provided a {@link Client} on Svelte’s context
44 * this can be set to {@link getContextClient}’s return value.
45 */
46 client: Client;
47 /** Updates the {@link OperationContext} for the executed GraphQL query operation.
48 *
49 * @remarks
50 * `context` may be passed to {@link queryStore}, to update the {@link OperationContext}
51 * of a query operation. This may be used to update the `context` that exchanges
52 * will receive for a single hook.
53 *
54 * @example
55 * ```ts
56 * queryStore({
57 * query,
58 * context: {
59 * additionalTypenames: ['Item'],
60 * },
61 * });
62 * ```
63 */
64 context?: Partial<OperationContext>;
65 /** Sets the {@link RequestPolicy} for the executed GraphQL query operation.
66 *
67 * @remarks
68 * `requestPolicy` modifies the {@link RequestPolicy} of the GraphQL query operation
69 * that the {@link queryStore} executes, and indicates a caching strategy for cache exchanges.
70 *
71 * For example, when set to `'cache-and-network'`, the `queryStore` will
72 * receive a cached result with `stale: true` and an API request will be
73 * sent in the background.
74 *
75 * @see {@link OperationContext.requestPolicy} for where this value is set.
76 */
77 requestPolicy?: RequestPolicy;
78 /** Prevents the {@link queryStore} from automatically executing GraphQL query operations.
79 *
80 * @remarks
81 * `pause` may be set to `true` to stop the {@link queryStore} from executing
82 * automatically. The store will stop receiving updates from the {@link Client}
83 * and won’t execute the query operation, until either it’s set to `false`
84 * or {@link Pausable.resume} is called.
85 *
86 * @see {@link https://urql.dev/goto/docs/basics/svelte#pausing-queries} for
87 * documentation on the `pause` option.
88 */
89 pause?: boolean;
90} & GraphQLRequestParams<Data, Variables>;
91
92/** Function to create a `queryStore` that runs a GraphQL query and updates with GraphQL results.
93 *
94 * @param args - a {@link QueryArgs} object, to pass a `query`, `variables`, and options.
95 * @returns a {@link OperationResultStore} of query results, which implements {@link Pausable}.
96 *
97 * @remarks
98 * `queryStore` allows GraphQL queries to be defined as Svelte stores.
99 * Given {@link QueryArgs.query}, it executes the GraphQL query on the
100 * {@link QueryArgs.client}.
101 *
102 * The returned store updates with {@link OperationResult} values when
103 * the `Client` has new results for the query.
104 *
105 * @see {@link https://urql.dev/goto/docs/basics/svelte#queries} for `queryStore` docs.
106 *
107 * @example
108 * ```ts
109 * import { queryStore, gql, getContextClient } from '@urql/svelte';
110 *
111 * const todos = queryStore({
112 * client: getContextClient(),
113 * query: gql`{ todos { id, title } }`,
114 * });
115 * ```
116 */
117export function queryStore<
118 Data = any,
119 Variables extends AnyVariables = AnyVariables,
120>(
121 args: QueryArgs<Data, Variables>
122): OperationResultStore<Data, Variables> &
123 Pausable & { reexecute: (context: Partial<OperationContext>) => void } {
124 const request = createRequest(args.query, args.variables as Variables);
125
126 const context: Partial<OperationContext> = {
127 requestPolicy: args.requestPolicy,
128 ...args.context,
129 };
130
131 const operation = args.client.createRequestOperation(
132 'query',
133 request,
134 context
135 );
136
137 const operation$ = writable(operation);
138
139 const initialState: OperationResultState<Data, Variables> = {
140 ...initialResult,
141 operation,
142 };
143
144 const isPaused$ = writable(!!args.pause);
145
146 const result$ = writable(initialState, () => {
147 return pipe(
148 fromStore(isPaused$),
149 switchMap(
150 (isPaused): Source<Partial<OperationResultState<Data, Variables>>> => {
151 if (isPaused) {
152 return never as any;
153 }
154
155 return pipe(
156 fromStore(operation$),
157 switchMap(operation => {
158 return concat<Partial<OperationResultState<Data, Variables>>>([
159 fromValue({ fetching: true, stale: false, hasNext: false }),
160 pipe(
161 args.client.executeRequestOperation(operation),
162 map(
163 ({
164 stale,
165 data,
166 error,
167 extensions,
168 operation,
169 hasNext,
170 }) => ({
171 fetching: false,
172 stale: !!stale,
173 data,
174 error,
175 operation,
176 extensions,
177 hasNext,
178 })
179 )
180 ),
181 fromValue({ fetching: false, hasNext: false }),
182 ]);
183 })
184 );
185 }
186 ),
187 scan(
188 (result: OperationResultState<Data, Variables>, partial) => ({
189 ...result,
190 ...partial,
191 }),
192 initialState
193 ),
194 subscribe(result => {
195 result$.set(result);
196 })
197 ).unsubscribe;
198 });
199
200 const reexecute = (context: Partial<OperationContext>) => {
201 const newContext = { ...context, ...args.context };
202 const operation = args.client.createRequestOperation(
203 'query',
204 request,
205 newContext
206 );
207 isPaused$.set(false);
208 operation$.set(operation);
209 };
210
211 return {
212 ...derived(result$, (result, set) => {
213 set(result);
214 }),
215 ...createPausable(isPaused$),
216 reexecute,
217 };
218}