From 6a32d894a61d3af5c2e8f549c59aba1c28425ba8 Mon Sep 17 00:00:00 2001 From: ElisePatrikainen Date: Thu, 21 Mar 2024 20:42:24 +0100 Subject: [PATCH 1/4] refactor!: rename type 'UseQueryKey' to 'UseEntryKey' BREAKING CHANGE: the exported type 'UseQueryKey' is replaced by the more generic type 'UseEntryKey', which will be also used to type mutations --- playground/src/pages/ecom/item/[id].vue | 1 + src/entry-options.ts | 7 +++++++ src/index.ts | 5 ++++- src/plugins/invalidate-queries.ts | 6 +++--- src/query-options.ts | 10 ++-------- src/query-store.ts | 11 ++++++----- src/use-mutation.ts | 10 ++++++---- src/use-query.ts | 6 +++--- 8 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 src/entry-options.ts diff --git a/playground/src/pages/ecom/item/[id].vue b/playground/src/pages/ecom/item/[id].vue index c3c1c718..f39acb5b 100644 --- a/playground/src/pages/ecom/item/[id].vue +++ b/playground/src/pages/ecom/item/[id].vue @@ -25,6 +25,7 @@ const itemAvailability = ref() watch(() => item.value?.availability, (value) => itemAvailability.value = value) const { mutate: bookProduct } = useMutation({ + key: ['book-item'], keys: (product) => [['items'], ['items', product.id]], mutation: async (product: ProductListItem) => { await delay(Math.random() * 1000 + 200) diff --git a/src/entry-options.ts b/src/entry-options.ts new file mode 100644 index 00000000..7feb032d --- /dev/null +++ b/src/entry-options.ts @@ -0,0 +1,7 @@ +import type { EntryNodeKey } from './tree-map' +import type { _ObjectFlat } from './utils' + +/** + * Key used to identify a query. Always an array. + */ +export type UseEntryKey = Array diff --git a/src/index.ts b/src/index.ts index 2e0b95a1..1a6dbb4f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,10 @@ export { defineQuery } from './define-query' // export { type UseQueryKeyList } from './query-keys' export { - type UseQueryKey, + type UseEntryKey, +} from './entry-options' + +export { type UseQueryOptions, type UseQueryOptionsWithDefaults, } from './query-options' diff --git a/src/plugins/invalidate-queries.ts b/src/plugins/invalidate-queries.ts index 6e1ca050..ac5185eb 100644 --- a/src/plugins/invalidate-queries.ts +++ b/src/plugins/invalidate-queries.ts @@ -1,7 +1,7 @@ /** * @module @pinia/colada/plugins/invalidate-queries */ -import type { UseQueryKey } from '@pinia/colada' +import type { UseEntryKey } from '@pinia/colada' export interface UseMutationOptionsInvalidateQueries { /** @@ -9,8 +9,8 @@ export interface UseMutationOptionsInvalidateQueries { * `@pinia/colada/plugins/invalidate-queries` is installed. */ invalidateKeys?: - | UseQueryKey[] - | ((data: TResult, vars: TVars) => UseQueryKey[]) + | UseEntryKey[] + | ((data: TResult, vars: TVars) => UseEntryKey[]) } declare module '@pinia/colada' { diff --git a/src/query-options.ts b/src/query-options.ts index f17f32a8..1683ff4f 100644 --- a/src/query-options.ts +++ b/src/query-options.ts @@ -1,6 +1,5 @@ import { type InjectionKey, type MaybeRefOrGetter, inject } from 'vue' -import type { EntryNodeKey } from './tree-map' -import type { _ObjectFlat } from './utils' +import type { UseEntryKey } from './entry-options' import type { QueryPluginOptions } from './query-plugin' import type { ErrorDefault } from './types-extension' @@ -9,11 +8,6 @@ import type { ErrorDefault } from './types-extension' */ export type _RefetchOnControl = boolean | 'always' -/** - * Key used to identify a query. Always an array. - */ -export type UseQueryKey = Array - /** * Context object passed to the `query` function of `useQuery()`. * @see {@link UseQueryOptions} @@ -59,7 +53,7 @@ export interface UseQueryOptions { * }) * ``` */ - key: MaybeRefOrGetter + key: MaybeRefOrGetter /** * The function that will be called to fetch the data. It **must** be async. diff --git a/src/query-store.ts b/src/query-store.ts index 6cf03658..e2e16678 100644 --- a/src/query-store.ts +++ b/src/query-store.ts @@ -12,7 +12,8 @@ import { } from 'vue' import { stringifyFlatObject } from './utils' import { type EntryNodeKey, TreeMapNode } from './tree-map' -import type { UseQueryKey, UseQueryOptionsWithDefaults } from './query-options' +import type { UseEntryKey } from './entry-options' +import type { UseQueryOptionsWithDefaults } from './query-options' import type { ErrorDefault } from './types-extension' import type { defineQuery } from './define-query' @@ -152,7 +153,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { } function ensureEntry( - keyRaw: UseQueryKey, + keyRaw: UseEntryKey, options: UseQueryOptionsWithDefaults, ): UseQueryEntry { if (process.env.NODE_ENV !== 'production' && keyRaw.length === 0) { @@ -187,7 +188,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { * @param options.refetch - if true, it will refetch the data */ function invalidateEntry( - key: UseQueryKey, + key: UseEntryKey, { refetch: shouldRefetch = true, exact = false, @@ -310,7 +311,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { // TODO: tests, remove function version function setQueryData( - key: UseQueryKey, + key: UseEntryKey, data: TResult | ((data: Ref) => void), ) { const entry = caches.get(key.map(stringifyFlatObject)) as @@ -331,7 +332,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { } function getQueryData( - key: UseQueryKey, + key: UseEntryKey, ): TResult | undefined { const entry = caches.get(key.map(stringifyFlatObject)) as | UseQueryEntry diff --git a/src/use-mutation.ts b/src/use-mutation.ts index 796266fd..453bc82d 100644 --- a/src/use-mutation.ts +++ b/src/use-mutation.ts @@ -1,7 +1,7 @@ import { computed, shallowRef } from 'vue' -import type { ComputedRef, ShallowRef } from 'vue' +import type { ComputedRef, MaybeRefOrGetter, ShallowRef } from 'vue' import { useQueryCache } from './query-store' -import type { UseQueryKey } from './query-options' +import type { UseEntryKey } from './entry-options' import type { ErrorDefault } from './types-extension' import { type _Awaitable, noop } from './utils' @@ -11,8 +11,8 @@ import { type _Awaitable, noop } from './utils' * @internal */ type _MutationKeys = - | UseQueryKey[] - | ((data: TResult, vars: TVars) => UseQueryKey[]) + | UseEntryKey[] + | ((data: TResult, vars: TVars) => UseEntryKey[]) /** * The status of the mutation. @@ -69,6 +69,8 @@ export interface UseMutationOptions< */ mutation: (vars: TVars, context: NoInfer) => Promise + key?: MaybeRefOrGetter + // TODO: move this to a plugin that calls invalidateEntry() /** * Keys to invalidate if the mutation succeeds so that `useQuery()` refetch if used. diff --git a/src/use-query.ts b/src/use-query.ts index b99d1a5b..e31f5b62 100644 --- a/src/use-query.ts +++ b/src/use-query.ts @@ -13,8 +13,8 @@ import { import { IS_CLIENT, computedRef, useEventListener } from './utils' import { type _UseQueryEntry_State, useQueryCache } from './query-store' import { useQueryOptions } from './query-options' +import type { UseEntryKey } from './entry-options' import type { - UseQueryKey, UseQueryOptions, UseQueryOptionsWithDefaults, } from './query-options' @@ -176,9 +176,9 @@ export function useQuery( * @returns - the computed key */ function _computedKeyWithWarnings( - key: MaybeRefOrGetter, + key: MaybeRefOrGetter, warnChecksMap: WeakMap, -): () => UseQueryKey { +): () => UseEntryKey { const componentInstance = getCurrentInstance() // probably correct scope, no need to warn if (!componentInstance) return () => toValue(key) From 8110feb9fd0f0e5372574a7f4dc6b9707b1a59a7 Mon Sep 17 00:00:00 2001 From: ElisePatrikainen Date: Sat, 23 Mar 2024 16:50:37 +0100 Subject: [PATCH 2/4] refactor!: rename type `UseEntryKey` to `EntryKey` --- src/entry-options.ts | 2 +- src/index.ts | 2 +- src/plugins/invalidate-queries.ts | 6 +++--- src/query-options.ts | 4 ++-- src/query-store.ts | 10 +++++----- src/use-mutation.ts | 8 ++++---- src/use-query.ts | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/entry-options.ts b/src/entry-options.ts index 7feb032d..3972fd25 100644 --- a/src/entry-options.ts +++ b/src/entry-options.ts @@ -4,4 +4,4 @@ import type { _ObjectFlat } from './utils' /** * Key used to identify a query. Always an array. */ -export type UseEntryKey = Array +export type EntryKey = Array diff --git a/src/index.ts b/src/index.ts index 1a6dbb4f..1a17a2c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,7 +17,7 @@ export { defineQuery } from './define-query' // export { type UseQueryKeyList } from './query-keys' export { - type UseEntryKey, + type EntryKey, } from './entry-options' export { diff --git a/src/plugins/invalidate-queries.ts b/src/plugins/invalidate-queries.ts index ac5185eb..63fab6e9 100644 --- a/src/plugins/invalidate-queries.ts +++ b/src/plugins/invalidate-queries.ts @@ -1,7 +1,7 @@ /** * @module @pinia/colada/plugins/invalidate-queries */ -import type { UseEntryKey } from '@pinia/colada' +import type { EntryKey } from '@pinia/colada' export interface UseMutationOptionsInvalidateQueries { /** @@ -9,8 +9,8 @@ export interface UseMutationOptionsInvalidateQueries { * `@pinia/colada/plugins/invalidate-queries` is installed. */ invalidateKeys?: - | UseEntryKey[] - | ((data: TResult, vars: TVars) => UseEntryKey[]) + | EntryKey[] + | ((data: TResult, vars: TVars) => EntryKey[]) } declare module '@pinia/colada' { diff --git a/src/query-options.ts b/src/query-options.ts index 1683ff4f..d707de5b 100644 --- a/src/query-options.ts +++ b/src/query-options.ts @@ -1,5 +1,5 @@ import { type InjectionKey, type MaybeRefOrGetter, inject } from 'vue' -import type { UseEntryKey } from './entry-options' +import type { EntryKey } from './entry-options' import type { QueryPluginOptions } from './query-plugin' import type { ErrorDefault } from './types-extension' @@ -53,7 +53,7 @@ export interface UseQueryOptions { * }) * ``` */ - key: MaybeRefOrGetter + key: MaybeRefOrGetter /** * The function that will be called to fetch the data. It **must** be async. diff --git a/src/query-store.ts b/src/query-store.ts index e2e16678..bd9dc02e 100644 --- a/src/query-store.ts +++ b/src/query-store.ts @@ -12,7 +12,7 @@ import { } from 'vue' import { stringifyFlatObject } from './utils' import { type EntryNodeKey, TreeMapNode } from './tree-map' -import type { UseEntryKey } from './entry-options' +import type { EntryKey } from './entry-options' import type { UseQueryOptionsWithDefaults } from './query-options' import type { ErrorDefault } from './types-extension' import type { defineQuery } from './define-query' @@ -153,7 +153,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { } function ensureEntry( - keyRaw: UseEntryKey, + keyRaw: EntryKey, options: UseQueryOptionsWithDefaults, ): UseQueryEntry { if (process.env.NODE_ENV !== 'production' && keyRaw.length === 0) { @@ -188,7 +188,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { * @param options.refetch - if true, it will refetch the data */ function invalidateEntry( - key: UseEntryKey, + key: EntryKey, { refetch: shouldRefetch = true, exact = false, @@ -311,7 +311,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { // TODO: tests, remove function version function setQueryData( - key: UseEntryKey, + key: EntryKey, data: TResult | ((data: Ref) => void), ) { const entry = caches.get(key.map(stringifyFlatObject)) as @@ -332,7 +332,7 @@ export const useQueryCache = defineStore(QUERY_STORE_ID, () => { } function getQueryData( - key: UseEntryKey, + key: EntryKey, ): TResult | undefined { const entry = caches.get(key.map(stringifyFlatObject)) as | UseQueryEntry diff --git a/src/use-mutation.ts b/src/use-mutation.ts index 453bc82d..2c833187 100644 --- a/src/use-mutation.ts +++ b/src/use-mutation.ts @@ -1,7 +1,7 @@ import { computed, shallowRef } from 'vue' import type { ComputedRef, MaybeRefOrGetter, ShallowRef } from 'vue' import { useQueryCache } from './query-store' -import type { UseEntryKey } from './entry-options' +import type { EntryKey } from './entry-options' import type { ErrorDefault } from './types-extension' import { type _Awaitable, noop } from './utils' @@ -11,8 +11,8 @@ import { type _Awaitable, noop } from './utils' * @internal */ type _MutationKeys = - | UseEntryKey[] - | ((data: TResult, vars: TVars) => UseEntryKey[]) + | EntryKey[] + | ((data: TResult, vars: TVars) => EntryKey[]) /** * The status of the mutation. @@ -69,7 +69,7 @@ export interface UseMutationOptions< */ mutation: (vars: TVars, context: NoInfer) => Promise - key?: MaybeRefOrGetter + key?: MaybeRefOrGetter // TODO: move this to a plugin that calls invalidateEntry() /** diff --git a/src/use-query.ts b/src/use-query.ts index e31f5b62..540aa5f8 100644 --- a/src/use-query.ts +++ b/src/use-query.ts @@ -13,7 +13,7 @@ import { import { IS_CLIENT, computedRef, useEventListener } from './utils' import { type _UseQueryEntry_State, useQueryCache } from './query-store' import { useQueryOptions } from './query-options' -import type { UseEntryKey } from './entry-options' +import type { EntryKey } from './entry-options' import type { UseQueryOptions, UseQueryOptionsWithDefaults, @@ -176,9 +176,9 @@ export function useQuery( * @returns - the computed key */ function _computedKeyWithWarnings( - key: MaybeRefOrGetter, + key: MaybeRefOrGetter, warnChecksMap: WeakMap, -): () => UseEntryKey { +): () => EntryKey { const componentInstance = getCurrentInstance() // probably correct scope, no need to warn if (!componentInstance) return () => toValue(key) From bc8a47fd18c4a4839f20d792383c48d84fa013b2 Mon Sep 17 00:00:00 2001 From: ElisePatrikainen Date: Sat, 23 Mar 2024 19:24:33 +0100 Subject: [PATCH 3/4] feat(mutation): allow passing mutation variables to mutation key getter --- src/entry-options.ts | 2 +- src/use-mutation.test-d.ts | 11 +++++++++++ src/use-mutation.ts | 8 ++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/entry-options.ts b/src/entry-options.ts index 3972fd25..3f28af6c 100644 --- a/src/entry-options.ts +++ b/src/entry-options.ts @@ -2,6 +2,6 @@ import type { EntryNodeKey } from './tree-map' import type { _ObjectFlat } from './utils' /** - * Key used to identify a query. Always an array. + * Key used to identify a query or a mutation. Always an array. */ export type EntryKey = Array diff --git a/src/use-mutation.test-d.ts b/src/use-mutation.test-d.ts index 0f7f3f38..3784cb6f 100644 --- a/src/use-mutation.test-d.ts +++ b/src/use-mutation.test-d.ts @@ -2,6 +2,17 @@ import { expectTypeOf, it } from 'vitest' import { useMutation } from './use-mutation' it('types the parameters for the key', () => { + useMutation({ + mutation: (_one: string) => Promise.resolve({ name: 'foo' }), + key(result, one) { + expectTypeOf(one).toBeString() + expectTypeOf(result).toEqualTypeOf<{ name: string }>() + return ['foo'] + }, + }) +}) + +it('types the parameters for the keys', () => { const { mutate, mutateAsync } = useMutation({ mutation: (_one: string) => Promise.resolve({ name: 'foo' }), keys(result, one) { diff --git a/src/use-mutation.ts b/src/use-mutation.ts index 2c833187..d801c226 100644 --- a/src/use-mutation.ts +++ b/src/use-mutation.ts @@ -1,10 +1,14 @@ import { computed, shallowRef } from 'vue' -import type { ComputedRef, MaybeRefOrGetter, ShallowRef } from 'vue' +import type { ComputedRef, ShallowRef } from 'vue' import { useQueryCache } from './query-store' import type { EntryKey } from './entry-options' import type { ErrorDefault } from './types-extension' import { type _Awaitable, noop } from './utils' +type _MutationKey = + | EntryKey + | ((data: TResult, vars: TVars) => EntryKey) + // TODO: move to a plugin /** * The keys to invalidate when a mutation succeeds. @@ -69,7 +73,7 @@ export interface UseMutationOptions< */ mutation: (vars: TVars, context: NoInfer) => Promise - key?: MaybeRefOrGetter + key?: _MutationKey // TODO: move this to a plugin that calls invalidateEntry() /** From d16cf2d00e0b58650efe44e9e08c35049d54e24e Mon Sep 17 00:00:00 2001 From: ElisePatrikainen Date: Sun, 31 Mar 2024 15:52:43 +0200 Subject: [PATCH 4/4] refactor(mutation): remove passing mutation context to mutation key getter --- playground/src/pages/ecom/item/[id].vue | 2 +- src/use-mutation.test-d.ts | 3 +-- src/use-mutation.ts | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/playground/src/pages/ecom/item/[id].vue b/playground/src/pages/ecom/item/[id].vue index f39acb5b..180ee319 100644 --- a/playground/src/pages/ecom/item/[id].vue +++ b/playground/src/pages/ecom/item/[id].vue @@ -25,7 +25,7 @@ const itemAvailability = ref() watch(() => item.value?.availability, (value) => itemAvailability.value = value) const { mutate: bookProduct } = useMutation({ - key: ['book-item'], + key: (product) => ['book-item', product.id], keys: (product) => [['items'], ['items', product.id]], mutation: async (product: ProductListItem) => { await delay(Math.random() * 1000 + 200) diff --git a/src/use-mutation.test-d.ts b/src/use-mutation.test-d.ts index 3784cb6f..ba8b90b7 100644 --- a/src/use-mutation.test-d.ts +++ b/src/use-mutation.test-d.ts @@ -4,9 +4,8 @@ import { useMutation } from './use-mutation' it('types the parameters for the key', () => { useMutation({ mutation: (_one: string) => Promise.resolve({ name: 'foo' }), - key(result, one) { + key(one) { expectTypeOf(one).toBeString() - expectTypeOf(result).toEqualTypeOf<{ name: string }>() return ['foo'] }, }) diff --git a/src/use-mutation.ts b/src/use-mutation.ts index d801c226..b4a1277b 100644 --- a/src/use-mutation.ts +++ b/src/use-mutation.ts @@ -5,9 +5,9 @@ import type { EntryKey } from './entry-options' import type { ErrorDefault } from './types-extension' import { type _Awaitable, noop } from './utils' -type _MutationKey = +type _MutationKey = | EntryKey - | ((data: TResult, vars: TVars) => EntryKey) + | ((vars: TVars) => EntryKey) // TODO: move to a plugin /** @@ -73,7 +73,7 @@ export interface UseMutationOptions< */ mutation: (vars: TVars, context: NoInfer) => Promise - key?: _MutationKey + key?: _MutationKey // TODO: move this to a plugin that calls invalidateEntry() /**