Skip to content

Commit 464b31b

Browse files
committed
feat: allow direct access to fetch
1 parent 1a76462 commit 464b31b

File tree

10 files changed

+172
-77
lines changed

10 files changed

+172
-77
lines changed

.eslintrc.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ module.exports = {
66
rules: {
77
'@typescript-eslint/no-inferrable-types': 1,
88
'@typescript-eslint/explicit-function-return-type': 0,
9+
'@typescript-eslint/no-explicit-any': 0,
10+
'@typescript-eslint/explicit-module-boundary-types': 0,
911
},
1012
extends: ['@siroc'],
1113
}

src/cache.ts

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
/**
1212
* Cached data, status of fetch, timestamp of last fetch, error
1313
*/
14-
type CacheEntry<T> = [T, FetchStatus, number, any]
14+
type CacheEntry<T> = [T, FetchStatus, number, any, Promise<T>]
1515

1616
const cache = reactive<Record<string, CacheEntry<any>>>({})
1717

@@ -21,13 +21,29 @@ export function ensureInstance() {
2121
return instance
2222
}
2323

24+
export function getServerInstance() {
25+
const instance = getCurrentInstance()
26+
27+
if (instance?.$isServer) return instance
28+
return false
29+
}
30+
2431
export type FetchStatus =
2532
| 'initialised'
2633
| 'loading'
2734
| 'server loaded'
2835
| 'client loaded'
2936
| 'error'
3037

38+
interface SetCacheOptions<T, K> {
39+
key: string
40+
value?: T | K
41+
status?: FetchStatus
42+
error?: any
43+
promise?: Promise<T | K>
44+
time?: number
45+
}
46+
3147
export interface CacheOptions<K> {
3248
initialValue?: K
3349
/**
@@ -55,9 +71,6 @@ export function useCache<T, K = null>(
5571
fetcher: (key: string) => Promise<T>,
5672
options: CacheOptions<K> = {}
5773
) {
58-
const instance = ensureInstance()
59-
const isServer = instance.$isServer
60-
6174
const {
6275
initialValue = null,
6376
deduplicate = false,
@@ -67,89 +80,103 @@ export function useCache<T, K = null>(
6780

6881
const enableSSR = !clientOnly && strategy !== 'client'
6982

70-
function initialiseCache(
71-
key: string,
72-
value: any,
73-
status: FetchStatus = 'initialised',
83+
function initialiseCache({
84+
key,
85+
value,
86+
error = null,
87+
status = 'initialised',
7488
time = new Date().getTime(),
75-
error = null
76-
) {
89+
}: SetCacheOptions<T, K>) {
7790
Vue.set(cache, key, [value, status, time, error])
7891
}
7992

80-
if (enableSSR && !isServer) {
93+
const serverInstance = getServerInstance()
94+
if (enableSSR && !serverInstance) {
8195
const prefetchState =
8296
(window as any).__VSANITY_STATE__ ||
8397
((window as any).__NUXT__ && (window as any).__NUXT__.vsanity)
8498
if (prefetchState && prefetchState[key.value]) {
85-
initialiseCache(
86-
key.value,
87-
...(prefetchState[key.value] as CacheEntry<any>)
88-
)
99+
const [value, status, time, error] = prefetchState[
100+
key.value
101+
] as CacheEntry<T>
102+
103+
initialiseCache({
104+
key: key.value,
105+
value,
106+
status,
107+
time,
108+
error,
109+
})
89110
}
90111
}
91112

92113
function verifyKey(key: string) {
93114
const emptyCache = !(key in cache)
94-
if (emptyCache) initialiseCache(key, initialValue)
115+
if (emptyCache) initialiseCache({ key, value: initialValue as K })
95116
return emptyCache || cache[key][1] === 'initialised'
96117
}
97118

98-
function setCache(
99-
key: string,
100-
value: any = (cache[key] && cache[key][0]) || initialValue,
101-
status: FetchStatus = cache[key] && cache[key][1],
102-
error: any = null
103-
) {
104-
if (!(key in cache)) initialiseCache(key, value, status)
119+
function setCache({
120+
key,
121+
value = cache[key]?.[0] || initialValue,
122+
status = cache[key]?.[1],
123+
error = null,
124+
promise = cache[key]?.[4],
125+
}: SetCacheOptions<T, K>) {
126+
if (!(key in cache)) initialiseCache({ key, value, status })
127+
105128
Vue.set(cache[key], 0, value)
106129
Vue.set(cache[key], 1, status)
107130
Vue.set(cache[key], 2, new Date().getTime())
108131
Vue.set(cache[key], 3, error)
132+
Vue.set(cache[key], 4, promise)
109133
}
110134

111-
async function fetch(query = key.value, force?: boolean) {
135+
function fetch(query = key.value, force?: boolean) {
112136
if (
113137
!force &&
114138
deduplicate &&
115-
cache[query][1] === 'loading' &&
139+
cache[query]?.[1] === 'loading' &&
116140
(deduplicate === true ||
117-
deduplicate < new Date().getTime() - cache[query][2])
141+
deduplicate < new Date().getTime() - cache[query]?.[2])
118142
)
119-
return
120-
121-
try {
122-
setCache(query, undefined, 'loading')
123-
124-
setCache(
125-
query,
126-
await fetcher(query),
127-
isServer ? 'server loaded' : 'client loaded'
143+
return Promise.resolve(cache[query][4] || initialValue) as Promise<T>
144+
145+
const promise = fetcher(query)
146+
setCache({ key: query, status: 'loading', promise })
147+
148+
promise
149+
.then(value =>
150+
setCache({
151+
key: query,
152+
value,
153+
status: serverInstance ? 'server loaded' : 'client loaded',
154+
})
128155
)
129-
} catch (e) {
130-
setCache(query, undefined, 'error', e)
131-
}
156+
.catch(error => setCache({ key: query, status: 'error', error }))
157+
return promise
132158
}
133159

134-
if (enableSSR && isServer) {
135-
if (instance.$ssrContext) {
136-
if (instance.$ssrContext.nuxt && !instance.$ssrContext.nuxt.vsanity) {
137-
instance.$ssrContext.nuxt.vsanity = {}
138-
} else if (!instance.$ssrContext.vsanity) {
139-
instance.$ssrContext.vsanity = {}
160+
if (enableSSR && serverInstance) {
161+
const ctx = serverInstance.$ssrContext
162+
if (ctx) {
163+
if (ctx.nuxt && !ctx.nuxt.vsanity) {
164+
ctx.nuxt.vsanity = {}
165+
} else if (!ctx.vsanity) {
166+
ctx.vsanity = {}
140167
}
141168
}
142169

143170
onServerPrefetch(async () => {
144-
await fetch(key.value, verifyKey(key.value))
145-
if (
146-
instance.$ssrContext &&
147-
!['loading', 'initialised'].includes(cache[key.value][1])
148-
) {
149-
if (instance.$ssrContext.nuxt) {
150-
instance.$ssrContext.nuxt.vsanity[key.value] = cache[key.value]
171+
try {
172+
await fetch(key.value, verifyKey(key.value))
173+
// eslint-disable-next-line
174+
} catch {}
175+
if (ctx && !['loading', 'initialised'].includes(cache[key.value]?.[1])) {
176+
if (ctx.nuxt) {
177+
ctx.nuxt.vsanity[key.value] = cache[key.value]
151178
} else {
152-
instance.$ssrContext.vsanity[key.value] = cache[key.value]
179+
ctx.vsanity[key.value] = cache[key.value]
153180
}
154181
}
155182
})
@@ -175,10 +202,10 @@ export function useCache<T, K = null>(
175202

176203
watch(
177204
key,
178-
async key => {
205+
key => {
179206
if (strategy === 'server' && status.value === 'server loaded') return
180207

181-
await fetch(key, verifyKey(key))
208+
fetch(key, verifyKey(key))
182209
},
183210
{ immediate: true }
184211
)

src/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { provide } from '@vue/composition-api'
1+
import { provide, inject } from '@vue/composition-api'
22

33
import { ClientConfig } from '@sanity/client'
44

55
import { useCache, ensureInstance } from './cache'
6+
import type { FetchStatus, CacheOptions } from './cache'
67
import { useSanityImage, imageBuilderSymbol } from './image'
78
import {
89
useSanityFetcher,
910
useSanityQuery,
10-
Client,
11-
Options,
1211
clientSymbol,
1312
previewClientSymbol,
1413
optionsSymbol,
1514
} from './query'
15+
import type { Client, Options } from './query'
1616

1717
interface RequiredConfig {
1818
/**
@@ -64,4 +64,15 @@ export function useCustomClient(client: Client, defaultOptions: Options = {}) {
6464
provide(optionsSymbol, defaultOptions)
6565
}
6666

67+
export function fetch(query: string) {
68+
ensureInstance()
69+
const client = inject(clientSymbol)
70+
if (!client)
71+
throw new Error(
72+
'You must call useSanityClient before using sanity resources in this project.'
73+
)
74+
return client.fetch(query)
75+
}
76+
6777
export { useCache, useSanityFetcher, useSanityImage, useSanityQuery }
78+
export type { Options, FetchStatus, CacheOptions }

src/query.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ interface Result<T> {
3535
* The status of the query. Can be 'server loaded', 'loading', 'client loaded' or 'error'.
3636
*/
3737
status: Ref<FetchStatus>
38+
/**
39+
* An error returned in the course of fetching
40+
*/
41+
error: any
42+
/**
43+
* Get result directly from fetcher (integrates with cache)
44+
*/
45+
fetch: () => Promise<T>
3846
}
3947

4048
export type Options = Omit<CacheOptions<any>, 'initialValue'> & {
@@ -101,7 +109,10 @@ export function useSanityFetcher(
101109
query => {
102110
const subscription = previewClient
103111
.listen(query, listenOptions)
104-
.subscribe(event => event.result && setCache(query, event.result))
112+
.subscribe(
113+
event =>
114+
event.result && setCache({ key: query, value: event.result })
115+
)
105116

106117
const unwatch = watch(
107118
computedQuery,

test/cache.spec.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import Vue from 'vue'
2-
import CompositionApi, { ref, watch } from '@vue/composition-api'
1+
import { ref, watch } from '@vue/composition-api'
32

4-
import { useCache } from '..'
3+
import { useCache } from '../src'
54
import { runInSetup } from './helpers/mount'
65

7-
Vue.use(CompositionApi)
8-
96
jest.setTimeout(10000)
107

118
describe('cache', () => {

test/image.spec.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
import Vue from 'vue'
2-
import CompositionApi, { ref } from '@vue/composition-api'
1+
import { ref } from '@vue/composition-api'
32

43
import { runInSetup } from './helpers/mount'
5-
import { useSanityImage, useSanityClient } from '..'
6-
7-
Vue.use(CompositionApi)
4+
import { useSanityImage, useSanityClient } from '../src'
85

96
const config = {
107
projectId: 'id',

test/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Vue from 'vue'
22
import CompositionApi from '@vue/composition-api'
33

4-
import { useSanityClient } from '..'
4+
import { useSanityClient } from '../src'
55
import { runInSetup } from './helpers/mount'
66

77
Vue.config.productionTip = false

test/query.spec.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import Vue from 'vue'
2-
import CompositionApi, { ref } from '@vue/composition-api'
1+
import { ref } from '@vue/composition-api'
32
import flushPromises from 'flush-promises'
43
import { defineDocument } from 'sanity-typed-queries'
54

@@ -8,11 +7,10 @@ import {
87
useSanityFetcher,
98
useSanityClient,
109
useSanityQuery,
11-
} from '..'
10+
fetch as _fetch,
11+
} from '../src'
1212
import { runInSetup } from './helpers/mount'
1313

14-
Vue.use(CompositionApi)
15-
1614
const config = {
1715
projectId: 'id',
1816
dataset: 'production',
@@ -87,6 +85,25 @@ describe('fetcher', () => {
8785
expect(results.value.status).toBe('server loaded')
8886
})
8987

88+
test('allows direct access to client', async () => {
89+
const result = await runInSetup(() => {
90+
useCustomClient({ fetch: async t => `fetched-${t}` })
91+
const data = _fetch('test')
92+
return { data }
93+
})
94+
expect(await result.value.data).toBe('fetched-test')
95+
const errored = await runInSetup(() => {
96+
let data = false
97+
try {
98+
_fetch('test')
99+
} catch {
100+
data = true
101+
}
102+
return { data }
103+
})
104+
expect(errored.value.data).toBe(true)
105+
})
106+
90107
test('allows custom client to be provided', async () => {
91108
const result = await runInSetup(() => {
92109
useCustomClient({ fetch: async t => `fetched-${t}` })

test/ssr.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
*/
44

55
import Vue from 'vue'
6-
import VueCompositionApi, { ref, createElement } from '@vue/composition-api'
6+
import { ref, createElement } from '@vue/composition-api'
77
import { createRenderer } from 'vue-server-renderer'
88

99
import { fetcher } from './helpers/utils'
10-
import { useCache } from '..'
10+
import { useCache } from '../src'
1111

12-
Vue.use(VueCompositionApi)
1312
Vue.config.productionTip = false
1413
Vue.config.devtools = false
1514

0 commit comments

Comments
 (0)