diff --git a/.changeset/dog-brown-fox.md b/.changeset/dog-brown-fox.md new file mode 100644 index 00000000000..dd2f40f2a25 --- /dev/null +++ b/.changeset/dog-brown-fox.md @@ -0,0 +1,10 @@ +--- +'@tanstack/react-query': patch +'@tanstack/preact-query': patch +'@tanstack/solid-query': patch +'@tanstack/vue-query': patch +'@tanstack/svelte-query': patch +'@tanstack/lit-query': patch +--- + +fix(types): add `TMutation` generic to `useMutationState` with caller-side assertion warning in JSDoc diff --git a/packages/lit-query/src/useMutationState.ts b/packages/lit-query/src/useMutationState.ts index ed362ea0944..a2271e7fc0a 100644 --- a/packages/lit-query/src/useMutationState.ts +++ b/packages/lit-query/src/useMutationState.ts @@ -14,14 +14,29 @@ import { } from './accessor.js' import { BaseController } from './controllers/BaseController.js' +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + /** * Options accepted by `useMutationState`. */ -export type MutationStateOptions = { +export type MutationStateOptions< + TResult, + TMutation extends Mutation = + MutationTypeFromResult, +> = { /** Filters used to select mutations from the mutation cache. */ filters?: Accessor /** Maps each matching mutation to the value returned by the accessor. */ - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } /** @@ -35,13 +50,16 @@ export type MutationStateAccessor = ValueAccessor & { destroy: () => void } -class MutationStateController extends BaseController { +class MutationStateController< + TResult, + TMutation extends Mutation = MutationTypeFromResult, +> extends BaseController { private queryClient: QueryClient | undefined private unsubscribe: (() => void) | undefined constructor( host: ReactiveControllerHost, - private readonly options: MutationStateOptions, + private readonly options: MutationStateOptions, queryClient?: QueryClient, ) { super(host, [], queryClient) @@ -148,7 +166,7 @@ class MutationStateController extends BaseController { return mutations.map((mutation) => { if (select) { - return select(mutation) + return select(mutation as TMutation) } return mutation.state as TResult @@ -188,15 +206,23 @@ class MutationStateController extends BaseController { * } * } * ``` + * + * @template TResult - The type of values returned by the `select` callback. + * @template TMutation - Narrows the type of the `mutation` argument passed to + * `select`. This is a caller-side assertion — the mutation cache stores + * mutations as the base `Mutation` type, so it is the caller's responsibility + * to ensure `TMutation` matches the actual mutations in the cache (e.g. by + * specifying a `mutationKey` in `filters`). */ export function useMutationState< TResult = MutationState, + TMutation extends Mutation = MutationTypeFromResult, >( host: ReactiveControllerHost, - options: MutationStateOptions = {}, + options: MutationStateOptions = {}, queryClient?: QueryClient, ): MutationStateAccessor { - const controller = new MutationStateController(host, options, queryClient) + const controller = new MutationStateController(host, options, queryClient) return Object.assign( createValueAccessor(() => controller.current), { diff --git a/packages/preact-query/src/useMutationState.ts b/packages/preact-query/src/useMutationState.ts index 61fcd1b1c9a..6ab2689c7c9 100644 --- a/packages/preact-query/src/useMutationState.ts +++ b/packages/preact-query/src/useMutationState.ts @@ -22,25 +22,58 @@ export function useIsMutating( ).length } -type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: MutationStateOptions = {}, +/** + * @template TResult - The type of values returned by the `select` callback. + * @template TMutation - Narrows the type of the `mutation` argument passed to + * `select`. This is a caller-side assertion — the mutation cache stores + * mutations as the base `Mutation` type, so it is the caller's responsibility + * to ensure `TMutation` matches the actual mutations in the cache (e.g. by + * specifying a `mutationKey` in `filters`). + */ +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: MutationStateOptions = {}, queryClient?: QueryClient, ): Array { const mutationCache = useQueryClient(queryClient).getMutationCache() diff --git a/packages/react-query/src/useMutationState.ts b/packages/react-query/src/useMutationState.ts index dfd0c41da3a..0fe68e819fa 100644 --- a/packages/react-query/src/useMutationState.ts +++ b/packages/react-query/src/useMutationState.ts @@ -22,25 +22,58 @@ export function useIsMutating( ).length } -type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: MutationStateOptions = {}, +/** + * @template TResult - The type of values returned by the `select` callback. + * @template TMutation - Narrows the type of the `mutation` argument passed to + * `select`. This is a caller-side assertion — the mutation cache stores + * mutations as the base `Mutation` type, so it is the caller's responsibility + * to ensure `TMutation` matches the actual mutations in the cache (e.g. by + * specifying a `mutationKey` in `filters`). + */ +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: MutationStateOptions = {}, queryClient?: QueryClient, ): Array { const mutationCache = useQueryClient(queryClient).getMutationCache() diff --git a/packages/solid-query/src/useMutationState.ts b/packages/solid-query/src/useMutationState.ts index 2a405a1ae4b..8164983a04f 100644 --- a/packages/solid-query/src/useMutationState.ts +++ b/packages/solid-query/src/useMutationState.ts @@ -10,25 +10,58 @@ import type { import type { Accessor } from 'solid-js' import type { QueryClient } from './QueryClient' -type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: Accessor> = () => ({}), +/** + * @template TResult - The type of values returned by the `select` callback. + * @template TMutation - Narrows the type of the `mutation` argument passed to + * `select`. This is a caller-side assertion — the mutation cache stores + * mutations as the base `Mutation` type, so it is the caller's responsibility + * to ensure `TMutation` matches the actual mutations in the cache (e.g. by + * specifying a `mutationKey` in `filters`). + */ +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: Accessor> = () => ({}), queryClient?: Accessor, ): Accessor> { const client = createMemo(() => useQueryClient(queryClient?.())) diff --git a/packages/svelte-query/src/types.ts b/packages/svelte-query/src/types.ts index 9a64d10dc3f..a37d8f6468d 100644 --- a/packages/svelte-query/src/types.ts +++ b/packages/svelte-query/src/types.ts @@ -136,12 +136,25 @@ export type CreateMutationResult< TOnMutateResult = unknown, > = CreateBaseMutationResult +export type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + /** Options for useMutationState */ -export type MutationStateOptions = { +export type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: ( - mutation: Mutation, - ) => TResult + select?: (mutation: TMutation) => TResult } export type QueryClientProviderProps = { diff --git a/packages/svelte-query/src/useMutationState.svelte.ts b/packages/svelte-query/src/useMutationState.svelte.ts index c517e64b480..87d166864bb 100644 --- a/packages/svelte-query/src/useMutationState.svelte.ts +++ b/packages/svelte-query/src/useMutationState.svelte.ts @@ -1,26 +1,45 @@ import { replaceEqualDeep } from '@tanstack/query-core' import { useQueryClient } from './useQueryClient.js' import type { + Mutation, MutationCache, MutationState, QueryClient, } from '@tanstack/query-core' -import type { MutationStateOptions } from './types.js' +import type { MutationStateOptions, MutationTypeFromResult } from './types.js' -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( - options: MutationStateOptions = {}, +/** + * @template TResult - The type of values returned by the `select` callback. + * @template TMutation - Narrows the type of the `mutation` argument passed to + * `select`. This is a caller-side assertion — the mutation cache stores + * mutations as the base `Mutation` type, so it is the caller's responsibility + * to ensure `TMutation` matches the actual mutations in the cache (e.g. by + * specifying a `mutationKey` in `filters`). + */ +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( + options: MutationStateOptions = {}, queryClient?: QueryClient, ): Array { const mutationCache = useQueryClient(queryClient).getMutationCache() diff --git a/packages/vue-query/src/useMutationState.ts b/packages/vue-query/src/useMutationState.ts index 3395b5fb743..778948b5c77 100644 --- a/packages/vue-query/src/useMutationState.ts +++ b/packages/vue-query/src/useMutationState.ts @@ -48,27 +48,60 @@ export function useIsMutating( return length } -export type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +export type MutationStateOptions< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +> = { filters?: MutationFilters - select?: (mutation: Mutation) => TResult + select?: (mutation: TMutation) => TResult } -function getResult( +function getResult< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( mutationCache: MutationCache, - options: MutationStateOptions, + options: MutationStateOptions, ): Array { return mutationCache .findAll(options.filters) .map( (mutation): TResult => - (options.select ? options.select(mutation) : mutation.state) as TResult, + (options.select + ? options.select(mutation as TMutation) + : mutation.state) as TResult, ) } -export function useMutationState( +/** + * @template TResult - The type of values returned by the `select` callback. + * @template TMutation - Narrows the type of the `mutation` argument passed to + * `select`. This is a caller-side assertion — the mutation cache stores + * mutations as the base `Mutation` type, so it is the caller's responsibility + * to ensure `TMutation` matches the actual mutations in the cache (e.g. by + * specifying a `mutationKey` in `filters`). + */ +export function useMutationState< + TResult = MutationState, + TMutation extends Mutation = + MutationTypeFromResult, +>( options: - | MutationStateOptions - | (() => MutationStateOptions) = {}, + | MutationStateOptions + | (() => MutationStateOptions) = {}, queryClient?: QueryClient, ): Readonly>> { const resolvedOptions = computed(() => {