Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking β€œSign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: customizable cache key #52

Merged
merged 4 commits into from
Sep 15, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: customizable async data key
johannschopplich committed Sep 15, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit d5d4084b458fede3462e3e11a7e406cf978efea9
4 changes: 3 additions & 1 deletion src/runtime/composables/$api.ts
Original file line number Diff line number Diff line change
@@ -52,7 +52,9 @@ export function _$api<T = any>(
opts: ApiFetchOptions & BaseApiFetchOptions = {},
): Promise<T> {
const nuxt = useNuxtApp()
const { apiParty } = useRuntimeConfig().public
const promiseMap = (nuxt._promiseMap = nuxt._promiseMap || new Map()) as Map<string, Promise<T>>

const {
pathParams,
query,
@@ -63,7 +65,7 @@ export function _$api<T = any>(
cache = false,
...fetchOptions
} = opts
const { apiParty } = useRuntimeConfig().public

const key = `$party${hash([
endpointId,
path,
78 changes: 60 additions & 18 deletions src/runtime/composables/useApiData.ts
Original file line number Diff line number Diff line change
@@ -60,10 +60,17 @@ export type UseOpenApiDataOptions<
M extends IgnoreCase<keyof P & HttpMethod> = IgnoreCase<keyof P & 'get'>,
> = BaseUseApiDataOptions<OpenApiResponse<P[Lowercase<M>]>> & ComputedOptions<OpenApiRequestOptions<P, M>>

export type UseApiData = <T = any>(
path: MaybeRefOrGetter<string>,
opts?: UseApiDataOptions<T>,
) => AsyncData<T | undefined, FetchError>
export interface UseApiData {
<T = any>(
path: MaybeRefOrGetter<string>,
opts?: UseApiDataOptions<T>,
): AsyncData<T | undefined, FetchError>
<T = any>(
key: MaybeRefOrGetter<string>,
path: MaybeRefOrGetter<string>,
opts?: UseApiDataOptions<T>,
): AsyncData<T | undefined, FetchError>
}

export interface UseOpenApiData<Paths extends Record<string, PathItemObject>> {
<P extends GETPlainPaths<Paths>>(
@@ -78,13 +85,42 @@ export interface UseOpenApiData<Paths extends Record<string, PathItemObject>> {
path: MaybeRefOrGetter<P>,
opts: UseOpenApiDataOptions<Paths[`/${P}`], M> & { method: M },
): AsyncData<OpenApiResponse<Paths[`/${P}`][Lowercase<M>]> | undefined, FetchError<OpenApiError<Paths[`/${P}`][Lowercase<M>]>>>
// Support for custom unique key
<P extends GETPlainPaths<Paths>>(
key: MaybeRefOrGetter<string>,
path: MaybeRefOrGetter<P>,
opts?: Omit<UseOpenApiDataOptions<Paths[`/${P}`]>, 'method'>,
): AsyncData<OpenApiResponse<Paths[`/${P}`]['get']> | undefined, FetchError<OpenApiError<Paths[`/${P}`]['get']>>>
<P extends GETPaths<Paths>>(
key: MaybeRefOrGetter<string>,
path: MaybeRefOrGetter<P>,
opts: Omit<UseOpenApiDataOptions<Paths[`/${P}`]>, 'method'>,
): AsyncData<OpenApiResponse<Paths[`/${P}`]['get']> | undefined, FetchError<OpenApiError<Paths[`/${P}`]['get']>>>
<P extends AllPaths<Paths>, M extends IgnoreCase<keyof Paths[`/${P}`] & HttpMethod>>(
key: MaybeRefOrGetter<string>,
path: MaybeRefOrGetter<P>,
opts: UseOpenApiDataOptions<Paths[`/${P}`], M> & { method: M },
): AsyncData<OpenApiResponse<Paths[`/${P}`][Lowercase<M>]> | undefined, FetchError<OpenApiError<Paths[`/${P}`][Lowercase<M>]>>>
}

export function _useApiData<T = any>(
endpointId: string,
key: MaybeRefOrGetter<string>,
path: MaybeRefOrGetter<string>,
opts: UseApiDataOptions<T>,
): AsyncData<T | undefined, FetchError>
export function _useApiData<T = any>(
endpointId: string,
path: MaybeRefOrGetter<string>,
opts: UseApiDataOptions<T> = {},
opts: UseApiDataOptions<T>,
): AsyncData<T | undefined, FetchError>
export function _useApiData<T = any>(
endpointId: string,
...args: [MaybeRefOrGetter<string>, UseApiDataOptions<T>] | [MaybeRefOrGetter<string>, MaybeRefOrGetter<string>, UseApiDataOptions<T>]
) {
const [key = undefined] = args.length === 3 ? [args[0]] : []
const [path, opts = {}] = args.length === 3 ? [args[1], args[2]] : args

const { apiParty } = useRuntimeConfig().public
const {
server,
@@ -103,7 +139,20 @@ export function _useApiData<T = any>(
cache = true,
...fetchOptions
} = opts

const _path = computed(() => resolvePath(toValue(path), toValue(pathParams)))
const _key = computed(() => `$party${
key
? toValue(key)
: hash([
endpointId,
_path.value,
toValue(query),
toValue(method),
...(isFormData(toValue(body)) ? [] : [toValue(body)]),
])
}`,
)

if (client && !apiParty.allowClient)
throw new Error('Client-side API requests are disabled. Set "allowClient: true" in the module options to enable them.')
@@ -140,23 +189,16 @@ export function _useApiData<T = any>(
}

let controller: AbortController | undefined
const key = computed(() => `$party${hash([
endpointId,
_path.value,
toValue(query),
toValue(method),
...(isFormData(toValue(body)) ? [] : [toValue(body)]),
])}`)

return useAsyncData<T, FetchError>(
key.value,
_key.value,
async (nuxt) => {
controller?.abort?.()

// Workaround to persist response client-side
// https://github.com/nuxt/nuxt/issues/15445
if ((nuxt!.isHydrating || cache) && key.value in nuxt!.payload.data)
return nuxt!.payload.data[key.value]
if ((nuxt!.isHydrating || cache) && _key.value in nuxt!.payload.data)
return nuxt!.payload.data[_key.value]

controller = new AbortController()

@@ -197,14 +239,14 @@ export function _useApiData<T = any>(
}
catch (error) {
// Invalidate cache if request fails
if (key.value in nuxt!.payload.data)
delete nuxt!.payload.data[key.value]
if (_key.value in nuxt!.payload.data)
delete nuxt!.payload.data[_key.value]

throw error
}

if (cache)
nuxt!.payload.data[key.value] = result
nuxt!.payload.data[_key.value] = result

return result
},