Skip to content

Commit 487b2b6

Browse files
ToTeToGabrola
andauthored
feat(core): c.responses utility (#539)
Co-authored-by: Youssef Gaber <1728215+Gabrola@users.noreply.github.com>
1 parent 8f4cfe6 commit 487b2b6

3 files changed

Lines changed: 122 additions & 7 deletions

File tree

.changeset/shaggy-lizards-rhyme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ts-rest/core': minor
3+
---
4+
5+
Add `c.responses` utility

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

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ describe('contract', () => {
347347
method: 'GET',
348348
path: '/posts/:id',
349349
responses: {
350-
200: c.body<{ id: number }>(),
350+
200: c.type<{ id: number }>(),
351351
},
352352
},
353353
});
@@ -370,14 +370,111 @@ describe('contract', () => {
370370
>;
371371
});
372372

373+
it('should be typed correctly with separate query route', () => {
374+
const getPost = c.query({
375+
method: 'GET',
376+
path: '/posts/:id',
377+
responses: {
378+
200: c.type<{ id: number }>(),
379+
},
380+
});
381+
382+
const contract = c.router({
383+
getPost,
384+
});
385+
386+
type ContractShape = Expect<
387+
Equal<
388+
typeof contract,
389+
{
390+
getPost: {
391+
method: 'GET';
392+
path: '/posts/:id';
393+
responses: {
394+
200: ContractPlainType<{
395+
id: number;
396+
}>;
397+
};
398+
};
399+
}
400+
>
401+
>;
402+
});
403+
404+
it('should be typed correctly with separate mutation route', () => {
405+
const createPost = c.mutation({
406+
method: 'POST',
407+
path: '/posts',
408+
responses: {
409+
200: c.type<{ id: number }>(),
410+
},
411+
body: c.type<{ title: string }>(),
412+
});
413+
414+
const contract = c.router({
415+
createPost,
416+
});
417+
418+
type ContractShape = Expect<
419+
Equal<
420+
typeof contract,
421+
{
422+
createPost: {
423+
method: 'POST';
424+
path: '/posts';
425+
responses: {
426+
200: ContractPlainType<{
427+
id: number;
428+
}>;
429+
};
430+
body: ContractPlainType<{
431+
title: string;
432+
}>;
433+
};
434+
}
435+
>
436+
>;
437+
});
438+
439+
it('should be typed correctly with separate responses', () => {
440+
const responses = c.responses({
441+
200: c.type<{ id: number }>(),
442+
});
443+
444+
const contract = c.router({
445+
getPost: {
446+
method: 'GET',
447+
path: '/posts/:id',
448+
responses,
449+
},
450+
});
451+
452+
type ContractShape = Expect<
453+
Equal<
454+
typeof contract,
455+
{
456+
getPost: {
457+
method: 'GET';
458+
path: '/posts/:id';
459+
responses: {
460+
200: ContractPlainType<{
461+
id: number;
462+
}>;
463+
};
464+
};
465+
}
466+
>
467+
>;
468+
});
469+
373470
it('should add strictStatusCodes=true option to routes', () => {
374471
const contract = c.router(
375472
{
376473
getPost: {
377474
method: 'GET',
378475
path: '/posts/:id',
379476
responses: {
380-
200: c.body<{ id: number }>(),
477+
200: c.type<{ id: number }>(),
381478
},
382479
},
383480
},
@@ -405,7 +502,7 @@ describe('contract', () => {
405502
method: 'GET',
406503
path: '/posts/:id',
407504
responses: {
408-
200: c.body<{ id: number }>(),
505+
200: c.type<{ id: number }>(),
409506
},
410507
},
411508
},
@@ -433,7 +530,7 @@ describe('contract', () => {
433530
method: 'GET',
434531
path: '/posts/:id',
435532
responses: {
436-
200: c.body<{ id: number }>(),
533+
200: c.type<{ id: number }>(),
437534
},
438535
strictStatusCodes: true,
439536
},
@@ -462,7 +559,7 @@ describe('contract', () => {
462559
method: 'GET',
463560
path: '/posts/:id',
464561
responses: {
465-
200: c.body<{ id: number }>(),
562+
200: c.type<{ id: number }>(),
466563
},
467564
strictStatusCodes: false,
468565
},

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ export const isAppRoute = (obj: AppRoute | AppRouter): obj is AppRoute => {
177177
return 'method' in obj && 'path' in obj;
178178
};
179179

180+
type NarrowObject<T> = {
181+
[K in keyof T]: T[K];
182+
};
183+
180184
/**
181185
* The instantiated ts-rest client
182186
*/
@@ -196,12 +200,20 @@ type ContractInstance = {
196200
* A single query route, should exist within
197201
* a {@link AppRouter}
198202
*/
199-
query: <T extends AppRouteQuery>(query: T) => T;
203+
query: <T extends AppRouteQuery>(query: NarrowObject<T>) => T;
200204
/**
201205
* A single mutation route, should exist within
202206
* a {@link AppRouter}
203207
*/
204-
mutation: <T extends AppRouteMutation>(mutation: T) => T;
208+
mutation: <T extends AppRouteMutation>(mutation: NarrowObject<T>) => T;
209+
responses: <
210+
TResponses extends Record<
211+
number,
212+
ContractAnyType | ContractOtherResponse<ContractAnyType>
213+
>,
214+
>(
215+
responses: NarrowObject<TResponses>,
216+
) => TResponses;
205217
/**
206218
* @deprecated Please use type() instead.
207219
*/
@@ -276,6 +288,7 @@ export const initContract = (): ContractInstance => {
276288
router: (endpoints, options) => recursivelyApplyOptions(endpoints, options),
277289
query: (args) => args,
278290
mutation: (args) => args,
291+
responses: (args) => args,
279292
response: () => ContractPlainTypeRuntimeSymbol,
280293
body: () => ContractPlainTypeRuntimeSymbol,
281294
type: () => ContractPlainTypeRuntimeSymbol,

0 commit comments

Comments
 (0)