From f6d01c5396fb9495e3685f94d7fa24cff81b2da3 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 20 Dec 2023 12:02:07 +0100 Subject: [PATCH] refactor!: rename options --- src/data-fetching-store.ts | 10 +++++++--- src/index.ts | 2 ++ src/use-mutation.ts | 13 ++++++++----- src/use-query.ts | 32 +++++++++++++++++++++++++++----- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/data-fetching-store.ts b/src/data-fetching-store.ts index e539f3a..d2f618a 100644 --- a/src/data-fetching-store.ts +++ b/src/data-fetching-store.ts @@ -25,7 +25,11 @@ export const useDataFetchingStore = defineStore('PiniaColada', () => { function ensureEntry( key: UseQueryKey, - { fetcher, initialValue, cacheTime }: UseQueryOptionsWithDefaults + { + fetcher, + initialData: initialValue, + staleTime: cacheTime, + }: UseQueryOptionsWithDefaults ): UseDataFetchingQueryEntry { // ensure the data console.log('⚙️ Ensuring entry', key) @@ -112,10 +116,10 @@ export const useDataFetchingStore = defineStore('PiniaColada', () => { * @param key - the key of the query to invalidate * @param refresh - whether to force a refresh of the data */ - function invalidateEntry(key: string, refresh = false) { + function invalidateEntry(key: UseQueryKey, refresh = false) { if (!queryEntriesRegistry.has(key)) { console.warn( - `⚠️ trying to invalidate "${key}" but it's not in the registry` + `⚠️ trying to invalidate "${String(key)}" but it's not in the registry` ) return } diff --git a/src/index.ts b/src/index.ts index 5bc7ce2..ddcfcdc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,3 +15,5 @@ export { } from './use-query' export { useDataFetchingStore } from './data-fetching-store' + +// TODO: idea of plugin that persists the cached values diff --git a/src/use-mutation.ts b/src/use-mutation.ts index c9cb63b..2bf9314 100644 --- a/src/use-mutation.ts +++ b/src/use-mutation.ts @@ -1,9 +1,10 @@ import { computed, ref, type ComputedRef, shallowRef } from 'vue' import { useDataFetchingStore } from './data-fetching-store' +import { type UseQueryKey } from './use-query' type _MutatorKeys = readonly ( - | string - | ((context: { variables: TParams; result: TResult }) => string) + | UseQueryKey + | ((context: { variables: TParams; result: TResult }) => UseQueryKey) )[] export interface UseMutationsOptions< @@ -15,6 +16,8 @@ export interface UseMutationsOptions< */ mutator: (...args: TParams) => Promise keys?: _MutatorKeys + + // TODO: onMutate for optimistic updates } // export const USE_MUTATIONS_DEFAULTS = {} satisfies Partial @@ -59,9 +62,9 @@ export function useMutation< if (options.keys) { for (const key of options.keys) { store.invalidateEntry( - typeof key === 'string' - ? key - : key({ variables: args, result: _data }), + typeof key === 'function' + ? key({ variables: args, result: _data }) + : key, true ) } diff --git a/src/use-query.ts b/src/use-query.ts index a90c213..3fbad07 100644 --- a/src/use-query.ts +++ b/src/use-query.ts @@ -5,6 +5,7 @@ import { onMounted, onServerPrefetch, toValue, + onScopeDispose, } from 'vue' import { useDataFetchingStore } from './data-fetching-store' @@ -28,6 +29,7 @@ export interface UseDataFetchingQueryEntry { */ isFetching: () => boolean + // TODO: should we just have refresh and allow a parameter to force a refresh? instead of having fetch and refresh /** * Refreshes the data ignoring any cache but still decouples the refreshes (only one refresh at a time) * @returns a promise that resolves when the refresh is done @@ -59,16 +61,27 @@ export interface UseQueryOptions { key: UseQueryKey | (() => UseQueryKey) fetcher: () => Promise - cacheTime?: number - initialValue?: () => TResult + /** + * Time in ms after which the data is considered stale and will be refreshed on next read + */ + staleTime?: number + + /** + * Time in ms after which, once the data is no longer in used, it will be garbage collected to free resources. + */ + gcTime?: number + + initialData?: () => TResult refetchOnWindowFocus?: boolean refetchOnReconnect?: boolean } + /** * Default options for `useQuery()`. Modifying this object will affect all the queries that don't override these */ export const USE_QUERY_DEFAULTS = { - cacheTime: 1000 * 5, + staleTime: 1000 * 5, // 5 seconds + gcTime: 1000 * 60 * 5, // 5 minutes refetchOnWindowFocus: true as boolean, refetchOnReconnect: true as boolean, } satisfies Partial @@ -105,10 +118,18 @@ export function useQuery( onMounted(entry.value.refresh) // TODO: optimize so it doesn't refresh if we are hydrating + // TODO: we could save the time it was fetched to avoid fetching again. This is useful to not refetch during SSR app but do refetch in SSG apps if the data is stale. Careful with timers and timezones + + onScopeDispose(() => { + // TODO: add a reference count to the entry and garbage collect it if it's 0 after the given delay + }) + if (IS_CLIENT) { if (options.refetchOnWindowFocus) { - useEventListener(window, 'focus', () => { - entry.value.refresh() + useEventListener(document, 'visibilitychange', () => { + if (document.visibilityState === 'visible') { + entry.value.refresh() + } }) } @@ -120,6 +141,7 @@ export function useQuery( } const queryReturn = { + // TODO: optimize so we create only one computed per entry. We could have an application plugin that creates an effectScope and allows us to inject the scope to create entries data: computed(() => entry.value.data()), error: computed(() => entry.value.error()), isFetching: computed(() => entry.value.isFetching()),