@orpc/server 0.10.0 → 0.11.0

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 (64) hide show
  1. package/dist/{chunk-TDFYNRZV.js → chunk-CVLK2PBB.js} +0 -1
  2. package/dist/fetch.js +1 -2
  3. package/dist/index.js +1 -2
  4. package/dist/src/adapters/fetch.d.ts +0 -1
  5. package/dist/src/builder.d.ts +0 -1
  6. package/dist/src/index.d.ts +0 -1
  7. package/dist/src/middleware.d.ts +0 -1
  8. package/dist/src/procedure-builder.d.ts +0 -1
  9. package/dist/src/procedure-caller.d.ts +0 -1
  10. package/dist/src/procedure-implementer.d.ts +0 -1
  11. package/dist/src/procedure.d.ts +0 -1
  12. package/dist/src/router-builder.d.ts +0 -1
  13. package/dist/src/router-caller.d.ts +0 -1
  14. package/dist/src/router-implementer.d.ts +0 -1
  15. package/dist/src/router.d.ts +0 -1
  16. package/dist/src/types.d.ts +0 -1
  17. package/dist/src/utils.d.ts +0 -1
  18. package/package.json +12 -16
  19. package/dist/chunk-TDFYNRZV.js.map +0 -1
  20. package/dist/fetch.js.map +0 -1
  21. package/dist/index.js.map +0 -1
  22. package/dist/src/adapters/fetch.d.ts.map +0 -1
  23. package/dist/src/builder.d.ts.map +0 -1
  24. package/dist/src/index.d.ts.map +0 -1
  25. package/dist/src/middleware.d.ts.map +0 -1
  26. package/dist/src/procedure-builder.d.ts.map +0 -1
  27. package/dist/src/procedure-caller.d.ts.map +0 -1
  28. package/dist/src/procedure-implementer.d.ts.map +0 -1
  29. package/dist/src/procedure.d.ts.map +0 -1
  30. package/dist/src/router-builder.d.ts.map +0 -1
  31. package/dist/src/router-caller.d.ts.map +0 -1
  32. package/dist/src/router-implementer.d.ts.map +0 -1
  33. package/dist/src/router.d.ts.map +0 -1
  34. package/dist/src/types.d.ts.map +0 -1
  35. package/dist/src/utils.d.ts.map +0 -1
  36. package/dist/tsconfig.tsbuildinfo +0 -1
  37. package/src/adapters/fetch.test.ts +0 -629
  38. package/src/adapters/fetch.ts +0 -290
  39. package/src/builder.test.ts +0 -371
  40. package/src/builder.ts +0 -238
  41. package/src/index.ts +0 -16
  42. package/src/middleware.test.ts +0 -260
  43. package/src/middleware.ts +0 -136
  44. package/src/procedure-builder.test.ts +0 -223
  45. package/src/procedure-builder.ts +0 -158
  46. package/src/procedure-caller.test.ts +0 -171
  47. package/src/procedure-caller.ts +0 -138
  48. package/src/procedure-implementer.test.ts +0 -220
  49. package/src/procedure-implementer.ts +0 -102
  50. package/src/procedure.test.ts +0 -317
  51. package/src/procedure.ts +0 -237
  52. package/src/router-builder.test.ts +0 -106
  53. package/src/router-builder.ts +0 -122
  54. package/src/router-caller.test.ts +0 -126
  55. package/src/router-caller.ts +0 -64
  56. package/src/router-implementer.test.ts +0 -116
  57. package/src/router-implementer.ts +0 -113
  58. package/src/router.test-d.ts +0 -48
  59. package/src/router.test.ts +0 -142
  60. package/src/router.ts +0 -91
  61. package/src/types.test.ts +0 -18
  62. package/src/types.ts +0 -13
  63. package/src/utils.test.ts +0 -16
  64. package/src/utils.ts +0 -16
@@ -1,629 +0,0 @@
1
- import { ORPC_HEADER, ORPC_HEADER_VALUE } from '@orpc/contract'
2
- import { oz } from '@orpc/zod'
3
- import { describe, expect, it } from 'vitest'
4
- import { z } from 'zod'
5
- import { ORPCError, os } from '..'
6
- import { createFetchHandler } from './fetch'
7
-
8
- const router = os.router({
9
- throw: os.func(() => {
10
- throw new Error('test')
11
- }),
12
- ping: os.func(() => {
13
- return 'ping'
14
- }),
15
- ping2: os.route({ method: 'GET', path: '/ping2' }).func(() => {
16
- return 'ping2'
17
- }),
18
- })
19
-
20
- const handler = createFetchHandler({ router })
21
-
22
- describe('simple', () => {
23
- const osw = os.context<{ auth?: boolean }>()
24
- const router = osw.router({
25
- ping: osw.func(async () => 'pong'),
26
- ping2: osw
27
- .route({ method: 'GET', path: '/ping2' })
28
- .func(async () => 'pong2'),
29
- })
30
- const handler = createFetchHandler({
31
- router,
32
- })
33
-
34
- it('200: public url', async () => {
35
- const response = await handler({
36
- prefix: '/orpc',
37
- request: new Request('http://localhost/orpc/ping', {
38
- method: 'POST',
39
- }),
40
- context: () => ({ auth: true }),
41
- })
42
-
43
- expect(response.status).toBe(200)
44
- expect(await response.json()).toEqual('pong')
45
-
46
- const response2 = await handler({
47
- prefix: '/orpc',
48
- request: new Request('http://localhost/orpc/ping2', {
49
- method: 'GET',
50
- }),
51
- context: { auth: true },
52
- })
53
-
54
- expect(response2.status).toBe(200)
55
- expect(await response2.json()).toEqual('pong2')
56
- })
57
-
58
- it('200: internal url', async () => {
59
- const response = await handler({
60
- request: new Request('http://localhost/ping'),
61
- context: { auth: true },
62
- })
63
-
64
- expect(response.status).toBe(200)
65
- expect(await response.json()).toEqual('pong')
66
-
67
- const response2 = await handler({
68
- prefix: '/orpc',
69
- request: new Request('http://localhost/orpc/ping2'),
70
- context: { auth: true },
71
- })
72
-
73
- expect(response2.status).toBe(200)
74
- expect(await response2.json()).toEqual('pong2')
75
- })
76
-
77
- it('404', async () => {
78
- const response = await handler({
79
- prefix: '/orpc',
80
- request: new Request('http://localhost/orpc/pingp', {
81
- method: 'POST',
82
- }),
83
- context: { auth: true },
84
- })
85
-
86
- expect(response.status).toBe(404)
87
- })
88
- })
89
-
90
- describe('procedure throw error', () => {
91
- it('unknown error', async () => {
92
- const response = await handler({
93
- request: new Request('https://local.com/throw', { method: 'POST' }),
94
- })
95
-
96
- expect(response.status).toBe(500)
97
- expect(await response.json()).toEqual({
98
- code: 'INTERNAL_SERVER_ERROR',
99
- status: 500,
100
- message: 'Internal server error',
101
- })
102
- })
103
-
104
- it('orpc error', async () => {
105
- const router = os.router({
106
- ping: os.func(() => {
107
- throw new ORPCError({ code: 'TIMEOUT' })
108
- }),
109
- })
110
-
111
- const handler = createFetchHandler({ router })
112
-
113
- const response = await handler({
114
- request: new Request('https://local.com/ping', { method: 'POST' }),
115
- })
116
-
117
- expect(response.status).toBe(408)
118
- expect(await response.json()).toEqual({
119
- code: 'TIMEOUT',
120
- status: 408,
121
- message: '',
122
- })
123
- })
124
-
125
- it('orpc error with data', async () => {
126
- const router = os.router({
127
- ping: os.func(() => {
128
- throw new ORPCError({
129
- code: 'PAYLOAD_TOO_LARGE',
130
- message: 'test',
131
- data: { max: '10mb' },
132
- })
133
- }),
134
- })
135
-
136
- const handler = createFetchHandler({ router })
137
-
138
- const response = await handler({
139
- request: new Request('https://local.com/ping', { method: 'POST' }),
140
- })
141
-
142
- expect(response.status).toBe(413)
143
- expect(await response.json()).toEqual({
144
- code: 'PAYLOAD_TOO_LARGE',
145
- status: 413,
146
- message: 'test',
147
- data: { max: '10mb' },
148
- })
149
- })
150
-
151
- it('orpc error with custom status', async () => {
152
- const router = os.router({
153
- ping: os.func(() => {
154
- throw new ORPCError({
155
- code: 'PAYLOAD_TOO_LARGE',
156
- status: 100,
157
- })
158
- }),
159
-
160
- ping2: os.func(() => {
161
- throw new ORPCError({
162
- code: 'PAYLOAD_TOO_LARGE',
163
- status: 488,
164
- })
165
- }),
166
- })
167
-
168
- const handler = createFetchHandler({ router })
169
-
170
- const response = await handler({
171
- request: new Request('https://local.com/ping', { method: 'POST' }),
172
- })
173
-
174
- expect(response.status).toBe(500)
175
- expect(await response.json()).toEqual({
176
- code: 'INTERNAL_SERVER_ERROR',
177
- status: 500,
178
- message: 'Internal server error',
179
- })
180
-
181
- const response2 = await handler({
182
- request: new Request('https://local.com/ping2', { method: 'POST' }),
183
- })
184
-
185
- expect(response2.status).toBe(488)
186
- expect(await response2.json()).toEqual({
187
- code: 'PAYLOAD_TOO_LARGE',
188
- status: 488,
189
- message: '',
190
- })
191
- })
192
-
193
- it('input validation error', async () => {
194
- const router = os.router({
195
- ping: os
196
- .input(z.object({}))
197
- .output(z.string())
198
- .func(() => {
199
- return 'unnoq'
200
- }),
201
- })
202
-
203
- const handler = createFetchHandler({ router })
204
-
205
- const response = await handler({
206
- request: new Request('https://local.com/ping', { method: 'POST' }),
207
- })
208
-
209
- expect(response.status).toBe(400)
210
- expect(await response.json()).toEqual({
211
- code: 'BAD_REQUEST',
212
- status: 400,
213
- message: 'Validation input failed',
214
- issues: [
215
- {
216
- code: 'invalid_type',
217
- expected: 'object',
218
- message: 'Required',
219
- path: [],
220
- received: 'undefined',
221
- },
222
- ],
223
- })
224
- })
225
-
226
- it('output validation error', async () => {
227
- const router = os.router({
228
- ping: os
229
- .input(z.string())
230
- .output(z.string())
231
- .func(() => {
232
- return 12344 as any
233
- }),
234
- })
235
-
236
- const handler = createFetchHandler({ router })
237
-
238
- const response = await handler({
239
- request: new Request('https://local.com/ping', {
240
- method: 'POST',
241
- body: '"hi"',
242
- }),
243
- })
244
-
245
- expect(response.status).toBe(500)
246
- expect(await response.json()).toEqual({
247
- code: 'INTERNAL_SERVER_ERROR',
248
- status: 500,
249
- message: 'Validation output failed',
250
- })
251
- })
252
- })
253
-
254
- describe('hooks', () => {
255
- it('on success', async () => {
256
- const onSuccess = vi.fn()
257
- const onError = vi.fn()
258
- const onFinish = vi.fn()
259
-
260
- const handler = createFetchHandler({
261
- router,
262
- hooks: async (context, hooks) => {
263
- try {
264
- const response = await hooks.next()
265
- onSuccess(response)
266
- return response
267
- }
268
- catch (e) {
269
- onError(e)
270
- throw e
271
- }
272
- finally {
273
- onFinish()
274
- }
275
- },
276
- })
277
-
278
- await handler({
279
- prefix: '/orpc',
280
- request: new Request('http://localhost/orpc/ping', {
281
- method: 'POST',
282
- }),
283
- })
284
-
285
- expect(onSuccess).toHaveBeenCalledTimes(1)
286
- expect(onError).toHaveBeenCalledTimes(0)
287
- expect(onFinish).toHaveBeenCalledTimes(1)
288
-
289
- expect(onSuccess.mock.calls[0]?.[0]).toBeInstanceOf(Response)
290
- expect(onFinish.mock.calls[0]?.[1]).toBe(undefined)
291
- })
292
-
293
- it('on failed', async () => {
294
- const onSuccess = vi.fn()
295
- const onError = vi.fn()
296
- const onFinish = vi.fn()
297
-
298
- const handler = createFetchHandler({
299
- router,
300
- hooks: async (context, hooks) => {
301
- try {
302
- const response = await hooks.next()
303
- onSuccess(response)
304
- return response
305
- }
306
- catch (e) {
307
- onError(e)
308
- throw e
309
- }
310
- finally {
311
- onFinish()
312
- }
313
- },
314
- })
315
-
316
- await handler({
317
- prefix: '/orpc',
318
- request: new Request('http://localhost/orpc/throw', {
319
- method: 'POST',
320
- }),
321
- })
322
-
323
- expect(onSuccess).toHaveBeenCalledTimes(0)
324
- expect(onError).toHaveBeenCalledTimes(1)
325
- expect(onFinish).toHaveBeenCalledTimes(1)
326
-
327
- expect(onError.mock.calls[0]?.[0]).toBeInstanceOf(Error)
328
- expect(onError.mock.calls[0]?.[0]?.message).toBe('test')
329
- expect(onFinish.mock.calls[0]?.[0]).toBe(undefined)
330
- })
331
- })
332
-
333
- describe('file upload', () => {
334
- const router = os.router({
335
- signal: os.input(z.instanceof(Blob)).func((input) => {
336
- return input
337
- }),
338
- multiple: os
339
- .input(
340
- z.object({ first: z.instanceof(Blob), second: z.instanceof(Blob) }),
341
- )
342
- .func((input) => {
343
- return input
344
- }),
345
- })
346
-
347
- const handler = createFetchHandler({ router })
348
-
349
- const blob1 = new Blob(['hello'], { type: 'text/plain;charset=utf-8' })
350
- const blob2 = new Blob(['"world"'], { type: 'image/png' })
351
- const blob3 = new Blob(['unnoq'], { type: 'application/octet-stream' })
352
-
353
- it('single file', async () => {
354
- const rForm = new FormData()
355
- rForm.set('meta', JSON.stringify([]))
356
- rForm.set('maps', JSON.stringify([[]]))
357
- rForm.set('0', blob3)
358
-
359
- const response = await handler({
360
- prefix: '/orpc',
361
- request: new Request('http://localhost/orpc/signal', {
362
- method: 'POST',
363
- body: rForm,
364
- headers: {
365
- [ORPC_HEADER]: ORPC_HEADER_VALUE,
366
- },
367
- }),
368
- })
369
-
370
- expect(response.status).toBe(200)
371
- const form = await response.formData()
372
-
373
- const file0 = form.get('0') as File
374
- expect(file0).toBeInstanceOf(File)
375
- expect(file0.name).toBe('blob')
376
- expect(file0.type).toBe('application/octet-stream')
377
- expect(await file0.text()).toBe('unnoq')
378
- })
379
-
380
- it('multiple file', async () => {
381
- const form = new FormData()
382
- form.set('data', JSON.stringify({ first: blob1, second: blob2 }))
383
- form.set('meta', JSON.stringify([]))
384
- form.set('maps', JSON.stringify([['first'], ['second']]))
385
- form.set('0', blob1)
386
- form.set('1', blob2)
387
-
388
- const response = await handler({
389
- prefix: '/orpc',
390
- request: new Request('http://localhost/orpc/multiple', {
391
- method: 'POST',
392
- body: form,
393
- headers: {
394
- [ORPC_HEADER]: ORPC_HEADER_VALUE,
395
- },
396
- }),
397
- })
398
-
399
- expect(response.status).toBe(200)
400
-
401
- const form_ = await response.formData()
402
- const file0 = form_.get('0') as File
403
- const file1 = form_.get('1') as File
404
-
405
- expect(file0).toBeInstanceOf(File)
406
- expect(file0.name).toBe('blob')
407
- expect(file0.type).toBe('text/plain;charset=utf-8')
408
- expect(await file0.text()).toBe('hello')
409
-
410
- expect(file1).toBeInstanceOf(File)
411
- expect(file1.name).toBe('blob')
412
- expect(file1.type).toBe('image/png')
413
- expect(await file1.text()).toBe('"world"')
414
- })
415
- })
416
-
417
- describe('accept header', () => {
418
- const router = os.router({
419
- ping: os.func(async () => 'pong'),
420
- })
421
- const handler = createFetchHandler({
422
- router,
423
- })
424
-
425
- it('application/json', async () => {
426
- const response = await handler({
427
- prefix: '/orpc',
428
- request: new Request('http://localhost/orpc/ping', {
429
- method: 'POST',
430
- headers: {
431
- Accept: 'application/json',
432
- },
433
- }),
434
- })
435
-
436
- expect(response.headers.get('Content-Type')).toEqual('application/json')
437
-
438
- expect(await response.json()).toEqual('pong')
439
- })
440
-
441
- it('multipart/form-data', async () => {
442
- const response = await handler({
443
- prefix: '/orpc',
444
- request: new Request('http://localhost/orpc/ping', {
445
- method: 'POST',
446
- headers: {
447
- Accept: 'multipart/form-data',
448
- },
449
- }),
450
- })
451
-
452
- expect(response.headers.get('Content-Type')).toContain(
453
- 'multipart/form-data',
454
- )
455
-
456
- const form = await response.formData()
457
- expect(form.get('')).toEqual('pong')
458
- })
459
-
460
- it('application/x-www-form-urlencoded', async () => {
461
- const response = await handler({
462
- prefix: '/orpc',
463
- request: new Request('http://localhost/orpc/ping', {
464
- method: 'POST',
465
- headers: {
466
- Accept: 'application/x-www-form-urlencoded',
467
- },
468
- }),
469
- })
470
-
471
- expect(response.headers.get('Content-Type')).toEqual(
472
- 'application/x-www-form-urlencoded',
473
- )
474
-
475
- const params = new URLSearchParams(await response.text())
476
- expect(params.get('')).toEqual('pong')
477
- })
478
-
479
- it('*/*', async () => {
480
- const response = await handler({
481
- prefix: '/orpc',
482
- request: new Request('http://localhost/orpc/ping', {
483
- method: 'POST',
484
- headers: {
485
- Accept: '*/*',
486
- },
487
- }),
488
- })
489
-
490
- expect(response.headers.get('Content-Type')).toEqual('application/json')
491
- expect(await response.json()).toEqual('pong')
492
- })
493
-
494
- it('invalid', async () => {
495
- const response = await handler({
496
- prefix: '/orpc',
497
- request: new Request('http://localhost/orpc/ping', {
498
- method: 'POST',
499
- headers: {
500
- Accept: 'invalid',
501
- },
502
- }),
503
- })
504
-
505
- expect(response.headers.get('Content-Type')).toEqual('application/json')
506
- expect(await response.json()).toEqual({
507
- code: 'NOT_ACCEPTABLE',
508
- message: 'Unsupported content-type: invalid',
509
- status: 406,
510
- })
511
- })
512
- })
513
-
514
- describe('dynamic params', () => {
515
- const router = os.router({
516
- deep: os
517
- .route({
518
- method: 'POST',
519
- path: '/{id}/{id2}',
520
- })
521
- .input(
522
- z.object({
523
- id: z.number(),
524
- id2: z.string(),
525
- file: oz.file(),
526
- }),
527
- )
528
- .func(input => input),
529
-
530
- find: os
531
- .route({
532
- method: 'GET',
533
- path: '/{id}',
534
- })
535
- .input(
536
- z.object({
537
- id: z.number(),
538
- }),
539
- )
540
- .func(input => input),
541
- })
542
-
543
- const handlers = [
544
- createFetchHandler({
545
- router,
546
- }),
547
- createFetchHandler({
548
- router,
549
- serverless: true,
550
- }),
551
- ]
552
-
553
- it.each(handlers)('should handle dynamic params', async (handler) => {
554
- const response = await handler({
555
- request: new Request('http://localhost/123'),
556
- })
557
-
558
- expect(response.status).toEqual(200)
559
- expect(response.headers.get('Content-Type')).toEqual('application/json')
560
- expect(await response.json()).toEqual({ id: 123 })
561
- })
562
-
563
- it.each(handlers)('should handle deep dynamic params', async (handler) => {
564
- const form = new FormData()
565
- form.append('file', new Blob(['hello']), 'hello.txt')
566
-
567
- const response = await handler({
568
- request: new Request('http://localhost/123/dfdsfds', {
569
- method: 'POST',
570
- body: form,
571
- }),
572
- })
573
-
574
- expect(response.status).toEqual(200)
575
- const rForm = await response.formData()
576
- expect(rForm.get('id')).toEqual('123')
577
- expect(rForm.get('id2')).toEqual('dfdsfds')
578
- })
579
- })
580
-
581
- describe('can control method on POST request', () => {
582
- const router = os.router({
583
- update: os
584
- .route({
585
- method: 'PUT',
586
- path: '/{id}',
587
- })
588
- .input(
589
- z.object({
590
- id: z.number(),
591
- file: oz.file(),
592
- }),
593
- )
594
- .func(input => input),
595
- })
596
-
597
- const handlers = [
598
- createFetchHandler({
599
- router,
600
- }),
601
- createFetchHandler({
602
- router,
603
- serverless: true,
604
- }),
605
- ]
606
-
607
- it.each(handlers)('work', async (handler) => {
608
- const form = new FormData()
609
- form.set('file', new File(['hello'], 'hello.txt'))
610
-
611
- const response = await handler({
612
- request: new Request('http://localhost/123', {
613
- method: 'POST',
614
- body: form,
615
- }),
616
- })
617
-
618
- expect(response.status).toEqual(404)
619
-
620
- const response2 = await handler({
621
- request: new Request('http://localhost/123?method=PUT', {
622
- method: 'POST',
623
- body: form,
624
- }),
625
- })
626
-
627
- expect(response2.status).toEqual(200)
628
- })
629
- })