@@ -2,14 +2,6 @@ import { capitalize } from "pastable/server";
2
2
import { prettify } from "./format.ts" ;
3
3
import type { mapOpenApiEndpoints } from "./map-openapi-endpoints.ts" ;
4
4
5
- // Default error status codes (4xx and 5xx ranges)
6
- export const DEFAULT_ERROR_STATUS_CODES = [
7
- 400 , 401 , 402 , 403 , 404 , 405 , 406 , 407 , 408 , 409 , 410 , 411 , 412 , 413 , 414 , 415 , 416 , 417 , 418 , 421 , 422 , 423 , 424 ,
8
- 425 , 426 , 428 , 429 , 431 , 451 , 500 , 501 , 502 , 503 , 504 , 505 , 506 , 507 , 508 , 510 , 511 ,
9
- ] as const ;
10
-
11
- export type ErrorStatusCode = ( typeof DEFAULT_ERROR_STATUS_CODES ) [ number ] ;
12
-
13
5
type GeneratorOptions = ReturnType < typeof mapOpenApiEndpoints > ;
14
6
type GeneratorContext = Required < GeneratorOptions > & {
15
7
errorStatusCodes ?: readonly number [ ] ;
@@ -18,12 +10,10 @@ type GeneratorContext = Required<GeneratorOptions> & {
18
10
export const generateTanstackQueryFile = async ( ctx : GeneratorContext & { relativeApiClientPath : string } ) => {
19
11
const endpointMethods = new Set ( ctx . endpointList . map ( ( endpoint ) => endpoint . method . toLowerCase ( ) ) ) ;
20
12
21
- // Use configured error status codes or default
22
- const errorStatusCodes = ctx . errorStatusCodes ?? DEFAULT_ERROR_STATUS_CODES ;
23
-
24
13
const file = `
25
14
import { queryOptions } from "@tanstack/react-query"
26
- import type { EndpointByMethod, ApiClient, SafeApiResponse } from "${ ctx . relativeApiClientPath } "
15
+ import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus } from "${ ctx . relativeApiClientPath } "
16
+ import { errorStatusCodes, TypedResponseError } from "${ ctx . relativeApiClientPath } "
27
17
28
18
type EndpointQueryKey<TOptions extends EndpointParameters> = [
29
19
TOptions & {
@@ -76,8 +66,6 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
76
66
77
67
type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
78
68
79
- type ErrorStatusCode = ${ errorStatusCodes . join ( " | " ) } ;
80
-
81
69
// </ApiClientTypes>
82
70
83
71
// <ApiClient>
@@ -97,6 +85,7 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
97
85
/** type-only property if you need easy access to the endpoint params */
98
86
"~endpoint": {} as TEndpoint,
99
87
queryKey,
88
+ queryFn: {} as "You need to pass .queryOptions to the useQuery hook",
100
89
queryOptions: queryOptions({
101
90
queryFn: async ({ queryKey, signal, }) => {
102
91
const requestParams = {
@@ -110,6 +99,7 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
110
99
},
111
100
queryKey: queryKey
112
101
}),
102
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
113
103
mutationOptions: {
114
104
mutationKey: queryKey,
115
105
mutationFn: async (localOptions: TEndpoint extends { parameters: infer Parameters} ? Parameters: never) => {
@@ -134,84 +124,65 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
134
124
135
125
// <ApiClient.request>
136
126
/**
137
- * Generic mutation method with full type-safety for any endpoint that doesnt require parameters to be passed initially
127
+ * Generic mutation method with full type-safety for any endpoint; it doesnt require parameters to be passed initially
128
+ * but instead will require them to be passed when calling the mutation.mutate() method
138
129
*/
139
130
mutation<
140
131
TMethod extends keyof EndpointByMethod,
141
132
TPath extends keyof EndpointByMethod[TMethod],
142
133
TEndpoint extends EndpointByMethod[TMethod][TPath],
143
134
TWithResponse extends boolean = false,
144
135
TSelection = TWithResponse extends true
145
- ? SafeApiResponse <TEndpoint>
136
+ ? InferResponseByStatus <TEndpoint, SuccessStatusCode >
146
137
: TEndpoint extends { response: infer Res } ? Res : never,
147
138
TError = TEndpoint extends { responses: infer TResponses }
148
139
? TResponses extends Record<string | number, unknown>
149
- ? {
150
- [K in keyof TResponses]: K extends string
151
- ? K extends \`\${infer TStatusCode extends number}\`
152
- ? TStatusCode extends ErrorStatusCode
153
- ? Omit<Response, 'status'> & { status: TStatusCode; data: TResponses[K] }
154
- : never
155
- : never
156
- : K extends number
157
- ? K extends ErrorStatusCode
158
- ? Omit<Response, 'status'> & { status: K; data: TResponses[K] }
159
- : never
160
- : never;
161
- }[keyof TResponses]
140
+ ? InferResponseByStatus<TEndpoint, ErrorStatusCode>
162
141
: Error
163
142
: Error
164
143
>(method: TMethod, path: TPath, options?: {
165
144
withResponse?: TWithResponse;
166
145
selectFn?: (res: TWithResponse extends true
167
- ? SafeApiResponse <TEndpoint>
146
+ ? InferResponseByStatus <TEndpoint, SuccessStatusCode >
168
147
: TEndpoint extends { response: infer Res } ? Res : never
169
148
) => TSelection;
149
+ throwOnStatusError?: boolean
170
150
}) {
171
151
const mutationKey = [{ method, path }] as const;
172
152
return {
173
153
/** type-only property if you need easy access to the endpoint params */
174
154
"~endpoint": {} as TEndpoint,
175
155
mutationKey: mutationKey,
156
+ mutationFn: {} as "You need to pass .mutationOptions to the useMutation hook",
176
157
mutationOptions: {
177
158
mutationKey: mutationKey,
178
- mutationFn: async (params: TEndpoint extends { parameters: infer Parameters } ? Parameters : never): Promise<TSelection> => {
179
- const withResponse = options?.withResponse ?? false;
159
+ mutationFn: async <TLocalWithResponse extends boolean = TWithResponse, TLocalSelection = TLocalWithResponse extends true
160
+ ? InferResponseByStatus<TEndpoint, SuccessStatusCode>
161
+ : TEndpoint extends { response: infer Res }
162
+ ? Res
163
+ : never>
164
+ (params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
165
+ withResponse?: TLocalWithResponse;
166
+ throwOnStatusError?: boolean;
167
+ }): Promise<TLocalSelection> => {
168
+ const withResponse = params.withResponse ??options?.withResponse ?? false;
169
+ const throwOnStatusError = params.throwOnStatusError ?? options?.throwOnStatusError ?? (withResponse ? false : true);
180
170
const selectFn = options?.selectFn;
171
+ const response = await (this.client as any)[method](path, { ...params as any, withResponse: true, throwOnStatusError: false });
181
172
182
- if (withResponse) {
183
- // Type assertion is safe because we're handling the method dynamically
184
- const response = await (this.client as any)[method](path, { ...params as any, withResponse: true });
185
- if (!response.ok) {
186
- // Create a Response-like error object with additional data property
187
- const error = Object.assign(Object.create(Response.prototype), {
188
- ...response,
189
- data: response.data
190
- }) as TError;
191
- throw error;
192
- }
193
- const res = selectFn ? selectFn(response as any) : response;
194
- return res as TSelection;
195
- }
196
-
197
- // Type assertion is safe because we're handling the method dynamically
198
- // Always get the full response for error handling, even when withResponse is false
199
- const response = await (this.client as any)[method](path, { ...params as any, withResponse: true });
200
- if (!response.ok) {
201
- // Create a Response-like error object with additional data property
202
- const error = Object.assign(Object.create(Response.prototype), {
203
- ...response,
204
- data: response.data
205
- }) as TError;
206
- throw error;
173
+ if (throwOnStatusError && errorStatusCodes.includes(response.status as never)) {
174
+ throw new TypedResponseError(response as never);
207
175
}
208
176
209
177
// Return just the data if withResponse is false, otherwise return the full response
210
178
const finalResponse = withResponse ? response : response.data;
211
179
const res = selectFn ? selectFn(finalResponse as any) : finalResponse;
212
- return res as TSelection ;
180
+ return res as never ;
213
181
}
214
- } as import("@tanstack/react-query").UseMutationOptions<TSelection, TError, TEndpoint extends { parameters: infer Parameters } ? Parameters : never>,
182
+ } satisfies import("@tanstack/react-query").UseMutationOptions<TSelection, TError, (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
183
+ withResponse?: boolean;
184
+ throwOnStatusError?: boolean;
185
+ }>,
215
186
}
216
187
}
217
188
// </ApiClient.request>
0 commit comments