Skip to content

Commit 756ab86

Browse files
authored
perf: add server fetch cache for api responses (npmx-dev#183)
1 parent 55387a5 commit 756ab86

10 files changed

Lines changed: 622 additions & 152 deletions

File tree

app/composables/useCachedFetch.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import type { H3Event } from 'h3'
2+
3+
/**
4+
* Type for the cachedFetch function attached to event context.
5+
*/
6+
export type CachedFetchFunction = <T = unknown>(
7+
url: string,
8+
options?: {
9+
method?: string
10+
body?: unknown
11+
headers?: Record<string, string>
12+
},
13+
ttl?: number,
14+
) => Promise<T>
15+
16+
/**
17+
* Get the cachedFetch function from the current request context.
18+
*
19+
* IMPORTANT: This must be called in the composable setup context (outside of
20+
* useAsyncData handlers). The returned function can then be used inside handlers.
21+
*
22+
* @example
23+
* ```ts
24+
* export function usePackage(name: MaybeRefOrGetter<string>) {
25+
* // Get cachedFetch in setup context
26+
* const cachedFetch = useCachedFetch()
27+
*
28+
* return useLazyAsyncData(
29+
* () => `package:${toValue(name)}`,
30+
* // Use it inside the handler
31+
* () => cachedFetch<Packument>(`https://registry.npmjs.org/${toValue(name)}`)
32+
* )
33+
* }
34+
* ```
35+
*/
36+
export function useCachedFetch(): CachedFetchFunction {
37+
// On client, return a function that just uses $fetch
38+
if (import.meta.client) {
39+
return async <T = unknown>(
40+
url: string,
41+
options: {
42+
method?: string
43+
body?: unknown
44+
headers?: Record<string, string>
45+
} = {},
46+
_ttl?: number,
47+
): Promise<T> => {
48+
return (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
49+
}
50+
}
51+
52+
// On server, get the cachedFetch from request context
53+
const event = useRequestEvent()
54+
const serverCachedFetch = event?.context?.cachedFetch
55+
56+
// If cachedFetch is available from middleware, return it
57+
if (serverCachedFetch) {
58+
return serverCachedFetch as CachedFetchFunction
59+
}
60+
61+
// Fallback: return a function that uses regular $fetch
62+
// (shouldn't happen in normal operation)
63+
return async <T = unknown>(
64+
url: string,
65+
options: {
66+
method?: string
67+
body?: unknown
68+
headers?: Record<string, string>
69+
} = {},
70+
_ttl?: number,
71+
): Promise<T> => {
72+
return (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
73+
}
74+
}
75+
76+
/**
77+
* Create a cachedFetch function from an H3Event.
78+
* Useful when you have direct access to the event.
79+
*/
80+
export function getCachedFetchFromEvent(event: H3Event | undefined): CachedFetchFunction {
81+
const serverCachedFetch = event?.context?.cachedFetch
82+
83+
if (serverCachedFetch) {
84+
return serverCachedFetch as CachedFetchFunction
85+
}
86+
87+
// Fallback to regular $fetch
88+
return async <T = unknown>(
89+
url: string,
90+
options: {
91+
method?: string
92+
body?: unknown
93+
headers?: Record<string, string>
94+
} = {},
95+
_ttl?: number,
96+
): Promise<T> => {
97+
return (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
98+
}
99+
}

0 commit comments

Comments
 (0)