snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:13:08 +05:00
commit 3a013ccdfe
3570 changed files with 490194 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import type { QueryClient } from '@tanstack/query-core'
import {
createMutation,
setQueryClientContext,
useIsMutating,
useMutationState,
} from '../../src/index.js'
import type {
Accessor,
CreateMutationOptions,
MutationStateOptions,
} from '../../src/index.js'
import type { MutationFilters } from '@tanstack/query-core'
type Props = {
queryClient: QueryClient
mutationOpts: Accessor<CreateMutationOptions<string>>
isMutatingFilters?: MutationFilters
mutationStateOpts?: MutationStateOptions
}
let {
queryClient,
mutationOpts,
isMutatingFilters,
mutationStateOpts,
}: Props = $props()
setQueryClientContext(queryClient)
const mutation = createMutation(mutationOpts)
const isMutating = useIsMutating(isMutatingFilters)
const mutationState = useMutationState(mutationStateOpts)
let clientIsMutating = $state(0)
$effect(() => {
const mutationCache = queryClient.getMutationCache()
clientIsMutating = isMutatingFilters
? queryClient.isMutating(isMutatingFilters)
: queryClient.isMutating()
const unsubscribe = mutationCache.subscribe(() => {
clientIsMutating = isMutatingFilters
? queryClient.isMutating(isMutatingFilters)
: queryClient.isMutating()
})
return unsubscribe
})
</script>
<button onclick={() => mutation.mutate()}>mutate</button>
<div>isMutating: {isMutating.current}</div>
<div>clientIsMutating: {clientIsMutating}</div>
<div>
mutationState: {JSON.stringify(mutationState.map((state) => state.data))}
</div>

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import type { QueryClient } from '@tanstack/query-core'
import {
createMutation,
setQueryClientContext,
useIsMutating,
useMutationState,
} from '../../src/index.js'
import type {
Accessor,
CreateMutationOptions,
MutationStateOptions,
} from '../../src/index.js'
import type { MutationFilters } from '@tanstack/query-core'
type Props = {
queryClient: QueryClient
mutationOpts1: Accessor<CreateMutationOptions<string>>
mutationOpts2: Accessor<CreateMutationOptions<string>>
isMutatingFilters?: MutationFilters
mutationStateOpts?: MutationStateOptions
}
let {
queryClient,
mutationOpts1,
mutationOpts2,
isMutatingFilters,
mutationStateOpts,
}: Props = $props()
setQueryClientContext(queryClient)
const mutation1 = createMutation(mutationOpts1)
const mutation2 = createMutation(mutationOpts2)
const isMutating = useIsMutating(isMutatingFilters)
const mutationState = useMutationState(mutationStateOpts)
let clientIsMutating = $state(0)
$effect(() => {
const mutationCache = queryClient.getMutationCache()
clientIsMutating = isMutatingFilters
? queryClient.isMutating(isMutatingFilters)
: queryClient.isMutating()
const unsubscribe = mutationCache.subscribe(() => {
clientIsMutating = isMutatingFilters
? queryClient.isMutating(isMutatingFilters)
: queryClient.isMutating()
})
return unsubscribe
})
</script>
<button onclick={() => mutation1.mutate()}>mutate1</button>
<button onclick={() => mutation2.mutate()}>mutate2</button>
<div>isMutating: {isMutating.current}</div>
<div>clientIsMutating: {clientIsMutating}</div>
<div>
mutationState: {JSON.stringify(mutationState.map((state) => state.data))}
</div>

View File

@@ -0,0 +1,344 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { fireEvent, render } from '@testing-library/svelte'
import { QueryClient } from '@tanstack/query-core'
import { queryKey, sleep } from '@tanstack/query-test-utils'
import { mutationOptions } from '../../src/index.js'
import Base from './Base.svelte'
import Multi from './Multi.svelte'
describe('mutationOptions', () => {
let queryClient: QueryClient
beforeEach(() => {
vi.useFakeTimers()
queryClient = new QueryClient()
})
afterEach(() => {
queryClient.clear()
vi.useRealTimers()
})
it('should return the object received as a parameter without any modification (with mutationKey in mutationOptions)', () => {
const object = {
mutationKey: ['key'],
mutationFn: () => sleep(10).then(() => 5),
} as const
expect(mutationOptions(object)).toBe(object)
})
it('should return the object received as a parameter without any modification (without mutationKey in mutationOptions)', () => {
const object = {
mutationFn: () => sleep(10).then(() => 5),
} as const
expect(mutationOptions(object)).toBe(object)
})
it('should return the number of fetching mutations when used with useIsMutating (with mutationKey in mutationOptions)', async () => {
const key = queryKey()
const mutationOpts = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(50).then(() => 'data'),
})
const rendered = render(Base, {
props: { queryClient, mutationOpts: () => mutationOpts },
})
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('isMutating: 1')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(50)
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
})
it('should return the number of fetching mutations when used with useIsMutating (without mutationKey in mutationOptions)', async () => {
const mutationOpts = mutationOptions({
mutationFn: () => sleep(50).then(() => 'data'),
})
const rendered = render(Base, {
props: { queryClient, mutationOpts: () => mutationOpts },
})
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('isMutating: 1')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(50)
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
})
it('should return the number of fetching mutations when used with useIsMutating', async () => {
const key = queryKey()
const mutationOpts1 = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(50).then(() => 'data1'),
})
const mutationOpts2 = mutationOptions({
mutationFn: () => sleep(50).then(() => 'data2'),
})
const rendered = render(Multi, {
props: {
queryClient,
mutationOpts1: () => mutationOpts1,
mutationOpts2: () => mutationOpts2,
},
})
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate1/i }))
fireEvent.click(rendered.getByRole('button', { name: /mutate2/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('isMutating: 2')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(50)
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
})
it('should return the number of fetching mutations when used with useIsMutating (filter mutationOpts1.mutationKey)', async () => {
const key = queryKey()
const mutationOpts1 = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(50).then(() => 'data1'),
})
const mutationOpts2 = mutationOptions({
mutationFn: () => sleep(50).then(() => 'data2'),
})
const rendered = render(Multi, {
props: {
queryClient,
mutationOpts1: () => mutationOpts1,
mutationOpts2: () => mutationOpts2,
isMutatingFilters: { mutationKey: mutationOpts1.mutationKey },
},
})
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate1/i }))
fireEvent.click(rendered.getByRole('button', { name: /mutate2/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('isMutating: 1')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(50)
expect(rendered.getByText('isMutating: 0')).toBeInTheDocument()
})
it('should return the number of fetching mutations when used with queryClient.isMutating (with mutationKey in mutationOptions)', async () => {
const key = queryKey()
const mutationOpts = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(500).then(() => 'data'),
})
const rendered = render(Base, {
props: {
queryClient,
mutationOpts: () => mutationOpts,
isMutatingFilters: { mutationKey: mutationOpts.mutationKey },
},
})
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('clientIsMutating: 1')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(500)
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
})
it('should return the number of fetching mutations when used with queryClient.isMutating (without mutationKey in mutationOptions)', async () => {
const mutationOpts = mutationOptions({
mutationFn: () => sleep(500).then(() => 'data'),
})
const rendered = render(Base, {
props: { queryClient, mutationOpts: () => mutationOpts },
})
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('clientIsMutating: 1')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(500)
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
})
it('should return the number of fetching mutations when used with queryClient.isMutating', async () => {
const key = queryKey()
const mutationOpts1 = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(500).then(() => 'data1'),
})
const mutationOpts2 = mutationOptions({
mutationFn: () => sleep(500).then(() => 'data2'),
})
const rendered = render(Multi, {
props: {
queryClient,
mutationOpts1: () => mutationOpts1,
mutationOpts2: () => mutationOpts2,
},
})
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate1/i }))
fireEvent.click(rendered.getByRole('button', { name: /mutate2/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('clientIsMutating: 2')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(500)
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
})
it('should return the number of fetching mutations when used with queryClient.isMutating (filter mutationOpts1.mutationKey)', async () => {
const key = queryKey()
const mutationOpts1 = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(500).then(() => 'data1'),
})
const mutationOpts2 = mutationOptions({
mutationFn: () => sleep(500).then(() => 'data2'),
})
const rendered = render(Multi, {
props: {
queryClient,
mutationOpts1: () => mutationOpts1,
mutationOpts2: () => mutationOpts2,
isMutatingFilters: { mutationKey: mutationOpts1.mutationKey },
},
})
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate1/i }))
fireEvent.click(rendered.getByRole('button', { name: /mutate2/i }))
await vi.advanceTimersByTimeAsync(0)
expect(rendered.getByText('clientIsMutating: 1')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(500)
expect(rendered.getByText('clientIsMutating: 0')).toBeInTheDocument()
})
it('should return mutation states when used with useMutationState (with mutationKey in mutationOptions)', async () => {
const key = queryKey()
const mutationOpts = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(10).then(() => 'data'),
})
const rendered = render(Base, {
props: {
queryClient,
mutationOpts: () => mutationOpts,
mutationStateOpts: {
filters: {
mutationKey: mutationOpts.mutationKey,
status: 'success',
},
},
},
})
expect(rendered.getByText('mutationState: []')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('mutationState: ["data"]')).toBeInTheDocument()
})
it('should return mutation states when used with useMutationState (without mutationKey in mutationOptions)', async () => {
const mutationOpts = mutationOptions({
mutationFn: () => sleep(10).then(() => 'data'),
})
const rendered = render(Base, {
props: {
queryClient,
mutationOpts: () => mutationOpts,
mutationStateOpts: {
filters: { status: 'success' },
},
},
})
expect(rendered.getByText('mutationState: []')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('mutationState: ["data"]')).toBeInTheDocument()
})
it('should return mutation states when used with useMutationState', async () => {
const key = queryKey()
const mutationOpts1 = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(10).then(() => 'data1'),
})
const mutationOpts2 = mutationOptions({
mutationFn: () => sleep(10).then(() => 'data2'),
})
const rendered = render(Multi, {
props: {
queryClient,
mutationOpts1: () => mutationOpts1,
mutationOpts2: () => mutationOpts2,
mutationStateOpts: {
filters: { status: 'success' },
},
},
})
expect(rendered.getByText('mutationState: []')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate1/i }))
fireEvent.click(rendered.getByRole('button', { name: /mutate2/i }))
await vi.advanceTimersByTimeAsync(10)
expect(
rendered.getByText('mutationState: ["data1","data2"]'),
).toBeInTheDocument()
})
it('should return mutation states when used with useMutationState (filter mutationOpts1.mutationKey)', async () => {
const key = queryKey()
const mutationOpts1 = mutationOptions({
mutationKey: key,
mutationFn: () => sleep(10).then(() => 'data1'),
})
const mutationOpts2 = mutationOptions({
mutationFn: () => sleep(10).then(() => 'data2'),
})
const rendered = render(Multi, {
props: {
queryClient,
mutationOpts1: () => mutationOpts1,
mutationOpts2: () => mutationOpts2,
mutationStateOpts: {
filters: {
mutationKey: mutationOpts1.mutationKey,
status: 'success',
},
},
},
})
expect(rendered.getByText('mutationState: []')).toBeInTheDocument()
fireEvent.click(rendered.getByRole('button', { name: /mutate1/i }))
fireEvent.click(rendered.getByRole('button', { name: /mutate2/i }))
await vi.advanceTimersByTimeAsync(10)
expect(rendered.getByText('mutationState: ["data1"]')).toBeInTheDocument()
})
})

View File

@@ -0,0 +1,246 @@
import { assertType, describe, expectTypeOf, it } from 'vitest'
import { QueryClient } from '@tanstack/query-core'
import { queryKey } from '@tanstack/query-test-utils'
import {
createMutation,
mutationOptions,
useIsMutating,
useMutationState,
} from '../../src/index.js'
import type {
DefaultError,
MutationFunctionContext,
MutationState,
WithRequired,
} from '@tanstack/query-core'
import type {
CreateMutationOptions,
CreateMutationResult,
} from '../../src/types.js'
describe('mutationOptions', () => {
it('should not allow excess properties', () => {
const key = queryKey()
// @ts-expect-error this is a good error, because onMutates does not exist!
mutationOptions({
mutationFn: () => Promise.resolve(5),
mutationKey: key,
onMutates: 1000,
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<number>()
},
})
})
it('should infer types for callbacks', () => {
const key = queryKey()
mutationOptions({
mutationFn: () => Promise.resolve(5),
mutationKey: key,
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<number>()
},
})
})
it('should infer types for onError callback', () => {
const key = queryKey()
mutationOptions({
mutationFn: () => {
throw new Error('fail')
},
mutationKey: key,
onError: (error) => {
expectTypeOf(error).toEqualTypeOf<DefaultError>()
},
})
})
it('should infer types for variables', () => {
const key = queryKey()
mutationOptions<number, DefaultError, { id: string }>({
mutationFn: (vars) => {
expectTypeOf(vars).toEqualTypeOf<{ id: string }>()
return Promise.resolve(5)
},
mutationKey: key,
})
})
it('should infer result type correctly', () => {
const key = queryKey()
mutationOptions<number, DefaultError, void, { name: string }>({
mutationFn: () => Promise.resolve(5),
mutationKey: key,
onMutate: () => {
return { name: 'onMutateResult' }
},
onSuccess: (_data, _variables, onMutateResult) => {
expectTypeOf(onMutateResult).toEqualTypeOf<{ name: string }>()
},
})
})
it('should infer context type correctly', () => {
const key = queryKey()
mutationOptions<number>({
mutationFn: (_variables, context) => {
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
return Promise.resolve(5)
},
mutationKey: key,
onMutate: (_variables, context) => {
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
},
onSuccess: (_data, _variables, _onMutateResult, context) => {
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
},
onError: (_error, _variables, _onMutateResult, context) => {
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
},
onSettled: (_data, _error, _variables, _onMutateResult, context) => {
expectTypeOf(context).toEqualTypeOf<MutationFunctionContext>()
},
})
})
it('should error if mutationFn return type mismatches TData', () => {
assertType(
mutationOptions<number>({
// @ts-expect-error this is a good error, because return type is string, not number
mutationFn: async () => Promise.resolve('wrong return'),
}),
)
})
it('should allow mutationKey to be omitted', () => {
return mutationOptions({
mutationFn: () => Promise.resolve(123),
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<number>()
},
})
})
it('should infer all types when not explicitly provided', () => {
const key = queryKey()
expectTypeOf(
mutationOptions({
mutationFn: (id: string) => Promise.resolve(id.length),
mutationKey: key,
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<number>()
},
}),
).toEqualTypeOf<
WithRequired<
CreateMutationOptions<number, DefaultError, string>,
'mutationKey'
>
>()
expectTypeOf(
mutationOptions({
mutationFn: (id: string) => Promise.resolve(id.length),
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<number>()
},
}),
).toEqualTypeOf<
Omit<CreateMutationOptions<number, DefaultError, string>, 'mutationKey'>
>()
})
it('should work when used with createMutation', () => {
const key = queryKey()
const mutation = createMutation(() =>
mutationOptions({
mutationKey: key,
mutationFn: () => Promise.resolve('data'),
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<string>()
},
}),
)
expectTypeOf(mutation).toEqualTypeOf<
CreateMutationResult<string, DefaultError, void, unknown>
>()
createMutation(() =>
// should allow when used with createMutation without mutationKey
mutationOptions({
mutationFn: () => Promise.resolve('data'),
onSuccess: (data) => {
expectTypeOf(data).toEqualTypeOf<string>()
},
}),
)
})
it('should work when used with useIsMutating', () => {
const key = queryKey()
const isMutating = useIsMutating(
mutationOptions({
mutationKey: key,
mutationFn: () => Promise.resolve(5),
}),
)
expectTypeOf(isMutating.current).toEqualTypeOf<number>()
useIsMutating(
// @ts-expect-error filters should have mutationKey
mutationOptions({
mutationFn: () => Promise.resolve(5),
}),
)
})
it('should work when used with queryClient.isMutating', () => {
const key = queryKey()
const queryClient = new QueryClient()
const isMutating = queryClient.isMutating(
mutationOptions({
mutationKey: key,
mutationFn: () => Promise.resolve(5),
}),
)
expectTypeOf(isMutating).toEqualTypeOf<number>()
queryClient.isMutating(
// @ts-expect-error filters should have mutationKey
mutationOptions({
mutationFn: () => Promise.resolve(5),
}),
)
})
it('should work when used with useMutationState', () => {
const key = queryKey()
const mutationState = useMutationState({
filters: mutationOptions({
mutationKey: key,
mutationFn: () => Promise.resolve(5),
}),
})
expectTypeOf(mutationState).toEqualTypeOf<
Array<MutationState<unknown, Error, unknown, unknown>>
>()
useMutationState({
// @ts-expect-error filters should have mutationKey
filters: mutationOptions({
mutationFn: () => Promise.resolve(5),
}),
})
})
})