Skip to content

Commit bf21a75

Browse files
authored
refactor: use unified types across all packages (#250)
* refactor: use unified types across all packages * fix fastify and other lib server headers type * fix fastify docs
1 parent 61bf1c1 commit bf21a75

22 files changed

Lines changed: 623 additions & 629 deletions

File tree

.changeset/tiny-sheep-boil.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@ts-rest/core': patch
3+
'@ts-rest/express': patch
4+
'@ts-rest/fastify': patch
5+
'@ts-rest/nest': patch
6+
'@ts-rest/next': patch
7+
'@ts-rest/react-query': patch
8+
'@ts-rest/solid-query': patch
9+
---
10+
11+
Internal refactor of types

.eslintrc.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,17 @@
2424
{
2525
"files": ["*.ts", "*.tsx"],
2626
"extends": ["plugin:@nx/typescript"],
27-
"rules": {}
27+
"rules": {
28+
"@typescript-eslint/ban-types": [
29+
"error",
30+
{
31+
"extendDefaults": true,
32+
"types": {
33+
"{}": false
34+
}
35+
}
36+
]
37+
}
2838
},
2939
{
3040
"files": ["*.js", "*.jsx"],

apps/docs/docs/fastify.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ const start = async () => {
5151
start();
5252
```
5353

54-
`s.registerRouter` is a function that takes a contract, a corresponding router with implementations for each route and an fastify app, and it will
54+
`s.registerRouter` is a function that takes a contract, a corresponding router with implementations for each route and a fastify app, and it will
5555
create the corresponding fastify routes for each endpoint with the correct method, paths and middleware and attach them to your fastify app.
5656

5757
## Options
5858

59-
You can pass an optional options object as the last argument for `createfastifyEndpoints`.
59+
You can pass an optional options object as the last argument for `s.registerRouter`.
6060

6161
```typescript
6262
type Options = {
@@ -80,7 +80,7 @@ If there is a corresponding response Zod schema defined in the contract for the
8080
If validation fails a `ResponseValidationError` will be thrown causing a 500 response to be returned.
8181

8282
```typescript
83-
createfastifyEndpoints(contract, router, app, {
83+
s.registerRouter(contract, router, app, {
8484
validateResponses: true,
8585
});
8686
```

libs/ts-rest/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ export * from './lib/type-utils';
77
export * from './lib/zod-utils';
88
export * from './lib/server';
99
export * from './lib/response-validation-error';
10-
export * from './lib/infer-types';
1110
export * from './lib/unknown-status-error';
11+
export * from './lib/infer-types';

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,6 @@ describe('client', () => {
506506

507507
const result = await client.posts.patchPost({
508508
params: { id: '1' },
509-
body: null,
510509
});
511510

512511
expect(result.body).toStrictEqual(value);
@@ -531,7 +530,6 @@ describe('client', () => {
531530

532531
const result = await client.posts.deletePost({
533532
params: { id: '1' },
534-
body: null,
535533
});
536534

537535
expect(result.body).toStrictEqual(value);

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

Lines changed: 21 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
1-
import {
2-
AppRoute,
3-
AppRouteMutation,
4-
AppRouter,
5-
AppRouteStrictStatusCodes,
6-
isAppRoute,
7-
} from './dsl';
8-
import { insertParamsIntoPath, ParamsFromUrl } from './paths';
1+
import { AppRoute, AppRouteMutation, AppRouter, isAppRoute } from './dsl';
2+
import { insertParamsIntoPath } from './paths';
93
import { convertQueryParamsToUrlString } from './query';
10-
import { HTTPStatusCode } from './status-codes';
11-
import {
12-
AreAllPropertiesOptional,
13-
Extends,
14-
LowercaseKeys,
15-
Merge,
16-
OptionalIfAllOptional,
17-
PartialByLooseKeys,
18-
Prettify,
19-
Without,
20-
ZodInferOrType,
21-
ZodInputOrType,
22-
} from './type-utils';
4+
import { AreAllPropertiesOptional, Prettify } from './type-utils';
235
import { UnknownStatusError } from './unknown-status-error';
6+
import {
7+
ClientInferRequest,
8+
ClientInferResponses,
9+
PartialClientInferRequest,
10+
} from './infer-types';
2411

2512
type RecursiveProxyObj<T extends AppRouter, TClientArgs extends ClientArgs> = {
2613
[TKey in keyof T]: T[TKey] extends AppRoute
@@ -30,133 +17,28 @@ type RecursiveProxyObj<T extends AppRouter, TClientArgs extends ClientArgs> = {
3017
: never;
3118
};
3219

33-
type AppRouteMutationType<T> = ZodInputOrType<T>;
34-
35-
/**
36-
* Extract the path params from the path in the contract
37-
*/
38-
export type PathParamsFromUrl<T extends AppRoute> = ParamsFromUrl<
39-
T['path']
40-
> extends infer U
41-
? U
42-
: never;
43-
44-
/**
45-
* Merge `PathParamsFromUrl<T>` with pathParams schema if it exists
46-
*/
47-
export type PathParamsWithCustomValidators<
48-
T extends AppRoute,
49-
TClientOrServer extends 'client' | 'server' = 'server'
50-
> = T['pathParams'] extends undefined
51-
? PathParamsFromUrl<T>
52-
: Merge<
53-
PathParamsFromUrl<T>,
54-
TClientOrServer extends 'server'
55-
? ZodInferOrType<T['pathParams']>
56-
: ZodInputOrType<T['pathParams']>
57-
>;
58-
59-
// Allow FormData if the contentType is multipart/form-data
60-
type AppRouteBodyOrFormData<T extends AppRouteMutation> =
61-
T['contentType'] extends 'multipart/form-data'
62-
? FormData | AppRouteMutationType<T['body']>
63-
: AppRouteMutationType<T['body']>;
64-
65-
/**
66-
* Extract any extra parameters from the client args
67-
*/
68-
export type ExtractExtraParametersFromClientArgs<
69-
TClientArgs extends ClientArgs
70-
> = TClientArgs['api'] extends ApiFetcher
71-
? Omit<Parameters<TClientArgs['api']>[0], keyof Parameters<ApiFetcher>[0]>
72-
: // eslint-disable-next-line @typescript-eslint/ban-types
73-
{};
74-
75-
type DataReturnArgsBase<
76-
TRoute extends AppRoute,
77-
TClientArgs extends ClientArgs,
78-
THeaders = Prettify<
79-
'headers' extends keyof TRoute
80-
? PartialByLooseKeys<
81-
LowercaseKeys<ZodInputOrType<TRoute['headers']>>,
82-
keyof LowercaseKeys<TClientArgs['baseHeaders']>
83-
>
84-
: never
85-
>
86-
> = {
87-
body: TRoute extends AppRouteMutation
88-
? AppRouteBodyOrFormData<TRoute>
89-
: never;
90-
params: PathParamsFromUrl<TRoute>;
91-
query: 'query' extends keyof TRoute
92-
? AppRouteMutationType<TRoute['query']>
93-
: never;
94-
headers: THeaders;
95-
extraHeaders?: {
96-
[K in NonNullable<keyof THeaders>]?: never;
97-
} & Record<string, string | undefined>;
98-
} & ExtractExtraParametersFromClientArgs<TClientArgs>;
99-
100-
type DataReturnArgs<
101-
TRoute extends AppRoute,
102-
TClientArgs extends ClientArgs
103-
> = OptionalIfAllOptional<
104-
Without<DataReturnArgsBase<TRoute, TClientArgs>, never>
105-
>;
106-
107-
export type ApiRouteResponse<T, TStrictStatusCodes = false> =
108-
| {
109-
[K in keyof T]: {
110-
status: K;
111-
body: ZodInferOrType<T[K]>;
112-
headers: Headers;
113-
};
114-
}[keyof T]
115-
| (TStrictStatusCodes extends true
116-
? never
117-
: {
118-
status: Exclude<HTTPStatusCode, keyof T>;
119-
body: unknown;
120-
headers: Headers;
121-
});
122-
12320
/**
12421
* @deprecated Only safe to use on the client-side. Use `ServerInferResponses`/`ClientInferResponses` instead.
12522
*/
126-
export type ApiResponseForRoute<T extends AppRoute> = ApiRouteResponse<
127-
T['responses'],
128-
Extends<T, AppRouteStrictStatusCodes>
129-
>;
23+
export type ApiResponseForRoute<T extends AppRoute> = ClientInferResponses<T>;
13024

13125
/**
13226
* @deprecated Only safe to use on the client-side. Use `ServerInferResponses`/`ClientInferResponses` instead.
13327
*/
13428
export function getRouteResponses<T extends AppRouter>(router: T) {
135-
return {} as {
136-
[K in keyof typeof router]: typeof router[K] extends AppRoute
137-
? ApiResponseForRoute<typeof router[K]>
138-
: 'not a route';
139-
};
29+
return {} as ClientInferResponses<T>;
14030
}
14131

142-
type AppRouteFunctionReturn<TRoute extends AppRoute> = ApiRouteResponse<
143-
TRoute['responses'],
144-
Extends<TRoute, AppRouteStrictStatusCodes>
145-
>;
146-
14732
/**
14833
* Returned from a mutation or query call
14934
*/
15035
export type AppRouteFunction<
15136
TRoute extends AppRoute,
152-
TClientArgs extends ClientArgs
153-
> = AreAllPropertiesOptional<DataReturnArgs<TRoute, TClientArgs>> extends true
154-
? (
155-
args?: Prettify<DataReturnArgs<TRoute, TClientArgs>>
156-
) => Promise<Prettify<AppRouteFunctionReturn<TRoute>>>
157-
: (
158-
args: Prettify<DataReturnArgs<TRoute, TClientArgs>>
159-
) => Promise<Prettify<AppRouteFunctionReturn<TRoute>>>;
37+
TClientArgs extends ClientArgs,
38+
TArgs = PartialClientInferRequest<TRoute, TClientArgs>
39+
> = AreAllPropertiesOptional<TArgs> extends true
40+
? (args?: Prettify<TArgs>) => Promise<Prettify<ClientInferResponses<TRoute>>>
41+
: (args: Prettify<TArgs>) => Promise<Prettify<ClientInferResponses<TRoute>>>;
16042

16143
export interface ClientArgs {
16244
baseUrl: string;
@@ -338,12 +220,17 @@ export const getCompleteUrl = (
338220
return `${baseUrl}${path}${queryComponent}`;
339221
};
340222

223+
type FullClientInferRequest = ClientInferRequest<
224+
AppRouteMutation & { path: '/:placeholder' },
225+
ClientArgs
226+
>;
227+
341228
export const getRouteQuery = <TAppRoute extends AppRoute>(
342229
route: TAppRoute,
343230
clientArgs: InitClientArgs
344231
) => {
345232
const knownResponseStatuses = Object.keys(route.responses);
346-
return async (inputArgs?: DataReturnArgsBase<any, ClientArgs>) => {
233+
return async (inputArgs?: FullClientInferRequest) => {
347234
const { query, params, body, headers, extraHeaders, ...extraInputArgs } =
348235
inputArgs || {};
349236

0 commit comments

Comments
 (0)