@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +75 -10
  3. package/index.cjs.d.ts +1 -0
  4. package/index.cjs.default.js +1 -0
  5. package/index.cjs.js +807 -0
  6. package/index.cjs.mjs +2 -0
  7. package/index.esm.js +762 -0
  8. package/package.json +13 -3
  9. package/src/lib/client.d.ts +107 -0
  10. package/src/lib/dsl.d.ts +222 -0
  11. package/src/lib/infer-types.d.ts +78 -0
  12. package/src/lib/paths.d.ts +30 -0
  13. package/src/lib/query.d.ts +17 -0
  14. package/src/lib/response-error.d.ts +20 -0
  15. package/src/lib/response-validation-error.d.ts +14 -0
  16. package/src/lib/server.d.ts +18 -0
  17. package/src/lib/standard-schema-utils.d.ts +68 -0
  18. package/src/lib/standard-schema.d.ts +55 -0
  19. package/src/lib/status-codes.d.ts +6 -0
  20. package/src/lib/{test-helpers.ts → test-helpers.d.ts} +1 -6
  21. package/src/lib/type-guards.d.ts +12 -0
  22. package/src/lib/type-utils.d.ts +96 -0
  23. package/src/lib/unknown-status-error.d.ts +10 -0
  24. package/src/lib/validation-error.d.ts +11 -0
  25. package/.babelrc +0 -10
  26. package/.eslintrc.json +0 -21
  27. package/LICENCE +0 -21
  28. package/jest.config.ts +0 -16
  29. package/project.json +0 -51
  30. package/src/lib/client.spec.ts +0 -1330
  31. package/src/lib/client.ts +0 -481
  32. package/src/lib/dsl.spec.ts +0 -1308
  33. package/src/lib/dsl.ts +0 -472
  34. package/src/lib/fetch.spec.ts +0 -102
  35. package/src/lib/infer-types.spec.ts +0 -935
  36. package/src/lib/infer-types.ts +0 -282
  37. package/src/lib/paths.spec.ts +0 -138
  38. package/src/lib/paths.ts +0 -61
  39. package/src/lib/query.spec.ts +0 -329
  40. package/src/lib/query.ts +0 -114
  41. package/src/lib/response-error.spec.ts +0 -67
  42. package/src/lib/response-error.ts +0 -61
  43. package/src/lib/response-validation-error.ts +0 -24
  44. package/src/lib/server.spec.ts +0 -163
  45. package/src/lib/server.ts +0 -83
  46. package/src/lib/standard-schema-utils.spec.ts +0 -218
  47. package/src/lib/standard-schema-utils.ts +0 -280
  48. package/src/lib/standard-schema.ts +0 -71
  49. package/src/lib/status-codes.ts +0 -75
  50. package/src/lib/type-guards.spec.ts +0 -355
  51. package/src/lib/type-guards.ts +0 -99
  52. package/src/lib/type-utils.spec.ts +0 -59
  53. package/src/lib/type-utils.ts +0 -234
  54. package/src/lib/unknown-status-error.ts +0 -15
  55. package/src/lib/validation-error.ts +0 -36
  56. package/tsconfig.json +0 -22
  57. package/tsconfig.lib.json +0 -10
  58. package/tsconfig.spec.json +0 -9
  59. package/typedoc.json +0 -5
  60. /package/src/{index.ts → index.d.ts} +0 -0
@@ -1,1308 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import { z } from 'zod';
3
- import {
4
- initContract,
5
- ContractOtherResponse,
6
- ContractPlainType,
7
- ContractPlainTypeRuntimeSymbol,
8
- ContractNoBodyType,
9
- MergeHeaders,
10
- AppRoute,
11
- InferHeadersInput,
12
- } from './dsl';
13
- import type { Equal, Expect } from './test-helpers';
14
- import { Prettify } from './type-utils';
15
- import * as v from 'valibot';
16
-
17
- const c = initContract();
18
-
19
- describe('contract', () => {
20
- it('should be typed correctly', () => {
21
- const contract = c.router({
22
- getPost: {
23
- method: 'GET',
24
- path: '/posts/:id',
25
- responses: {
26
- 200: z.object({
27
- id: z.number(),
28
- }),
29
- },
30
- },
31
- });
32
-
33
- type ContractShape = typeof contract;
34
- type TestContractShape = Expect<
35
- Equal<
36
- ContractShape,
37
- {
38
- getPost: {
39
- method: 'GET';
40
- path: '/posts/:id';
41
- responses: {
42
- 200: z.ZodObject<
43
- {
44
- id: z.ZodNumber;
45
- },
46
- z.core.$strip
47
- >;
48
- };
49
- };
50
- }
51
- >
52
- >;
53
- });
54
-
55
- it('should be typed correctly with nested routers', () => {
56
- const contract = c.router({
57
- posts: {
58
- getPost: {
59
- method: 'GET',
60
- path: '/posts/:id',
61
- responses: {
62
- 200: z.object({
63
- id: z.number(),
64
- }),
65
- },
66
- },
67
- },
68
- });
69
-
70
- type ContractShape = Expect<
71
- Equal<
72
- typeof contract,
73
- {
74
- posts: {
75
- getPost: {
76
- method: 'GET';
77
- path: '/posts/:id';
78
- responses: {
79
- 200: z.ZodObject<
80
- {
81
- id: z.ZodNumber;
82
- },
83
- z.core.$strip
84
- >;
85
- };
86
- };
87
- };
88
- }
89
- >
90
- >;
91
- });
92
-
93
- it('should be typed correctly with headers', () => {
94
- const contract = c.router({
95
- posts: {
96
- getPost: {
97
- method: 'GET',
98
- path: '/posts/:id',
99
- responses: {
100
- 200: z.object({
101
- id: z.number(),
102
- }),
103
- },
104
- headers: {
105
- 'x-foo': z.string(),
106
- },
107
- },
108
- },
109
- });
110
-
111
- type ContractShape = typeof contract.posts.getPost;
112
- type TestContractShape = Expect<
113
- Equal<
114
- ContractShape,
115
- {
116
- method: 'GET';
117
- path: '/posts/:id';
118
- responses: {
119
- 200: z.ZodObject<
120
- {
121
- id: z.ZodNumber;
122
- },
123
- z.core.$strip
124
- >;
125
- };
126
- headers: {
127
- 'x-foo': z.ZodString;
128
- };
129
- }
130
- >
131
- >;
132
- });
133
-
134
- it('should be typed correctly with base headers', () => {
135
- const contract = c.router(
136
- {
137
- posts: {
138
- getPost: {
139
- method: 'GET',
140
- path: '/posts/:id',
141
- responses: {
142
- 200: z.object({
143
- id: z.number(),
144
- }),
145
- },
146
- },
147
- },
148
- },
149
- {
150
- baseHeaders: {
151
- 'x-foo': z.string(),
152
- },
153
- },
154
- );
155
-
156
- type ContractShape = Expect<
157
- Equal<
158
- typeof contract,
159
- {
160
- posts: {
161
- getPost: {
162
- method: 'GET';
163
- path: '/posts/:id';
164
- responses: {
165
- 200: z.ZodObject<
166
- {
167
- id: z.ZodNumber;
168
- },
169
- z.core.$strip
170
- >;
171
- };
172
- headers: {
173
- 'x-foo': z.ZodString;
174
- };
175
- };
176
- };
177
- }
178
- >
179
- >;
180
- });
181
-
182
- it('should be typed correctly with merged headers', () => {
183
- const contract = c.router(
184
- {
185
- posts: {
186
- getPost: {
187
- method: 'GET',
188
- path: '/posts/:id',
189
- responses: {
190
- 200: z.object({
191
- id: z.number(),
192
- }),
193
- },
194
- headers: {
195
- 'x-bar': z.string(),
196
- },
197
- },
198
- },
199
- },
200
- {
201
- baseHeaders: {
202
- 'x-foo': z.string(),
203
- },
204
- },
205
- );
206
-
207
- type ContractShape = Expect<
208
- Equal<
209
- typeof contract,
210
- {
211
- posts: {
212
- getPost: {
213
- method: 'GET';
214
- path: '/posts/:id';
215
- responses: {
216
- 200: z.ZodObject<
217
- {
218
- id: z.ZodNumber;
219
- },
220
- z.core.$strip
221
- >;
222
- };
223
- headers: {
224
- 'x-foo': z.ZodString;
225
- 'x-bar': z.ZodString;
226
- };
227
- };
228
- };
229
- }
230
- >
231
- >;
232
- });
233
-
234
- it('should be typed correctly with merged plain type headers', () => {
235
- const contract = c.router(
236
- {
237
- posts: {
238
- getPost: {
239
- method: 'GET',
240
- path: '/posts/:id',
241
- responses: {
242
- 200: z.object({
243
- id: z.number(),
244
- }),
245
- },
246
- headers: { 'x-bar': c.type<string>() },
247
- },
248
- },
249
- },
250
- {
251
- baseHeaders: { 'x-foo': c.type<string>() },
252
- },
253
- );
254
-
255
- type ContractShape = Expect<
256
- Equal<
257
- typeof contract,
258
- {
259
- posts: {
260
- getPost: {
261
- method: 'GET';
262
- path: '/posts/:id';
263
- responses: {
264
- 200: z.ZodObject<
265
- {
266
- id: z.ZodNumber;
267
- },
268
- z.core.$strip
269
- >;
270
- };
271
- headers: {
272
- 'x-foo': ContractPlainType<string>;
273
- 'x-bar': ContractPlainType<string>;
274
- };
275
- };
276
- };
277
- }
278
- >
279
- >;
280
- });
281
-
282
- it('should be typed correctly with overridden headers', () => {
283
- const contract = c.router(
284
- {
285
- posts: {
286
- getPost: {
287
- method: 'GET',
288
- path: '/posts/:id',
289
- responses: {
290
- 200: z.object({
291
- id: z.number(),
292
- }),
293
- },
294
- headers: {
295
- 'x-foo': z.string().optional(),
296
- },
297
- },
298
- },
299
- },
300
- {
301
- baseHeaders: {
302
- 'x-foo': z.string(),
303
- },
304
- },
305
- );
306
-
307
- type ContractShape = Expect<
308
- Equal<
309
- typeof contract,
310
- {
311
- posts: {
312
- getPost: {
313
- method: 'GET';
314
- path: '/posts/:id';
315
- responses: {
316
- 200: z.ZodObject<
317
- {
318
- id: z.ZodNumber;
319
- },
320
- z.core.$strip
321
- >;
322
- };
323
- headers: {
324
- 'x-foo': z.ZodOptional<z.ZodString>;
325
- };
326
- };
327
- };
328
- }
329
- >
330
- >;
331
- });
332
-
333
- it('should be typed without zod', () => {
334
- const contract = c.router({
335
- getPost: {
336
- method: 'GET',
337
- path: '/posts/:id',
338
- responses: {
339
- 200: c.type<{ id: number }>(),
340
- },
341
- },
342
- });
343
-
344
- type ContractShape = Expect<
345
- Equal<
346
- typeof contract,
347
- {
348
- getPost: {
349
- method: 'GET';
350
- path: '/posts/:id';
351
- responses: {
352
- 200: ContractPlainType<{
353
- id: number;
354
- }>;
355
- };
356
- };
357
- }
358
- >
359
- >;
360
- });
361
-
362
- it('should be typed correctly with separate query route', () => {
363
- const getPost = c.query({
364
- method: 'GET',
365
- path: '/posts/:id',
366
- responses: {
367
- 200: c.type<{ id: number }>(),
368
- },
369
- });
370
-
371
- const contract = c.router({
372
- getPost,
373
- });
374
-
375
- type ContractShape = Expect<
376
- Equal<
377
- typeof contract,
378
- {
379
- getPost: {
380
- method: 'GET';
381
- path: '/posts/:id';
382
- responses: {
383
- 200: ContractPlainType<{
384
- id: number;
385
- }>;
386
- };
387
- };
388
- }
389
- >
390
- >;
391
- });
392
-
393
- it('should be typed correctly with separate mutation route', () => {
394
- const createPost = c.mutation({
395
- method: 'POST',
396
- path: '/posts',
397
- responses: {
398
- 200: c.type<{ id: number }>(),
399
- },
400
- body: c.type<{ title: string }>(),
401
- });
402
-
403
- const contract = c.router({
404
- createPost,
405
- });
406
-
407
- type ContractShape = Expect<
408
- Equal<
409
- typeof contract,
410
- {
411
- createPost: {
412
- method: 'POST';
413
- path: '/posts';
414
- responses: {
415
- 200: ContractPlainType<{
416
- id: number;
417
- }>;
418
- };
419
- body: ContractPlainType<{
420
- title: string;
421
- }>;
422
- };
423
- }
424
- >
425
- >;
426
- });
427
-
428
- it('should be typed correctly with separate responses', () => {
429
- const responses = c.responses({
430
- 200: c.type<{ id: number }>(),
431
- });
432
-
433
- const contract = c.router({
434
- getPost: {
435
- method: 'GET',
436
- path: '/posts/:id',
437
- responses,
438
- },
439
- });
440
-
441
- type ContractShape = Expect<
442
- Equal<
443
- typeof contract,
444
- {
445
- getPost: {
446
- method: 'GET';
447
- path: '/posts/:id';
448
- responses: {
449
- 200: ContractPlainType<{
450
- id: number;
451
- }>;
452
- };
453
- };
454
- }
455
- >
456
- >;
457
- });
458
-
459
- it('should be typed correctly with separate responses with spread', () => {
460
- const responses = c.responses({
461
- 200: c.type<{ id: number }>(),
462
- });
463
-
464
- const contract = c.router({
465
- getPost: {
466
- method: 'GET',
467
- path: '/posts/:id',
468
- responses: {
469
- ...responses,
470
- },
471
- },
472
- });
473
-
474
- type ContractShape = Expect<
475
- Equal<
476
- typeof contract,
477
- {
478
- getPost: {
479
- method: 'GET';
480
- path: '/posts/:id';
481
- responses: {
482
- 200: ContractPlainType<{
483
- id: number;
484
- }>;
485
- };
486
- };
487
- }
488
- >
489
- >;
490
- });
491
-
492
- it('should add strictStatusCodes=true option to routes', () => {
493
- const contract = c.router(
494
- {
495
- getPost: {
496
- method: 'GET',
497
- path: '/posts/:id',
498
- responses: {
499
- 200: c.type<{ id: number }>(),
500
- },
501
- },
502
- },
503
- {
504
- strictStatusCodes: true,
505
- },
506
- );
507
-
508
- expect(contract.getPost.strictStatusCodes).toStrictEqual(true);
509
-
510
- type ContractShape = Expect<
511
- Equal<
512
- Pick<typeof contract.getPost, 'strictStatusCodes'>,
513
- {
514
- strictStatusCodes: true;
515
- }
516
- >
517
- >;
518
- });
519
-
520
- it('should add strictStatusCodes=false option to routes', () => {
521
- const contract = c.router(
522
- {
523
- getPost: {
524
- method: 'GET',
525
- path: '/posts/:id',
526
- responses: {
527
- 200: c.type<{ id: number }>(),
528
- },
529
- },
530
- },
531
- {
532
- strictStatusCodes: false,
533
- },
534
- );
535
-
536
- expect(contract.getPost.strictStatusCodes).toStrictEqual(false);
537
-
538
- type ContractShape = Expect<
539
- Equal<
540
- Pick<typeof contract.getPost, 'strictStatusCodes'>,
541
- {
542
- strictStatusCodes: false;
543
- }
544
- >
545
- >;
546
- });
547
-
548
- it('should merge strictStatusCodes options correctly is route is true', () => {
549
- const contract = c.router(
550
- {
551
- getPost: {
552
- method: 'GET',
553
- path: '/posts/:id',
554
- responses: {
555
- 200: c.type<{ id: number }>(),
556
- },
557
- strictStatusCodes: true,
558
- },
559
- },
560
- {
561
- strictStatusCodes: false,
562
- },
563
- );
564
-
565
- expect(contract.getPost.strictStatusCodes).toStrictEqual(true);
566
-
567
- type ContractShape = Expect<
568
- Equal<
569
- Pick<typeof contract.getPost, 'strictStatusCodes'>,
570
- {
571
- strictStatusCodes: true;
572
- }
573
- >
574
- >;
575
- });
576
-
577
- it('should merge strictStatusCodes options correctly if route is false', () => {
578
- const contract = c.router(
579
- {
580
- getPost: {
581
- method: 'GET',
582
- path: '/posts/:id',
583
- responses: {
584
- 200: c.type<{ id: number }>(),
585
- },
586
- strictStatusCodes: false,
587
- },
588
- },
589
- {
590
- strictStatusCodes: true,
591
- },
592
- );
593
-
594
- expect(contract.getPost.strictStatusCodes).toStrictEqual(false);
595
-
596
- type ContractShape = Expect<
597
- Equal<
598
- Pick<typeof contract.getPost, 'strictStatusCodes'>,
599
- {
600
- strictStatusCodes: false;
601
- }
602
- >
603
- >;
604
- });
605
-
606
- it('should merge metadata options from router to its routes', () => {
607
- const contract = c.router(
608
- {
609
- getPost: {
610
- method: 'GET',
611
- path: '/posts/:id',
612
- responses: {
613
- 200: c.type<{ id: number }>(),
614
- },
615
- },
616
- deletePost: {
617
- method: 'DELETE',
618
- path: '/posts/:id',
619
- body: c.type<undefined>(),
620
- metadata: {
621
- requireAuth: false,
622
- headerName: 'x-authorization',
623
- },
624
- responses: {
625
- 200: c.type<{ id: number }>(),
626
- },
627
- },
628
- },
629
- {
630
- metadata: {
631
- requireAuth: true,
632
- },
633
- },
634
- );
635
-
636
- expect(contract.getPost.metadata).toStrictEqual({
637
- requireAuth: true,
638
- });
639
-
640
- expect(contract.deletePost.metadata).toStrictEqual({
641
- requireAuth: false,
642
- headerName: 'x-authorization',
643
- });
644
-
645
- type MetadataShape = Expect<
646
- Equal<
647
- typeof contract.getPost.metadata,
648
- {
649
- requireAuth: boolean;
650
- }
651
- >
652
- >;
653
-
654
- type MetadataShape2 = Expect<
655
- Equal<
656
- Prettify<typeof contract.deletePost.metadata>,
657
- {
658
- requireAuth: boolean;
659
- headerName: string;
660
- }
661
- >
662
- >;
663
- });
664
-
665
- describe('pathPrefix', () => {
666
- it('Should recursively apply pathPrefix to path', () => {
667
- const postsContractNested = c.router(
668
- {
669
- getPost: {
670
- path: '/:id',
671
- method: 'GET',
672
- responses: { 200: c.type<{ id: string }>() },
673
- },
674
- },
675
- { pathPrefix: '/posts' },
676
- );
677
- const postsContract = c.router(
678
- {
679
- posts: postsContractNested,
680
- },
681
- { pathPrefix: '/v1' },
682
- );
683
- expect(postsContractNested.getPost.path).toStrictEqual('/posts/:id');
684
- expect(postsContract.posts.getPost.path).toStrictEqual('/v1/posts/:id');
685
-
686
- type PostsContractNestedShape = Expect<
687
- Equal<
688
- typeof postsContractNested,
689
- {
690
- getPost: {
691
- path: '/posts/:id';
692
- method: 'GET';
693
- responses: { 200: ContractPlainType<{ id: string }> };
694
- };
695
- }
696
- >
697
- >;
698
-
699
- type PostsContractShape = Expect<
700
- Equal<
701
- typeof postsContract,
702
- {
703
- posts: {
704
- getPost: {
705
- path: '/v1/posts/:id';
706
- method: 'GET';
707
- responses: { 200: ContractPlainType<{ id: string }> };
708
- };
709
- };
710
- }
711
- >
712
- >;
713
- });
714
- });
715
-
716
- describe('validateResponseOnClient', () => {
717
- it('Should recursively apply validateResponseOnClient to routes', () => {
718
- const postsContractNested = c.router({
719
- getPost: {
720
- path: '/:id',
721
- method: 'GET',
722
- responses: { 200: c.type<{ id: string }>() },
723
- },
724
- });
725
- const postsContract = c.router(
726
- {
727
- posts: postsContractNested,
728
- },
729
- { validateResponseOnClient: true },
730
- );
731
- expect(postsContractNested.getPost).toHaveProperty(
732
- 'validateResponseOnClient',
733
- undefined,
734
- );
735
- expect(postsContract.posts.getPost).toHaveProperty(
736
- 'validateResponseOnClient',
737
- true,
738
- );
739
- });
740
-
741
- it('Should not override validateResponseOnClient if set on nested router', () => {
742
- const postsContractNested = c.router(
743
- {
744
- getPost: {
745
- path: '/:id',
746
- method: 'GET',
747
- responses: { 200: c.type<{ id: string }>() },
748
- },
749
- },
750
- { validateResponseOnClient: false },
751
- );
752
- const postsContract = c.router(
753
- {
754
- posts: postsContractNested,
755
- },
756
- { validateResponseOnClient: true },
757
- );
758
- expect(postsContractNested.getPost).toHaveProperty(
759
- 'validateResponseOnClient',
760
- false,
761
- );
762
- expect(postsContract.posts.getPost).toHaveProperty(
763
- 'validateResponseOnClient',
764
- false,
765
- );
766
- });
767
-
768
- it('Should not override validateResponseOnClient when set directly on route', () => {
769
- const postsContract = c.router(
770
- {
771
- getPost: {
772
- path: '/:id',
773
- method: 'GET',
774
- responses: { 200: c.type<{ id: string }>() },
775
- },
776
- getPostDangerously: {
777
- path: '/:id/dangerous',
778
- method: 'GET',
779
- responses: { 200: c.type<{ id: string }>() },
780
- validateResponseOnClient: false,
781
- },
782
- },
783
- { validateResponseOnClient: true },
784
- );
785
-
786
- expect(postsContract.getPost).toHaveProperty(
787
- 'validateResponseOnClient',
788
- true,
789
- );
790
- expect(postsContract.getPostDangerously).toHaveProperty(
791
- 'validateResponseOnClient',
792
- false,
793
- );
794
- });
795
- });
796
-
797
- it('should set type correctly for non-json response', () => {
798
- const contract = c.router({
799
- getCss: {
800
- method: 'GET',
801
- path: '/style.css',
802
- responses: {
803
- 200: c.otherResponse({
804
- contentType: 'text/css',
805
- body: c.type<string>(),
806
- }),
807
- },
808
- },
809
- });
810
-
811
- expect(contract.getCss.responses['200']).toEqual({
812
- contentType: 'text/css',
813
- body: ContractPlainTypeRuntimeSymbol,
814
- });
815
-
816
- type ResponseType = Expect<
817
- Equal<
818
- (typeof contract.getCss.responses)['200'],
819
- ContractOtherResponse<ContractPlainType<string>>
820
- >
821
- >;
822
- });
823
-
824
- it('should set type correctly for no body', () => {
825
- const contract = c.router({
826
- get: {
827
- method: 'GET',
828
- path: '/',
829
- responses: {
830
- 204: c.noBody(),
831
- },
832
- },
833
- });
834
-
835
- type ResponseType = Expect<
836
- Equal<(typeof contract.get.responses)['204'], ContractNoBodyType>
837
- >;
838
- });
839
-
840
- it('should be typed correctly with merged common responses', () => {
841
- const contract = c.router(
842
- {
843
- posts: {
844
- getPost: {
845
- method: 'GET',
846
- path: '/posts/:id',
847
- responses: {
848
- 200: z.object({
849
- id: z.number(),
850
- }),
851
- },
852
- },
853
- },
854
- },
855
- {
856
- commonResponses: {
857
- 404: z.object({
858
- message: z.string(),
859
- }),
860
- },
861
- },
862
- );
863
-
864
- type ContractShape = Expect<
865
- Equal<
866
- typeof contract,
867
- {
868
- posts: {
869
- getPost: {
870
- method: 'GET';
871
- path: '/posts/:id';
872
- responses: {
873
- 200: z.ZodObject<
874
- {
875
- id: z.ZodNumber;
876
- },
877
- z.core.$strip
878
- >;
879
- 404: z.ZodObject<
880
- {
881
- message: z.ZodString;
882
- },
883
- z.core.$strip
884
- >;
885
- };
886
- };
887
- };
888
- }
889
- >
890
- >;
891
- });
892
-
893
- it('should be typed correctly with merged common responses and overriding common response', () => {
894
- const contract = c.router(
895
- {
896
- posts: {
897
- getPost: {
898
- method: 'GET',
899
- path: '/posts/:id',
900
- responses: {
901
- 200: z.object({
902
- id: z.number(),
903
- }),
904
- 400: z.object({
905
- overrideReason: z.string(),
906
- }),
907
- },
908
- },
909
- },
910
- },
911
- {
912
- commonResponses: {
913
- 400: z.object({
914
- reason: z.string(),
915
- }),
916
- 404: z.object({
917
- message: z.string(),
918
- }),
919
- },
920
- },
921
- );
922
-
923
- type ContractShape = Expect<
924
- Equal<
925
- typeof contract,
926
- {
927
- posts: {
928
- getPost: {
929
- method: 'GET';
930
- path: '/posts/:id';
931
- responses: {
932
- 200: z.ZodObject<
933
- {
934
- id: z.ZodNumber;
935
- },
936
- z.core.$strip
937
- >;
938
- 400: z.ZodObject<
939
- {
940
- overrideReason: z.ZodString;
941
- },
942
- z.core.$strip
943
- >;
944
- 404: z.ZodObject<
945
- {
946
- message: z.ZodString;
947
- },
948
- z.core.$strip
949
- >;
950
- };
951
- };
952
- };
953
- }
954
- >
955
- >;
956
- });
957
- });
958
-
959
- describe('header types', () => {
960
- it('should handle two zod objects', () => {
961
- const leftSchema = {
962
- left: z.string(),
963
- };
964
-
965
- const rightSchema = {
966
- right: z.string(),
967
- };
968
-
969
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
970
- type TestResult = Expect<
971
- Equal<
972
- Result,
973
- {
974
- left: z.ZodString;
975
- right: z.ZodString;
976
- }
977
- >
978
- >;
979
- });
980
- it('should handle left being undefined', () => {
981
- const leftSchema = undefined;
982
- const rightSchema = {
983
- right: z.string(),
984
- };
985
-
986
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
987
- type TestResult = Expect<
988
- Equal<
989
- Result,
990
- {
991
- right: z.ZodString;
992
- }
993
- >
994
- >;
995
- });
996
-
997
- it('should handle right being undefined', () => {
998
- const leftSchema = {
999
- left: z.string(),
1000
- };
1001
-
1002
- const rightSchema = undefined;
1003
-
1004
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1005
- type TestResult = Expect<
1006
- Equal<
1007
- Result,
1008
- {
1009
- left: z.ZodString;
1010
- }
1011
- >
1012
- >;
1013
- });
1014
-
1015
- it('should handle both being undefined', () => {
1016
- const leftSchema = undefined;
1017
- const rightSchema = undefined;
1018
-
1019
- type Resut = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1020
- type TestResult = Expect<Equal<Resut, unknown>>;
1021
- });
1022
-
1023
- it('should handle two records', () => {
1024
- const leftSchema = {
1025
- left: z.string(),
1026
- };
1027
-
1028
- const rightSchema = {
1029
- right: z.string(),
1030
- };
1031
-
1032
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1033
-
1034
- type TestResult = Expect<
1035
- Equal<
1036
- Result,
1037
- {
1038
- left: z.ZodString;
1039
- right: z.ZodString;
1040
- }
1041
- >
1042
- >;
1043
- });
1044
-
1045
- it('should handle left undefined and right record', () => {
1046
- const leftSchema = undefined;
1047
- const rightSchema = {
1048
- right: z.string(),
1049
- };
1050
-
1051
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1052
-
1053
- type TestResult = Expect<
1054
- Equal<
1055
- Result,
1056
- {
1057
- right: z.ZodString;
1058
- }
1059
- >
1060
- >;
1061
- });
1062
-
1063
- it('should handle left record and right undefined', () => {
1064
- const leftSchema = {
1065
- left: z.string(),
1066
- };
1067
-
1068
- const rightSchema = undefined;
1069
-
1070
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1071
-
1072
- type TestResult = Expect<Equal<Result, { left: z.ZodString }>>; // left wins
1073
- });
1074
-
1075
- it('should handle right null and left record', () => {
1076
- const leftSchema = {
1077
- left: z.string(),
1078
- };
1079
-
1080
- const rightSchema = {
1081
- left: null,
1082
- };
1083
-
1084
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1085
-
1086
- type TestResult = Expect<Equal<Result, {}>>; // correctly unset 'left'
1087
- });
1088
-
1089
- it('should handle left null and right record', () => {
1090
- const leftSchema = {
1091
- left: null,
1092
- };
1093
-
1094
- const rightSchema = {
1095
- right: z.string(),
1096
- };
1097
-
1098
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1099
-
1100
- type TestResult = Expect<Equal<Result, { right: z.ZodString }>>; // right wins
1101
- });
1102
-
1103
- it('should handle both null', () => {
1104
- const leftSchema = {
1105
- left: null,
1106
- };
1107
-
1108
- const rightSchema = {
1109
- right: null,
1110
- };
1111
-
1112
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1113
-
1114
- type TestResult = Expect<Equal<Result, {}>>; // correctly unset 'left' and 'right'
1115
- });
1116
-
1117
- it('should handle both entirely undefined', () => {
1118
- const leftSchema = undefined;
1119
- const rightSchema = undefined;
1120
-
1121
- type Result = MergeHeaders<typeof leftSchema, typeof rightSchema>;
1122
-
1123
- type TestResult = Expect<Equal<Result, unknown>>;
1124
- });
1125
-
1126
- describe('InferHeadersInput', () => {
1127
- it('should handle undefined', () => {
1128
- const route = c.router({
1129
- getPost: {
1130
- method: 'GET',
1131
- path: '/posts/:id',
1132
- responses: {
1133
- 200: c.type<{ id: number }>(),
1134
- },
1135
- headers: undefined,
1136
- },
1137
- });
1138
-
1139
- type Actual = InferHeadersInput<typeof route.getPost>;
1140
- type TestResult = Expect<Equal<Actual, undefined>>;
1141
- });
1142
-
1143
- it('should handle empty object', () => {
1144
- const route = c.router({
1145
- getPost: {
1146
- method: 'GET',
1147
- path: '/posts/:id',
1148
- headers: {},
1149
- responses: {
1150
- 200: c.type<{ id: number }>(),
1151
- },
1152
- },
1153
- });
1154
-
1155
- type Actual = InferHeadersInput<typeof route.getPost>;
1156
- type TestResult = Expect<Equal<Actual, {}>>;
1157
- });
1158
-
1159
- it('should handle zod object', () => {
1160
- const route = c.router({
1161
- getPost: {
1162
- method: 'GET',
1163
- path: '/posts/:id',
1164
- headers: {
1165
- 'x-foo': z.string(),
1166
- },
1167
- responses: {
1168
- 200: c.type<{ id: number }>(),
1169
- },
1170
- },
1171
- });
1172
-
1173
- type Actual = InferHeadersInput<typeof route.getPost>;
1174
- type TestResult = Expect<Equal<Actual, { 'x-foo': string }>>;
1175
- });
1176
-
1177
- it('should lowercase keys', () => {
1178
- const route = c.router({
1179
- getPost: {
1180
- method: 'GET',
1181
- path: '/posts/:id',
1182
- headers: {
1183
- 'X-FOO': z.string(),
1184
- },
1185
- responses: {
1186
- 200: c.type<{ id: number }>(),
1187
- },
1188
- },
1189
- });
1190
-
1191
- type Actual = InferHeadersInput<typeof route.getPost>;
1192
- type TestResult = Expect<Equal<Actual, { 'x-foo': string }>>;
1193
- });
1194
-
1195
- it('should handle standard schema', () => {
1196
- const route = c.router({
1197
- getPost: {
1198
- method: 'GET',
1199
- path: '/posts/:id',
1200
- headers: {
1201
- 'x-foo': v.string(),
1202
- },
1203
- responses: {
1204
- 200: c.type<{ id: number }>(),
1205
- },
1206
- },
1207
- });
1208
-
1209
- type Actual = InferHeadersInput<typeof route.getPost>;
1210
- type TestResult = Expect<Equal<Actual, { 'x-foo': string }>>;
1211
- });
1212
-
1213
- it('should handle mixed object and zod', () => {
1214
- const route = c.router({
1215
- getPost: {
1216
- method: 'GET',
1217
- path: '/posts/:id',
1218
- headers: {
1219
- 'x-foo': v.string(),
1220
- 'x-bar': z.string(),
1221
- },
1222
- responses: {
1223
- 200: c.type<{ id: number }>(),
1224
- },
1225
- },
1226
- });
1227
-
1228
- type Actual = InferHeadersInput<typeof route.getPost>;
1229
- type TestResult = Expect<
1230
- Equal<Actual, { 'x-foo': string; 'x-bar': string }>
1231
- >;
1232
- });
1233
-
1234
- it('should handle optional headers', () => {
1235
- const route = c.router({
1236
- getPost: {
1237
- method: 'GET',
1238
- path: '/posts/:id',
1239
- headers: {
1240
- 'x-foo': v.optional(v.string()),
1241
- },
1242
- responses: {
1243
- 200: c.type<{ id: number }>(),
1244
- },
1245
- },
1246
- });
1247
-
1248
- const headers = {
1249
- 'x-foo': v.optional(v.string()),
1250
- };
1251
-
1252
- type Actual = InferHeadersInput<typeof route.getPost, typeof headers>;
1253
- type TestResult = Expect<Equal<Actual, { 'x-foo'?: string | undefined }>>;
1254
- });
1255
-
1256
- it('should handle optional headers with base headers', () => {
1257
- const route = c.router(
1258
- {
1259
- getPost: {
1260
- method: 'GET',
1261
- path: '/posts/:id',
1262
- headers: {
1263
- 'x-foo': v.string(),
1264
- },
1265
- responses: {
1266
- 200: c.type<{ id: number }>(),
1267
- },
1268
- },
1269
- },
1270
- {
1271
- baseHeaders: {
1272
- 'x-bar': v.string(),
1273
- },
1274
- },
1275
- );
1276
-
1277
- type Actual = InferHeadersInput<typeof route.getPost>;
1278
- type TestResult = Expect<
1279
- Equal<Actual, { 'x-foo': string; 'x-bar': string }>
1280
- >;
1281
- });
1282
-
1283
- it('should unset header with null', () => {
1284
- const route = c.router(
1285
- {
1286
- getPost: {
1287
- method: 'GET',
1288
- path: '/posts/:id',
1289
- headers: {
1290
- 'x-foo': null,
1291
- },
1292
- responses: {
1293
- 200: c.type<{ id: number }>(),
1294
- },
1295
- },
1296
- },
1297
- {
1298
- baseHeaders: {
1299
- 'x-foo': v.string(),
1300
- },
1301
- },
1302
- );
1303
-
1304
- type Actual = InferHeadersInput<typeof route.getPost>;
1305
- type TestResult = Expect<Equal<Actual, {}>>;
1306
- });
1307
- });
1308
- });