Skip to content

Commit 77b8023

Browse files
committed
Fix query key issue
1 parent 92b27d7 commit 77b8023

File tree

7 files changed

+202
-116
lines changed

7 files changed

+202
-116
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"packages/react-query/package.json":"Patch","packages/fetch/package.json":"Patch"},"note":"Fix query-key issue","date":"2026-04-04T08:47:23.233333200Z"}

packages/fetch/src/__tests__/api.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,3 +539,37 @@ test('request uses method from options when provided', async () => {
539539
expect(request.method).toBe('POST')
540540
}
541541
})
542+
543+
test('resolveEndpoint returns key as url when no URL map entry exists', () => {
544+
const api = new DevupApi('https://api.example.com', undefined, 'openapi.json')
545+
const result = api.resolveEndpoint('GET' as any, '/users' as any)
546+
expect(result.url).toBe('/users')
547+
expect(result.method).toBe('GET')
548+
})
549+
550+
test('resolveEndpoint returns key as url for unknown operationId', () => {
551+
const api = new DevupApi('https://api.example.com', undefined, 'openapi.json')
552+
const result = api.resolveEndpoint('POST' as any, 'unknownOp' as any)
553+
expect(result.url).toBe('unknownOp')
554+
expect(result.method).toBe('POST')
555+
})
556+
557+
test('resolveEndpoint normalizes lowercase method to uppercase', () => {
558+
const api = new DevupApi('https://api.example.com', undefined, 'openapi.json')
559+
const result = api.resolveEndpoint('get' as any, '/users' as any)
560+
expect(result.method).toBe('GET')
561+
expect(result.url).toBe('/users')
562+
})
563+
564+
test.each([
565+
['GET', '/users'],
566+
['POST', '/users'],
567+
['PUT', '/users/{id}'],
568+
['DELETE', '/users/{id}'],
569+
['PATCH', '/users/{id}'],
570+
] as const)('resolveEndpoint preserves method %s for path %s', (method, path) => {
571+
const api = new DevupApi('https://api.example.com', undefined, 'openapi.json')
572+
const result = api.resolveEndpoint(method as any, path as any)
573+
expect(result.method).toBe(method)
574+
expect(result.url).toBe(path)
575+
})

packages/fetch/src/api.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
BoildApiOption,
55
ConditionalKeys,
66
DevupApiMethodKey,
7+
DevupApiMethodKeys,
78
DevupApiRequestInit,
89
DevupApiServers,
910
DevupApiStructKey,
@@ -14,6 +15,7 @@ import type {
1415
DevupPostApiStructScope,
1516
DevupPutApiStructScope,
1617
ExtractValue,
18+
HttpMethod,
1719
Middleware,
1820
} from '@devup-api/core'
1921
import { convertResponse } from './response-converter'
@@ -383,4 +385,15 @@ export class DevupApi<S extends ConditionalKeys<DevupApiServers>> {
383385
use(...middleware: Middleware[]): void {
384386
this.middleware.push(...middleware)
385387
}
388+
389+
resolveEndpoint<M extends DevupApiMethodKeys>(
390+
method: M,
391+
key: DevupApiMethodKey<S, M>,
392+
) {
393+
return getApiEndpointInfo(
394+
key,
395+
this.serverName,
396+
method.toUpperCase() as HttpMethod,
397+
)
398+
}
386399
}

packages/react-query/src/__tests__/index.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,5 @@ test('index.ts exports', () => {
77
expect({ ...indexModule }).toEqual({
88
createQueryClient: expect.any(Function),
99
DevupQueryClient: expect.any(Function),
10-
getQueryKey: expect.any(Function),
1110
})
1211
})
Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,153 @@
11
/** biome-ignore-all lint/suspicious/noExplicitAny: any is used to allow for flexibility in the type */
22
import { expect, test } from 'bun:test'
33
import { createApi } from '@devup-api/fetch'
4-
import { DevupQueryClient, getQueryKey } from '../query-client'
4+
import { DevupQueryClient } from '../query-client'
5+
6+
const api = createApi({ baseUrl: 'https://api.example.com' })
7+
const queryClient = new DevupQueryClient(api as any)
58

69
test('DevupQueryClient constructor', () => {
7-
const api = createApi({ baseUrl: 'https://api.example.com' })
8-
const queryClient = new DevupQueryClient(api)
910
expect(queryClient).toBeInstanceOf(DevupQueryClient)
1011
})
1112

1213
test('DevupQueryClient useQuery method exists', () => {
13-
const api = createApi({ baseUrl: 'https://api.example.com' })
14-
const queryClient = new DevupQueryClient(api)
1514
expect(typeof queryClient.useQuery).toBe('function')
1615
})
1716

1817
test('DevupQueryClient useMutation method exists', () => {
19-
const api = createApi({ baseUrl: 'https://api.example.com' })
20-
const queryClient = new DevupQueryClient(api)
2118
expect(typeof queryClient.useMutation).toBe('function')
2219
})
2320

2421
test('DevupQueryClient useSuspenseQuery method exists', () => {
25-
const api = createApi({ baseUrl: 'https://api.example.com' })
26-
const queryClient = new DevupQueryClient(api)
2722
expect(typeof queryClient.useSuspenseQuery).toBe('function')
2823
})
2924

3025
test('DevupQueryClient useInfiniteQuery method exists', () => {
31-
const api = createApi({ baseUrl: 'https://api.example.com' })
32-
const queryClient = new DevupQueryClient(api)
3326
expect(typeof queryClient.useInfiniteQuery).toBe('function')
3427
})
3528

3629
test('DevupQueryClient useQueries method exists', () => {
37-
const api = createApi({ baseUrl: 'https://api.example.com' })
38-
const queryClient = new DevupQueryClient(api)
3930
expect(typeof queryClient.useQueries).toBe('function')
4031
})
4132

4233
test('getQueryKey returns correct key without options', () => {
43-
const result = getQueryKey('get', '/test', undefined)
34+
const result = queryClient.getQueryKey('get' as any, '/test' as any)
4435
expect(result).toEqual(['get', '/test'])
4536
})
4637

4738
test('getQueryKey returns correct key with options', () => {
4839
const options = { params: { id: '123' } }
49-
const result = getQueryKey('get', '/test', options)
40+
const result = queryClient.getQueryKey(
41+
'get' as any,
42+
'/test' as any,
43+
options as any,
44+
)
5045
expect(result).toEqual(['get', '/test', options])
5146
})
5247

5348
test('getQueryKey handles different methods', () => {
5449
const methods = ['get', 'post', 'put', 'delete', 'patch'] as const
5550
for (const method of methods) {
56-
const result = getQueryKey(method, '/test', undefined)
51+
const result = queryClient.getQueryKey(method as any, '/test' as any)
5752
expect(result).toEqual([method, '/test'])
5853
}
5954
})
6055

6156
test('getQueryKey handles different paths', () => {
6257
const paths = ['/test', '/users', '/users/{id}'] as const
6358
for (const path of paths) {
64-
const result = getQueryKey('get', path, undefined)
59+
const result = queryClient.getQueryKey('get' as any, path as any)
6560
expect(result).toEqual(['get', path])
6661
}
6762
})
6863

6964
test('getQueryKey handles different option types', () => {
7065
const options1 = { params: { id: '123' } }
71-
const result1 = getQueryKey('get', '/test', options1)
66+
const result1 = queryClient.getQueryKey(
67+
'get' as any,
68+
'/test' as any,
69+
options1 as any,
70+
)
7271
expect(result1).toEqual(['get', '/test', options1])
7372

7473
const options2 = { query: { page: 1 } }
75-
const result2 = getQueryKey('get', '/test', options2)
74+
const result2 = queryClient.getQueryKey(
75+
'get' as any,
76+
'/test' as any,
77+
options2 as any,
78+
)
7679
expect(result2).toEqual(['get', '/test', options2])
7780

7881
const options3 = { params: { id: '123' }, query: { page: 1 } }
79-
const result3 = getQueryKey('get', '/test', options3)
82+
const result3 = queryClient.getQueryKey(
83+
'get' as any,
84+
'/test' as any,
85+
options3 as any,
86+
)
8087
expect(result3).toEqual(['get', '/test', options3])
8188
})
89+
90+
test('getQueryKey normalizes method to lowercase', () => {
91+
const upper = queryClient.getQueryKey('GET' as any, '/test' as any)
92+
const lower = queryClient.getQueryKey('get' as any, '/test' as any)
93+
expect(upper).toEqual(lower)
94+
expect(upper).toEqual(['get', '/test'])
95+
})
96+
97+
test('getQueryKey normalizes method to lowercase with options', () => {
98+
const options = { params: { id: '123' } }
99+
const upper = queryClient.getQueryKey(
100+
'POST' as any,
101+
'/test' as any,
102+
options as any,
103+
)
104+
const lower = queryClient.getQueryKey(
105+
'post' as any,
106+
'/test' as any,
107+
options as any,
108+
)
109+
expect(upper).toEqual(lower)
110+
expect(upper).toEqual(['post', '/test', options])
111+
})
112+
113+
test('getQueryKey normalizes all HTTP methods', () => {
114+
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const
115+
for (const method of methods) {
116+
const result = queryClient.getQueryKey(method as any, '/test' as any)
117+
expect(result[0]).toBe(method.toLowerCase())
118+
}
119+
})
120+
121+
test('resolveEndpoint falls back to key itself when no URL map entry', () => {
122+
const resolved = api.resolveEndpoint('GET' as any, '/users' as any)
123+
expect(resolved.url).toBe('/users')
124+
expect(resolved.method).toBe('GET')
125+
})
126+
127+
test('resolveEndpoint returns key as url for unknown operationId', () => {
128+
const resolved = api.resolveEndpoint('GET' as any, 'unknownOp' as any)
129+
expect(resolved.url).toBe('unknownOp')
130+
})
131+
132+
test('getQueryKey produces consistent keys for same resolved URL', () => {
133+
const resolvedUrl = api.resolveEndpoint('GET' as any, '/users' as any).url
134+
const key1 = queryClient.getQueryKey('GET' as any, resolvedUrl as any)
135+
const key2 = queryClient.getQueryKey('get' as any, '/users' as any)
136+
expect(key1).toEqual(key2)
137+
expect(key1).toEqual(['get', '/users'])
138+
})
139+
140+
test('getQueryKey produces identical keys when resolved URL matches path', () => {
141+
const key1 = queryClient.getQueryKey(
142+
'GET' as any,
143+
'/users' as any,
144+
{ query: { page: 1 } } as any,
145+
)
146+
const key2 = queryClient.getQueryKey(
147+
'get' as any,
148+
'/users' as any,
149+
{ query: { page: 1 } } as any,
150+
)
151+
expect(key1).toEqual(key2)
152+
expect(key1).toEqual(['get', '/users', { query: { page: 1 } }])
153+
})

packages/react-query/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { createQueryClient } from './create-query-client'
2-
export { DevupQueryClient, getQueryKey } from './query-client'
2+
export { DevupQueryClient } from './query-client'

0 commit comments

Comments
 (0)