From 8663c5e1cf76e7d91f16343e02518ec8a11b9023 Mon Sep 17 00:00:00 2001 From: Osraka <98612432+Osraka@users.noreply.github.com> Date: Tue, 1 Sep 2026 02:40:17 +0300 Subject: [PATCH 1/2] fix(lit-query): propagate mutation state generics --- .changeset/lit-mutation-state-generics.md | 5 +++ .../__tests__/mutation-state-types.test.ts | 31 +++++++++++++++ packages/lit-query/src/useMutationState.ts | 38 +++++++++++++++---- 3 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 .changeset/lit-mutation-state-generics.md create mode 100644 packages/lit-query/src/__tests__/mutation-state-types.test.ts diff --git a/.changeset/lit-mutation-state-generics.md b/.changeset/lit-mutation-state-generics.md new file mode 100644 index 00000000000..95d828c3be7 --- /dev/null +++ b/.changeset/lit-mutation-state-generics.md @@ -0,0 +1,5 @@ +--- +'@tanstack/lit-query': patch +--- + +fix(types): propagate generic type parameters to the `useMutationState` select callback diff --git a/packages/lit-query/src/__tests__/mutation-state-types.test.ts b/packages/lit-query/src/__tests__/mutation-state-types.test.ts new file mode 100644 index 00000000000..27d79604014 --- /dev/null +++ b/packages/lit-query/src/__tests__/mutation-state-types.test.ts @@ -0,0 +1,31 @@ +import { QueryClient } from '@tanstack/query-core' +import { LitElement } from 'lit' +import { describe, expectTypeOf, it } from 'vitest' +import { useMutationState } from '../useMutationState.js' +import { generateElementName } from './test-utils.js' +import type { Mutation, MutationState } from '@tanstack/query-core' + +describe('useMutationState types', () => { + it('propagates typed mutation state generics to the select callback', () => { + type MyData = { data: Array } + type MyError = { code: number; message: string } + type MyVariables = { id: number } + + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + useMutationState>( + new Host(), + { + filters: { mutationKey: ['key'] }, + select: (mutation) => { + expectTypeOf(mutation).toEqualTypeOf< + Mutation + >() + return mutation.state + }, + }, + new QueryClient(), + ) + }) +}) diff --git a/packages/lit-query/src/useMutationState.ts b/packages/lit-query/src/useMutationState.ts index 74ee115c64f..033b3ed35e5 100644 --- a/packages/lit-query/src/useMutationState.ts +++ b/packages/lit-query/src/useMutationState.ts @@ -13,11 +13,26 @@ import type { ReactiveControllerHost } from 'lit' /** * Options accepted by `useMutationState`. */ -export type MutationStateOptions = { +type MutationTypeFromResult = [TResult] extends [ + MutationState< + infer TData, + infer TError, + infer TVariables, + infer TOnMutateResult + >, +] + ? Mutation + : Mutation + +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 } /** @@ -31,13 +46,16 @@ export type MutationStateAccessor = ValueAccessor> & { destroy: () => void } -class MutationStateController extends BaseController> { +class MutationStateController< + TResult, + TMutation extends Mutation, +> 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) @@ -144,7 +162,7 @@ class MutationStateController extends BaseController> { return mutations.map((mutation) => { if (select) { - return select(mutation) + return select(mutation as TMutation) } return mutation.state as TResult @@ -187,12 +205,18 @@ class MutationStateController extends BaseController> { */ 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), { From ba15bebfdf582bc97ad29213014799eef9f15808 Mon Sep 17 00:00:00 2001 From: Osraka <98612432+Osraka@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:59:40 +0300 Subject: [PATCH 2/2] fix(lit-query): preserve default mutation error type --- .../__tests__/mutation-state-types.test.ts | 52 ++++++++++++++++++- packages/lit-query/src/useMutationState.ts | 7 ++- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/packages/lit-query/src/__tests__/mutation-state-types.test.ts b/packages/lit-query/src/__tests__/mutation-state-types.test.ts index 27d79604014..3b44d9dfeb9 100644 --- a/packages/lit-query/src/__tests__/mutation-state-types.test.ts +++ b/packages/lit-query/src/__tests__/mutation-state-types.test.ts @@ -2,10 +2,58 @@ import { QueryClient } from '@tanstack/query-core' import { LitElement } from 'lit' import { describe, expectTypeOf, it } from 'vitest' import { useMutationState } from '../useMutationState.js' -import { generateElementName } from './test-utils.js' -import type { Mutation, MutationState } from '@tanstack/query-core' +import { generateElementName } from './utils.js' +import type { + Mutation, + MutationState, + MutationStatus, +} from '@tanstack/query-core' describe('useMutationState types', () => { + it('defaults to mutation state with the default error type', () => { + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + const result = useMutationState( + new Host(), + { filters: { status: 'pending' } }, + new QueryClient(), + ) + + expectTypeOf(result()).toEqualTypeOf< + Array> + >() + }) + + it('infers the selected status', () => { + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + const result = useMutationState( + new Host(), + { + filters: { status: 'pending' }, + select: (mutation) => mutation.state.status, + }, + new QueryClient(), + ) + + expectTypeOf(result()).toEqualTypeOf>() + }) + + it('allows selecting a default error message', () => { + class Host extends LitElement {} + customElements.define(generateElementName(), Host) + + const result = useMutationState( + new Host(), + { select: (mutation) => mutation.state.error?.message }, + new QueryClient(), + ) + + expectTypeOf(result()).toEqualTypeOf>() + }) + it('propagates typed mutation state generics to the select callback', () => { type MyData = { data: Array } type MyError = { code: number; message: string } diff --git a/packages/lit-query/src/useMutationState.ts b/packages/lit-query/src/useMutationState.ts index bfe2bb92c19..4872be82ccf 100644 --- a/packages/lit-query/src/useMutationState.ts +++ b/packages/lit-query/src/useMutationState.ts @@ -10,9 +10,7 @@ import type { } from '@tanstack/query-core' import type { ReactiveControllerHost } from 'lit' -/** - * Options accepted by `useMutationState`. - */ +/** Recovers mutation generics when the selected result is a mutation state. */ type MutationTypeFromResult = [TResult] extends [ MutationState< infer TData, @@ -24,6 +22,7 @@ type MutationTypeFromResult = [TResult] extends [ ? Mutation : Mutation +/** Options accepted by `useMutationState`. */ export type MutationStateOptions< TResult, TMutation extends Mutation = @@ -202,7 +201,7 @@ class MutationStateController< * ``` */ export function useMutationState< - TResult = MutationState, + TResult = MutationState, TMutation extends Mutation = MutationTypeFromResult, >(