[READ-ONLY] Mirror of https://github.com/danielroe/vue-sanity. Sanity integration for Vue Composition API
composition-api
hacktoberfest
javascript
nuxt
sanity
typescript
vue
vuejs
6.9 kB
260 lines
1import {
2 computed,
3 inject,
4 watch,
5 InjectionKey,
6 Ref,
7 isRef,
8} from '@vue/composition-api'
9import minifier from 'minify-groq'
10import type { SanityClient } from '@sanity/client'
11import type { QueryBuilder } from 'sanity-typed-queries'
12
13import { useCache, CacheOptions, FetchStatus } from './cache'
14
15export interface Client {
16 fetch: (query: string) => Promise<any>
17 [key: string]: any
18}
19
20export const clientSymbol: InjectionKey<Client> = Symbol('Sanity client')
21export const previewClientSymbol: InjectionKey<SanityClient> = Symbol(
22 'Sanity client for previews'
23)
24export const optionsSymbol: InjectionKey<Options> = Symbol(
25 'Default query options'
26)
27
28type Query = string | (() => string | null | undefined | false)
29
30interface Result<T> {
31 /**
32 * An automatically synced and updated result of the Sanity query.
33 */
34 data: Ref<T>
35 /**
36 * The status of the query. Can be 'server loaded', 'loading', 'client loaded' or 'error'.
37 */
38 status: Ref<FetchStatus>
39 /**
40 * An error returned in the course of fetching
41 */
42 error: any
43 /**
44 * Get result directly from fetcher (integrates with cache)
45 */
46 fetch: () => Promise<T>
47}
48
49export type Options = Omit<CacheOptions<any>, 'initialValue'> & {
50 /**
51 * Whether to listen to real-time updates from Sanity. You can also pass an object of options to pass to `client.listen`. Defaults to false.
52 */
53 listen?: boolean | Record<string, any>
54}
55
56/**
57 *
58 * @param query A string, or a function that retuns a query string. If the return value changes, a new Sanity query will be run and the return value automatically updated.
59 */
60export function useSanityFetcher<T extends any>(query: Query): Result<T | null>
61
62/**
63 *
64 * @param query A function that retuns a query string. If the return value changes, a new Sanity query will be run and the return value automatically updated.
65 * @param initialValue The value to return before the Sanity client returns an actual result. Defaults to null.
66 * @param mapper A function that transforms the result from Sanity, before returning it to your component.
67 */
68export function useSanityFetcher<T extends any, R extends any = T>(
69 query: Query,
70 initialValue: R,
71 mapper?: (result: any) => T,
72 options?: Options
73): Result<T | R>
74
75export function useSanityFetcher(
76 query: Query,
77 initialValue = null,
78 mapper = (result: any) => result,
79 queryOptions?: Options
80) {
81 const client = inject(clientSymbol)
82 const defaultOptions = inject(optionsSymbol, {})
83
84 const options = {
85 ...defaultOptions,
86 initialValue,
87 ...queryOptions,
88 }
89 if (!client)
90 throw new Error(
91 'You must call useSanityClient before using sanity resources in this project.'
92 )
93
94 const computedQuery =
95 typeof query === 'string'
96 ? minifier(query).replace(/\n/g, ' ')
97 : computed(() => minifier(query() || '').replace(/\n/g, ' '))
98
99 const { data, status, setCache, error, fetch } = useCache(
100 computedQuery,
101 query => (query ? client.fetch(query).then(mapper) : Promise.resolve(null)),
102 options
103 )
104
105 if (options.listen) {
106 const previewClient = inject(previewClientSymbol, client as SanityClient)
107 if ('listen' in previewClient) {
108 const listenOptions =
109 typeof options.listen === 'boolean' ? undefined : options.listen
110
111 const subscribe = (query: string) =>
112 previewClient.listen(query, listenOptions).subscribe(
113 event =>
114 event.result &&
115 setCache({
116 key: query,
117 value: event.result,
118 })
119 )
120 if (isRef(computedQuery)) {
121 watch(
122 computedQuery,
123 query => {
124 const subscription = subscribe(query)
125
126 const unwatch = watch(
127 computedQuery,
128 newQuery => {
129 if (newQuery !== query) {
130 subscription.unsubscribe()
131 unwatch()
132 }
133 },
134 { immediate: true }
135 )
136 },
137 { immediate: true }
138 )
139 } else {
140 subscribe(computedQuery)
141 }
142 }
143 }
144
145 return { data, status, error, fetch }
146}
147
148export function useSanityQuery<
149 Builder extends Pick<
150 QueryBuilder<Schema, Mappings, Type, Project, Exclude>,
151 'use'
152 >,
153 Schema,
154 Mappings extends Record<string, any>,
155 Type,
156 Project extends boolean,
157 Exclude extends string
158>(
159 builder: Builder | (() => Builder)
160): Result<
161 ReturnType<Builder['use']>[1] extends Array<any>
162 ? ReturnType<Builder['use']>[1]
163 : ReturnType<Builder['use']>[1] | null
164>
165
166export function useSanityQuery<
167 Builder extends Pick<
168 QueryBuilder<Schema, Mappings, Type, Project, Exclude>,
169 'use'
170 >,
171 Schema,
172 Mappings extends Record<string, any>,
173 Type,
174 Project extends boolean,
175 Exclude extends string
176>(
177 builder: Builder | (() => Builder),
178 initialValue: null
179): Result<ReturnType<Builder['use']>[1] | null>
180
181export function useSanityQuery<
182 Builder extends Pick<
183 QueryBuilder<Schema, Mappings, Type, Project, Exclude>,
184 'use'
185 >,
186 Schema,
187 Mappings extends Record<string, any>,
188 Type,
189 Project extends boolean,
190 Exclude extends string,
191 InitialValue
192>(
193 builder: Builder | (() => Builder),
194 initialValue: InitialValue
195): Result<ReturnType<Builder['use']>[1] | InitialValue>
196
197export function useSanityQuery<
198 Builder extends Pick<
199 QueryBuilder<Schema, Mappings, Type, Project, Exclude>,
200 'use'
201 >,
202 Schema,
203 Mappings extends Record<string, any>,
204 Type,
205 Project extends boolean,
206 Exclude extends string,
207 Mapper extends (result: ReturnType<Builder['use']>[1]) => any
208>(
209 builder: Builder | (() => Builder),
210 initialValue: null,
211 mapper: Mapper,
212 options?: Options
213): Result<ReturnType<Mapper> | null>
214
215export function useSanityQuery<
216 Builder extends Pick<
217 QueryBuilder<Schema, Mappings, Type, Project, Exclude>,
218 'use'
219 >,
220 Schema,
221 Mappings extends Record<string, any>,
222 Type,
223 Project extends boolean,
224 Exclude extends string,
225 InitialValue,
226 Mapper extends (result: ReturnType<Builder['use']>[1]) => any
227>(
228 builder: Builder | (() => Builder),
229 initialValue: InitialValue,
230 mapper: Mapper,
231 options?: Options
232): Result<ReturnType<Mapper> | InitialValue>
233
234export function useSanityQuery<
235 Builder extends Pick<
236 QueryBuilder<Schema, Mappings, Type, Project, Exclude>,
237 'use'
238 >,
239 Schema,
240 Mappings extends Record<string, any>,
241 Type,
242 Project extends boolean,
243 Exclude extends string
244>(
245 builder: Builder | (() => Builder),
246 initialValue = null,
247 mapper = (result: any) => result,
248 options?: Options
249) {
250 const query =
251 'use' in builder ? () => builder.use()[0] : () => builder().use()[0]
252 const type = 'use' in builder ? builder.use()[1] : builder().use()[1]
253
254 return useSanityFetcher<typeof type>(
255 query,
256 initialValue || type,
257 mapper,
258 options
259 )
260}