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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. package/dist/{chunk-TDFYNRZV.js → chunk-FL4ZAGNE.js} +134 -57
  2. package/dist/fetch.js +75 -649
  3. package/dist/index.js +190 -116
  4. package/dist/src/builder.d.ts +5 -1
  5. package/dist/src/fetch/handle.d.ts +7 -0
  6. package/dist/src/fetch/handler.d.ts +3 -0
  7. package/dist/src/fetch/index.d.ts +4 -0
  8. package/dist/src/{adapters/fetch.d.ts → fetch/types.d.ts} +9 -16
  9. package/dist/src/index.d.ts +2 -0
  10. package/dist/src/lazy.d.ts +23 -0
  11. package/dist/src/procedure-caller.d.ts +6 -5
  12. package/dist/src/procedure-implementer.d.ts +5 -1
  13. package/dist/src/procedure.d.ts +5 -2
  14. package/dist/src/router-builder.d.ts +5 -0
  15. package/dist/src/router-caller.d.ts +2 -2
  16. package/dist/src/router-implementer.d.ts +7 -3
  17. package/dist/src/router.d.ts +4 -3
  18. package/package.json +15 -18
  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
- })