Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changepacks/changepack_log_2_T-K8HzrduvcHMnQArqi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"packages/react-query/package.json":"Patch"},"note":"Fix useQueries type","date":"2026-03-25T11:48:18.986238800Z"}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

.claude
.sisyphus
.omc
17 changes: 9 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"private": true,
"devDependencies": {
"@biomejs/biome": "^2.3",
"@devup-api/react-query": "workspace:*",
"@testing-library/react": "^16.3.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/bun": "latest",
Expand Down
213 changes: 213 additions & 0 deletions packages/react-query/src/__tests__/types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/**
* Type tests for DevupQueryClient
* Verify that useQueries type inference works correctly per element
*/
import { describe, expectTypeOf, test } from 'bun:test'
import type { DevupGetApiStruct } from '@devup-api/core'
import type { DevupQueryClient } from '../query-client'

// =============================================================================
// Test Fixtures
// =============================================================================

declare module '@devup-api/core' {
interface DevupApiServers {
'react-query-test.json': never
}

interface DevupGetApiStruct {
'react-query-test.json': {
'/users': {
response: { id: number; name: string }[]
error: { message: string }
}
'/users/{id}': {
params: { id: string }
response: { id: number; name: string; email: string }
error: { message: string; code: number }
}
'/posts': {
response: { id: number; title: string }[]
error: { message: string }
}
}
}

interface DevupPostApiStruct {
'react-query-test.json': {
'/users': {
body: { name: string; email: string }
response: { id: number }
error: { message: string }
}
}
}

interface DevupDeleteApiStruct {
'react-query-test.json': {
'/users/{id}': {
params: { id: string }
response: { success: boolean }
error: { message: string }
}
}
}
}

type QC = DevupQueryClient<'react-query-test.json'>

// =============================================================================
// useQueries - Per-element return type inference
// =============================================================================

describe('useQueries per-element type inference', () => {
test('different endpoints return different response types', () => {
type Result = ReturnType<
(
qc: QC,
) => ReturnType<
typeof qc.useQueries<[['get', '/users'], ['get', '/posts']]>
>
>

expectTypeOf<Result[0]['data']>().toEqualTypeOf<
{ id: number; name: string }[] | undefined
>()
expectTypeOf<Result[1]['data']>().toEqualTypeOf<
{ id: number; title: string }[] | undefined
>()
})

test('different endpoints return different error types', () => {
type Result = ReturnType<
(
qc: QC,
) => ReturnType<
typeof qc.useQueries<
[
['get', '/users/{id}', { params: { id: string } }],
['get', '/users'],
]
>
>
>

expectTypeOf<Result[0]['data']>().toEqualTypeOf<
{ id: number; name: string; email: string } | undefined
>()
expectTypeOf<Result[0]['error']>().toEqualTypeOf<{
message: string
code: number
} | null>()
expectTypeOf<Result[1]['data']>().toEqualTypeOf<
{ id: number; name: string }[] | undefined
>()
expectTypeOf<Result[1]['error']>().toEqualTypeOf<{
message: string
} | null>()
})

test('single element preserves exact type', () => {
type Result = ReturnType<
(qc: QC) => ReturnType<typeof qc.useQueries<[['get', '/posts']]>>
>

expectTypeOf<Result[0]['data']>().toEqualTypeOf<
{ id: number; title: string }[] | undefined
>()
})

test('result types are not intersected', () => {
type Result = ReturnType<
(
qc: QC,
) => ReturnType<
typeof qc.useQueries<[['get', '/users'], ['get', '/posts']]>
>
>

// result[0] should NOT have title (from /posts)
type Data0 = NonNullable<Result[0]['data']>[number]
expectTypeOf<
'title' extends keyof Data0 ? true : false
>().toEqualTypeOf<false>()

// result[1] should NOT have name (from /users)
type Data1 = NonNullable<Result[1]['data']>[number]
expectTypeOf<
'name' extends keyof Data1 ? true : false
>().toEqualTypeOf<false>()
})
})

// =============================================================================
// useQueries - params constraint
// =============================================================================

describe('useQueries params constraint', () => {
test('endpoint with params accepts params option', () => {
// This should be valid: correct params provided
type _Valid = ReturnType<
(
qc: QC,
) => ReturnType<
typeof qc.useQueries<
[['get', '/users/{id}', { params: { id: string } }]]
>
>
>
})

test('endpoint without params has optional options', () => {
// This should be valid: no options needed
type _Valid = ReturnType<
(qc: QC) => ReturnType<typeof qc.useQueries<[['get', '/users']]>>
>
})
})

// =============================================================================
// useQuery - single query type inference (baseline)
// =============================================================================

describe('useQuery type inference baseline', () => {
test('response type inferred from endpoint', () => {
type Result = ReturnType<
(
qc: QC,
) => ReturnType<
typeof qc.useQuery<
'get',
DevupGetApiStruct['react-query-test.json'],
'/users'
>
>
>

expectTypeOf<Result['data']>().toEqualTypeOf<
{ id: number; name: string }[] | undefined
>()
})

test('error type inferred from endpoint', () => {
type Result = ReturnType<
(
qc: QC,
) => ReturnType<
typeof qc.useQuery<
'get',
DevupGetApiStruct['react-query-test.json'],
'/users/{id}'
>
>
>

expectTypeOf<Result['data']>().toEqualTypeOf<
{ id: number; name: string; email: string } | undefined
>()
expectTypeOf<Result['error']>().toEqualTypeOf<{
message: string
code: number
} | null>()
})
})
Loading