Skip to content

Commit 1491c24

Browse files
authored
Merge pull request #7 from softnetics/supakorn/feat/throw-on-error-option
feat: `throwOnError` options and test cases.
2 parents 9b99770 + a1e4c9b commit 1491c24

5 files changed

Lines changed: 128 additions & 15 deletions

File tree

.changeset/silly-tips-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@softnetics/hono-react-query": patch
3+
---
4+
5+
add: `throwOnError` options and test cases.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"format:check": "prettier --check .",
2424
"typecheck": "tsc --noEmit",
2525
"test": "vitest run",
26+
"test:watch": "vitest run --watch",
2627
"build": "tsup"
2728
},
2829
"peerDependencies": {

src/index.spec.tsx

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query'
1+
import {
2+
type DefinedInitialDataOptions,
3+
type UseMutationResult,
4+
type UseQueryResult,
5+
} from '@tanstack/react-query'
26
import { Hono } from 'hono'
37
import { describe, expect, expectTypeOf, it } from 'vitest'
48

@@ -12,6 +16,7 @@ const basicHonoApp = new Hono()
1216
if (!c.req.param('id')) {
1317
return c.json({ error: 'User ID is required' }, 400)
1418
}
19+
1520
return c.json({ user: { id: 'id', name: 'John Doe' } }, 200)
1621
})
1722
.post('/users', async (c) => {
@@ -40,6 +45,86 @@ describe('createReactQueryClient', () => {
4045
expect(client.useOptimisticUpdateQuery).toBeDefined()
4146
})
4247

48+
it('Should contain Error in Data', () => {
49+
const client = createReactQueryClient<BasicHonoApp>({
50+
baseUrl: 'http://localhost:3000',
51+
})
52+
53+
const queryOptions = client.queryOptions('/users/:id', '$get', {
54+
input: {
55+
param: { id: 'none' },
56+
},
57+
options: {
58+
throwOnError: false,
59+
},
60+
})
61+
62+
type DataAndError =
63+
typeof queryOptions extends DefinedInitialDataOptions<infer TDataAndError>
64+
? TDataAndError
65+
: never
66+
67+
expectTypeOf<DataAndError>().toEqualTypeOf<
68+
| {
69+
data: {
70+
user: {
71+
id: string
72+
name: string
73+
}
74+
}
75+
status: 200
76+
format: 'json'
77+
}
78+
| Error
79+
| HonoResponseError<
80+
{
81+
error: string
82+
},
83+
400,
84+
'json'
85+
>
86+
>()
87+
})
88+
89+
it("Shouldn't contain Error in Data", () => {
90+
const client = createReactQueryClient<BasicHonoApp>({
91+
baseUrl: 'http://localhost:3000',
92+
})
93+
94+
const queryOptions = client.queryOptions('/users/:id', '$get', {
95+
input: {
96+
param: { id: 'none' },
97+
},
98+
})
99+
100+
type Result =
101+
typeof queryOptions extends DefinedInitialDataOptions<infer TData, infer TError>
102+
? { data: TData; error: TError }
103+
: never
104+
105+
expectTypeOf<Result['data']>().toEqualTypeOf<{
106+
data: {
107+
user: {
108+
id: string
109+
name: string
110+
}
111+
}
112+
status: 200
113+
format: 'json'
114+
}>()
115+
116+
expectTypeOf<Result['error']>().toEqualTypeOf<
117+
| Error
118+
| HonoResponseError<
119+
{
120+
error: string
121+
},
122+
400,
123+
'json'
124+
>
125+
>()
126+
})
127+
43128
it('should create the query client with the correct types', () => {
44129
const client = createReactQueryClient<BasicHonoApp>({
45130
baseUrl: 'http://localhost:3000',

src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ function getter(obj: object, paths: string[]): any {
5555
}, obj)
5656
}
5757

58-
async function responseParser(response: Response): Promise<any> {
58+
async function responseParser(response: Response, throwOnError?: boolean): Promise<any> {
5959
let data: any
6060
const contentType = response.headers.get('content-type')
6161
if (contentType?.includes('application/json')) {
@@ -78,6 +78,8 @@ async function responseParser(response: Response): Promise<any> {
7878

7979
if (response.ok) {
8080
return res
81+
} else if (!throwOnError) {
82+
return new HonoResponseError(res)
8183
} else {
8284
throw new HonoResponseError(res)
8385
}
@@ -106,6 +108,8 @@ function queryOptionsFactory<T extends Record<string, any>>(
106108
const handler = getter(client, paths)
107109
const payload = (honoPayload as any)?.input ?? {}
108110

111+
const isThrowOnError = honoPayload.options?.throwOnError ?? true
112+
109113
return queryOptions({
110114
queryKey: createQueryKey(
111115
method.toString(),
@@ -114,7 +118,7 @@ function queryOptionsFactory<T extends Record<string, any>>(
114118
),
115119
queryFn: async () => {
116120
const response = await handler(payload, honoPayload.options)
117-
return responseParser(response)
121+
return responseParser(response, isThrowOnError)
118122
},
119123
...hookOptions,
120124
}) as any

src/types.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
DefaultError,
23
DefinedInitialDataOptions,
34
DefinedUseQueryResult,
45
InvalidateQueryFilters,
@@ -53,9 +54,11 @@ type ErrorResponse<T> =
5354
>
5455
: never
5556

56-
export type HonoPayload<TInput> = undefined extends TInput
57-
? { options?: ClientRequestOptions }
58-
: { input: TInput; options?: ClientRequestOptions }
57+
type HonoPayloadOptions = ClientRequestOptions & { throwOnError?: boolean }
58+
export type HonoPayload<
59+
TInput,
60+
TOptions extends HonoPayloadOptions | undefined = HonoPayloadOptions | undefined,
61+
> = undefined extends TInput ? { options?: TOptions } : { input: TInput; options?: TOptions }
5962

6063
export type UseHonoQuery<TApp extends Record<string, any>> = <
6164
TPath extends keyof TApp,
@@ -128,20 +131,35 @@ export type HonoQueryOptions<TApp extends Record<string, any>> = <
128131
>,
129132
'queryKey' | 'queryFn'
130133
>,
134+
TOptions extends HonoPayloadOptions | undefined = HonoPayloadOptions | undefined,
131135
>(
132136
path: TPath,
133137
method: TMethod,
134-
honoPayload: HonoPayload<InferFunctionInput<TApp[TPath][TMethod]>>,
138+
honoPayload: HonoPayload<InferFunctionInput<TApp[TPath][TMethod]>, TOptions>,
135139
queryOptions?: TQueryOptions
136140
) => 'initialData' extends keyof TQueryOptions
137-
? DefinedInitialDataOptions<
138-
SuccessResponse<InferFunctionReturn<TApp[TPath][TMethod]>>,
139-
ErrorResponse<InferFunctionReturn<TApp[TPath][TMethod]>> | Error
140-
>
141-
: UndefinedInitialDataOptions<
142-
SuccessResponse<InferFunctionReturn<TApp[TPath][TMethod]>>,
143-
ErrorResponse<InferFunctionReturn<TApp[TPath][TMethod]>> | Error
144-
>
141+
? TOptions extends { throwOnError: false }
142+
? DefinedInitialDataOptions<
143+
| SuccessResponse<InferFunctionReturn<TApp[TPath][TMethod]>>
144+
| ErrorResponse<InferFunctionReturn<TApp[TPath][TMethod]>>
145+
| Error,
146+
DefaultError
147+
>
148+
: DefinedInitialDataOptions<
149+
SuccessResponse<InferFunctionReturn<TApp[TPath][TMethod]>>,
150+
ErrorResponse<InferFunctionReturn<TApp[TPath][TMethod]>> | Error
151+
>
152+
: TOptions extends { throwOnError: false }
153+
? UndefinedInitialDataOptions<
154+
| SuccessResponse<InferFunctionReturn<TApp[TPath][TMethod]>>
155+
| ErrorResponse<InferFunctionReturn<TApp[TPath][TMethod]>>
156+
| Error,
157+
DefaultError
158+
>
159+
: UndefinedInitialDataOptions<
160+
SuccessResponse<InferFunctionReturn<TApp[TPath][TMethod]>>,
161+
ErrorResponse<InferFunctionReturn<TApp[TPath][TMethod]>> | Error
162+
>
145163

146164
export type HonoMutationOptions<TApp extends Record<string, any>> = <
147165
TPath extends keyof TApp,

0 commit comments

Comments
 (0)