1import { type App, getCurrentScope, type Ref } from 'vue';
2import { inject, provide, isRef, shallowRef } from 'vue';
3import type { ClientOptions } from '@urql/core';
4import { Client } from '@urql/core';
5
6// WeakMap to store client instances as fallback when client is provided and used in the same component
7const clientsPerScope = new WeakMap<{}, Ref<Client>>();
8
9/** Provides a {@link Client} to a component and it’s children.
10 *
11 * @param opts - {@link ClientOptions}, a {@link Client}, or a reactive ref object of a `Client`.
12 *
13 * @remarks
14 * `provideClient` provides a {@link Client} to `@urql/vue`’s GraphQL
15 * functions in children components.
16 *
17 * Hint: GraphQL functions and {@link useClient} will see the
18 * provided `Client`, even if `provideClient` has been called
19 * in the same component’s `setup` function.
20 *
21 * @example
22 * ```ts
23 * <script setup>
24 * import { provideClient } from '@urql/vue';
25 * // All of `@urql/core` is also re-exported by `@urql/vue`:
26 * import { Client, cacheExchange, fetchExchange } from '@urql/core';
27 *
28 * provideClient(new Client({
29 * url: 'https://API',
30 * exchanges: [cacheExchange, fetchExchange],
31 * }));
32 * </script>
33 * ```
34 */
35export function provideClient(opts: ClientOptions | Client | Ref<Client>) {
36 let client: Ref<Client>;
37 if (!isRef(opts)) {
38 client = shallowRef(opts instanceof Client ? opts : new Client(opts));
39 } else {
40 client = opts;
41 }
42
43 const scope = getCurrentScope();
44 if (scope) {
45 clientsPerScope.set(scope, client);
46 }
47
48 provide('$urql', client);
49 return client.value;
50}
51
52/** Provides a {@link Client} to a Vue app.
53 *
54 * @param app - the Vue {@link App}
55 * @param opts - {@link ClientOptions}, a {@link Client}, or a reactive ref object of a `Client`.
56 *
57 * @remarks
58 * `install` provides a {@link Client} to `@urql/vue`’s GraphQL
59 * functions in a Vue app.
60 *
61 * @example
62 * ```ts
63 * import * as urql from '@urql/vue';
64 * // All of `@urql/core` is also re-exported by `@urql/vue`:
65 * import { cacheExchange, fetchExchange } from '@urql/core';
66 *
67 * import { createApp } from 'vue';
68 * import Root from './App.vue';
69 *
70 * const app = createApp(Root);
71 * app.use(urql, {
72 * url: 'http://localhost:3000/graphql',
73 * exchanges: [cacheExchange, fetchExchange],
74 * });
75 * ```
76 */
77export function install(app: App, opts: ClientOptions | Client | Ref<Client>) {
78 let client: Ref<Client>;
79 if (!isRef(opts)) {
80 client = shallowRef(opts instanceof Client ? opts : new Client(opts));
81 } else {
82 client = opts;
83 }
84 app.provide('$urql', client);
85}
86
87/** Returns a provided reactive ref object of a {@link Client}.
88 *
89 * @remarks
90 * `useClient` may be called in a reactive context to retrieve a
91 * reactive ref object of a {@link Client} that’s previously been
92 * provided with {@link provideClient} in the current or a parent’s
93 * `setup` function.
94 *
95 * @throws
96 * In development, if `useClient` is called outside of a reactive context
97 * or no {@link Client} was provided, an error will be thrown.
98 */
99export function useClient(): Ref<Client> {
100 const scope = getCurrentScope();
101 if (process.env.NODE_ENV !== 'production' && !scope) {
102 throw new Error(
103 'use* function must be called within a reactive context (component setup, composable, or effect scope).'
104 );
105 }
106
107 let client = inject('$urql') as Ref<Client> | undefined;
108 if (!client) {
109 client = clientsPerScope.get(scope!);
110 }
111
112 if (process.env.NODE_ENV !== 'production' && !client) {
113 throw new Error(
114 'No urql Client was provided. Did you forget to install the plugin or call `provideClient` in a parent?'
115 );
116 }
117
118 return client!;
119}