Skip to content

Commit

Permalink
refactor!: rename options
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Dec 20, 2023
1 parent 237c57a commit f6d01c5
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 13 deletions.
10 changes: 7 additions & 3 deletions src/data-fetching-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export const useDataFetchingStore = defineStore('PiniaColada', () => {

function ensureEntry<TResult = unknown, TError = Error>(
key: UseQueryKey,
{ fetcher, initialValue, cacheTime }: UseQueryOptionsWithDefaults<TResult>
{
fetcher,
initialData: initialValue,
staleTime: cacheTime,
}: UseQueryOptionsWithDefaults<TResult>
): UseDataFetchingQueryEntry<TResult, TError> {
// ensure the data
console.log('⚙️ Ensuring entry', key)
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export {
} from './use-query'

export { useDataFetchingStore } from './data-fetching-store'

// TODO: idea of plugin that persists the cached values
13 changes: 8 additions & 5 deletions src/use-mutation.ts
Original file line number Diff line number Diff line change
@@ -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<TParams extends readonly any[], TResult> = readonly (
| string
| ((context: { variables: TParams; result: TResult }) => string)
| UseQueryKey
| ((context: { variables: TParams; result: TResult }) => UseQueryKey)
)[]

export interface UseMutationsOptions<
Expand All @@ -15,6 +16,8 @@ export interface UseMutationsOptions<
*/
mutator: (...args: TParams) => Promise<TResult>
keys?: _MutatorKeys<TParams, TResult>

// TODO: onMutate for optimistic updates
}
// export const USE_MUTATIONS_DEFAULTS = {} satisfies Partial<UseMutationsOptions>

Expand Down Expand Up @@ -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
)
}
Expand Down
32 changes: 27 additions & 5 deletions src/use-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
onMounted,
onServerPrefetch,
toValue,
onScopeDispose,
} from 'vue'
import { useDataFetchingStore } from './data-fetching-store'

Expand All @@ -28,6 +29,7 @@ export interface UseDataFetchingQueryEntry<TResult = unknown, TError = any> {
*/
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
Expand Down Expand Up @@ -59,16 +61,27 @@ export interface UseQueryOptions<TResult = unknown> {
key: UseQueryKey | (() => UseQueryKey)
fetcher: () => Promise<TResult>

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<UseQueryOptions>
Expand Down Expand Up @@ -105,10 +118,18 @@ export function useQuery<TResult, TError = Error>(
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()
}
})
}

Expand All @@ -120,6 +141,7 @@ export function useQuery<TResult, TError = Error>(
}

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()),
Expand Down

0 comments on commit f6d01c5

Please sign in to comment.