1import type {
2 AnyVariables,
3 Client,
4 CombinedError,
5 DocumentInput,
6 GraphQLRequest,
7 Operation,
8 OperationContext,
9 OperationResult,
10 OperationResultSource,
11} from '@urql/core';
12import { createRequest } from '@urql/core';
13import { type Ref, unref } from 'vue';
14import { watchEffect, isReadonly, computed, ref, shallowRef, isRef } from 'vue';
15import type { UseSubscriptionArgs } from './useSubscription';
16import type { UseQueryArgs } from './useQuery';
17
18export type MaybeRefOrGetter<T> = T | (() => T) | Ref<T>;
19export type MaybeRefOrGetterObj<T extends {}> =
20 T extends Record<string, never>
21 ? T
22 : { [K in keyof T]: MaybeRefOrGetter<T[K]> };
23
24const isFunction = <T>(val: MaybeRefOrGetter<T>): val is () => T =>
25 typeof val === 'function';
26
27const toValue = <T>(source: MaybeRefOrGetter<T>): T =>
28 isFunction(source) ? source() : unref(source);
29
30export const createRequestWithArgs = <
31 T = any,
32 V extends AnyVariables = AnyVariables,
33>(
34 args:
35 | UseQueryArgs<T, V>
36 | UseSubscriptionArgs<T, V>
37 | { query: MaybeRefOrGetter<DocumentInput<T, V>>; variables: V }
38): GraphQLRequest<T, V> => {
39 const _args = toValue(args);
40 return createRequest<T, V>(
41 toValue(_args.query),
42 toValue(_args.variables as MaybeRefOrGetter<V>)
43 );
44};
45
46export const useRequestState = <
47 T = any,
48 V extends AnyVariables = AnyVariables,
49>() => {
50 const hasNext: Ref<boolean> = ref(false);
51 const stale: Ref<boolean> = ref(false);
52 const fetching: Ref<boolean> = ref(false);
53 const error: Ref<CombinedError | undefined> = shallowRef();
54 const operation: Ref<Operation<T, V> | undefined> = shallowRef();
55 const extensions: Ref<Record<string, any> | undefined> = shallowRef();
56 return {
57 hasNext,
58 stale,
59 fetching,
60 error,
61 operation,
62 extensions,
63 };
64};
65
66export function useClientState<T = any, V extends AnyVariables = AnyVariables>(
67 args: UseQueryArgs<T, V> | UseSubscriptionArgs<T, V>,
68 client: Ref<Client>,
69 method: keyof Pick<Client, 'executeSubscription' | 'executeQuery'>
70) {
71 const source: Ref<OperationResultSource<OperationResult<T, V>> | undefined> =
72 shallowRef();
73
74 const isPaused: Ref<boolean> = isRef(args.pause)
75 ? args.pause
76 : typeof args.pause === 'function'
77 ? computed(args.pause)
78 : ref(!!args.pause);
79
80 const request = computed(() => createRequestWithArgs<T, V>(args));
81
82 const requestOptions = computed(() => {
83 return 'requestPolicy' in args
84 ? {
85 requestPolicy: toValue(args.requestPolicy),
86 ...toValue(args.context),
87 }
88 : {
89 ...toValue(args.context),
90 };
91 });
92
93 const pause = () => {
94 if (!isReadonly(isPaused)) {
95 isPaused.value = true;
96 }
97 };
98
99 const resume = () => {
100 if (!isReadonly(isPaused)) {
101 isPaused.value = false;
102 }
103 };
104
105 const executeRaw = (opts?: Partial<OperationContext>) => {
106 return client.value[method]<T, V>(request.value, {
107 ...requestOptions.value,
108 ...opts,
109 });
110 };
111
112 const execute = (opts?: Partial<OperationContext>) => {
113 source.value = executeRaw(opts);
114 };
115
116 // it's important to use `watchEffect()` here instead of `watch()`
117 // because it listening for reactive variables inside `executeRaw()` function
118 const teardown = watchEffect(() => {
119 source.value = !isPaused.value ? executeRaw() : undefined;
120 });
121
122 return {
123 source,
124 isPaused,
125 pause,
126 resume,
127 execute,
128 teardown,
129 };
130}