-
Notifications
You must be signed in to change notification settings - Fork 19
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
useCachedAsyncData composable #25
Comments
This sounds interesting, I will give this a try! |
Oh yes this would be so handy! |
@or2e I'm now looking into implementing this. Was there a particular reason you added the |
99% of the time they match |
Right - so I guess it would be fine to reuse that key and make it required. For the |
@or2e @Crease29 I've implemented it now and opened a PR, if you like you can take a look and tell me if the implementation makes sense 😄 The docs are here: https://deploy-preview-58--nuxt-multi-cache.netlify.app/composables/useCachedAsyncData |
Thank you so much! :) Regarding the documentation: |
It seems your implementation uses the regular That's what I've done in my current project. Here is the implementation : // useCacheableAsyncData.ts
import { toRef, toValue } from 'vue';
import { callWithNuxt, type NuxtApp } from '#app';
import type { AsyncDataOptions, KeysOf } from '#app/composables/asyncData';
import { useDataCache } from '#nuxt-multi-cache/composables';
import { assertValidCacheKey, assertValidTtl } from '~/lib/cache-utils';
export interface TimestampedPayload<T> {
payload: T;
issuedAt: number;
}
function wrapPayloadWithTimestamp<T>(payload: T, issuedAt = Date.now()): TimestampedPayload<T> {
return {
payload,
issuedAt,
};
}
function unwrapTimestampedPayload<T>({ payload }: TimestampedPayload<T>): T {
return payload;
}
export type CachedAsyncDataOptions<
// eslint-disable-next-line unicorn/prevent-abbreviations
ResT,
DataT = ResT,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = null,
> = Omit<AsyncDataOptions<ResT, DataT, PickKeys, DefaultT>, 'transform' | 'getCachedData'> & {
/**
* Time To Live in milliseconds
* @example 60_000 for 1 min
*/
ttl?: number;
cacheTags?: string[] | ((response: ResT) => string[]);
};
export default async function useCacheableAsyncData<
// eslint-disable-next-line unicorn/prevent-abbreviations
ResT,
NuxtErrorDataT = unknown,
DataT extends TimestampedPayload<ResT> = TimestampedPayload<ResT>,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = DataT,
>(
key: string,
handler: (context?: NuxtApp) => Promise<ResT>,
options?: CachedAsyncDataOptions<ResT, DataT, PickKeys, DefaultT>,
) {
const { ttl, cacheTags = [], ...otherOptions } = options ?? {};
if (ttl !== undefined) {
assertValidTtl(ttl);
}
assertValidCacheKey(key);
const { data, ...others } = await useAsyncData<
ResT,
NuxtErrorDataT,
TimestampedPayload<ResT> | undefined,
PickKeys,
DefaultT
>(
key,
async (nuxtApp) => {
const { value, addToCache } = await useDataCache<ResT>(key);
if (value) {
return value;
}
const response = nuxtApp === undefined ? await handler(nuxtApp) : await callWithNuxt(nuxtApp, handler, [nuxtApp]);
await addToCache(
response,
Array.isArray(cacheTags) ? cacheTags : cacheTags(response),
ttl ? ttl / 1000 : undefined,
);
return response;
},
{
...otherOptions,
transform(input) {
return wrapPayloadWithTimestamp(input);
},
getCachedData(key, nuxtApp) {
const data: DataT | undefined = (nuxtApp.payload.data[key] ?? nuxtApp.static.data[key]) as DataT | undefined;
// No data in payload
if (data === undefined) {
return;
}
if (ttl !== undefined && data.issuedAt + ttl < Date.now()) {
return;
}
return data;
},
},
);
// data.value cannot be undefined at this point. Using `as` to fix type. Maybe this is a typing issue in Nuxt source code
const value = toValue(data.value) as TimestampedPayload<ResT>;
return { data: toRef(() => unwrapTimestampedPayload(value)), ...others };
} Usage: const { data } = useCacheableAsyncData(
'some:cache:key',
async () => {
return Promise.resolve('Some data');
},
{
deep: false,
ttl: import.meta.server ? CACHE_TTL_MS_12_HOURS : CACHE_TTL_MS_15_MIN,
},
); Using I didn't found any downside for now. WDYT ? |
@bgondy I actually have been thinking about extending In this case here, however, since the underlying |
Alright, I gave this a shot and added client-side caching. I've used the example from @bgondy as a basis, but changed the behaviour:
I first wanted to have a single Let me know if this makes sense. And a big thanks to all for all the constructive feedback and ideas! |
This is so cool ! LGTM |
* feat: implement useCachedAsyncData (#25) * check options before spreading * prevent "Excessive stack depth comparing types" * Apply suggestions from code review Co-authored-by: Kai Neuwerth <[email protected]> * feat: implement client-side caching in useCachedAsyncData (#25) --------- Co-authored-by: Kai Neuwerth <[email protected]>
Now available in 3.3.0 |
I may be wrong, but most often we have to deal with
asyncData
To avoid routine, I suggest creating a wrap-composable
useCachedAsyncData
Example
The text was updated successfully, but these errors were encountered: