@orpc/server 0.0.0-next.b15d206 → 0.0.0-next.c59d67c

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 (52) hide show
  1. package/dist/fetch.js +73 -648
  2. package/dist/src/fetch/handle.d.ts +7 -0
  3. package/dist/src/fetch/handler.d.ts +3 -0
  4. package/dist/src/fetch/index.d.ts +4 -0
  5. package/dist/src/{adapters/fetch.d.ts → fetch/types.d.ts} +9 -16
  6. package/package.json +15 -18
  7. package/dist/chunk-TDFYNRZV.js.map +0 -1
  8. package/dist/fetch.js.map +0 -1
  9. package/dist/index.js.map +0 -1
  10. package/dist/src/adapters/fetch.d.ts.map +0 -1
  11. package/dist/src/builder.d.ts.map +0 -1
  12. package/dist/src/index.d.ts.map +0 -1
  13. package/dist/src/middleware.d.ts.map +0 -1
  14. package/dist/src/procedure-builder.d.ts.map +0 -1
  15. package/dist/src/procedure-caller.d.ts.map +0 -1
  16. package/dist/src/procedure-implementer.d.ts.map +0 -1
  17. package/dist/src/procedure.d.ts.map +0 -1
  18. package/dist/src/router-builder.d.ts.map +0 -1
  19. package/dist/src/router-caller.d.ts.map +0 -1
  20. package/dist/src/router-implementer.d.ts.map +0 -1
  21. package/dist/src/router.d.ts.map +0 -1
  22. package/dist/src/types.d.ts.map +0 -1
  23. package/dist/src/utils.d.ts.map +0 -1
  24. package/dist/tsconfig.tsbuildinfo +0 -1
  25. package/src/adapters/fetch.test.ts +0 -629
  26. package/src/adapters/fetch.ts +0 -290
  27. package/src/builder.test.ts +0 -371
  28. package/src/builder.ts +0 -238
  29. package/src/index.ts +0 -16
  30. package/src/middleware.test.ts +0 -260
  31. package/src/middleware.ts +0 -136
  32. package/src/procedure-builder.test.ts +0 -223
  33. package/src/procedure-builder.ts +0 -158
  34. package/src/procedure-caller.test.ts +0 -171
  35. package/src/procedure-caller.ts +0 -138
  36. package/src/procedure-implementer.test.ts +0 -220
  37. package/src/procedure-implementer.ts +0 -102
  38. package/src/procedure.test.ts +0 -317
  39. package/src/procedure.ts +0 -237
  40. package/src/router-builder.test.ts +0 -106
  41. package/src/router-builder.ts +0 -122
  42. package/src/router-caller.test.ts +0 -126
  43. package/src/router-caller.ts +0 -64
  44. package/src/router-implementer.test.ts +0 -116
  45. package/src/router-implementer.ts +0 -113
  46. package/src/router.test-d.ts +0 -48
  47. package/src/router.test.ts +0 -142
  48. package/src/router.ts +0 -91
  49. package/src/types.test.ts +0 -18
  50. package/src/types.ts +0 -13
  51. package/src/utils.test.ts +0 -16
  52. 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
- })