Skip to content

Commit 36e5bd4

Browse files
committed
fix: fix typing for query
1 parent f27a68b commit 36e5bd4

5 files changed

Lines changed: 70 additions & 16 deletions

File tree

.changeset/swift-points-brush.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@ts-rest/core': patch
3+
'@ts-rest/react-query': patch
4+
---
5+
6+
Fix typing for query

libs/ts-rest/core/src/lib/client.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,24 @@ describe('client', () => {
135135
const value = { key: 'value' };
136136
api.mockResolvedValue({ body: value, status: 200 });
137137

138+
const result = await client.posts.getPosts({});
139+
140+
expect(result).toStrictEqual({ body: value, status: 200 });
141+
142+
expect(api).toHaveBeenCalledWith({
143+
method: 'GET',
144+
path: 'http://api.com/posts',
145+
headers: {
146+
'Content-Type': 'application/json',
147+
},
148+
body: undefined,
149+
});
150+
});
151+
152+
it('w/ no query parameters', async () => {
153+
const value = { key: 'value' };
154+
api.mockResolvedValue({ body: value, status: 200 });
155+
138156
const result = await client.posts.getPosts({ query: {} });
139157

140158
expect(result).toStrictEqual({ body: value, status: 200 });

libs/ts-rest/core/src/lib/client.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { HTTPStatusCode } from './status-codes';
66
import {
77
AreAllPropertiesOptional,
88
Merge,
9+
OptionalIfAllOptional,
910
Without,
1011
ZodInferOrType,
1112
} from './type-utils';
@@ -43,18 +44,20 @@ type AppRouteBodyOrFormData<T extends AppRouteMutation> =
4344
? FormData | AppRouteMutationType<T['body']>
4445
: AppRouteMutationType<T['body']>;
4546

46-
interface DataReturnArgs<TRoute extends AppRoute> {
47+
interface DataReturnArgsBase<TRoute extends AppRoute> {
4748
body: TRoute extends AppRouteMutation
4849
? AppRouteBodyOrFormData<TRoute>
4950
: never;
5051
params: PathParamsFromUrl<TRoute>;
51-
query: AreAllPropertiesOptional<
52-
AppRouteMutationType<TRoute['query']>
53-
> extends false
52+
query: 'query' extends keyof TRoute
5453
? AppRouteMutationType<TRoute['query']>
5554
: never;
5655
}
5756

57+
type DataReturnArgs<TRoute extends AppRoute> = OptionalIfAllOptional<
58+
DataReturnArgsBase<TRoute>
59+
>;
60+
5861
export type ApiRouteResponse<T> =
5962
| {
6063
[K in keyof T]: {

libs/ts-rest/core/src/lib/type-utils.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,30 @@ type NarrowNotZod<T> = Try<T, ZodType, NarrowRaw<T>>;
8989

9090
export type Narrow<T> = Try<T, [], NarrowNotZod<T>>;
9191

92-
export type AreAllPropertiesOptional<T> = {
93-
[K in keyof T]-?: undefined extends T[K] ? true : false;
94-
}[keyof T] extends true
95-
? true
92+
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
93+
94+
// https://github.com/ts-essentials/ts-essentials/blob/4c451652ba7c20b0e0b965e0b7755fd4d7844127/lib/types.ts#L228
95+
type OptionalKeys<T> = T extends unknown
96+
? {
97+
[K in keyof T]-?: undefined extends { [K2 in keyof T]: K2 }[K]
98+
? K
99+
: never;
100+
}[keyof T]
101+
: never;
102+
103+
export type AreAllPropertiesOptional<T> = T extends Record<string, unknown>
104+
? Exclude<keyof T, OptionalKeys<T>> extends never
105+
? true
106+
: false
96107
: false;
108+
109+
export type OptionalIfAllOptional<
110+
T,
111+
Select extends keyof T = keyof T
112+
> = PartialBy<
113+
T,
114+
Select &
115+
{
116+
[K in keyof T]: AreAllPropertiesOptional<T[K]> extends true ? K : never;
117+
}[keyof T]
118+
>;

libs/ts-rest/react-query/src/lib/react-query.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
getRouteQuery,
2626
HTTPStatusCode,
2727
isAppRoute,
28+
OptionalIfAllOptional,
2829
PathParamsFromUrl,
2930
SuccessfulHttpStatusCode,
3031
Without,
@@ -58,20 +59,24 @@ type UseQueryArgs<TAppRoute extends AppRoute> = {
5859
: never;
5960
};
6061

61-
type DataReturnArgs<TRoute extends AppRoute> = {
62+
type DataReturnArgsBase<TRoute extends AppRoute> = {
6263
body: TRoute extends AppRouteMutation
6364
? AppRouteMutationType<TRoute['body']> extends null
6465
? never
6566
: AppRouteMutationType<TRoute['body']>
6667
: never;
6768
params: PathParamsFromUrl<TRoute>;
68-
query: AreAllPropertiesOptional<
69-
AppRouteMutationType<TRoute['query']>
70-
> extends false
71-
? AppRouteMutationType<TRoute['query']>
69+
query: 'query' extends keyof TRoute
70+
? AppRouteMutationType<TRoute['query']> extends null
71+
? never
72+
: AppRouteMutationType<TRoute['query']>
7273
: never;
7374
};
7475

76+
type DataReturnArgs<TRoute extends AppRoute> = OptionalIfAllOptional<
77+
DataReturnArgsBase<TRoute>
78+
>;
79+
7580
/**
7681
* Split up the data and error to support react-query style
7782
* useQuery and useMutation error handling
@@ -177,7 +182,7 @@ const getRouteUseQuery = <TAppRoute extends AppRoute>(
177182
) => {
178183
return (
179184
queryKey: QueryKey,
180-
args?: DataReturnArgs<TAppRoute>,
185+
args?: DataReturnArgs<any>,
181186
options?: UseQueryOptions<TAppRoute['responses']>
182187
) => {
183188
const dataFn: QueryFunction<TAppRoute['responses']> = async () => {
@@ -208,7 +213,7 @@ const getRouteUseInfiniteQuery = <TAppRoute extends AppRoute>(
208213
) => {
209214
return (
210215
queryKey: QueryKey,
211-
args: (context: QueryFunctionContext) => DataReturnArgs<TAppRoute>,
216+
args: (context: QueryFunctionContext) => DataReturnArgs<any>,
212217
options?: UseInfiniteQueryOptions<TAppRoute['responses']>
213218
) => {
214219
const dataFn: QueryFunction<TAppRoute['responses']> = async (
@@ -247,7 +252,7 @@ const getRouteUseMutation = <TAppRoute extends AppRoute>(
247252
clientArgs: ClientArgs
248253
) => {
249254
return (options?: UseMutationOptions<TAppRoute['responses']>) => {
250-
const mutationFunction = async (args?: DataReturnArgs<TAppRoute>) => {
255+
const mutationFunction = async (args?: DataReturnArgs<any>) => {
251256
const path = getCompleteUrl(
252257
args?.query,
253258
clientArgs.baseUrl,

0 commit comments

Comments
 (0)