@dokploy/trpc-openapi 0.0.2 → 0.0.4

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 (68) hide show
  1. package/dist/generator/paths.d.ts.map +1 -1
  2. package/dist/generator/paths.js +2 -2
  3. package/dist/generator/paths.js.map +1 -1
  4. package/dist/generator/schema.d.ts.map +1 -1
  5. package/dist/generator/schema.js +3 -13
  6. package/dist/generator/schema.js.map +1 -1
  7. package/dist/types.d.ts +12 -10
  8. package/dist/types.d.ts.map +1 -1
  9. package/dist/utils/procedure.d.ts +4 -3
  10. package/dist/utils/procedure.d.ts.map +1 -1
  11. package/dist/utils/procedure.js +30 -7
  12. package/dist/utils/procedure.js.map +1 -1
  13. package/package.json +14 -9
  14. package/assets/trpc-openapi-graph.png +0 -0
  15. package/assets/trpc-openapi-readme.png +0 -0
  16. package/assets/trpc-openapi.svg +0 -4
  17. package/examples/with-express/README.md +0 -11
  18. package/examples/with-express/package.json +0 -28
  19. package/examples/with-express/src/database.ts +0 -67
  20. package/examples/with-express/src/index.ts +0 -27
  21. package/examples/with-express/src/openapi.ts +0 -13
  22. package/examples/with-express/src/router.ts +0 -424
  23. package/examples/with-express/tsconfig.json +0 -102
  24. package/examples/with-interop/README.md +0 -10
  25. package/examples/with-interop/package.json +0 -13
  26. package/examples/with-interop/src/index.ts +0 -17
  27. package/examples/with-interop/tsconfig.json +0 -103
  28. package/examples/with-nextjs/.eslintrc.json +0 -3
  29. package/examples/with-nextjs/README.md +0 -12
  30. package/examples/with-nextjs/next-env.d.ts +0 -5
  31. package/examples/with-nextjs/next.config.js +0 -6
  32. package/examples/with-nextjs/package.json +0 -33
  33. package/examples/with-nextjs/public/favicon.ico +0 -0
  34. package/examples/with-nextjs/src/pages/_app.tsx +0 -7
  35. package/examples/with-nextjs/src/pages/api/[...trpc].ts +0 -18
  36. package/examples/with-nextjs/src/pages/api/openapi.json.ts +0 -10
  37. package/examples/with-nextjs/src/pages/api/trpc/[...trpc].ts +0 -9
  38. package/examples/with-nextjs/src/pages/index.tsx +0 -12
  39. package/examples/with-nextjs/src/server/database.ts +0 -67
  40. package/examples/with-nextjs/src/server/openapi.ts +0 -13
  41. package/examples/with-nextjs/src/server/router.ts +0 -425
  42. package/examples/with-nextjs/tsconfig.json +0 -24
  43. package/jest.config.ts +0 -12
  44. package/pnpm-workspace.yaml +0 -7
  45. package/src/adapters/express.ts +0 -20
  46. package/src/adapters/index.ts +0 -3
  47. package/src/adapters/next.ts +0 -64
  48. package/src/adapters/node-http/core.ts +0 -203
  49. package/src/adapters/node-http/errors.ts +0 -45
  50. package/src/adapters/node-http/input.ts +0 -76
  51. package/src/adapters/node-http/procedures.ts +0 -64
  52. package/src/adapters/standalone.ts +0 -19
  53. package/src/generator/index.ts +0 -51
  54. package/src/generator/paths.ts +0 -127
  55. package/src/generator/schema.ts +0 -238
  56. package/src/index.ts +0 -42
  57. package/src/types.ts +0 -79
  58. package/src/utils/method.ts +0 -8
  59. package/src/utils/path.ts +0 -12
  60. package/src/utils/procedure.ts +0 -45
  61. package/src/utils/zod.ts +0 -115
  62. package/test/adapters/express.test.ts +0 -150
  63. package/test/adapters/next.test.ts +0 -162
  64. package/test/adapters/standalone.test.ts +0 -1335
  65. package/test/generator.test.ts +0 -2897
  66. package/tsconfig.build.json +0 -4
  67. package/tsconfig.eslint.json +0 -5
  68. package/tsconfig.json +0 -19
@@ -1,1335 +0,0 @@
1
- /* eslint-disable @typescript-eslint/ban-types */
2
- import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
3
- import { TRPCError, initTRPC } from '@trpc/server';
4
- import { createHTTPHandler } from '@trpc/server/adapters/standalone';
5
- import { Server } from 'http';
6
- import fetch from 'node-fetch';
7
- import superjson from 'superjson';
8
- import { z } from 'zod';
9
-
10
- import {
11
- CreateOpenApiHttpHandlerOptions,
12
- OpenApiErrorResponse,
13
- OpenApiMeta,
14
- OpenApiRouter,
15
- createOpenApiHttpHandler,
16
- } from '../../src';
17
- import * as zodUtils from '../../src/utils/zod';
18
-
19
- // @ts-expect-error - global fetch
20
- global.fetch = fetch;
21
-
22
- const createContextMock = jest.fn();
23
- const responseMetaMock = jest.fn();
24
- const onErrorMock = jest.fn();
25
-
26
- const clearMocks = () => {
27
- createContextMock.mockClear();
28
- responseMetaMock.mockClear();
29
- onErrorMock.mockClear();
30
- };
31
-
32
- const createHttpServerWithRouter = <TRouter extends OpenApiRouter>(
33
- handlerOpts: CreateOpenApiHttpHandlerOptions<TRouter>,
34
- ) => {
35
- const openApiHttpHandler = createOpenApiHttpHandler<TRouter>({
36
- router: handlerOpts.router,
37
- createContext: handlerOpts.createContext ?? createContextMock,
38
- responseMeta: handlerOpts.responseMeta ?? responseMetaMock,
39
- onError: handlerOpts.onError ?? onErrorMock,
40
- maxBodySize: handlerOpts.maxBodySize,
41
- } as any);
42
- const httpHandler = createHTTPHandler<TRouter>({
43
- router: handlerOpts.router,
44
- createContext: handlerOpts.createContext ?? createContextMock,
45
- responseMeta: handlerOpts.responseMeta ?? responseMetaMock,
46
- onError: handlerOpts.onError ?? onErrorMock,
47
- maxBodySize: handlerOpts.maxBodySize,
48
- } as any);
49
-
50
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
51
- const server = new Server((req, res) => {
52
- if (req.url!.startsWith('/trpc')) {
53
- req.url = req.url!.replace('/trpc', '');
54
- return httpHandler(req, res);
55
- }
56
- return openApiHttpHandler(req, res);
57
- });
58
-
59
- server.listen(0);
60
- const port = (server.address() as any).port as number;
61
- const url = `http://localhost:${port}`;
62
-
63
- return {
64
- url,
65
- close: () => server.close(),
66
- };
67
- };
68
-
69
- const t = initTRPC.meta<OpenApiMeta>().context<any>().create();
70
-
71
- describe('standalone adapter', () => {
72
- afterEach(() => {
73
- clearMocks();
74
- });
75
-
76
- // Please note: validating router does not happen in `production`.
77
- test('with invalid router', () => {
78
- const appRouter = t.router({
79
- invalidRoute: t.procedure
80
- .meta({ openapi: { method: 'GET', path: '/invalid-route' } })
81
- .input(z.void())
82
- .query(({ input }) => input),
83
- });
84
-
85
- expect(() => {
86
- createOpenApiHttpHandler({
87
- router: appRouter,
88
- });
89
- }).toThrowError('[query.invalidRoute] - Output parser expects a Zod validator');
90
- });
91
-
92
- test('with not found path', async () => {
93
- const appRouter = t.router({
94
- ping: t.procedure
95
- .meta({ openapi: { method: 'POST', path: '/ping' } })
96
- .input(z.void())
97
- .output(z.literal('pong'))
98
- .mutation(() => 'pong' as const),
99
- });
100
-
101
- const { url, close } = createHttpServerWithRouter({
102
- router: appRouter,
103
- });
104
-
105
- const res = await fetch(`${url}/pingg`, { method: 'POST' });
106
- const body = (await res.json()) as OpenApiErrorResponse;
107
-
108
- expect(res.status).toBe(404);
109
- expect(body).toEqual({ message: 'Not found', code: 'NOT_FOUND' });
110
- expect(createContextMock).toHaveBeenCalledTimes(0);
111
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
112
- expect(onErrorMock).toHaveBeenCalledTimes(1);
113
-
114
- close();
115
- });
116
-
117
- test('with not found method', async () => {
118
- const appRouter = t.router({
119
- ping: t.procedure
120
- .meta({ openapi: { method: 'POST', path: '/ping' } })
121
- .input(z.void())
122
- .output(z.literal('pong'))
123
- .mutation(() => 'pong' as const),
124
- });
125
-
126
- const { url, close } = createHttpServerWithRouter({
127
- router: appRouter,
128
- });
129
-
130
- const res = await fetch(`${url}/ping`, { method: 'PATCH' });
131
- const body = (await res.json()) as OpenApiErrorResponse;
132
-
133
- expect(res.status).toBe(404);
134
- expect(body).toEqual({ message: 'Not found', code: 'NOT_FOUND' });
135
- expect(createContextMock).toHaveBeenCalledTimes(0);
136
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
137
- expect(onErrorMock).toHaveBeenCalledTimes(1);
138
-
139
- close();
140
- });
141
-
142
- test('with missing content-type header', async () => {
143
- const appRouter = t.router({
144
- echo: t.procedure
145
- .meta({ openapi: { method: 'POST', path: '/echo' } })
146
- .input(z.object({ payload: z.string() }))
147
- .output(z.object({ payload: z.string() }))
148
- .mutation(({ input }) => ({ payload: input.payload })),
149
- });
150
-
151
- const { url, close } = createHttpServerWithRouter({
152
- router: appRouter,
153
- });
154
-
155
- const res = await fetch(`${url}/echo`, {
156
- method: 'POST',
157
- body: JSON.stringify('James'),
158
- });
159
- const body = (await res.json()) as OpenApiErrorResponse;
160
-
161
- expect(res.status).toBe(400);
162
- expect(body).toEqual({
163
- message: 'Input validation failed',
164
- code: 'BAD_REQUEST',
165
- issues: [
166
- {
167
- code: 'invalid_type',
168
- expected: 'string',
169
- message: 'Required',
170
- path: ['payload'],
171
- received: 'undefined',
172
- },
173
- ],
174
- });
175
- expect(createContextMock).toHaveBeenCalledTimes(1);
176
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
177
- expect(onErrorMock).toHaveBeenCalledTimes(1);
178
-
179
- close();
180
- });
181
-
182
- test('with invalid content-type', async () => {
183
- const appRouter = t.router({
184
- echo: t.procedure
185
- .meta({ openapi: { method: 'POST', path: '/echo' } })
186
- .input(z.object({ payload: z.string() }))
187
- .output(z.object({ payload: z.string() }))
188
- .mutation(({ input }) => ({ payload: input.payload })),
189
- });
190
-
191
- const { url, close } = createHttpServerWithRouter({
192
- router: appRouter,
193
- });
194
-
195
- const res = await fetch(`${url}/echo`, {
196
- method: 'POST',
197
- headers: { 'Content-Type': 'text/plain' },
198
- body: 'non-json-string',
199
- });
200
- const body = (await res.json()) as OpenApiErrorResponse;
201
-
202
- expect(res.status).toBe(400);
203
- expect(body).toEqual({
204
- message: 'Input validation failed',
205
- code: 'BAD_REQUEST',
206
- issues: [
207
- {
208
- code: 'invalid_type',
209
- expected: 'string',
210
- message: 'Required',
211
- path: ['payload'],
212
- received: 'undefined',
213
- },
214
- ],
215
- });
216
- expect(createContextMock).toHaveBeenCalledTimes(1);
217
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
218
- expect(onErrorMock).toHaveBeenCalledTimes(1);
219
-
220
- close();
221
- });
222
-
223
- test('with missing input', async () => {
224
- const appRouter = t.router({
225
- echo: t.procedure
226
- .meta({ openapi: { method: 'GET', path: '/echo' } })
227
- .input(z.object({ payload: z.string() }))
228
- .output(z.object({ payload: z.string() }))
229
- .query(({ input }) => ({ payload: input.payload })),
230
- });
231
-
232
- const { url, close } = createHttpServerWithRouter({
233
- router: appRouter,
234
- });
235
-
236
- const res = await fetch(`${url}/echo`, { method: 'GET' });
237
- const body = (await res.json()) as OpenApiErrorResponse;
238
-
239
- expect(res.status).toBe(400);
240
- expect(body).toEqual({
241
- message: 'Input validation failed',
242
- code: 'BAD_REQUEST',
243
- issues: [
244
- {
245
- code: 'invalid_type',
246
- expected: 'string',
247
- message: 'Required',
248
- path: ['payload'],
249
- received: 'undefined',
250
- },
251
- ],
252
- });
253
- expect(createContextMock).toHaveBeenCalledTimes(1);
254
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
255
- expect(onErrorMock).toHaveBeenCalledTimes(1);
256
-
257
- close();
258
- });
259
-
260
- test('with wrong input type', async () => {
261
- const appRouter = t.router({
262
- echo: t.procedure
263
- .meta({ openapi: { method: 'POST', path: '/echo' } })
264
- .input(z.object({ payload: z.string() }))
265
- .output(z.object({ payload: z.string() }))
266
- .mutation(({ input }) => ({ payload: input.payload })),
267
- });
268
-
269
- const { url, close } = createHttpServerWithRouter({
270
- router: appRouter,
271
- });
272
-
273
- const res = await fetch(`${url}/echo`, {
274
- method: 'POST',
275
- headers: { 'Content-Type': 'application/json' },
276
- body: JSON.stringify({ payload: 123 }),
277
- });
278
- const body = (await res.json()) as OpenApiErrorResponse;
279
-
280
- expect(res.status).toBe(400);
281
- expect(body).toEqual({
282
- message: 'Input validation failed',
283
- code: 'BAD_REQUEST',
284
- issues: [
285
- {
286
- code: 'invalid_type',
287
- expected: 'string',
288
- message: 'Expected string, received number',
289
- path: ['payload'],
290
- received: 'number',
291
- },
292
- ],
293
- });
294
- expect(createContextMock).toHaveBeenCalledTimes(1);
295
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
296
- expect(onErrorMock).toHaveBeenCalledTimes(1);
297
-
298
- close();
299
- });
300
-
301
- test('with bad output', async () => {
302
- const appRouter = t.router({
303
- echo: t.procedure
304
- .meta({ openapi: { method: 'POST', path: '/echo' } })
305
- .input(z.object({ payload: z.string() }))
306
- .output(z.object({ payload: z.string() }))
307
- // @ts-expect-error - fail on purpose
308
- .mutation(() => 'fail'),
309
- });
310
-
311
- const { url, close } = createHttpServerWithRouter({
312
- router: appRouter,
313
- });
314
-
315
- const res = await fetch(`${url}/echo`, {
316
- method: 'POST',
317
- headers: { 'Content-Type': 'application/json' },
318
- body: JSON.stringify({ payload: '@jlalmes' }),
319
- });
320
- const body = (await res.json()) as OpenApiErrorResponse;
321
-
322
- expect(res.status).toBe(500);
323
- expect(body).toEqual({
324
- message: 'Output validation failed',
325
- code: 'INTERNAL_SERVER_ERROR',
326
- });
327
- expect(createContextMock).toHaveBeenCalledTimes(1);
328
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
329
- expect(onErrorMock).toHaveBeenCalledTimes(1);
330
-
331
- close();
332
- });
333
-
334
- test('with valid routes', async () => {
335
- const appRouter = t.router({
336
- sayHelloQuery: t.procedure
337
- .meta({ openapi: { method: 'GET', path: '/say-hello' } })
338
- .input(z.object({ name: z.string() }))
339
- .output(z.object({ greeting: z.string() }))
340
- .query(({ input }) => ({ greeting: `Hello ${input.name}!` })),
341
- sayHelloMutation: t.procedure
342
- .meta({ openapi: { method: 'POST', path: '/say-hello' } })
343
- .input(z.object({ name: z.string() }))
344
- .output(z.object({ greeting: z.string() }))
345
- .mutation(({ input }) => ({ greeting: `Hello ${input.name}!` })),
346
- });
347
-
348
- const { url, close } = createHttpServerWithRouter({
349
- router: appRouter,
350
- });
351
-
352
- {
353
- const res = await fetch(`${url}/say-hello?name=James`, { method: 'GET' });
354
- const body = await res.json();
355
-
356
- expect(res.status).toBe(200);
357
- expect(body).toEqual({ greeting: 'Hello James!' });
358
- expect(createContextMock).toHaveBeenCalledTimes(1);
359
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
360
- expect(onErrorMock).toHaveBeenCalledTimes(0);
361
-
362
- clearMocks();
363
- }
364
- {
365
- const res = await fetch(`${url}/say-hello`, {
366
- method: 'POST',
367
- headers: { 'Content-Type': 'application/json' },
368
- body: JSON.stringify({ name: 'James' }),
369
- });
370
- const body = await res.json();
371
-
372
- expect(res.status).toBe(200);
373
- expect(body).toEqual({ greeting: 'Hello James!' });
374
- expect(createContextMock).toHaveBeenCalledTimes(1);
375
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
376
- expect(onErrorMock).toHaveBeenCalledTimes(0);
377
- }
378
-
379
- close();
380
- });
381
-
382
- test('with void input', async () => {
383
- const appRouter = t.router({
384
- pingQuery: t.procedure
385
- .meta({ openapi: { method: 'GET', path: '/ping' } })
386
- .input(z.void())
387
- .output(z.literal('pong'))
388
- .query(() => 'pong' as const),
389
- pingMutation: t.procedure
390
- .meta({ openapi: { method: 'POST', path: '/ping' } })
391
- .input(z.void())
392
- .output(z.literal('pong'))
393
- .mutation(() => 'pong' as const),
394
- });
395
-
396
- const { url, close } = createHttpServerWithRouter({
397
- router: appRouter,
398
- });
399
-
400
- {
401
- const res = await fetch(`${url}/ping`, { method: 'GET' });
402
- const body = await res.json();
403
-
404
- expect(res.status).toBe(200);
405
- expect(body).toEqual('pong');
406
- expect(createContextMock).toHaveBeenCalledTimes(1);
407
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
408
- expect(onErrorMock).toHaveBeenCalledTimes(0);
409
-
410
- clearMocks();
411
- }
412
- {
413
- const res = await fetch(`${url}/ping`, { method: 'POST' });
414
- const body = await res.json();
415
-
416
- expect(res.status).toBe(200);
417
- expect(body).toEqual('pong');
418
- expect(createContextMock).toHaveBeenCalledTimes(1);
419
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
420
- expect(onErrorMock).toHaveBeenCalledTimes(0);
421
- }
422
-
423
- close();
424
- });
425
-
426
- test('with void output', async () => {
427
- const appRouter = t.router({
428
- ping: t.procedure
429
- .meta({ openapi: { method: 'GET', path: '/ping' } })
430
- .input(z.object({ ping: z.string() }))
431
- .output(z.void())
432
- .query(() => undefined),
433
- });
434
-
435
- const { url, close } = createHttpServerWithRouter({
436
- router: appRouter,
437
- });
438
-
439
- const res = await fetch(`${url}/ping?ping=ping`, { method: 'GET' });
440
- let body;
441
- try {
442
- body = await res.json();
443
- } catch (e) {
444
- // do nothing
445
- }
446
-
447
- expect(res.status).toBe(200);
448
- expect(body).toEqual(undefined);
449
- expect(createContextMock).toHaveBeenCalledTimes(1);
450
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
451
- expect(onErrorMock).toHaveBeenCalledTimes(0);
452
-
453
- close();
454
- });
455
-
456
- test('with createContext', async () => {
457
- type Context = { id: 1234567890 };
458
-
459
- const t2 = initTRPC.meta<OpenApiMeta>().context<Context>().create();
460
-
461
- const appRouter = t2.router({
462
- echo: t2.procedure
463
- .meta({ openapi: { method: 'GET', path: '/echo' } })
464
- .input(z.object({ payload: z.string() }))
465
- .output(z.object({ payload: z.string(), context: z.object({ id: z.number() }) }))
466
- .query(({ input, ctx }) => ({ payload: input.payload, context: ctx })),
467
- });
468
-
469
- const { url, close } = createHttpServerWithRouter({
470
- createContext: (): Context => ({ id: 1234567890 }),
471
- router: appRouter,
472
- });
473
-
474
- const res = await fetch(`${url}/echo?payload=jlalmes`, { method: 'GET' });
475
- const body = await res.json();
476
-
477
- expect(res.status).toBe(200);
478
- expect(body).toEqual({
479
- payload: 'jlalmes',
480
- context: { id: 1234567890 },
481
- });
482
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
483
- expect(onErrorMock).toHaveBeenCalledTimes(0);
484
-
485
- close();
486
- });
487
-
488
- test('with responseMeta', async () => {
489
- const appRouter = t.router({
490
- echo: t.procedure
491
- .meta({ openapi: { method: 'GET', path: '/echo' } })
492
- .input(z.object({ payload: z.string() }))
493
- .output(z.object({ payload: z.string(), context: z.undefined() }))
494
- .query(({ input, ctx }) => ({ payload: input.payload, context: ctx })),
495
- });
496
-
497
- const { url, close } = createHttpServerWithRouter({
498
- router: appRouter,
499
- responseMeta: () => ({ status: 202, headers: { 'x-custom': 'custom header' } }),
500
- });
501
-
502
- const res = await fetch(`${url}/echo?payload=jlalmes`, { method: 'GET' });
503
- const body = await res.json();
504
-
505
- expect(res.status).toBe(202);
506
- expect(res.headers.get('x-custom')).toBe('custom header');
507
- expect(body).toEqual({
508
- payload: 'jlalmes',
509
- context: undefined,
510
- });
511
- expect(createContextMock).toHaveBeenCalledTimes(1);
512
- expect(onErrorMock).toHaveBeenCalledTimes(0);
513
-
514
- close();
515
- });
516
-
517
- test('with skipped transformer', async () => {
518
- const t2 = initTRPC.meta<OpenApiMeta>().context<any>().create({
519
- transformer: superjson,
520
- });
521
-
522
- const appRouter = t2.router({
523
- echo: t2.procedure
524
- .meta({ openapi: { method: 'GET', path: '/echo' } })
525
- .input(z.object({ payload: z.string() }))
526
- .output(z.object({ payload: z.string(), context: z.undefined() }))
527
- .query(({ input, ctx }) => ({ payload: input.payload })),
528
- });
529
-
530
- const { url, close } = createHttpServerWithRouter({
531
- router: appRouter,
532
- });
533
-
534
- const res = await fetch(`${url}/echo?payload=jlalmes`, { method: 'GET' });
535
- const body = await res.json();
536
-
537
- expect(res.status).toBe(200);
538
- expect(body).toEqual({
539
- payload: 'jlalmes',
540
- });
541
- expect(createContextMock).toHaveBeenCalledTimes(1);
542
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
543
- expect(onErrorMock).toHaveBeenCalledTimes(0);
544
-
545
- close();
546
- });
547
-
548
- test('with warmup request', async () => {
549
- const appRouter = t.router({});
550
-
551
- const { url, close } = createHttpServerWithRouter({
552
- router: appRouter,
553
- });
554
-
555
- const res = await fetch(`${url}/any-endpoint`, { method: 'HEAD' });
556
-
557
- expect(res.status).toBe(204);
558
- expect(createContextMock).toHaveBeenCalledTimes(0);
559
- expect(responseMetaMock).toHaveBeenCalledTimes(0);
560
- expect(onErrorMock).toHaveBeenCalledTimes(0);
561
-
562
- close();
563
- });
564
-
565
- test('with invalid json', async () => {
566
- const appRouter = t.router({
567
- echo: t.procedure
568
- .meta({ openapi: { method: 'POST', path: '/echo' } })
569
- .input(z.object({ payload: z.string() }))
570
- .output(z.object({ payload: z.string() }))
571
- .mutation(({ input }) => ({ payload: input.payload })),
572
- });
573
-
574
- const { url, close } = createHttpServerWithRouter({
575
- router: appRouter,
576
- });
577
-
578
- const res = await fetch(`${url}/echo`, {
579
- method: 'POST',
580
- headers: { 'Content-Type': 'application/json' },
581
- // @ts-expect-error - not JSON.stringified
582
- body: { payload: 'James' },
583
- });
584
- const body = (await res.json()) as OpenApiErrorResponse;
585
-
586
- expect(res.status).toBe(400);
587
- expect(body).toEqual({
588
- message: 'Failed to parse request body',
589
- code: 'PARSE_ERROR',
590
- });
591
- expect(createContextMock).toHaveBeenCalledTimes(0);
592
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
593
- expect(onErrorMock).toHaveBeenCalledTimes(1);
594
-
595
- close();
596
- });
597
-
598
- test('with maxBodySize', async () => {
599
- const appRouter = t.router({
600
- echo: t.procedure
601
- .meta({ openapi: { method: 'POST', path: '/echo' } })
602
- .input(z.object({ payload: z.string() }))
603
- .output(z.object({ payload: z.string() }))
604
- .mutation(({ input }) => ({ payload: input.payload })),
605
- });
606
-
607
- const requestBody = JSON.stringify({ payload: 'James' });
608
-
609
- const { url, close } = createHttpServerWithRouter({
610
- router: appRouter,
611
- maxBodySize: requestBody.length,
612
- });
613
-
614
- {
615
- const res = await fetch(`${url}/echo`, {
616
- method: 'POST',
617
- headers: { 'Content-Type': 'application/json' },
618
- body: requestBody,
619
- });
620
- const body = await res.json();
621
-
622
- expect(res.status).toBe(200);
623
- expect(body).toEqual({
624
- payload: 'James',
625
- });
626
- expect(createContextMock).toHaveBeenCalledTimes(1);
627
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
628
- expect(onErrorMock).toHaveBeenCalledTimes(0);
629
-
630
- clearMocks();
631
- }
632
- {
633
- const res = await fetch(`${url}/echo`, {
634
- method: 'POST',
635
- headers: { 'Content-Type': 'application/json' },
636
- body: JSON.stringify({ payload: 'James!' }),
637
- });
638
- const body = (await res.json()) as OpenApiErrorResponse;
639
-
640
- expect(res.status).toBe(413);
641
- expect(body).toEqual({
642
- message: 'Request body too large',
643
- code: 'PAYLOAD_TOO_LARGE',
644
- });
645
- expect(createContextMock).toHaveBeenCalledTimes(0);
646
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
647
- expect(onErrorMock).toHaveBeenCalledTimes(1);
648
- }
649
-
650
- close();
651
- });
652
-
653
- test('with multiple input query string params', async () => {
654
- const appRouter = t.router({
655
- sayHello: t.procedure
656
- .meta({ openapi: { method: 'GET', path: '/say-hello' } })
657
- .input(z.object({ name: z.string() }))
658
- .output(z.object({ greeting: z.string() }))
659
- .query(({ input }) => ({ greeting: `Hello ${input.name}!` })),
660
- });
661
-
662
- const { url, close } = createHttpServerWithRouter({
663
- router: appRouter,
664
- });
665
-
666
- {
667
- const res = await fetch(`${url}/say-hello?name=James&name=jlalmes`, { method: 'GET' });
668
- const body = await res.json();
669
-
670
- expect(res.status).toBe(200);
671
- expect(body).toEqual({ greeting: 'Hello James!' });
672
- expect(createContextMock).toHaveBeenCalledTimes(1);
673
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
674
- expect(onErrorMock).toHaveBeenCalledTimes(0);
675
- }
676
-
677
- close();
678
- });
679
-
680
- test('with case insensitivity', async () => {
681
- const appRouter = t.router({
682
- allLowerPath: t.procedure
683
- .meta({ openapi: { method: 'GET', path: '/lower' } })
684
- .input(z.object({ name: z.string() }))
685
- .output(z.object({ greeting: z.string() }))
686
- .query(({ input }) => ({ greeting: `Hello ${input.name}!` })),
687
- allUpperPath: t.procedure
688
- .meta({ openapi: { method: 'GET', path: '/UPPER' } })
689
- .input(z.object({ name: z.string() }))
690
- .output(z.object({ greeting: z.string() }))
691
- .query(({ input }) => ({ greeting: `Hello ${input.name}!` })),
692
- });
693
-
694
- const { url, close } = createHttpServerWithRouter({
695
- router: appRouter,
696
- });
697
-
698
- {
699
- const res = await fetch(`${url}/LOWER?name=James`, { method: 'GET' });
700
- const body = await res.json();
701
-
702
- expect(res.status).toBe(200);
703
- expect(body).toEqual({ greeting: 'Hello James!' });
704
- expect(createContextMock).toHaveBeenCalledTimes(1);
705
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
706
- expect(onErrorMock).toHaveBeenCalledTimes(0);
707
-
708
- clearMocks();
709
- }
710
- {
711
- const res = await fetch(`${url}/upper?name=James`, { method: 'GET' });
712
- const body = await res.json();
713
-
714
- expect(res.status).toBe(200);
715
- expect(body).toEqual({ greeting: 'Hello James!' });
716
- expect(createContextMock).toHaveBeenCalledTimes(1);
717
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
718
- expect(onErrorMock).toHaveBeenCalledTimes(0);
719
- }
720
-
721
- close();
722
- });
723
-
724
- test('with path parameters', async () => {
725
- const appRouter = t.router({
726
- sayHelloQuery: t.procedure
727
- .meta({ openapi: { method: 'GET', path: '/say-hello/{name}' } })
728
- .input(z.object({ name: z.string() }))
729
- .output(z.object({ greeting: z.string() }))
730
- .query(({ input }) => ({ greeting: `Hello ${input.name}!` })),
731
- sayHelloMutation: t.procedure
732
- .meta({ openapi: { method: 'POST', path: '/say-hello/{name}' } })
733
- .input(z.object({ name: z.string() }))
734
- .output(z.object({ greeting: z.string() }))
735
- .mutation(({ input }) => ({ greeting: `Hello ${input.name}!` })),
736
- sayHelloComplex: t.procedure
737
- .meta({ openapi: { method: 'GET', path: '/say-hello/{first}/{last}' } })
738
- .input(
739
- z.object({
740
- first: z.string(),
741
- last: z.string(),
742
- greeting: z.string(),
743
- }),
744
- )
745
- .output(z.object({ greeting: z.string() }))
746
- .query(({ input }) => ({
747
- greeting: `${input.greeting} ${input.first} ${input.last}!`,
748
- })),
749
- });
750
-
751
- const { url, close } = createHttpServerWithRouter({
752
- router: appRouter,
753
- });
754
-
755
- {
756
- const res = await fetch(`${url}/say-hello/James`, { method: 'GET' });
757
- const body = await res.json();
758
-
759
- expect(res.status).toBe(200);
760
- expect(body).toEqual({ greeting: 'Hello James!' });
761
- expect(createContextMock).toHaveBeenCalledTimes(1);
762
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
763
- expect(onErrorMock).toHaveBeenCalledTimes(0);
764
-
765
- clearMocks();
766
- }
767
- {
768
- const res = await fetch(`${url}/say-hello/James`, {
769
- method: 'POST',
770
- headers: { 'Content-Type': 'application/json' },
771
- body: JSON.stringify({ name: 'jlalmes' }),
772
- });
773
- const body = await res.json();
774
-
775
- expect(res.status).toBe(200);
776
- expect(body).toEqual({ greeting: 'Hello James!' });
777
- expect(createContextMock).toHaveBeenCalledTimes(1);
778
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
779
- expect(onErrorMock).toHaveBeenCalledTimes(0);
780
-
781
- clearMocks();
782
- }
783
- {
784
- const res = await fetch(`${url}/say-hello/James/Berry?greeting=Hello&first=jlalmes`, {
785
- method: 'GET',
786
- });
787
- const body = await res.json();
788
-
789
- expect(res.status).toBe(200);
790
- expect(body).toEqual({ greeting: 'Hello James Berry!' });
791
- expect(createContextMock).toHaveBeenCalledTimes(1);
792
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
793
- expect(onErrorMock).toHaveBeenCalledTimes(0);
794
- }
795
-
796
- close();
797
- });
798
-
799
- test('with bad output', async () => {
800
- const appRouter = t.router({
801
- badOutput: t.procedure
802
- .meta({ openapi: { method: 'GET', path: '/bad-output' } })
803
- .input(z.void())
804
- .output(z.string())
805
- // @ts-expect-error - intentional bad output
806
- .query(() => ({})),
807
- });
808
-
809
- const { url, close } = createHttpServerWithRouter({
810
- router: appRouter,
811
- });
812
-
813
- const res = await fetch(`${url}/bad-output`, { method: 'GET' });
814
- const body = (await res.json()) as OpenApiErrorResponse;
815
-
816
- expect(res.status).toBe(500);
817
- expect(body).toEqual({
818
- message: 'Output validation failed',
819
- code: 'INTERNAL_SERVER_ERROR',
820
- });
821
- expect(createContextMock).toHaveBeenCalledTimes(1);
822
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
823
- expect(onErrorMock).toHaveBeenCalledTimes(1);
824
-
825
- close();
826
- });
827
-
828
- test('with void and trpc client', async () => {
829
- // ensure monkey patch doesnt break router
830
- const appRouter = t.router({
831
- withVoidQuery: t.procedure
832
- .meta({ openapi: { method: 'GET', path: '/with-void' } })
833
- .input(z.void())
834
- .output(z.object({ payload: z.any() }))
835
- .query(({ input }) => ({ payload: input })),
836
- withVoidMutation: t.procedure
837
- .meta({ openapi: { method: 'POST', path: '/with-void' } })
838
- .input(z.void())
839
- .output(z.object({ payload: z.any() }))
840
- .mutation(({ input }) => ({ payload: input })),
841
- });
842
-
843
- const { url, close } = createHttpServerWithRouter({
844
- router: appRouter,
845
- });
846
-
847
- type AppRouter = typeof appRouter;
848
- const client = createTRPCProxyClient<AppRouter>({
849
- links: [httpBatchLink({ url: `${url}/trpc` })],
850
- });
851
-
852
- {
853
- const res = await client.withVoidQuery.query();
854
-
855
- expect(res).toEqual({});
856
- expect(createContextMock).toHaveBeenCalledTimes(1);
857
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
858
- expect(onErrorMock).toHaveBeenCalledTimes(0);
859
-
860
- clearMocks();
861
-
862
- await expect(() => {
863
- // @ts-expect-error - send monkey patched input type
864
- return client.withVoidQuery.query({});
865
- }).rejects.toThrowErrorMatchingInlineSnapshot(`
866
- "[
867
- {
868
- \\"code\\": \\"invalid_type\\",
869
- \\"expected\\": \\"void\\",
870
- \\"received\\": \\"object\\",
871
- \\"path\\": [],
872
- \\"message\\": \\"Expected void, received object\\"
873
- }
874
- ]"
875
- `);
876
- expect(createContextMock).toHaveBeenCalledTimes(1);
877
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
878
- expect(onErrorMock).toHaveBeenCalledTimes(1);
879
-
880
- clearMocks();
881
- }
882
- {
883
- const res = await client.withVoidMutation.mutate();
884
-
885
- expect(res).toEqual({});
886
- expect(createContextMock).toHaveBeenCalledTimes(1);
887
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
888
- expect(onErrorMock).toHaveBeenCalledTimes(0);
889
-
890
- clearMocks();
891
-
892
- await expect(() => {
893
- // @ts-expect-error - send monkey patched input type
894
- return client.withVoidMutation.mutate({});
895
- }).rejects.toThrowErrorMatchingInlineSnapshot(`
896
- "[
897
- {
898
- \\"code\\": \\"invalid_type\\",
899
- \\"expected\\": \\"void\\",
900
- \\"received\\": \\"object\\",
901
- \\"path\\": [],
902
- \\"message\\": \\"Expected void, received object\\"
903
- }
904
- ]"
905
- `);
906
- expect(createContextMock).toHaveBeenCalledTimes(1);
907
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
908
- expect(onErrorMock).toHaveBeenCalledTimes(1);
909
- }
910
-
911
- close();
912
- });
913
-
914
- test('with DELETE mutation', async () => {
915
- const appRouter = t.router({
916
- echoDelete: t.procedure
917
- .meta({ openapi: { method: 'DELETE', path: '/echo-delete' } })
918
- .input(z.object({ payload: z.string() }))
919
- .output(z.object({ payload: z.string() }))
920
- .mutation(({ input }) => input),
921
- });
922
-
923
- const { url, close } = createHttpServerWithRouter({
924
- router: appRouter,
925
- });
926
-
927
- const res = await fetch(`${url}/echo-delete?payload=jlalmes`, { method: 'DELETE' });
928
- const body = await res.json();
929
-
930
- expect(res.status).toBe(200);
931
- expect(body).toEqual({ payload: 'jlalmes' });
932
- expect(createContextMock).toHaveBeenCalledTimes(1);
933
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
934
- expect(onErrorMock).toHaveBeenCalledTimes(0);
935
-
936
- close();
937
- });
938
-
939
- test('with POST query', async () => {
940
- const appRouter = t.router({
941
- echoPost: t.procedure
942
- .meta({ openapi: { method: 'POST', path: '/echo-post' } })
943
- .input(z.object({ payload: z.string() }))
944
- .output(z.object({ payload: z.string() }))
945
- .query(({ input }) => input),
946
- });
947
-
948
- const { url, close } = createHttpServerWithRouter({
949
- router: appRouter,
950
- });
951
-
952
- const res = await fetch(`${url}/echo-post`, {
953
- method: 'POST',
954
- headers: { 'Content-Type': 'application/json' },
955
- body: JSON.stringify({ payload: 'jlalmes' }),
956
- });
957
- const body = await res.json();
958
-
959
- expect(res.status).toBe(200);
960
- expect(body).toEqual({ payload: 'jlalmes' });
961
- expect(createContextMock).toHaveBeenCalledTimes(1);
962
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
963
- expect(onErrorMock).toHaveBeenCalledTimes(0);
964
-
965
- close();
966
- });
967
-
968
- test('with thrown error', async () => {
969
- const appRouter = t.router({
970
- customError: t.procedure
971
- .meta({ openapi: { method: 'POST', path: '/custom-error' } })
972
- .input(z.void())
973
- .output(z.void())
974
- .mutation(() => {
975
- throw new Error('Custom error message');
976
- }),
977
- customTRPCError: t.procedure
978
- .meta({ openapi: { method: 'POST', path: '/custom-trpc-error' } })
979
- .input(z.void())
980
- .output(z.void())
981
- .mutation(() => {
982
- throw new TRPCError({
983
- message: 'Custom TRPCError message',
984
- code: 'CLIENT_CLOSED_REQUEST',
985
- });
986
- }),
987
- });
988
-
989
- const { url, close } = createHttpServerWithRouter({
990
- router: appRouter,
991
- });
992
-
993
- {
994
- const res = await fetch(`${url}/custom-error`, { method: 'POST' });
995
- const body = (await res.json()) as OpenApiErrorResponse;
996
-
997
- expect(res.status).toBe(500);
998
- expect(body).toEqual({
999
- message: 'Custom error message',
1000
- code: 'INTERNAL_SERVER_ERROR',
1001
- });
1002
- expect(createContextMock).toHaveBeenCalledTimes(1);
1003
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1004
- expect(onErrorMock).toHaveBeenCalledTimes(1);
1005
-
1006
- clearMocks();
1007
- }
1008
- {
1009
- const res = await fetch(`${url}/custom-trpc-error`, { method: 'POST' });
1010
- const body = (await res.json()) as OpenApiErrorResponse;
1011
-
1012
- expect(res.status).toBe(499);
1013
- expect(body).toEqual({
1014
- message: 'Custom TRPCError message',
1015
- code: 'CLIENT_CLOSED_REQUEST',
1016
- });
1017
- expect(createContextMock).toHaveBeenCalledTimes(1);
1018
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1019
- expect(onErrorMock).toHaveBeenCalledTimes(1);
1020
- }
1021
-
1022
- close();
1023
- });
1024
-
1025
- test('with error formatter', async () => {
1026
- const errorFormatterMock = jest.fn();
1027
-
1028
- const t2 = initTRPC
1029
- .meta<OpenApiMeta>()
1030
- .context<any>()
1031
- .create({
1032
- errorFormatter: ({ error, shape }) => {
1033
- errorFormatterMock();
1034
- if (error.code === 'INTERNAL_SERVER_ERROR') {
1035
- return { ...shape, message: 'Custom formatted error message' };
1036
- }
1037
- return shape;
1038
- },
1039
- });
1040
-
1041
- const appRouter = t2.router({
1042
- customFormattedError: t2.procedure
1043
- .meta({ openapi: { method: 'POST', path: '/custom-formatted-error' } })
1044
- .input(z.void())
1045
- .output(z.void())
1046
- .mutation(() => {
1047
- throw new Error('Custom error message');
1048
- }),
1049
- });
1050
-
1051
- const { url, close } = createHttpServerWithRouter({
1052
- router: appRouter,
1053
- });
1054
-
1055
- const res = await fetch(`${url}/custom-formatted-error`, { method: 'POST' });
1056
- const body = (await res.json()) as OpenApiErrorResponse;
1057
-
1058
- expect(res.status).toBe(500);
1059
- expect(body).toEqual({
1060
- message: 'Custom formatted error message',
1061
- code: 'INTERNAL_SERVER_ERROR',
1062
- });
1063
- expect(errorFormatterMock).toHaveBeenCalledTimes(1);
1064
- expect(createContextMock).toHaveBeenCalledTimes(1);
1065
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1066
- expect(onErrorMock).toHaveBeenCalledTimes(1);
1067
-
1068
- close();
1069
- });
1070
-
1071
- test('with nested routers', async () => {
1072
- const appRouter = t.router({
1073
- procedure: t.procedure
1074
- .meta({ openapi: { method: 'GET', path: '/procedure' } })
1075
- .input(z.object({ payload: z.string() }))
1076
- .output(z.object({ payload: z.string() }))
1077
- .query(({ input }) => ({ payload: input.payload })),
1078
- router: t.router({
1079
- procedure: t.procedure
1080
- .meta({ openapi: { method: 'GET', path: '/router/procedure' } })
1081
- .input(z.object({ payload: z.string() }))
1082
- .output(z.object({ payload: z.string() }))
1083
- .query(({ input }) => ({ payload: input.payload })),
1084
- router: t.router({
1085
- procedure: t.procedure
1086
- .meta({ openapi: { method: 'GET', path: '/router/router/procedure' } })
1087
- .input(z.object({ payload: z.string() }))
1088
- .output(z.object({ payload: z.string() }))
1089
- .query(({ input }) => ({ payload: input.payload })),
1090
- }),
1091
- }),
1092
- });
1093
-
1094
- const { url, close } = createHttpServerWithRouter({
1095
- router: appRouter,
1096
- });
1097
-
1098
- {
1099
- const res = await fetch(`${url}/procedure?payload=one`, { method: 'GET' });
1100
- const body = await res.json();
1101
-
1102
- expect(res.status).toBe(200);
1103
- expect(body).toEqual({ payload: 'one' });
1104
- expect(createContextMock).toHaveBeenCalledTimes(1);
1105
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1106
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1107
-
1108
- clearMocks();
1109
- }
1110
- {
1111
- const res = await fetch(`${url}/router/procedure?payload=two`, { method: 'GET' });
1112
- const body = await res.json();
1113
-
1114
- expect(res.status).toBe(200);
1115
- expect(body).toEqual({ payload: 'two' });
1116
- expect(createContextMock).toHaveBeenCalledTimes(1);
1117
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1118
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1119
-
1120
- clearMocks();
1121
- }
1122
- {
1123
- const res = await fetch(`${url}/router/router/procedure?payload=three`, { method: 'GET' });
1124
- const body = await res.json();
1125
-
1126
- expect(res.status).toBe(200);
1127
- expect(body).toEqual({ payload: 'three' });
1128
- expect(createContextMock).toHaveBeenCalledTimes(1);
1129
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1130
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1131
- }
1132
-
1133
- close();
1134
- });
1135
-
1136
- test('with multiple inputs', async () => {
1137
- const appRouter = t.router({
1138
- multiInput: t.procedure
1139
- .meta({ openapi: { method: 'GET', path: '/multi-input' } })
1140
- .input(z.object({ firstName: z.string() }))
1141
- .input(z.object({ lastName: z.string() }))
1142
- .output(z.object({ fullName: z.string() }))
1143
- .query(({ input }) => ({ fullName: `${input.firstName} ${input.lastName}` })),
1144
- });
1145
-
1146
- const { url, close } = createHttpServerWithRouter({
1147
- router: appRouter,
1148
- });
1149
-
1150
- const res = await fetch(`${url}/multi-input?firstName=James&lastName=Berry`, { method: 'GET' });
1151
- const body = await res.json();
1152
-
1153
- expect(res.status).toBe(200);
1154
- expect(body).toEqual({ fullName: 'James Berry' });
1155
- expect(createContextMock).toHaveBeenCalledTimes(1);
1156
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1157
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1158
-
1159
- close();
1160
- });
1161
-
1162
- test('with preprocess', async () => {
1163
- const appRouter = t.router({
1164
- preprocess: t.procedure
1165
- .meta({ openapi: { method: 'GET', path: '/preprocess' } })
1166
- .input(
1167
- z.object({
1168
- value: z.preprocess((arg) => [arg, arg], z.array(z.string())),
1169
- }),
1170
- )
1171
- .output(z.object({ result: z.string() }))
1172
- .query(({ input }) => ({ result: input.value.join('XXX') })),
1173
- });
1174
-
1175
- const { url, close } = createHttpServerWithRouter({
1176
- router: appRouter,
1177
- });
1178
-
1179
- const res = await fetch(`${url}/preprocess?value=lol`, { method: 'GET' });
1180
- const body = await res.json();
1181
-
1182
- expect(res.status).toBe(200);
1183
- expect(body).toEqual({ result: 'lolXXXlol' });
1184
- expect(createContextMock).toHaveBeenCalledTimes(1);
1185
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1186
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1187
-
1188
- close();
1189
- });
1190
-
1191
- test('with non-coerce preprocess', async () => {
1192
- // only applies when zod does not support (below version v3.20.0)
1193
-
1194
- // @ts-expect-error - hack to disable zodSupportsCoerce
1195
- // eslint-disable-next-line import/namespace
1196
- zodUtils.zodSupportsCoerce = false;
1197
- {
1198
- const appRouter = t.router({
1199
- plusOne: t.procedure
1200
- .meta({ openapi: { method: 'GET', path: '/plus-one' } })
1201
- .input(
1202
- z.object({
1203
- number: z.preprocess(
1204
- (arg) => (typeof arg === 'string' ? parseInt(arg) : arg),
1205
- z.number(),
1206
- ),
1207
- }),
1208
- )
1209
- .output(z.object({ result: z.number() }))
1210
- .query(({ input }) => ({ result: input.number + 1 })),
1211
- });
1212
-
1213
- const { url, close } = createHttpServerWithRouter({
1214
- router: appRouter,
1215
- });
1216
-
1217
- const res = await fetch(`${url}/plus-one?number=9`, { method: 'GET' });
1218
- const body = await res.json();
1219
-
1220
- expect(res.status).toBe(200);
1221
- expect(body).toEqual({ result: 10 });
1222
- expect(createContextMock).toHaveBeenCalledTimes(1);
1223
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1224
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1225
-
1226
- close();
1227
- }
1228
- // @ts-expect-error - hack to re-enable zodSupportsCoerce
1229
- // eslint-disable-next-line import/namespace
1230
- zodUtils.zodSupportsCoerce = true;
1231
- });
1232
-
1233
- test('with coerce', async () => {
1234
- const appRouter = t.router({
1235
- getPlusOne: t.procedure
1236
- .meta({ openapi: { method: 'GET', path: '/plus-one' } })
1237
- .input(z.object({ number: z.number() }))
1238
- .output(z.object({ result: z.number() }))
1239
- .query(({ input }) => ({ result: input.number + 1 })),
1240
- postPlusOne: t.procedure
1241
- .meta({ openapi: { method: 'POST', path: '/plus-one' } })
1242
- .input(z.object({ date: z.date() }))
1243
- .output(z.object({ result: z.number() }))
1244
- .mutation(({ input }) => ({ result: input.date.getTime() + 1 })),
1245
- pathPlusOne: t.procedure
1246
- .meta({ openapi: { method: 'GET', path: '/plus-one/{number}' } })
1247
- .input(z.object({ number: z.number() }))
1248
- .output(z.object({ result: z.number() }))
1249
- .query(({ input }) => ({ result: input.number + 1 })),
1250
- });
1251
-
1252
- const { url, close } = createHttpServerWithRouter({
1253
- router: appRouter,
1254
- });
1255
-
1256
- {
1257
- const res = await fetch(`${url}/plus-one?number=9`, { method: 'GET' });
1258
- const body = await res.json();
1259
-
1260
- expect(res.status).toBe(200);
1261
- expect(body).toEqual({ result: 10 });
1262
- expect(createContextMock).toHaveBeenCalledTimes(1);
1263
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1264
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1265
-
1266
- clearMocks();
1267
- }
1268
- {
1269
- const date = new Date();
1270
-
1271
- const res = await fetch(`${url}/plus-one`, {
1272
- method: 'POST',
1273
- headers: { 'Content-Type': 'application/json' },
1274
- body: JSON.stringify({ date }),
1275
- });
1276
- const body = await res.json();
1277
-
1278
- expect(res.status).toBe(200);
1279
- expect(body).toEqual({ result: date.getTime() + 1 });
1280
- expect(createContextMock).toHaveBeenCalledTimes(1);
1281
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1282
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1283
-
1284
- clearMocks();
1285
- }
1286
-
1287
- {
1288
- const res = await fetch(`${url}/plus-one/9`, { method: 'GET' });
1289
- const body = await res.json();
1290
-
1291
- expect(res.status).toBe(200);
1292
- expect(body).toEqual({ result: 10 });
1293
- expect(createContextMock).toHaveBeenCalledTimes(1);
1294
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1295
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1296
- }
1297
-
1298
- close();
1299
- });
1300
-
1301
- test('with x-www-form-urlencoded', async () => {
1302
- const appRouter = t.router({
1303
- echo: t.procedure
1304
- .meta({
1305
- openapi: {
1306
- method: 'POST',
1307
- path: '/echo',
1308
- contentTypes: ['application/x-www-form-urlencoded'],
1309
- },
1310
- })
1311
- .input(z.object({ payload: z.array(z.string()) }))
1312
- .output(z.object({ result: z.string() }))
1313
- .query(({ input }) => ({ result: input.payload.join(' ') })),
1314
- });
1315
-
1316
- const { url, close } = createHttpServerWithRouter({
1317
- router: appRouter,
1318
- });
1319
-
1320
- const res = await fetch(`${url}/echo`, {
1321
- method: 'POST',
1322
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1323
- body: 'payload=Hello&payload=World',
1324
- });
1325
- const body = await res.json();
1326
-
1327
- expect(res.status).toBe(200);
1328
- expect(body).toEqual({ result: 'Hello World' });
1329
- expect(createContextMock).toHaveBeenCalledTimes(1);
1330
- expect(responseMetaMock).toHaveBeenCalledTimes(1);
1331
- expect(onErrorMock).toHaveBeenCalledTimes(0);
1332
-
1333
- close();
1334
- });
1335
- });