@sapporta/rest-core 3.52.1 → 3.52.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +75 -10
- package/index.cjs.d.ts +1 -0
- package/index.cjs.default.js +1 -0
- package/index.cjs.js +807 -0
- package/index.cjs.mjs +2 -0
- package/index.esm.js +762 -0
- package/package.json +13 -3
- package/src/lib/client.d.ts +107 -0
- package/src/lib/dsl.d.ts +222 -0
- package/src/lib/infer-types.d.ts +78 -0
- package/src/lib/paths.d.ts +30 -0
- package/src/lib/query.d.ts +17 -0
- package/src/lib/response-error.d.ts +20 -0
- package/src/lib/response-validation-error.d.ts +14 -0
- package/src/lib/server.d.ts +18 -0
- package/src/lib/standard-schema-utils.d.ts +68 -0
- package/src/lib/standard-schema.d.ts +55 -0
- package/src/lib/status-codes.d.ts +6 -0
- package/src/lib/{test-helpers.ts → test-helpers.d.ts} +1 -6
- package/src/lib/type-guards.d.ts +12 -0
- package/src/lib/type-utils.d.ts +96 -0
- package/src/lib/unknown-status-error.d.ts +10 -0
- package/src/lib/validation-error.d.ts +11 -0
- package/.babelrc +0 -10
- package/.eslintrc.json +0 -21
- package/LICENCE +0 -21
- package/jest.config.ts +0 -16
- package/project.json +0 -51
- package/src/lib/client.spec.ts +0 -1330
- package/src/lib/client.ts +0 -481
- package/src/lib/dsl.spec.ts +0 -1308
- package/src/lib/dsl.ts +0 -472
- package/src/lib/fetch.spec.ts +0 -102
- package/src/lib/infer-types.spec.ts +0 -935
- package/src/lib/infer-types.ts +0 -282
- package/src/lib/paths.spec.ts +0 -138
- package/src/lib/paths.ts +0 -61
- package/src/lib/query.spec.ts +0 -329
- package/src/lib/query.ts +0 -114
- package/src/lib/response-error.spec.ts +0 -67
- package/src/lib/response-error.ts +0 -61
- package/src/lib/response-validation-error.ts +0 -24
- package/src/lib/server.spec.ts +0 -163
- package/src/lib/server.ts +0 -83
- package/src/lib/standard-schema-utils.spec.ts +0 -218
- package/src/lib/standard-schema-utils.ts +0 -280
- package/src/lib/standard-schema.ts +0 -71
- package/src/lib/status-codes.ts +0 -75
- package/src/lib/type-guards.spec.ts +0 -355
- package/src/lib/type-guards.ts +0 -99
- package/src/lib/type-utils.spec.ts +0 -59
- package/src/lib/type-utils.ts +0 -234
- package/src/lib/unknown-status-error.ts +0 -15
- package/src/lib/validation-error.ts +0 -36
- package/tsconfig.json +0 -22
- package/tsconfig.lib.json +0 -10
- package/tsconfig.spec.json +0 -9
- package/typedoc.json +0 -5
- /package/src/{index.ts → index.d.ts} +0 -0
|
@@ -1,935 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
UnknownOrUndefinedObjectValuesToOptionalKeys,
|
|
4
|
-
initContract,
|
|
5
|
-
} from './dsl';
|
|
6
|
-
import { Equal, Expect } from './test-helpers';
|
|
7
|
-
import {
|
|
8
|
-
ClientInferRequest,
|
|
9
|
-
ServerInferRequest,
|
|
10
|
-
ClientInferResponseBody,
|
|
11
|
-
ServerInferResponseBody,
|
|
12
|
-
ClientInferResponses,
|
|
13
|
-
ServerInferResponses,
|
|
14
|
-
InferResponseDefinedStatusCodes,
|
|
15
|
-
InferResponseUndefinedStatusCodes,
|
|
16
|
-
} from './infer-types';
|
|
17
|
-
import {
|
|
18
|
-
ErrorHttpStatusCode,
|
|
19
|
-
HTTPStatusCode,
|
|
20
|
-
SuccessfulHttpStatusCode,
|
|
21
|
-
} from './status-codes';
|
|
22
|
-
import { FetchOptions, OverridableClientArgs, initClient } from './client';
|
|
23
|
-
import { Prettify } from './type-utils';
|
|
24
|
-
import * as v from 'valibot';
|
|
25
|
-
|
|
26
|
-
const c = initContract();
|
|
27
|
-
|
|
28
|
-
const contract = c.router(
|
|
29
|
-
{
|
|
30
|
-
getPost: {
|
|
31
|
-
method: 'GET',
|
|
32
|
-
path: '/posts/:id',
|
|
33
|
-
pathParams: z.object({
|
|
34
|
-
id: z.string().transform((id) => Number(id)),
|
|
35
|
-
}),
|
|
36
|
-
query: z.object({
|
|
37
|
-
includeComments: z.boolean().default(false),
|
|
38
|
-
}),
|
|
39
|
-
responses: {
|
|
40
|
-
200: z.object({
|
|
41
|
-
id: z.number(),
|
|
42
|
-
title: z.string().default('Untitled'),
|
|
43
|
-
content: z.string(),
|
|
44
|
-
}),
|
|
45
|
-
404: z.object({
|
|
46
|
-
message: z.string(),
|
|
47
|
-
}),
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
createPost: {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
path: '/posts',
|
|
53
|
-
body: z.object({
|
|
54
|
-
title: z.string(),
|
|
55
|
-
content: z.string(),
|
|
56
|
-
}),
|
|
57
|
-
responses: {
|
|
58
|
-
201: z.object({
|
|
59
|
-
id: z.number(),
|
|
60
|
-
title: z.string(),
|
|
61
|
-
content: z.string(),
|
|
62
|
-
}),
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
uploadImage: {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
path: '/images',
|
|
68
|
-
contentType: 'multipart/form-data',
|
|
69
|
-
body: c.type<{ image: File; images: File[] }>(),
|
|
70
|
-
responses: {
|
|
71
|
-
201: c.otherResponse({
|
|
72
|
-
contentType: 'text/plain',
|
|
73
|
-
body: c.type<'Image uploaded successfully'>(),
|
|
74
|
-
}),
|
|
75
|
-
500: c.otherResponse({
|
|
76
|
-
contentType: 'text/plain',
|
|
77
|
-
body: z.literal('Image upload failed'),
|
|
78
|
-
}),
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
nested: {
|
|
82
|
-
getComments: {
|
|
83
|
-
method: 'GET',
|
|
84
|
-
path: '/posts/:id/comments',
|
|
85
|
-
pathParams: z.object({
|
|
86
|
-
id: z.string().transform((id) => Number(id)),
|
|
87
|
-
}),
|
|
88
|
-
headers: {
|
|
89
|
-
'pagination-page': z.string().transform(Number),
|
|
90
|
-
},
|
|
91
|
-
responses: {
|
|
92
|
-
200: z.object({
|
|
93
|
-
comments: z.array(
|
|
94
|
-
z.object({
|
|
95
|
-
id: z.number(),
|
|
96
|
-
content: z.string(),
|
|
97
|
-
}),
|
|
98
|
-
),
|
|
99
|
-
}),
|
|
100
|
-
404: c.type<null>(),
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
baseHeaders: {
|
|
107
|
-
Authorization: z.string(),
|
|
108
|
-
age: z.coerce.number().optional(),
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
const contractStrict = c.router(contract, {
|
|
114
|
-
strictStatusCodes: true,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const headerlessContract = c.router({
|
|
118
|
-
getPost: {
|
|
119
|
-
method: 'GET',
|
|
120
|
-
path: '/posts/:id',
|
|
121
|
-
pathParams: z.object({
|
|
122
|
-
id: z.string().transform((id) => Number(id)),
|
|
123
|
-
}),
|
|
124
|
-
query: z.object({
|
|
125
|
-
includeComments: z.boolean().default(false),
|
|
126
|
-
}),
|
|
127
|
-
responses: {
|
|
128
|
-
200: z.object({
|
|
129
|
-
id: z.number(),
|
|
130
|
-
title: z.string().default('Untitled'),
|
|
131
|
-
content: z.string(),
|
|
132
|
-
}),
|
|
133
|
-
404: z.object({
|
|
134
|
-
message: z.string(),
|
|
135
|
-
}),
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('type inference helpers', () => {
|
|
141
|
-
/**
|
|
142
|
-
* @name ServerInferResponsesWithUnknownStatusCodes
|
|
143
|
-
* Expect ServerInferResponses to include all defined status codes plus unknown status codes
|
|
144
|
-
* for endpoints that don't have strict status codes enabled
|
|
145
|
-
*/
|
|
146
|
-
type ServerInferResponsesWithUnknownStatusCodes = ServerInferResponses<
|
|
147
|
-
typeof contract
|
|
148
|
-
>;
|
|
149
|
-
type TestServerInferResponsesWithUnknownStatusCodes = Expect<
|
|
150
|
-
Equal<
|
|
151
|
-
ServerInferResponsesWithUnknownStatusCodes,
|
|
152
|
-
{
|
|
153
|
-
getPost:
|
|
154
|
-
| {
|
|
155
|
-
status: 200;
|
|
156
|
-
body: { title?: string | undefined; id: number; content: string };
|
|
157
|
-
}
|
|
158
|
-
| { status: 404; body: { message: string } }
|
|
159
|
-
| { status: Exclude<HTTPStatusCode, 200 | 404>; body: unknown };
|
|
160
|
-
createPost:
|
|
161
|
-
| {
|
|
162
|
-
status: 201;
|
|
163
|
-
body: { id: number; title: string; content: string };
|
|
164
|
-
}
|
|
165
|
-
| { status: Exclude<HTTPStatusCode, 201>; body: unknown };
|
|
166
|
-
uploadImage:
|
|
167
|
-
| {
|
|
168
|
-
status: 201;
|
|
169
|
-
body: 'Image uploaded successfully';
|
|
170
|
-
}
|
|
171
|
-
| {
|
|
172
|
-
status: 500;
|
|
173
|
-
body: 'Image upload failed';
|
|
174
|
-
}
|
|
175
|
-
| { status: Exclude<HTTPStatusCode, 201 | 500>; body: unknown };
|
|
176
|
-
nested: {
|
|
177
|
-
getComments:
|
|
178
|
-
| {
|
|
179
|
-
status: 200;
|
|
180
|
-
body: { comments: { id: number; content: string }[] };
|
|
181
|
-
}
|
|
182
|
-
| { status: 404; body: null }
|
|
183
|
-
| { status: Exclude<HTTPStatusCode, 200 | 404>; body: unknown };
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
>
|
|
187
|
-
>;
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* @name ServerInferResponsesWithStrictStatusCodes
|
|
191
|
-
* Expect ServerInferResponses to only include explicitly defined status codes
|
|
192
|
-
* when strict status codes are enabled, excluding unknown status codes
|
|
193
|
-
*/
|
|
194
|
-
type ServerInferResponsesWithStrictStatusCodes = ServerInferResponses<
|
|
195
|
-
typeof contractStrict
|
|
196
|
-
>;
|
|
197
|
-
type TestServerInferResponsesWithStrictStatusCodes = Expect<
|
|
198
|
-
Equal<
|
|
199
|
-
ServerInferResponsesWithStrictStatusCodes,
|
|
200
|
-
{
|
|
201
|
-
getPost:
|
|
202
|
-
| {
|
|
203
|
-
status: 200;
|
|
204
|
-
body: { title?: string | undefined; id: number; content: string };
|
|
205
|
-
}
|
|
206
|
-
| { status: 404; body: { message: string } };
|
|
207
|
-
createPost: {
|
|
208
|
-
status: 201;
|
|
209
|
-
body: { id: number; title: string; content: string };
|
|
210
|
-
};
|
|
211
|
-
uploadImage:
|
|
212
|
-
| {
|
|
213
|
-
status: 201;
|
|
214
|
-
body: 'Image uploaded successfully';
|
|
215
|
-
}
|
|
216
|
-
| {
|
|
217
|
-
status: 500;
|
|
218
|
-
body: 'Image upload failed';
|
|
219
|
-
};
|
|
220
|
-
nested: {
|
|
221
|
-
getComments:
|
|
222
|
-
| {
|
|
223
|
-
status: 200;
|
|
224
|
-
body: { comments: { id: number; content: string }[] };
|
|
225
|
-
}
|
|
226
|
-
| { status: 404; body: null };
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
>
|
|
230
|
-
>;
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* @name ServerInferResponsesIgnoreStrictMode
|
|
234
|
-
* Expect ServerInferResponses to include unknown status codes even when strict mode is enabled
|
|
235
|
-
* but explicitly ignored via the 'ignore' parameter
|
|
236
|
-
*/
|
|
237
|
-
type ServerInferResponsesIgnoreStrictMode = ServerInferResponses<
|
|
238
|
-
typeof contractStrict,
|
|
239
|
-
HTTPStatusCode,
|
|
240
|
-
'ignore'
|
|
241
|
-
>;
|
|
242
|
-
type TestServerInferResponsesIgnoreStrictMode = Expect<
|
|
243
|
-
Equal<
|
|
244
|
-
ServerInferResponsesIgnoreStrictMode,
|
|
245
|
-
{
|
|
246
|
-
getPost:
|
|
247
|
-
| {
|
|
248
|
-
status: 200;
|
|
249
|
-
body: { title?: string | undefined; id: number; content: string };
|
|
250
|
-
}
|
|
251
|
-
| { status: 404; body: { message: string } }
|
|
252
|
-
| { status: Exclude<HTTPStatusCode, 200 | 404>; body: unknown };
|
|
253
|
-
createPost:
|
|
254
|
-
| {
|
|
255
|
-
status: 201;
|
|
256
|
-
body: { id: number; title: string; content: string };
|
|
257
|
-
}
|
|
258
|
-
| { status: Exclude<HTTPStatusCode, 201>; body: unknown };
|
|
259
|
-
uploadImage:
|
|
260
|
-
| {
|
|
261
|
-
status: 201;
|
|
262
|
-
body: 'Image uploaded successfully';
|
|
263
|
-
}
|
|
264
|
-
| {
|
|
265
|
-
status: 500;
|
|
266
|
-
body: 'Image upload failed';
|
|
267
|
-
}
|
|
268
|
-
| { status: Exclude<HTTPStatusCode, 201 | 500>; body: unknown };
|
|
269
|
-
nested: {
|
|
270
|
-
getComments:
|
|
271
|
-
| {
|
|
272
|
-
status: 200;
|
|
273
|
-
body: { comments: { id: number; content: string }[] };
|
|
274
|
-
}
|
|
275
|
-
| { status: 404; body: null }
|
|
276
|
-
| { status: Exclude<HTTPStatusCode, 200 | 404>; body: unknown };
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
>
|
|
280
|
-
>;
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* @name ServerInferResponsesSpecificStatusCode
|
|
284
|
-
* Expect ServerInferResponses to filter responses to only include the specified status code (200),
|
|
285
|
-
* returning unknown body for endpoints that don't define that status code
|
|
286
|
-
*/
|
|
287
|
-
type ServerInferResponsesSpecificStatusCode = ServerInferResponses<
|
|
288
|
-
typeof contract,
|
|
289
|
-
200
|
|
290
|
-
>;
|
|
291
|
-
type TestServerInferResponsesSpecificStatusCode = Expect<
|
|
292
|
-
Equal<
|
|
293
|
-
ServerInferResponsesSpecificStatusCode,
|
|
294
|
-
{
|
|
295
|
-
getPost: {
|
|
296
|
-
status: 200;
|
|
297
|
-
body: { title?: string | undefined; id: number; content: string };
|
|
298
|
-
};
|
|
299
|
-
createPost: {
|
|
300
|
-
status: 200;
|
|
301
|
-
body: unknown;
|
|
302
|
-
};
|
|
303
|
-
uploadImage: {
|
|
304
|
-
status: 200;
|
|
305
|
-
body: unknown;
|
|
306
|
-
};
|
|
307
|
-
nested: {
|
|
308
|
-
getComments: {
|
|
309
|
-
status: 200;
|
|
310
|
-
body: { comments: { id: number; content: string }[] };
|
|
311
|
-
};
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
>
|
|
315
|
-
>;
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* @name ServerInferResponsesUndefinedStatusCode
|
|
319
|
-
* Expect ServerInferResponses to return unknown body for all endpoints
|
|
320
|
-
* when filtering by a status code (401) that is not defined in any endpoint
|
|
321
|
-
*/
|
|
322
|
-
type ServerInferResponsesUndefinedStatusCode = ServerInferResponses<
|
|
323
|
-
typeof contract,
|
|
324
|
-
401
|
|
325
|
-
>;
|
|
326
|
-
type TestServerInferResponsesUndefinedStatusCode = Expect<
|
|
327
|
-
Equal<
|
|
328
|
-
ServerInferResponsesUndefinedStatusCode,
|
|
329
|
-
{
|
|
330
|
-
getPost: {
|
|
331
|
-
status: 401;
|
|
332
|
-
body: unknown;
|
|
333
|
-
};
|
|
334
|
-
createPost: {
|
|
335
|
-
status: 401;
|
|
336
|
-
body: unknown;
|
|
337
|
-
};
|
|
338
|
-
uploadImage: {
|
|
339
|
-
status: 401;
|
|
340
|
-
body: unknown;
|
|
341
|
-
};
|
|
342
|
-
nested: {
|
|
343
|
-
getComments: {
|
|
344
|
-
status: 401;
|
|
345
|
-
body: unknown;
|
|
346
|
-
};
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
>
|
|
350
|
-
>;
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* @name ServerInferResponsesErrorStatusCodes
|
|
354
|
-
* Expect ServerInferResponses to filter responses to only include error status codes,
|
|
355
|
-
* showing defined error responses and unknown for undefined error codes
|
|
356
|
-
*/
|
|
357
|
-
type ServerInferResponsesErrorStatusCodes = ServerInferResponses<
|
|
358
|
-
typeof contractStrict,
|
|
359
|
-
ErrorHttpStatusCode,
|
|
360
|
-
'ignore'
|
|
361
|
-
>;
|
|
362
|
-
type TestServerInferResponsesErrorStatusCodes = Expect<
|
|
363
|
-
Equal<
|
|
364
|
-
ServerInferResponsesErrorStatusCodes,
|
|
365
|
-
{
|
|
366
|
-
getPost:
|
|
367
|
-
| { status: 404; body: { message: string } }
|
|
368
|
-
| { status: Exclude<ErrorHttpStatusCode, 404>; body: unknown };
|
|
369
|
-
createPost: { status: ErrorHttpStatusCode; body: unknown };
|
|
370
|
-
uploadImage:
|
|
371
|
-
| {
|
|
372
|
-
status: 500;
|
|
373
|
-
body: 'Image upload failed';
|
|
374
|
-
}
|
|
375
|
-
| { status: Exclude<ErrorHttpStatusCode, 500>; body: unknown };
|
|
376
|
-
nested: {
|
|
377
|
-
getComments:
|
|
378
|
-
| { status: 404; body: null }
|
|
379
|
-
| { status: Exclude<ErrorHttpStatusCode, 404>; body: unknown };
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
>
|
|
383
|
-
>;
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* @name ServerInferResponsesSuccessStatusCodesForced
|
|
387
|
-
* Expect ServerInferResponses to only include successful status codes when forced,
|
|
388
|
-
* filtering out error responses and unknown status codes
|
|
389
|
-
*/
|
|
390
|
-
type ServerInferResponsesSuccessStatusCodesForced = ServerInferResponses<
|
|
391
|
-
typeof contract,
|
|
392
|
-
SuccessfulHttpStatusCode,
|
|
393
|
-
'force'
|
|
394
|
-
>;
|
|
395
|
-
type TestServerInferResponsesSuccessStatusCodesForced = Expect<
|
|
396
|
-
Equal<
|
|
397
|
-
ServerInferResponsesSuccessStatusCodesForced,
|
|
398
|
-
{
|
|
399
|
-
getPost: {
|
|
400
|
-
status: 200;
|
|
401
|
-
body: { title?: string | undefined; id: number; content: string };
|
|
402
|
-
};
|
|
403
|
-
createPost: {
|
|
404
|
-
status: 201;
|
|
405
|
-
body: { id: number; title: string; content: string };
|
|
406
|
-
};
|
|
407
|
-
uploadImage: {
|
|
408
|
-
status: 201;
|
|
409
|
-
body: 'Image uploaded successfully';
|
|
410
|
-
};
|
|
411
|
-
nested: {
|
|
412
|
-
getComments: {
|
|
413
|
-
status: 200;
|
|
414
|
-
body: { comments: { id: number; content: string }[] };
|
|
415
|
-
};
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
>
|
|
419
|
-
>;
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* @name ClientInferResponsesWithHeaders
|
|
423
|
-
* Expect ClientInferResponses to include Headers object in all response types,
|
|
424
|
-
* distinguishing client-side responses from server-side responses
|
|
425
|
-
*/
|
|
426
|
-
type ClientInferResponsesWithHeaders = ClientInferResponses<typeof contract>;
|
|
427
|
-
type TestClientInferResponsesWithHeaders = Expect<
|
|
428
|
-
Equal<
|
|
429
|
-
ClientInferResponsesWithHeaders,
|
|
430
|
-
{
|
|
431
|
-
getPost:
|
|
432
|
-
| {
|
|
433
|
-
status: 200;
|
|
434
|
-
body: { title: string; id: number; content: string };
|
|
435
|
-
headers: Headers;
|
|
436
|
-
}
|
|
437
|
-
| {
|
|
438
|
-
status: 404;
|
|
439
|
-
body: { message: string };
|
|
440
|
-
headers: Headers;
|
|
441
|
-
}
|
|
442
|
-
| {
|
|
443
|
-
status: Exclude<HTTPStatusCode, 200 | 404>;
|
|
444
|
-
body: unknown;
|
|
445
|
-
headers: Headers;
|
|
446
|
-
};
|
|
447
|
-
createPost:
|
|
448
|
-
| {
|
|
449
|
-
status: 201;
|
|
450
|
-
body: { id: number; title: string; content: string };
|
|
451
|
-
headers: Headers;
|
|
452
|
-
}
|
|
453
|
-
| {
|
|
454
|
-
status: Exclude<HTTPStatusCode, 201>;
|
|
455
|
-
body: unknown;
|
|
456
|
-
headers: Headers;
|
|
457
|
-
};
|
|
458
|
-
uploadImage:
|
|
459
|
-
| {
|
|
460
|
-
status: 201;
|
|
461
|
-
body: 'Image uploaded successfully';
|
|
462
|
-
headers: Headers;
|
|
463
|
-
}
|
|
464
|
-
| {
|
|
465
|
-
status: 500;
|
|
466
|
-
body: 'Image upload failed';
|
|
467
|
-
headers: Headers;
|
|
468
|
-
}
|
|
469
|
-
| {
|
|
470
|
-
status: Exclude<HTTPStatusCode, 201 | 500>;
|
|
471
|
-
body: unknown;
|
|
472
|
-
headers: Headers;
|
|
473
|
-
};
|
|
474
|
-
nested: {
|
|
475
|
-
getComments:
|
|
476
|
-
| {
|
|
477
|
-
status: 200;
|
|
478
|
-
body: { comments: { id: number; content: string }[] };
|
|
479
|
-
headers: Headers;
|
|
480
|
-
}
|
|
481
|
-
| {
|
|
482
|
-
status: 404;
|
|
483
|
-
body: null;
|
|
484
|
-
headers: Headers;
|
|
485
|
-
}
|
|
486
|
-
| {
|
|
487
|
-
status: Exclude<HTTPStatusCode, 200 | 404>;
|
|
488
|
-
body: unknown;
|
|
489
|
-
headers: Headers;
|
|
490
|
-
};
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
>
|
|
494
|
-
>;
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* @name ServerInferResponseBodySpecificEndpoint
|
|
498
|
-
* Expect ServerInferResponseBody to extract the response body type for a specific endpoint and status code,
|
|
499
|
-
* including optional properties from Zod defaults
|
|
500
|
-
*/
|
|
501
|
-
type ServerInferResponseBodySpecificEndpoint = ServerInferResponseBody<
|
|
502
|
-
typeof contract.getPost,
|
|
503
|
-
200
|
|
504
|
-
>;
|
|
505
|
-
type TestServerInferResponseBodySpecificEndpoint = Expect<
|
|
506
|
-
Equal<
|
|
507
|
-
ServerInferResponseBodySpecificEndpoint,
|
|
508
|
-
{ title?: string | undefined; id: number; content: string }
|
|
509
|
-
>
|
|
510
|
-
>;
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* @name ClientInferResponseBodySpecificEndpoint
|
|
514
|
-
* Expect ClientInferResponseBody to extract the response body type for a specific endpoint and status code,
|
|
515
|
-
* with required properties (no optional from Zod defaults on client side)
|
|
516
|
-
*/
|
|
517
|
-
type ClientInferResponseBodySpecificEndpoint = ClientInferResponseBody<
|
|
518
|
-
typeof contract.getPost,
|
|
519
|
-
200
|
|
520
|
-
>;
|
|
521
|
-
type TestClientInferResponseBodySpecificEndpoint = Expect<
|
|
522
|
-
Equal<
|
|
523
|
-
ClientInferResponseBodySpecificEndpoint,
|
|
524
|
-
{ title: string; id: number; content: string }
|
|
525
|
-
>
|
|
526
|
-
>;
|
|
527
|
-
|
|
528
|
-
const commonErrors = c.responses({
|
|
529
|
-
400: c.type<{ message: string }>(),
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
const contractWithCommonErrors = c.router({
|
|
533
|
-
get: {
|
|
534
|
-
method: 'GET',
|
|
535
|
-
path: '/',
|
|
536
|
-
responses: {
|
|
537
|
-
...commonErrors,
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* @name ClientInferResponseBodyWithCommonResponses
|
|
544
|
-
* Expect ClientInferResponseBody to work with common response definitions,
|
|
545
|
-
* extracting the correct body type from shared response schemas
|
|
546
|
-
*/
|
|
547
|
-
type ClientInferResponseBodyWithCommonResponses = ClientInferResponseBody<
|
|
548
|
-
typeof contractWithCommonErrors.get,
|
|
549
|
-
400
|
|
550
|
-
>;
|
|
551
|
-
type TestClientInferResponseBodyWithCommonResponses = Expect<
|
|
552
|
-
Equal<ClientInferResponseBodyWithCommonResponses, { message: string }>
|
|
553
|
-
>;
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* @name ServerInferRequestWithTransforms
|
|
557
|
-
* Expect ServerInferRequest to include transformed types for path params and headers,
|
|
558
|
-
* showing how Zod transforms affect the inferred server-side types
|
|
559
|
-
*/
|
|
560
|
-
type ServerInferRequestWithTransforms = ServerInferRequest<typeof contract>;
|
|
561
|
-
type TestServerInferRequestWithTransforms = Expect<
|
|
562
|
-
Equal<
|
|
563
|
-
ServerInferRequestWithTransforms,
|
|
564
|
-
{
|
|
565
|
-
getPost: {
|
|
566
|
-
query: { includeComments: boolean };
|
|
567
|
-
params: { id: number };
|
|
568
|
-
headers: {
|
|
569
|
-
Authorization: string;
|
|
570
|
-
age: number | undefined;
|
|
571
|
-
};
|
|
572
|
-
};
|
|
573
|
-
createPost: {
|
|
574
|
-
body: { title: string; content: string };
|
|
575
|
-
headers: {
|
|
576
|
-
Authorization: string;
|
|
577
|
-
age: number | undefined;
|
|
578
|
-
};
|
|
579
|
-
};
|
|
580
|
-
uploadImage: {
|
|
581
|
-
body: {};
|
|
582
|
-
headers: {
|
|
583
|
-
Authorization: string;
|
|
584
|
-
age: number | undefined;
|
|
585
|
-
};
|
|
586
|
-
};
|
|
587
|
-
nested: {
|
|
588
|
-
getComments: {
|
|
589
|
-
params: { id: number };
|
|
590
|
-
headers: {
|
|
591
|
-
'pagination-page': number;
|
|
592
|
-
Authorization: string;
|
|
593
|
-
age: number | undefined;
|
|
594
|
-
};
|
|
595
|
-
};
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
>
|
|
599
|
-
>;
|
|
600
|
-
|
|
601
|
-
/**
|
|
602
|
-
* @name ServerInferRequestWithOverriddenHeaders
|
|
603
|
-
* Expect ServerInferRequest to merge contract headers with overridden server headers,
|
|
604
|
-
* allowing server-specific header types to be injected
|
|
605
|
-
*/
|
|
606
|
-
type ServerInferRequestWithOverriddenHeaders = ServerInferRequest<
|
|
607
|
-
typeof contract,
|
|
608
|
-
{
|
|
609
|
-
authorization: string | undefined;
|
|
610
|
-
age: string | undefined;
|
|
611
|
-
'content-type': string | undefined;
|
|
612
|
-
}
|
|
613
|
-
>;
|
|
614
|
-
type TestServerInferRequestWithOverriddenHeaders = Expect<
|
|
615
|
-
Equal<
|
|
616
|
-
ServerInferRequestWithOverriddenHeaders,
|
|
617
|
-
{
|
|
618
|
-
getPost: {
|
|
619
|
-
query: { includeComments: boolean };
|
|
620
|
-
params: { id: number };
|
|
621
|
-
headers: {
|
|
622
|
-
Authorization: string;
|
|
623
|
-
age: number | undefined;
|
|
624
|
-
'content-type': string | undefined;
|
|
625
|
-
};
|
|
626
|
-
};
|
|
627
|
-
createPost: {
|
|
628
|
-
body: { title: string; content: string };
|
|
629
|
-
headers: {
|
|
630
|
-
Authorization: string;
|
|
631
|
-
age: number | undefined;
|
|
632
|
-
'content-type': string | undefined;
|
|
633
|
-
};
|
|
634
|
-
};
|
|
635
|
-
uploadImage: {
|
|
636
|
-
body: {};
|
|
637
|
-
headers: {
|
|
638
|
-
Authorization: string;
|
|
639
|
-
age: number | undefined;
|
|
640
|
-
'content-type': string | undefined;
|
|
641
|
-
};
|
|
642
|
-
};
|
|
643
|
-
nested: {
|
|
644
|
-
getComments: {
|
|
645
|
-
params: { id: number };
|
|
646
|
-
headers: {
|
|
647
|
-
'pagination-page': number;
|
|
648
|
-
Authorization: string;
|
|
649
|
-
age: number | undefined;
|
|
650
|
-
'content-type': string | undefined;
|
|
651
|
-
};
|
|
652
|
-
};
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
>
|
|
656
|
-
>;
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* @name ClientInferRequestWithClientOptions
|
|
660
|
-
* Expect ClientInferRequest to include client-specific options like fetchOptions and extraHeaders,
|
|
661
|
-
* showing the difference between client and server request inference
|
|
662
|
-
*/
|
|
663
|
-
type ClientInferRequestWithClientOptions = ClientInferRequest<
|
|
664
|
-
typeof contract
|
|
665
|
-
>;
|
|
666
|
-
type TestClientInferRequestWithClientOptions = Expect<
|
|
667
|
-
Equal<
|
|
668
|
-
ClientInferRequestWithClientOptions,
|
|
669
|
-
{
|
|
670
|
-
getPost: {
|
|
671
|
-
query: { includeComments?: boolean | undefined };
|
|
672
|
-
params: { id: string };
|
|
673
|
-
headers: {
|
|
674
|
-
age?: unknown;
|
|
675
|
-
authorization: string;
|
|
676
|
-
};
|
|
677
|
-
extraHeaders?: {
|
|
678
|
-
authorization?: undefined;
|
|
679
|
-
age?: undefined;
|
|
680
|
-
} & Record<string, string>;
|
|
681
|
-
fetchOptions?: FetchOptions;
|
|
682
|
-
overrideClientOptions?: Partial<OverridableClientArgs>;
|
|
683
|
-
cache?: FetchOptions['cache'];
|
|
684
|
-
};
|
|
685
|
-
createPost: {
|
|
686
|
-
body: { title: string; content: string };
|
|
687
|
-
headers: {
|
|
688
|
-
age?: unknown;
|
|
689
|
-
authorization: string;
|
|
690
|
-
};
|
|
691
|
-
extraHeaders?: {
|
|
692
|
-
authorization?: undefined;
|
|
693
|
-
age?: undefined;
|
|
694
|
-
} & Record<string, string>;
|
|
695
|
-
fetchOptions?: FetchOptions;
|
|
696
|
-
overrideClientOptions?: Partial<OverridableClientArgs>;
|
|
697
|
-
cache?: FetchOptions['cache'];
|
|
698
|
-
};
|
|
699
|
-
uploadImage: {
|
|
700
|
-
body:
|
|
701
|
-
| {
|
|
702
|
-
image: File;
|
|
703
|
-
images: File[];
|
|
704
|
-
}
|
|
705
|
-
| FormData;
|
|
706
|
-
headers: {
|
|
707
|
-
age?: unknown;
|
|
708
|
-
authorization: string;
|
|
709
|
-
};
|
|
710
|
-
extraHeaders?: {
|
|
711
|
-
authorization?: undefined;
|
|
712
|
-
age?: undefined;
|
|
713
|
-
} & Record<string, string>;
|
|
714
|
-
fetchOptions?: FetchOptions;
|
|
715
|
-
overrideClientOptions?: Partial<OverridableClientArgs>;
|
|
716
|
-
cache?: FetchOptions['cache'];
|
|
717
|
-
};
|
|
718
|
-
nested: {
|
|
719
|
-
getComments: {
|
|
720
|
-
params: { id: string };
|
|
721
|
-
headers: {
|
|
722
|
-
authorization: string;
|
|
723
|
-
'pagination-page': string;
|
|
724
|
-
age?: unknown;
|
|
725
|
-
};
|
|
726
|
-
extraHeaders?: {
|
|
727
|
-
authorization?: undefined;
|
|
728
|
-
'pagination-page'?: undefined;
|
|
729
|
-
age?: undefined;
|
|
730
|
-
} & Record<string, string>;
|
|
731
|
-
fetchOptions?: FetchOptions;
|
|
732
|
-
overrideClientOptions?: Partial<OverridableClientArgs>;
|
|
733
|
-
cache?: FetchOptions['cache'];
|
|
734
|
-
};
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
>
|
|
738
|
-
>;
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
* @name ClientInferRequestWithoutBaseHeaders
|
|
742
|
-
* Expect ClientInferRequest to only include extraHeaders when no base headers are defined,
|
|
743
|
-
* demonstrating how base headers affect the client request interface
|
|
744
|
-
*/
|
|
745
|
-
type ClientInferRequestWithoutBaseHeaders = Omit<
|
|
746
|
-
ClientInferRequest<typeof headerlessContract>['getPost'],
|
|
747
|
-
'next'
|
|
748
|
-
>;
|
|
749
|
-
type TestClientInferRequestWithoutBaseHeaders = Expect<
|
|
750
|
-
Equal<
|
|
751
|
-
ClientInferRequestWithoutBaseHeaders,
|
|
752
|
-
{
|
|
753
|
-
query: { includeComments?: boolean | undefined };
|
|
754
|
-
params: { id: string };
|
|
755
|
-
extraHeaders?: Record<string, string>;
|
|
756
|
-
fetchOptions?: FetchOptions;
|
|
757
|
-
overrideClientOptions?: Partial<OverridableClientArgs>;
|
|
758
|
-
cache?: FetchOptions['cache'];
|
|
759
|
-
}
|
|
760
|
-
>
|
|
761
|
-
>;
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* @name InferResponseDefinedStatusCodesBasic
|
|
765
|
-
* Expect InferResponseDefinedStatusCodes to extract all explicitly defined status codes
|
|
766
|
-
* from an endpoint's response definitions
|
|
767
|
-
*/
|
|
768
|
-
type InferResponseDefinedStatusCodesBasic = InferResponseDefinedStatusCodes<
|
|
769
|
-
typeof contract.getPost
|
|
770
|
-
>;
|
|
771
|
-
type TestInferResponseDefinedStatusCodesBasic = Expect<
|
|
772
|
-
Equal<InferResponseDefinedStatusCodesBasic, 200 | 404>
|
|
773
|
-
>;
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* @name InferResponseDefinedStatusCodesFiltered
|
|
777
|
-
* Expect InferResponseDefinedStatusCodes to filter defined status codes by a specific type,
|
|
778
|
-
* only returning successful status codes that are explicitly defined
|
|
779
|
-
*/
|
|
780
|
-
type InferResponseDefinedStatusCodesFiltered =
|
|
781
|
-
InferResponseDefinedStatusCodes<
|
|
782
|
-
typeof contract.getPost,
|
|
783
|
-
SuccessfulHttpStatusCode
|
|
784
|
-
>;
|
|
785
|
-
type TestInferResponseDefinedStatusCodesFiltered = Expect<
|
|
786
|
-
Equal<InferResponseDefinedStatusCodesFiltered, 200>
|
|
787
|
-
>;
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* @name InferResponseDefinedStatusCodesErrorsOnly
|
|
791
|
-
* Expect InferResponseDefinedStatusCodes to filter defined status codes by error type,
|
|
792
|
-
* only returning error status codes that are explicitly defined
|
|
793
|
-
*/
|
|
794
|
-
type InferResponseDefinedStatusCodesErrorsOnly =
|
|
795
|
-
InferResponseDefinedStatusCodes<
|
|
796
|
-
typeof contract.getPost,
|
|
797
|
-
ErrorHttpStatusCode
|
|
798
|
-
>;
|
|
799
|
-
type TestInferResponseDefinedStatusCodesErrorsOnly = Expect<
|
|
800
|
-
Equal<InferResponseDefinedStatusCodesErrorsOnly, 404>
|
|
801
|
-
>;
|
|
802
|
-
|
|
803
|
-
/**
|
|
804
|
-
* @name InferResponseUndefinedStatusCodesBasic
|
|
805
|
-
* Expect InferResponseUndefinedStatusCodes to extract all status codes that are NOT explicitly defined,
|
|
806
|
-
* representing the complement of defined status codes
|
|
807
|
-
*/
|
|
808
|
-
type InferResponseUndefinedStatusCodesBasic =
|
|
809
|
-
InferResponseUndefinedStatusCodes<typeof contract.getPost>;
|
|
810
|
-
type TestInferResponseUndefinedStatusCodesBasic = Expect<
|
|
811
|
-
Equal<
|
|
812
|
-
InferResponseUndefinedStatusCodesBasic,
|
|
813
|
-
Exclude<HTTPStatusCode, 200 | 404>
|
|
814
|
-
>
|
|
815
|
-
>;
|
|
816
|
-
|
|
817
|
-
/**
|
|
818
|
-
* @name InferResponseUndefinedStatusCodesSuccessFiltered
|
|
819
|
-
* Expect InferResponseUndefinedStatusCodes to extract undefined successful status codes,
|
|
820
|
-
* showing which successful codes are not explicitly defined in the endpoint
|
|
821
|
-
*/
|
|
822
|
-
type InferResponseUndefinedStatusCodesSuccessFiltered =
|
|
823
|
-
InferResponseUndefinedStatusCodes<
|
|
824
|
-
typeof contract.getPost,
|
|
825
|
-
SuccessfulHttpStatusCode
|
|
826
|
-
>;
|
|
827
|
-
type TestInferResponseUndefinedStatusCodesSuccessFiltered = Expect<
|
|
828
|
-
Equal<
|
|
829
|
-
InferResponseUndefinedStatusCodesSuccessFiltered,
|
|
830
|
-
Exclude<SuccessfulHttpStatusCode, 200>
|
|
831
|
-
>
|
|
832
|
-
>;
|
|
833
|
-
|
|
834
|
-
/**
|
|
835
|
-
* @name InferResponseUndefinedStatusCodesErrorFiltered
|
|
836
|
-
* Expect InferResponseUndefinedStatusCodes to extract undefined error status codes,
|
|
837
|
-
* showing which error codes are not explicitly defined in the endpoint
|
|
838
|
-
*/
|
|
839
|
-
type InferResponseUndefinedStatusCodesErrorFiltered =
|
|
840
|
-
InferResponseUndefinedStatusCodes<
|
|
841
|
-
typeof contract.getPost,
|
|
842
|
-
ErrorHttpStatusCode
|
|
843
|
-
>;
|
|
844
|
-
type TestInferResponseUndefinedStatusCodesErrorFiltered = Expect<
|
|
845
|
-
Equal<
|
|
846
|
-
InferResponseUndefinedStatusCodesErrorFiltered,
|
|
847
|
-
Exclude<ErrorHttpStatusCode, 404>
|
|
848
|
-
>
|
|
849
|
-
>;
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
describe('ClientInferRequest', () => {
|
|
853
|
-
it('standard schema - optional headers', () => {
|
|
854
|
-
const contract = c.router({
|
|
855
|
-
getPost: {
|
|
856
|
-
method: 'GET',
|
|
857
|
-
path: '/post',
|
|
858
|
-
headers: {
|
|
859
|
-
'x-foo': v.optional(v.string()),
|
|
860
|
-
},
|
|
861
|
-
responses: {
|
|
862
|
-
200: c.noBody(),
|
|
863
|
-
},
|
|
864
|
-
},
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
const client = initClient(contract, { baseUrl: '' });
|
|
868
|
-
const testUsage = () => client.getPost({ headers: { 'x-foo': 'string' } });
|
|
869
|
-
|
|
870
|
-
type Actual = ClientInferRequest<typeof contract.getPost>['headers'];
|
|
871
|
-
type TestResult = Expect<
|
|
872
|
-
Equal<
|
|
873
|
-
Actual,
|
|
874
|
-
{
|
|
875
|
-
'x-foo'?: string | undefined;
|
|
876
|
-
}
|
|
877
|
-
>
|
|
878
|
-
>;
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
it('headers zod coerce', () => {
|
|
882
|
-
const contract = c.router({
|
|
883
|
-
getPost: {
|
|
884
|
-
method: 'GET',
|
|
885
|
-
path: '/post',
|
|
886
|
-
headers: {
|
|
887
|
-
'x-foo': z.coerce.number().optional(),
|
|
888
|
-
},
|
|
889
|
-
responses: {
|
|
890
|
-
200: c.noBody(),
|
|
891
|
-
},
|
|
892
|
-
},
|
|
893
|
-
});
|
|
894
|
-
|
|
895
|
-
const client = initClient(contract, { baseUrl: '' });
|
|
896
|
-
const testUsage = () => client.getPost({ headers: { 'x-foo': 1 } });
|
|
897
|
-
|
|
898
|
-
type Actual = ClientInferRequest<typeof contract.getPost>['headers'];
|
|
899
|
-
type TestResult = Expect<Equal<Actual, { 'x-foo'?: unknown }>>;
|
|
900
|
-
});
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
describe('UnknownOrUndefinedObjectValuesToOptionalKeys', () => {
|
|
904
|
-
it('should make undefined key optional', () => {
|
|
905
|
-
type Actual = Prettify<
|
|
906
|
-
UnknownOrUndefinedObjectValuesToOptionalKeys<{
|
|
907
|
-
foo: string | undefined;
|
|
908
|
-
}>
|
|
909
|
-
>;
|
|
910
|
-
type Assert = Expect<Equal<Actual, { foo?: string | undefined }>>;
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
it('should make unknown key optional', () => {
|
|
914
|
-
type Actual = Prettify<
|
|
915
|
-
UnknownOrUndefinedObjectValuesToOptionalKeys<{
|
|
916
|
-
foo: unknown;
|
|
917
|
-
}>
|
|
918
|
-
>;
|
|
919
|
-
type Assert = Expect<Equal<Actual, { foo?: unknown }>>;
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
it('should not affect a non-empty object', () => {
|
|
923
|
-
type Actual = Prettify<
|
|
924
|
-
UnknownOrUndefinedObjectValuesToOptionalKeys<{
|
|
925
|
-
foo: string;
|
|
926
|
-
}>
|
|
927
|
-
>;
|
|
928
|
-
type Assert = Expect<Equal<Actual, { foo: string }>>;
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
it('should not affect an empty object', () => {
|
|
932
|
-
type Actual = Prettify<UnknownOrUndefinedObjectValuesToOptionalKeys<{}>>;
|
|
933
|
-
type Assert = Expect<Equal<Actual, {}>>;
|
|
934
|
-
});
|
|
935
|
-
});
|