Skip to content

Commit b83dcee

Browse files
authored
feat(tanstack-query): update to 5.89 with streamOptions update (#1019)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Added a serializable streamed query utility with refetch modes (append/reset/replace) and maxChunks control. - Breaking Changes - Removed experimental streamedOptions APIs and related streaming types across framework integrations. - Mutation function signature now accepts a second options/context argument. - Behavior Changes - Live queries now reject on abort with the abort reason. - Documentation - Updated TanStack Query integration docs; legacy streamed options section removed. - Chores - Bumped TanStack Query dependencies to v5.89.0 across packages and playgrounds. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 057d007 commit b83dcee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+678
-1760
lines changed

apps/content/docs/integrations/tanstack-query-old/basic.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,6 @@ const query = useQuery(orpc.planet.find.queryOptions({
3131
}))
3232
```
3333

34-
## Streamed Query Options Utility
35-
36-
Use `.streamedOptions` to configure queries for [Event Iterator](/docs/event-iterator), which is built on top of [streamedQuery](https://tanstack.com/query/latest/docs/reference/streamedQuery). Use it with hooks like `useQuery`, `useSuspenseQuery`, or `prefetchQuery`.
37-
38-
```ts
39-
const query = useQuery(orpc.streamed.experimental_streamedOptions({
40-
input: { id: 123 }, // Specify input if needed
41-
context: { cache: true }, // Provide client context if needed
42-
queryFnOptions: { // Specify streamedQuery options if needed
43-
refetchMode: 'reset',
44-
maxChunks: 3,
45-
}
46-
// additional options...
47-
}))
48-
```
49-
5034
## Infinite Query Options Utility
5135

5236
Use `.infiniteOptions` to configure infinite queries. Use it with hooks like `useInfiniteQuery`, `useSuspenseInfiniteQuery`, or `prefetchInfiniteQuery`.

apps/content/docs/integrations/tanstack-query.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ const query = useQuery(orpc.planet.find.queryOptions({
9797

9898
## Streamed Query Options
9999

100-
Use `.streamedOptions` to configure queries for [Event Iterator](/docs/event-iterator). This is built on [TanStack Query streamedQuery](https://tanstack.com/query/latest/docs/reference/streamedQuery) and works with hooks like `useQuery`, `useSuspenseQuery`, or `prefetchQuery`.
100+
Use `.streamedOptions` to configure queries for [Event Iterator](/docs/event-iterator). Data is an array of events, and each new event is appended to the end of the array as it arrives.
101+
102+
Works with hooks like `useQuery`, `useSuspenseQuery`, or `prefetchQuery`.
101103

102104
```ts
103105
const query = useQuery(orpc.streamed.experimental_streamedOptions({
@@ -114,7 +116,9 @@ const query = useQuery(orpc.streamed.experimental_streamedOptions({
114116

115117
## Live Query Options
116118

117-
Use `.liveOptions` to configure live queries for [Event Iterator](/docs/event-iterator). Unlike `.streamedOptions` which accumulates chunks, live queries replace the entire result with each new chunk received. Works with hooks like `useQuery`, `useSuspenseQuery`, or `prefetchQuery`.
119+
Use `.liveOptions` to configure live queries for [Event Iterator](/docs/event-iterator). Data is always the latest event, replacing the previous value whenever a new one arrives.
120+
121+
Works with hooks like `useQuery`, `useSuspenseQuery`, or `prefetchQuery`.
118122

119123
```ts
120124
const query = useQuery(orpc.live.experimental_liveOptions({

apps/content/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
"@pinia/colada": "^0.17.3",
3232
"@sentry/node": "^10.8.0",
3333
"@shikijs/vitepress-twoslash": "^3.12.2",
34-
"@tanstack/react-query": "^5.85.9",
35-
"@tanstack/solid-query": "^5.85.9",
36-
"@tanstack/svelte-query": "^5.85.9",
37-
"@tanstack/vue-query": "^5.85.9",
34+
"@tanstack/react-query": "^5.89.0",
35+
"@tanstack/solid-query": "^5.89.0",
36+
"@tanstack/svelte-query": "^5.89.0",
37+
"@tanstack/vue-query": "^5.89.0",
3838
"@types/node": "^22.15.30",
3939
"ai": "5.0.30",
4040
"markdown-it-task-lists": "^2.1.1",

packages/react-query/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"@orpc/tanstack-query": "workspace:*"
4747
},
4848
"devDependencies": {
49-
"@tanstack/react-query": "^5.85.9",
49+
"@tanstack/react-query": "^5.89.0",
5050
"react": "^19.1.1",
5151
"zod": "^4.1.5"
5252
}

packages/react-query/src/procedure-utils.test-d.ts

Lines changed: 0 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -161,132 +161,6 @@ describe('ProcedureUtils', () => {
161161
})
162162
})
163163

164-
describe('.streamedOptions', () => {
165-
const utils = {} as ProcedureUtils<{ batch?: boolean }, UtilsInput, AsyncIterable<UtilsOutput[number]>, ErrorFromErrorMap<typeof baseErrorMap>>
166-
167-
it('can optional options', () => {
168-
const requiredUtils = {} as ProcedureUtils<{ batch: boolean }, 'input', UtilsOutput, Error>
169-
170-
utils.experimental_streamedOptions()
171-
utils.experimental_streamedOptions({ context: { batch: true } })
172-
utils.experimental_streamedOptions({ input: { search: 'search' } })
173-
utils.experimental_streamedOptions({ input: condition ? skipToken : { search: 'search' } })
174-
175-
requiredUtils.experimental_streamedOptions({
176-
context: { batch: true },
177-
input: 'input',
178-
})
179-
requiredUtils.experimental_streamedOptions({
180-
context: { batch: true },
181-
input: condition ? skipToken : 'input',
182-
})
183-
// @ts-expect-error input and context is required
184-
requiredUtils.experimental_streamedOptions()
185-
// @ts-expect-error input and context is required
186-
requiredUtils.experimental_streamedOptions({})
187-
// @ts-expect-error input is required
188-
requiredUtils.experimental_streamedOptions({ context: { batch: true } })
189-
// @ts-expect-error context is required
190-
requiredUtils.experimental_streamedOptions({ input: 'input' })
191-
// @ts-expect-error context is required
192-
requiredUtils.experimental_streamedOptions({ input: condition ? skipToken : 'input' })
193-
})
194-
195-
it('infer correct input type', () => {
196-
utils.experimental_streamedOptions({ input: { cursor: 1 }, context: { batch: true } })
197-
utils.experimental_streamedOptions({ input: condition ? { cursor: 2 } : skipToken, context: { batch: true } })
198-
// @ts-expect-error invalid input
199-
utils.experimental_streamedOptions({ input: { cursor: 'invalid' }, context: { batch: true } })
200-
// @ts-expect-error invalid input
201-
utils.experimental_streamedOptions({ input: condition ? { cursor: 'invalid' } : skipToken, context: { batch: true } })
202-
})
203-
204-
it('infer correct context type', () => {
205-
utils.experimental_streamedOptions({ context: { batch: true } })
206-
// @ts-expect-error invalid context
207-
utils.experimental_streamedOptions({ context: { batch: 'invalid' } })
208-
})
209-
210-
it('not usable in non event iterator output', () => {
211-
const utils = {} as ProcedureUtils<{ batch?: boolean }, UtilsInput, UtilsOutput, ErrorFromErrorMap<typeof baseErrorMap>>
212-
const query = useQuery(utils.experimental_streamedOptions())
213-
expectTypeOf(query.data).toExtend<undefined>()
214-
})
215-
216-
describe('works with useQuery', () => {
217-
it('without initial data', () => {
218-
const query = useQuery(utils.experimental_streamedOptions({
219-
select: data => ({ mapped: data }),
220-
throwOnError(error) {
221-
expectTypeOf(error).toEqualTypeOf<ErrorFromErrorMap<typeof baseErrorMap>>()
222-
return false
223-
},
224-
}))
225-
226-
expectTypeOf(query.data).toEqualTypeOf<{ mapped: UtilsOutput } | undefined>()
227-
expectTypeOf(query.error).toEqualTypeOf<ErrorFromErrorMap<typeof baseErrorMap> | null>()
228-
})
229-
230-
it('with initial data', () => {
231-
const query = useQuery(utils.experimental_streamedOptions({
232-
select: data => ({ mapped: data }),
233-
initialData: [{ title: 'title' }],
234-
}))
235-
236-
expectTypeOf(query.data).toEqualTypeOf<{ mapped: UtilsOutput }>()
237-
expectTypeOf(query.error).toEqualTypeOf<ErrorFromErrorMap<typeof baseErrorMap> | null>()
238-
})
239-
})
240-
241-
it('works with useQueries', async () => {
242-
const queries = useQueries({
243-
queries: [
244-
utils.experimental_streamedOptions({
245-
select: data => ({ mapped: data }),
246-
}),
247-
utils.experimental_streamedOptions({
248-
input: { search: 'search' },
249-
context: { batch: true },
250-
}),
251-
],
252-
})
253-
254-
expectTypeOf(queries[0].data).toEqualTypeOf<{ mapped: UtilsOutput } | undefined>()
255-
expectTypeOf(queries[1].data).toEqualTypeOf<UtilsOutput | undefined>()
256-
257-
expectTypeOf(queries[0].error).toEqualTypeOf<null | ErrorFromErrorMap<typeof baseErrorMap>>()
258-
expectTypeOf(queries[1].error).toEqualTypeOf<null | ErrorFromErrorMap<typeof baseErrorMap>>()
259-
})
260-
261-
it('works with useSuspenseQueries', () => {
262-
const queries = useSuspenseQueries({
263-
queries: [
264-
utils.experimental_streamedOptions({
265-
select: data => ({ mapped: data }),
266-
}),
267-
utils.experimental_streamedOptions({
268-
input: { search: 'search' },
269-
context: { batch: true },
270-
}),
271-
],
272-
})
273-
274-
expectTypeOf(queries[0].data).toEqualTypeOf<{ mapped: UtilsOutput }>()
275-
expectTypeOf(queries[1].data).toEqualTypeOf<UtilsOutput>()
276-
277-
expectTypeOf(queries[0].error).toEqualTypeOf<null | ErrorFromErrorMap<typeof baseErrorMap>>()
278-
expectTypeOf(queries[1].error).toEqualTypeOf<null | ErrorFromErrorMap<typeof baseErrorMap>>()
279-
})
280-
281-
it('works with fetchQuery', () => {
282-
expectTypeOf(
283-
queryClient.fetchQuery(utils.experimental_streamedOptions()),
284-
).toEqualTypeOf<
285-
Promise<UtilsOutput>
286-
>()
287-
})
288-
})
289-
290164
describe('.infiniteOptions', () => {
291165
const getNextPageParam = {} as GetNextPageParamFunction<number, UtilsOutput>
292166
const initialPageParam = 1

packages/react-query/src/procedure-utils.test.ts

Lines changed: 2 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as TanstackQueryModule from '@orpc/tanstack-query'
2-
import { skipToken, experimental_streamedQuery as streamedQuery } from '@tanstack/react-query'
2+
import { skipToken } from '@tanstack/react-query'
33
import { queryClient } from '../tests/shared'
44
import { createProcedureUtils } from './procedure-utils'
55

@@ -59,68 +59,6 @@ describe('createProcedureUtils', () => {
5959
})
6060
})
6161

62-
describe('.streamedOptions', () => {
63-
it('without skipToken', async () => {
64-
client.mockImplementationOnce(async function* (input) {
65-
yield '__1__'
66-
yield '__2__'
67-
return '__3__'
68-
})
69-
70-
const options = utils.experimental_streamedOptions({
71-
input: { search: '__search__' },
72-
context: { batch: '__batch__' },
73-
queryFnOptions: { refetchMode: 'replace' },
74-
})
75-
76-
expect('enabled' in options).toBe(false)
77-
expect(options.enabled).toBe(undefined)
78-
79-
expect(options.queryKey).toBe(generateOperationKeySpy.mock.results[0]!.value)
80-
expect(generateOperationKeySpy).toHaveBeenCalledTimes(1)
81-
expect(generateOperationKeySpy).toHaveBeenCalledWith(['ping'], {
82-
type: 'streamed',
83-
input: { search: '__search__' },
84-
fnOptions: { refetchMode: 'replace' },
85-
})
86-
87-
expect(options.queryFn).toBe(vi.mocked(streamedQuery).mock.results[0]!.value)
88-
expect(streamedQuery).toHaveBeenCalledTimes(1)
89-
expect(streamedQuery).toHaveBeenCalledWith({
90-
refetchMode: 'replace',
91-
queryFn: expect.any(Function),
92-
})
93-
94-
await expect(options.queryFn!({ signal, client: queryClient, queryKey: options.queryKey } as any)).resolves.toEqual(['__1__', '__2__'])
95-
expect(queryClient.getQueryData(options.queryKey)).toEqual(['__1__', '__2__'])
96-
97-
expect(client).toHaveBeenCalledTimes(1)
98-
expect(client).toBeCalledWith({ search: '__search__' }, { signal, context: { batch: '__batch__' } })
99-
})
100-
101-
it('with skipToken', async () => {
102-
const options = utils.experimental_streamedOptions({ input: skipToken, context: { batch: '__batch__' } })
103-
104-
expect(options.enabled).toBe(false)
105-
106-
expect(options.queryKey).toBe(generateOperationKeySpy.mock.results[0]!.value)
107-
expect(generateOperationKeySpy).toHaveBeenCalledTimes(1)
108-
expect(generateOperationKeySpy).toHaveBeenCalledWith(['ping'], { type: 'streamed', input: skipToken })
109-
110-
await expect(options.queryFn!({ signal, client: queryClient } as any)).rejects.toThrow('queryFn should not be called with skipToken used as input')
111-
expect(client).toHaveBeenCalledTimes(0)
112-
})
113-
114-
it('with unsupported output', async () => {
115-
client.mockResolvedValueOnce('__1__')
116-
const options = utils.experimental_streamedOptions({ input: { search: '__search__' }, context: { batch: '__batch__' } })
117-
118-
await expect(options.queryFn!({ signal, client: queryClient } as any)).rejects.toThrow('streamedQuery requires an event iterator output')
119-
expect(client).toHaveBeenCalledTimes(1)
120-
expect(client).toBeCalledWith({ search: '__search__' }, { signal, context: { batch: '__batch__' } })
121-
})
122-
})
123-
12462
describe('.infiniteOptions', () => {
12563
it('without skipToken', async () => {
12664
const getNextPageParam = vi.fn()
@@ -180,7 +118,7 @@ describe('createProcedureUtils', () => {
180118
expect(generateOperationKeySpy).toHaveBeenCalledTimes(1)
181119
expect(generateOperationKeySpy).toHaveBeenCalledWith(['ping'], { type: 'mutation' })
182120

183-
await expect(options.mutationFn!('__input__')).resolves.toEqual('__output__')
121+
await expect(options.mutationFn!('__input__', {} as any)).resolves.toEqual('__output__')
184122
expect(client).toHaveBeenCalledTimes(1)
185123
expect(client).toBeCalledWith('__input__', { context: { batch: '__batch__' } })
186124
})

packages/react-query/src/procedure-utils.ts

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,15 @@ import type { Client, ClientContext } from '@orpc/client'
22
import type { MaybeOptionalOptions } from '@orpc/shared'
33
import type { InfiniteData } from '@tanstack/react-query'
44
import type {
5-
experimental_InferStreamedOutput,
65
InfiniteOptionsBase,
76
InfiniteOptionsIn,
87
MutationOptions,
98
MutationOptionsIn,
109
QueryOptionsBase,
1110
QueryOptionsIn,
12-
experimental_StreamedOptionsBase as StreamedOptionsBase,
13-
experimental_StreamedOptionsIn as StreamedOptionsIn,
1411
} from './types'
15-
import { isAsyncIteratorObject } from '@orpc/shared'
1612
import { generateOperationKey } from '@orpc/tanstack-query'
17-
import { skipToken, experimental_streamedQuery as streamedQuery } from '@tanstack/react-query'
13+
import { skipToken } from '@tanstack/react-query'
1814

1915
export interface ProcedureUtils<TClientContext extends ClientContext, TInput, TOutput, TError> {
2016
/**
@@ -35,18 +31,6 @@ export interface ProcedureUtils<TClientContext extends ClientContext, TInput, TO
3531
>
3632
): NoInfer<U & Omit<QueryOptionsBase<TOutput, TError>, keyof U>>
3733

38-
/**
39-
* Generate [Event Iterator](https://orpc.unnoq.com/docs/event-iterator) options used for useQuery/useSuspenseQuery/prefetchQuery/...
40-
* Built on top of [steamedQuery](https://tanstack.com/query/latest/docs/reference/streamedQuery)
41-
*
42-
* @see {@link https://orpc.unnoq.com/docs/integrations/tanstack-query-old/basic#streamed-query-options-utility Tanstack Streamed Query Options Utility Docs}
43-
*/
44-
experimental_streamedOptions<U, USelectData = experimental_InferStreamedOutput<TOutput>>(
45-
...rest: MaybeOptionalOptions<
46-
U & StreamedOptionsIn<TClientContext, TInput, experimental_InferStreamedOutput<TOutput>, TError, USelectData>
47-
>
48-
): NoInfer<U & Omit<StreamedOptionsBase<experimental_InferStreamedOutput<TOutput>, TError>, keyof U>>
49-
5034
/**
5135
* Generate options used for useInfiniteQuery/useSuspenseInfiniteQuery/prefetchInfiniteQuery/...
5236
*
@@ -92,30 +76,6 @@ export function createProcedureUtils<TClientContext extends ClientContext, TInpu
9276
}
9377
},
9478

95-
experimental_streamedOptions(...[optionsIn = {} as any]) {
96-
return {
97-
queryKey: generateOperationKey(options.path, { type: 'streamed', input: optionsIn.input, fnOptions: optionsIn.queryFnOptions }),
98-
queryFn: streamedQuery({
99-
queryFn: async ({ signal }) => {
100-
if (optionsIn.input === skipToken) {
101-
throw new Error('queryFn should not be called with skipToken used as input')
102-
}
103-
104-
const output = await client(optionsIn.input, { signal, context: optionsIn.context })
105-
106-
if (!isAsyncIteratorObject(output)) {
107-
throw new Error('streamedQuery requires an event iterator output')
108-
}
109-
110-
return output
111-
},
112-
...optionsIn.queryFnOptions,
113-
}),
114-
...optionsIn.input === skipToken ? { enabled: false } : {},
115-
...optionsIn,
116-
}
117-
},
118-
11979
infiniteOptions(optionsIn) {
12080
return {
12181
queryKey: generateOperationKey(options.path, {

packages/react-query/src/types.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ClientContext } from '@orpc/client'
22
import type { SetOptional } from '@orpc/shared'
3-
import type { experimental_streamedQuery, QueryFunctionContext, QueryKey, SkipToken, UseInfiniteQueryOptions, UseMutationOptions, UseQueryOptions } from '@tanstack/react-query'
3+
import type { QueryFunctionContext, QueryKey, SkipToken, UseInfiniteQueryOptions, UseMutationOptions, UseQueryOptions } from '@tanstack/react-query'
44

55
export type QueryOptionsIn<TClientContext extends ClientContext, TInput, TOutput, TError, TSelectData>
66
= & (undefined extends TInput ? { input?: TInput | SkipToken } : { input: TInput | SkipToken })
@@ -15,17 +15,6 @@ export interface QueryOptionsBase<TOutput, TError> {
1515
enabled?: boolean
1616
}
1717

18-
type experimental_StreamedQueryOptions = Omit<Parameters<typeof experimental_streamedQuery>[0], 'queryFn'>
19-
20-
export type experimental_InferStreamedOutput<TOutput> = TOutput extends AsyncIterable<infer U> ? U[] : never
21-
22-
export type experimental_StreamedOptionsIn<TClientContext extends ClientContext, TInput, TOutput, TError, TSelectData>
23-
= & QueryOptionsIn<TClientContext, TInput, TOutput, TError, TSelectData>
24-
& { queryFnOptions?: experimental_StreamedQueryOptions }
25-
26-
export interface experimental_StreamedOptionsBase<TOutput, TError> extends QueryOptionsBase<TOutput, TError> {
27-
}
28-
2918
export type InfiniteOptionsIn<TClientContext extends ClientContext, TInput, TOutput, TError, TSelectData, TPageParam>
3019
= & { input: ((pageParam: TPageParam) => TInput) | SkipToken }
3120
& (Record<never, never> extends TClientContext ? { context?: TClientContext } : { context: TClientContext })

0 commit comments

Comments
 (0)