Skip to content

Commit

Permalink
sync @orpc/react-query
Browse files Browse the repository at this point in the history
  • Loading branch information
unnoq committed Dec 29, 2024
1 parent 6e2e386 commit bad022f
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 48 deletions.
14 changes: 11 additions & 3 deletions packages/react-query/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,23 @@ import type {

export type InferCursor<T> = T extends { cursor?: any } ? T['cursor'] : never

export type QueryOptions<TInput, TOutput, TSelectData> = (undefined extends TInput ? { input?: TInput } : { input: TInput }) & (
export type QueryOptions<TInput, TOutput, TClientContext, TSelectData> =
& (undefined extends TInput ? { input?: TInput } : { input: TInput })
& (undefined extends TClientContext ? { context?: TClientContext } : { context: TClientContext })
& (
SetOptional<UndefinedInitialDataOptions<TOutput, DefaultError, TSelectData, QueryKey>, 'queryKey'>
)

export type InfiniteOptions<TInput, TOutput, TSelectData> = (undefined extends TInput ? { input?: Omit<TInput, 'cursor'> } : { input: Omit<TInput, 'cursor'> }) & (
export type InfiniteOptions<TInput, TOutput, TClientContext, TSelectData> =
& (undefined extends TInput ? { input?: Omit<TInput, 'cursor'> } : { input: Omit<TInput, 'cursor'> })
& (undefined extends TClientContext ? { context?: TClientContext } : { context: TClientContext })
& (
SetOptional<
UndefinedInitialDataInfiniteOptions<TOutput, DefaultError, TSelectData, QueryKey, InferCursor<TInput>>,
'queryKey' | (undefined extends InferCursor<TInput> ? 'initialPageParam' : never)
>
)

export type MutationOptions<TInput, TOutput> = UseMutationOptions<TOutput, DefaultError, TInput>
export type MutationOptions<TInput, TOutput, TClientContext> =
& (undefined extends TClientContext ? { context?: TClientContext } : { context: TClientContext })
& UseMutationOptions<TOutput, DefaultError, TInput>
101 changes: 88 additions & 13 deletions packages/react-query/src/utils-procedure.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type { ProcedureClient } from '@orpc/server'
import type { InfiniteData, QueryKey } from '@tanstack/react-query'
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'
import type { ProcedureUtils } from './utils-procedure'
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
import { createProcedureUtils } from './utils-procedure'

describe('queryOptions', () => {
const client = vi.fn<ProcedureClient<number | undefined, string | undefined>>(
const client = vi.fn<ProcedureClient<number | undefined, string | undefined, unknown>>(
(...[input]) => Promise.resolve(input?.toString()),
)
const utils = createProcedureUtils(client, [])

const client2 = vi.fn((input: number) => Promise.resolve(input.toString()))
const client2 = {} as ProcedureClient<number, string, unknown>
const utils2 = createProcedureUtils(client2, [])

it('infer correct input type', () => {
Expand Down Expand Up @@ -51,13 +52,36 @@ describe('queryOptions', () => {
expectTypeOf(query.data).toEqualTypeOf<12344>()
}
})

describe('client context', () => {
it('can be optional', () => {
const utils = {} as ProcedureUtils<undefined, string, undefined | { batch?: boolean }>
useQuery(utils.queryOptions())
useQuery(utils.queryOptions({}))
useQuery(utils.queryOptions({ context: undefined }))
useQuery(utils.queryOptions({ context: { batch: true } }))
// @ts-expect-error --- invalid context
useQuery(utils.queryOptions({ context: { batch: 'invalid' } }))
})

it('required pass context when non-optional', () => {
const utils = {} as ProcedureUtils<undefined, string, { batch?: boolean }>
// @ts-expect-error --- missing context
useQuery(utils.queryOptions())
// @ts-expect-error --- missing context
useQuery(utils.queryOptions({}))
useQuery(utils.queryOptions({ context: { batch: true } }))
// @ts-expect-error --- invalid context
useQuery(utils.queryOptions({ context: { batch: 123 } }))
})
})
})

describe('infiniteOptions', () => {
const getNextPageParam = vi.fn()

it('cannot use on procedure without input object-able', () => {
const utils = createProcedureUtils({} as (input: number) => Promise<string>, [])
const utils = createProcedureUtils({} as ProcedureClient<number, string, unknown>, [])

// @ts-expect-error missing initialPageParam
utils.infiniteOptions({
Expand All @@ -80,7 +104,7 @@ describe('infiniteOptions', () => {
})

it('infer correct input type', () => {
const utils = createProcedureUtils({} as (input: { limit?: number, cursor: number }) => Promise<string>, [])
const utils = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor: number }, string, unknown>, [])

utils.infiniteOptions({
input: {
Expand Down Expand Up @@ -111,7 +135,7 @@ describe('infiniteOptions', () => {
})

it('infer correct initialPageParam type', () => {
const utils = createProcedureUtils({} as (input: { limit?: number, cursor: number }) => Promise<string>, [])
const utils = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor: number }, string, unknown>, [])

utils.infiniteOptions({
input: {},
Expand All @@ -134,14 +158,14 @@ describe('infiniteOptions', () => {
})

it('initialPageParam can be optional', () => {
const utils = createProcedureUtils({} as (input: { limit?: number, cursor?: number }) => Promise<string>, [])
const utils = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor?: number }, string, unknown>, [])

utils.infiniteOptions({
input: {},
getNextPageParam,
})

const utils2 = createProcedureUtils({} as (input: { limit?: number, cursor: number }) => Promise<string>, [])
const utils2 = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor: number }, string, unknown>, [])

// @ts-expect-error initialPageParam is required
utils2.infiniteOptions({
Expand All @@ -151,13 +175,13 @@ describe('infiniteOptions', () => {
})

it('input can be optional', () => {
const utils = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor?: number } | undefined, string>, [])
const utils = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor?: number } | undefined, string, unknown>, [])

utils.infiniteOptions({
getNextPageParam,
})

const utils2 = createProcedureUtils({} as (input: { limit?: number, cursor?: number }) => Promise<string>, [])
const utils2 = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor?: number }, string, unknown>, [])

// @ts-expect-error input is required
utils2.infiniteOptions({
Expand All @@ -166,7 +190,7 @@ describe('infiniteOptions', () => {
})

it('infer correct output type', () => {
const utils = createProcedureUtils({} as (input: { limit?: number, cursor: number }) => Promise<string>, [])
const utils = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor: number }, string, unknown>, [])
const query = useInfiniteQuery(utils.infiniteOptions({
input: {
limit: 1,
Expand All @@ -181,7 +205,7 @@ describe('infiniteOptions', () => {
})

it('work with select options', () => {
const utils = createProcedureUtils({} as (input: { limit?: number, cursor: number }) => Promise<string>, [])
const utils = createProcedureUtils({} as ProcedureClient<{ limit?: number, cursor: number }, string, unknown>, [])
const query = useInfiniteQuery(utils.infiniteOptions({
input: {
limit: 1,
Expand All @@ -199,10 +223,38 @@ describe('infiniteOptions', () => {
expectTypeOf(query.data).toEqualTypeOf<{ value: string }>()
}
})

describe('client context', () => {
it('can be optional', () => {
const utils = {} as ProcedureUtils<undefined | { limit?: number, cursor: number }, string, undefined | { batch?: boolean }>

const getNextPageParam = vi.fn()
const initialPageParam = 1

useInfiniteQuery(utils.infiniteOptions({ getNextPageParam, initialPageParam }))
useInfiniteQuery(utils.infiniteOptions({ getNextPageParam, initialPageParam, context: undefined }))
useInfiniteQuery(utils.infiniteOptions({ getNextPageParam, initialPageParam, context: { batch: true } }))
// @ts-expect-error --- invalid context
useInfiniteQuery(utils.infiniteOptions({ getNextPageParam, initialPageParam, context: { batch: 'invalid' } }))
})

it('required pass context when non-optional', () => {
const utils = {} as ProcedureUtils<undefined | { limit?: number, cursor: number }, string, { batch?: boolean }>

const getNextPageParam = vi.fn()
const initialPageParam = 1

// @ts-expect-error --- missing context
useInfiniteQuery(utils.infiniteOptions({ getNextPageParam, initialPageParam }))
useInfiniteQuery(utils.infiniteOptions({ getNextPageParam, initialPageParam, context: { batch: true } }))
// @ts-expect-error --- invalid context
useInfiniteQuery(utils.infiniteOptions({ getNextPageParam, initialPageParam, context: { batch: 'invalid' } }))
})
})
})

describe('mutationOptions', () => {
const client = vi.fn((input: number) => Promise.resolve(input.toString()))
const client = {} as ProcedureClient<number, string, unknown>
const utils = createProcedureUtils(client, [])

it('infer correct input type', () => {
Expand Down Expand Up @@ -237,4 +289,27 @@ describe('mutationOptions', () => {
expectTypeOf(option.mutationKey).toEqualTypeOf<QueryKey>()
expectTypeOf(option.mutationFn).toEqualTypeOf<(input: number) => Promise<string>>()
})

describe('client context', () => {
it('can be optional', () => {
const utils = {} as ProcedureUtils<undefined, string, undefined | { batch?: boolean }>
useMutation(utils.mutationOptions())
useMutation(utils.mutationOptions({}))
useMutation(utils.mutationOptions({ context: undefined }))
useMutation(utils.mutationOptions({ context: { batch: true } }))
// @ts-expect-error --- invalid context
useMutation(utils.mutationOptions({ context: { batch: 'invalid' } }))
})

it('required pass context when non-optional', () => {
const utils = {} as ProcedureUtils<undefined, string, { batch?: boolean }>
// @ts-expect-error --- missing context
useMutation(utils.mutationOptions())
// @ts-expect-error --- missing context
useMutation(utils.mutationOptions({}))
useMutation(utils.mutationOptions({ context: { batch: true } }))
// @ts-expect-error --- invalid context
useMutation(utils.mutationOptions({ context: { batch: 123 } }))
})
})
})
63 changes: 58 additions & 5 deletions packages/react-query/src/utils-procedure.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { ProcedureClient } from '@orpc/server'
import * as keyModule from './key'
import { createProcedureUtils } from './utils-procedure'

Expand All @@ -12,7 +11,7 @@ beforeEach(() => {
})

describe('queryOptions', () => {
const client = vi.fn<ProcedureClient<number | undefined, string | undefined>>((...[input]) => Promise.resolve(input?.toString()))
const client = vi.fn((...[input]) => Promise.resolve(input?.toString()))
const utils = createProcedureUtils(client, ['ping'])

it('works', async () => {
Expand All @@ -27,13 +26,29 @@ describe('queryOptions', () => {
expect(client).toHaveBeenCalledTimes(1)
expect(client).toBeCalledWith(1, { signal })
})

it('works with client context', async () => {
const client = vi.fn((...[input]) => Promise.resolve(input?.toString()))
const utils = createProcedureUtils(client, ['ping'])

const options = utils.queryOptions({ context: { batch: true } })

expect(options.queryKey).toEqual(['__ORPC__', ['ping'], { type: 'query' }])
expect(buildKeySpy).toHaveBeenCalledTimes(1)
expect(buildKeySpy).toHaveBeenCalledWith(['ping'], { type: 'query' })

client.mockResolvedValueOnce('__mocked__')
await expect((options as any).queryFn({ signal })).resolves.toEqual('__mocked__')
expect(client).toHaveBeenCalledTimes(1)
expect(client).toBeCalledWith(undefined, { signal, context: { batch: true } })
})
})

describe('infiniteOptions', () => {
const getNextPageParam = vi.fn()

it('works ', async () => {
const client = vi.fn<(input: { limit?: number, cursor: number | undefined }) => Promise<string>>()
const client = vi.fn()
const utils = createProcedureUtils(client, [])

const options = utils.infiniteOptions({
Expand All @@ -54,7 +69,7 @@ describe('infiniteOptions', () => {
})

it('works without initialPageParam', async () => {
const client = vi.fn<(input: { limit?: number, cursor: number | undefined }) => Promise<string>>()
const client = vi.fn()
const utils = createProcedureUtils(client, [])

const options = utils.infiniteOptions({
Expand All @@ -71,11 +86,31 @@ describe('infiniteOptions', () => {
expect(client).toHaveBeenCalledTimes(1)
expect(client).toBeCalledWith({ limit: 5, cursor: undefined }, { signal })
})

it('works with client context', async () => {
const client = vi.fn()
const utils = createProcedureUtils(client, [])

const options = utils.infiniteOptions({
context: { batch: true },
getNextPageParam,
initialPageParam: 1,
})

expect(options.queryKey).toEqual(['__ORPC__', [], { type: 'infinite' }])
expect(buildKeySpy).toHaveBeenCalledTimes(1)
expect(buildKeySpy).toHaveBeenCalledWith([], { type: 'infinite' })

client.mockResolvedValueOnce('__mocked__')
await expect((options as any).queryFn({ pageParam: 1, signal })).resolves.toEqual('__mocked__')
expect(client).toHaveBeenCalledTimes(1)
expect(client).toBeCalledWith({ limit: undefined, cursor: 1 }, { signal, context: { batch: true } })
})
})

describe('mutationOptions', () => {
it('works', async () => {
const client = vi.fn<ProcedureClient<number | undefined, string | undefined>>(
const client = vi.fn(
(...[input]) => Promise.resolve(input?.toString()),
)
const utils = createProcedureUtils(client, ['ping'])
Expand All @@ -91,4 +126,22 @@ describe('mutationOptions', () => {
expect(client).toHaveBeenCalledTimes(1)
expect(client).toBeCalledWith(1)
})

it('works with client context', async () => {
const client = vi.fn(
(...[input]) => Promise.resolve(input?.toString()),
)
const utils = createProcedureUtils(client, ['ping'])

const options = utils.mutationOptions({ context: { batch: true } })

expect(options.mutationKey).toEqual(['__ORPC__', ['ping'], { type: 'mutation' }])
expect(buildKeySpy).toHaveBeenCalledTimes(1)
expect(buildKeySpy).toHaveBeenCalledWith(['ping'], { type: 'mutation' })

client.mockResolvedValueOnce('__mocked__')
await expect(options.mutationFn(1)).resolves.toEqual('__mocked__')
expect(client).toHaveBeenCalledTimes(1)
expect(client).toBeCalledWith(1, { context: { batch: true } })
})
})
30 changes: 15 additions & 15 deletions packages/react-query/src/utils-procedure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,35 @@ import { buildKey } from './key'
/**
* Utils at procedure level
*/
export interface ProcedureUtils<TInput, TOutput> {
queryOptions: <U extends QueryOptions<TInput, TOutput, any>>(
...opts: [options: U] | (undefined extends TInput ? [] : never)
) => IsEqual<U, QueryOptions<TInput, TOutput, any>> extends true
export interface ProcedureUtils<TInput, TOutput, TClientContext> {
queryOptions: <U extends QueryOptions<TInput, TOutput, TClientContext, any>>(
...opts: [options: U] | (undefined extends TInput & TClientContext ? [] : never)
) => IsEqual<U, QueryOptions<TInput, TOutput, TClientContext, any>> extends true
? { queryKey: QueryKey, queryFn: () => Promise<TOutput> }
: Omit<{ queryKey: QueryKey, queryFn: () => Promise<TOutput> }, keyof U> & U

infiniteOptions: <U extends InfiniteOptions<TInput, TOutput, any>>(
infiniteOptions: <U extends InfiniteOptions<TInput, TOutput, TClientContext, any>>(
options: U
) => Omit<{ queryKey: QueryKey, queryFn: () => Promise<TOutput>, initialPageParam: undefined }, keyof U> & U

mutationOptions: <U extends MutationOptions<TInput, TOutput>>(
options?: U
) => IsEqual<U, MutationOptions<TInput, TOutput>> extends true
mutationOptions: <U extends MutationOptions<TInput, TOutput, TClientContext>>(
...opt: [options: U] | (undefined extends TClientContext ? [] : never)
) => IsEqual<U, MutationOptions<TInput, TOutput, TClientContext>> extends true
? { mutationKey: QueryKey, mutationFn: (input: TInput) => Promise<TOutput> }
: Omit<{ mutationKey: QueryKey, mutationFn: (input: TInput) => Promise<TOutput> }, keyof U> & U
}

export function createProcedureUtils<TInput, TOutput>(
client: ProcedureClient<TInput, TOutput>,
export function createProcedureUtils<TInput, TOutput, TClientContext>(
client: ProcedureClient<TInput, TOutput, TClientContext>,
path: string[],
): ProcedureUtils<TInput, TOutput> {
): ProcedureUtils<TInput, TOutput, TClientContext> {
return {
queryOptions(...[options]) {
const input = options?.input as any

return {
queryKey: buildKey(path, { type: 'query', input }),
queryFn: ({ signal }) => client(input, { signal }),
queryFn: ({ signal }) => client(input, { signal, context: options?.context } as any),
...(options as any),
}
},
Expand All @@ -45,15 +45,15 @@ export function createProcedureUtils<TInput, TOutput>(

return {
queryKey: buildKey(path, { type: 'infinite', input }),
queryFn: ({ pageParam, signal }) => client({ ...input, cursor: pageParam }, { signal }),
queryFn: ({ pageParam, signal }) => client({ ...input, cursor: pageParam }, { signal, context: options.context } as any),
...(options as any),
}
},

mutationOptions(options) {
mutationOptions(...[options]) {
return {
mutationKey: buildKey(path, { type: 'mutation' }),
mutationFn: input => client(input),
mutationFn: input => client(input, { context: options?.context } as any),
...(options as any),
}
},
Expand Down
Loading

0 comments on commit bad022f

Please sign in to comment.