Skip to content

Commit 9b47729

Browse files
committed
fix: tanstack inference in some edge cases
that I could not even reproduce minimally here 😅
1 parent d9a896a commit 9b47729

File tree

3 files changed

+39
-13
lines changed

3 files changed

+39
-13
lines changed

packages/typed-openapi/src/tanstack-query.generator.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
1212

1313
const file = `
1414
import { queryOptions } from "@tanstack/react-query"
15-
import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus } from "${ctx.relativeApiClientPath}"
15+
import type { EndpointByMethod, ApiClient, SuccessStatusCode, ErrorStatusCode, InferResponseByStatus, TypedSuccessResponse } from "${ctx.relativeApiClientPath}"
1616
import { errorStatusCodes, TypedResponseError } from "${ctx.relativeApiClientPath}"
1717
1818
type EndpointQueryKey<TOptions extends EndpointParameters> = [
@@ -66,6 +66,11 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
6666
6767
type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
6868
69+
type InferResponseData<TEndpoint, TStatusCode> = TypedSuccessResponse<any, any, any> extends
70+
InferResponseByStatus<TEndpoint, TStatusCode>
71+
? Extract<InferResponseByStatus<TEndpoint, TStatusCode>, { data: {}}>["data"]
72+
: Extract<InferResponseByStatus<TEndpoint, TStatusCode>["data"], {}>;
73+
6974
// </ApiClientTypes>
7075
7176
// <ApiClient>
@@ -98,7 +103,7 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
98103
withResponse: false as const
99104
};
100105
const res = await this.client.${method}(path, requestParams as never);
101-
return res as Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"];
106+
return res as InferResponseData<TEndpoint, SuccessStatusCode>;
102107
},
103108
queryKey: queryKey
104109
}),
@@ -123,7 +128,7 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
123128
TWithResponse extends boolean = false,
124129
TSelection = TWithResponse extends true
125130
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
126-
: Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"],
131+
: InferResponseData<TEndpoint, SuccessStatusCode>,
127132
TError = TEndpoint extends { responses: infer TResponses }
128133
? TResponses extends Record<string | number, unknown>
129134
? InferResponseByStatus<TEndpoint, ErrorStatusCode>
@@ -133,15 +138,15 @@ export const generateTanstackQueryFile = async (ctx: GeneratorContext & { relati
133138
withResponse?: TWithResponse;
134139
selectFn?: (res: TWithResponse extends true
135140
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
136-
: Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"]
141+
: InferResponseData<TEndpoint, SuccessStatusCode>
137142
) => TSelection;
138143
throwOnStatusError?: boolean
139144
throwOnError?: boolean | ((error: TError) => boolean)
140145
}) {
141146
const mutationKey = [{ method, path }] as const;
142147
const mutationFn = async <TLocalWithResponse extends boolean = TWithResponse, TLocalSelection = TLocalWithResponse extends true
143148
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
144-
: Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"]>
149+
: InferResponseData<TEndpoint, SuccessStatusCode>>
145150
(params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
146151
withResponse?: TLocalWithResponse;
147152
throwOnStatusError?: boolean;

packages/typed-openapi/tests/integration.types.tstyche.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Integration test for generated query client using TSTyche
22
// This test ensures the generated client (TS types only, no schema validation) has no inference errors
33

4-
import { mutationOptions, queryOptions, useMutation } from "@tanstack/react-query";
4+
import { mutationOptions, queryOptions, useMutation, useQuery } from "@tanstack/react-query";
55
import { describe, expect, it } from "tstyche";
66
import {
77
type Endpoints,
@@ -438,4 +438,19 @@ describe("Example API Client", () => {
438438
queryOptions(query);
439439
queryOptions(query.queryOptions);
440440
});
441+
442+
it("TanstackQueryApiClient useQuery withResponse: undefined (global+local)", async () => {
443+
const tanstack = {} as TanstackQueryApiClient;
444+
445+
const queryWithSomeResponseUnknownData = tanstack.get("/pet/findByStatus", { query: {status: "available"} });
446+
const queryHook = useQuery(queryWithSomeResponseUnknownData.queryOptions);
447+
448+
expect(queryHook.data).type.toBe<Schemas.Pet[]|undefined>();
449+
// ^?
450+
451+
const secondQuery = tanstack.get("/pet/custom");
452+
const secondQueryHook = useQuery(secondQuery.queryOptions);
453+
expect(secondQueryHook.data).type.toBe<Schemas.Pet|undefined>();
454+
// ^?
455+
});
441456
});

packages/typed-openapi/tests/tanstack-query.generator.test.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe("generator", () => {
2020
SuccessStatusCode,
2121
ErrorStatusCode,
2222
InferResponseByStatus,
23+
TypedSuccessResponse,
2324
} from "./api.client.ts";
2425
import { errorStatusCodes, TypedResponseError } from "./api.client.ts";
2526
@@ -75,6 +76,11 @@ describe("generator", () => {
7576
7677
type MaybeOptionalArg<T> = RequiredKeys<T> extends never ? [config?: T] : [config: T];
7778
79+
type InferResponseData<TEndpoint, TStatusCode> =
80+
TypedSuccessResponse<any, any, any> extends InferResponseByStatus<TEndpoint, TStatusCode>
81+
? Extract<InferResponseByStatus<TEndpoint, TStatusCode>, { data: {} }>["data"]
82+
: Extract<InferResponseByStatus<TEndpoint, TStatusCode>["data"], {}>;
83+
7884
// </ApiClientTypes>
7985
8086
// <ApiClient>
@@ -101,7 +107,7 @@ describe("generator", () => {
101107
withResponse: false as const,
102108
};
103109
const res = await this.client.put(path, requestParams as never);
104-
return res as Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"];
110+
return res as InferResponseData<TEndpoint, SuccessStatusCode>;
105111
},
106112
queryKey: queryKey,
107113
}),
@@ -131,7 +137,7 @@ describe("generator", () => {
131137
withResponse: false as const,
132138
};
133139
const res = await this.client.post(path, requestParams as never);
134-
return res as Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"];
140+
return res as InferResponseData<TEndpoint, SuccessStatusCode>;
135141
},
136142
queryKey: queryKey,
137143
}),
@@ -161,7 +167,7 @@ describe("generator", () => {
161167
withResponse: false as const,
162168
};
163169
const res = await this.client.get(path, requestParams as never);
164-
return res as Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"];
170+
return res as InferResponseData<TEndpoint, SuccessStatusCode>;
165171
},
166172
queryKey: queryKey,
167173
}),
@@ -191,7 +197,7 @@ describe("generator", () => {
191197
withResponse: false as const,
192198
};
193199
const res = await this.client.delete(path, requestParams as never);
194-
return res as Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"];
200+
return res as InferResponseData<TEndpoint, SuccessStatusCode>;
195201
},
196202
queryKey: queryKey,
197203
}),
@@ -213,7 +219,7 @@ describe("generator", () => {
213219
TWithResponse extends boolean = false,
214220
TSelection = TWithResponse extends true
215221
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
216-
: Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"],
222+
: InferResponseData<TEndpoint, SuccessStatusCode>,
217223
TError = TEndpoint extends { responses: infer TResponses }
218224
? TResponses extends Record<string | number, unknown>
219225
? InferResponseByStatus<TEndpoint, ErrorStatusCode>
@@ -227,7 +233,7 @@ describe("generator", () => {
227233
selectFn?: (
228234
res: TWithResponse extends true
229235
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
230-
: Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"],
236+
: InferResponseData<TEndpoint, SuccessStatusCode>,
231237
) => TSelection;
232238
throwOnStatusError?: boolean;
233239
throwOnError?: boolean | ((error: TError) => boolean);
@@ -238,7 +244,7 @@ describe("generator", () => {
238244
TLocalWithResponse extends boolean = TWithResponse,
239245
TLocalSelection = TLocalWithResponse extends true
240246
? InferResponseByStatus<TEndpoint, SuccessStatusCode>
241-
: Extract<InferResponseByStatus<TEndpoint, SuccessStatusCode>, { data: {} }>["data"],
247+
: InferResponseData<TEndpoint, SuccessStatusCode>,
242248
>(
243249
params: (TEndpoint extends { parameters: infer Parameters } ? Parameters : {}) & {
244250
withResponse?: TLocalWithResponse;

0 commit comments

Comments
 (0)