@orpc/server 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. package/dist/chunk-ACLC6USM.js +262 -0
  2. package/dist/chunk-ACLC6USM.js.map +1 -0
  3. package/dist/fetch.js +648 -0
  4. package/dist/fetch.js.map +1 -0
  5. package/dist/index.js +403 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/adapters/fetch.d.ts +36 -0
  8. package/dist/src/adapters/fetch.d.ts.map +1 -0
  9. package/dist/src/builder.d.ts +49 -0
  10. package/dist/src/builder.d.ts.map +1 -0
  11. package/dist/src/index.d.ts +15 -0
  12. package/dist/src/index.d.ts.map +1 -0
  13. package/dist/src/middleware.d.ts +16 -0
  14. package/dist/src/middleware.d.ts.map +1 -0
  15. package/dist/src/procedure-builder.d.ts +31 -0
  16. package/dist/src/procedure-builder.d.ts.map +1 -0
  17. package/dist/src/procedure-caller.d.ts +33 -0
  18. package/dist/src/procedure-caller.d.ts.map +1 -0
  19. package/dist/src/procedure-implementer.d.ts +19 -0
  20. package/dist/src/procedure-implementer.d.ts.map +1 -0
  21. package/dist/src/procedure.d.ts +29 -0
  22. package/dist/src/procedure.d.ts.map +1 -0
  23. package/dist/src/router-builder.d.ts +22 -0
  24. package/dist/src/router-builder.d.ts.map +1 -0
  25. package/dist/src/router-caller.d.ts +36 -0
  26. package/dist/src/router-caller.d.ts.map +1 -0
  27. package/dist/src/router-implementer.d.ts +20 -0
  28. package/dist/src/router-implementer.d.ts.map +1 -0
  29. package/dist/src/router.d.ts +14 -0
  30. package/dist/src/router.d.ts.map +1 -0
  31. package/dist/src/types.d.ts +18 -0
  32. package/dist/src/types.d.ts.map +1 -0
  33. package/dist/src/utils.d.ts +4 -0
  34. package/dist/src/utils.d.ts.map +1 -0
  35. package/dist/tsconfig.tsbuildinfo +1 -0
  36. package/package.json +57 -0
  37. package/src/adapters/fetch.test.ts +399 -0
  38. package/src/adapters/fetch.ts +228 -0
  39. package/src/builder.test.ts +362 -0
  40. package/src/builder.ts +236 -0
  41. package/src/index.ts +16 -0
  42. package/src/middleware.test.ts +279 -0
  43. package/src/middleware.ts +119 -0
  44. package/src/procedure-builder.test.ts +219 -0
  45. package/src/procedure-builder.ts +158 -0
  46. package/src/procedure-caller.test.ts +210 -0
  47. package/src/procedure-caller.ts +165 -0
  48. package/src/procedure-implementer.test.ts +215 -0
  49. package/src/procedure-implementer.ts +103 -0
  50. package/src/procedure.test.ts +312 -0
  51. package/src/procedure.ts +251 -0
  52. package/src/router-builder.test.ts +106 -0
  53. package/src/router-builder.ts +120 -0
  54. package/src/router-caller.test.ts +173 -0
  55. package/src/router-caller.ts +95 -0
  56. package/src/router-implementer.test.ts +116 -0
  57. package/src/router-implementer.ts +110 -0
  58. package/src/router.test.ts +142 -0
  59. package/src/router.ts +69 -0
  60. package/src/types.test.ts +18 -0
  61. package/src/types.ts +28 -0
  62. package/src/utils.test.ts +243 -0
  63. package/src/utils.ts +95 -0
@@ -0,0 +1,120 @@
1
+ import { DecoratedContractProcedure, type HTTPPath } from '@orpc/contract'
2
+ import {
3
+ type MapInputMiddleware,
4
+ type Middleware,
5
+ decorateMiddleware,
6
+ } from './middleware'
7
+ import { decorateProcedure, isProcedure } from './procedure'
8
+ import type { HandledRouter, Router } from './router'
9
+ import type { Context, MergeContext } from './types'
10
+
11
+ export class RouterBuilder<
12
+ TContext extends Context,
13
+ TExtraContext extends Context,
14
+ > {
15
+ constructor(
16
+ public zz$rb: {
17
+ prefix?: HTTPPath
18
+ tags?: string[]
19
+ middlewares?: Middleware<any, any, any, any>[]
20
+ },
21
+ ) {}
22
+
23
+ prefix(prefix: HTTPPath): RouterBuilder<TContext, TExtraContext> {
24
+ return new RouterBuilder({
25
+ ...this.zz$rb,
26
+ prefix: `${this.zz$rb.prefix ?? ''}${prefix}`,
27
+ })
28
+ }
29
+
30
+ tags(...tags: string[]): RouterBuilder<TContext, TExtraContext> {
31
+ if (!tags.length) return this
32
+
33
+ return new RouterBuilder({
34
+ ...this.zz$rb,
35
+ tags: [...(this.zz$rb.tags ?? []), ...tags],
36
+ })
37
+ }
38
+
39
+ use<
40
+ UExtraContext extends
41
+ | Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
42
+ | undefined = undefined,
43
+ >(
44
+ middleware: Middleware<
45
+ MergeContext<TContext, TExtraContext>,
46
+ UExtraContext,
47
+ unknown,
48
+ unknown
49
+ >,
50
+ ): RouterBuilder<TContext, MergeContext<TExtraContext, UExtraContext>>
51
+
52
+ use<
53
+ UExtraContext extends
54
+ | Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
55
+ | undefined = undefined,
56
+ UMappedInput = unknown,
57
+ >(
58
+ middleware: Middleware<
59
+ MergeContext<TContext, TExtraContext>,
60
+ UExtraContext,
61
+ UMappedInput,
62
+ unknown
63
+ >,
64
+ mapInput: MapInputMiddleware<unknown, UMappedInput>,
65
+ ): RouterBuilder<TContext, MergeContext<TExtraContext, UExtraContext>>
66
+
67
+ use(
68
+ middleware: Middleware<any, any, any, any>,
69
+ mapInput?: MapInputMiddleware<any, any>,
70
+ ): RouterBuilder<any, any> {
71
+ const middleware_ = mapInput
72
+ ? decorateMiddleware(middleware).mapInput(mapInput)
73
+ : middleware
74
+
75
+ return new RouterBuilder({
76
+ ...this.zz$rb,
77
+ middlewares: [...(this.zz$rb.middlewares || []), middleware_],
78
+ })
79
+ }
80
+
81
+ router<URouter extends Router<TContext>>(
82
+ router: URouter,
83
+ ): HandledRouter<URouter> {
84
+ const handled: Router<TContext> = {}
85
+
86
+ for (const key in router) {
87
+ const item = router[key]
88
+
89
+ if (isProcedure(item)) {
90
+ const builderMiddlewares = this.zz$rb.middlewares ?? []
91
+ const itemMiddlewares = item.zz$p.middlewares ?? []
92
+
93
+ const middlewares = [
94
+ ...builderMiddlewares,
95
+ ...itemMiddlewares.filter(
96
+ (item) => !builderMiddlewares.includes(item),
97
+ ),
98
+ ]
99
+
100
+ const contract = DecoratedContractProcedure.decorate(
101
+ item.zz$p.contract,
102
+ ).addTags(...(this.zz$rb.tags ?? []))
103
+
104
+ handled[key] = decorateProcedure({
105
+ zz$p: {
106
+ ...item.zz$p,
107
+ contract: this.zz$rb.prefix
108
+ ? contract.prefix(this.zz$rb.prefix)
109
+ : contract,
110
+ middlewares,
111
+ },
112
+ })
113
+ } else {
114
+ handled[key] = this.router(item as any)
115
+ }
116
+ }
117
+
118
+ return handled as HandledRouter<URouter>
119
+ }
120
+ }
@@ -0,0 +1,173 @@
1
+ import { z } from 'zod'
2
+ import { os, createRouterCaller } from '.'
3
+
4
+ describe('createRouterCaller', () => {
5
+ let internal = false
6
+ let context = { auth: true }
7
+
8
+ const osw = os.context<{ auth?: boolean }>()
9
+
10
+ const ping = osw
11
+ .input(z.object({ value: z.string().transform((v) => Number(v)) }))
12
+ .output(z.object({ value: z.number().transform((v) => v.toString()) }))
13
+ .handler((input, context, meta) => {
14
+ expect(context).toEqual(context)
15
+ expect(meta.internal).toEqual(internal)
16
+
17
+ return input
18
+ })
19
+
20
+ const pong = osw.handler((_, context, meta) => {
21
+ expect(context).toEqual(context)
22
+ expect(meta.internal).toBe(internal)
23
+
24
+ return { value: true }
25
+ })
26
+
27
+ const router = osw.router({
28
+ ping,
29
+ pong,
30
+ nested: {
31
+ ping,
32
+ pong,
33
+ },
34
+ })
35
+
36
+ it('infer context', () => {
37
+ createRouterCaller({
38
+ router,
39
+ // @ts-expect-error invalid context
40
+ context: { auth: 123 },
41
+ })
42
+
43
+ createRouterCaller({
44
+ router,
45
+ context,
46
+ })
47
+ })
48
+
49
+ it('with validate', () => {
50
+ const caller = createRouterCaller({
51
+ router,
52
+ context,
53
+ internal,
54
+ })
55
+
56
+ expectTypeOf(caller.ping).toMatchTypeOf<
57
+ (input: { value: string }) => Promise<{
58
+ value: string
59
+ }>
60
+ >()
61
+
62
+ expectTypeOf(caller.pong).toMatchTypeOf<
63
+ (input: unknown) => Promise<{
64
+ value: boolean
65
+ }>
66
+ >()
67
+
68
+ expectTypeOf(caller.nested.ping).toMatchTypeOf<
69
+ (input: { value: string }) => Promise<{
70
+ value: string
71
+ }>
72
+ >()
73
+
74
+ expectTypeOf(caller.nested.pong).toMatchTypeOf<
75
+ (input: unknown) => Promise<{
76
+ value: boolean
77
+ }>
78
+ >()
79
+
80
+ expect(caller.ping({ value: '123' })).resolves.toEqual({ value: '123' })
81
+ expect(caller.pong({ value: '123' })).resolves.toEqual({ value: true })
82
+
83
+ expect(caller.nested.ping({ value: '123' })).resolves.toEqual({
84
+ value: '123',
85
+ })
86
+ expect(caller.nested.pong({ value: '123' })).resolves.toEqual({
87
+ value: true,
88
+ })
89
+
90
+ // @ts-expect-error
91
+ expect(caller.ping({ value: new Date('2023-01-01') })).rejects.toThrowError(
92
+ 'Validation input failed',
93
+ )
94
+
95
+ // @ts-expect-error
96
+ expect(caller.nested.ping({ value: true })).rejects.toThrowError(
97
+ 'Validation input failed',
98
+ )
99
+ })
100
+
101
+ it('without validate', () => {
102
+ internal = true
103
+ context = { auth: false }
104
+
105
+ const caller = createRouterCaller({
106
+ router,
107
+ context,
108
+ internal,
109
+ validate: false,
110
+ })
111
+
112
+ expectTypeOf(caller.ping).toMatchTypeOf<
113
+ (input: { value: number }) => Promise<{
114
+ value: number
115
+ }>
116
+ >()
117
+
118
+ expectTypeOf(caller.pong).toMatchTypeOf<
119
+ (input: unknown) => Promise<{
120
+ value: boolean
121
+ }>
122
+ >()
123
+
124
+ expectTypeOf(caller.nested.ping).toMatchTypeOf<
125
+ (input: { value: number }) => Promise<{
126
+ value: number
127
+ }>
128
+ >()
129
+
130
+ expectTypeOf(caller.nested.pong).toMatchTypeOf<
131
+ (input: unknown) => Promise<{
132
+ value: boolean
133
+ }>
134
+ >()
135
+
136
+ expect(caller.ping({ value: 123 })).resolves.toEqual({ value: 123 })
137
+ expect(caller.pong({ value: 123 })).resolves.toEqual({ value: true })
138
+ expect(caller.nested.ping({ value: 123 })).resolves.toEqual({ value: 123 })
139
+ expect(caller.nested.pong({ value: 123 })).resolves.toEqual({ value: true })
140
+
141
+ // @ts-expect-error it's not validate so bellow still works
142
+ expect(caller.ping({ value: '123' })).resolves.toEqual({ value: '123' })
143
+ })
144
+
145
+ it('path', () => {
146
+ const ping = osw.handler((_, __, { path }) => {
147
+ return path
148
+ })
149
+
150
+ const router = osw.router({
151
+ ping,
152
+ nested: {
153
+ ping,
154
+ child: {
155
+ ping,
156
+ },
157
+ },
158
+ })
159
+
160
+ const caller = createRouterCaller({
161
+ router: router,
162
+ context,
163
+ })
164
+
165
+ expect(caller.ping('')).resolves.toEqual(['ping'])
166
+ expect(caller.nested.ping('')).resolves.toEqual(['nested', 'ping'])
167
+ expect(caller.nested.child.ping('')).resolves.toEqual([
168
+ 'nested',
169
+ 'child',
170
+ 'ping',
171
+ ])
172
+ })
173
+ })
@@ -0,0 +1,95 @@
1
+ import type {} from '@orpc/contract'
2
+ import { type Procedure, isProcedure } from './procedure'
3
+ import { type ProcedureCaller, createProcedureCaller } from './procedure-caller'
4
+ import type { Router } from './router'
5
+ import type { Meta, Promisable } from './types'
6
+ import {} from './utils'
7
+
8
+ export interface CreateRouterCallerOptions<
9
+ TRouter extends Router<any>,
10
+ TValidate extends boolean,
11
+ > {
12
+ router: TRouter
13
+
14
+ /**
15
+ * The context used when calling the procedure.
16
+ */
17
+ context: TRouter extends Router<infer UContext> ? UContext : never
18
+
19
+ /**
20
+ * Helpful hooks to do some logics on specific time.
21
+ */
22
+ hooks?: (
23
+ context: TRouter extends Router<infer UContext> ? UContext : never,
24
+ meta: Meta<unknown>,
25
+ ) => Promisable<void>
26
+
27
+ /**
28
+ * This is helpful for logging and analytics.
29
+ */
30
+ basePath?: string[]
31
+
32
+ /**
33
+ * This flag helpful when you want bypass some logics not necessary to internal server calls.
34
+ *
35
+ * @default true
36
+ */
37
+ internal?: boolean
38
+
39
+ /**
40
+ * Indicate whether validate input and output.
41
+ *
42
+ * @default true
43
+ */
44
+ validate?: TValidate
45
+ }
46
+
47
+ export type RouterCaller<
48
+ TRouter extends Router<any>,
49
+ TValidate extends boolean,
50
+ > = {
51
+ [K in keyof TRouter]: TRouter[K] extends Procedure<any, any, any, any, any>
52
+ ? ProcedureCaller<TRouter[K], TValidate>
53
+ : TRouter[K] extends Router<any>
54
+ ? RouterCaller<TRouter[K], TValidate>
55
+ : never
56
+ }
57
+
58
+ export function createRouterCaller<
59
+ TRouter extends Router<any>,
60
+ TValidate extends boolean = true,
61
+ >(
62
+ options: CreateRouterCallerOptions<TRouter, TValidate>,
63
+ ): RouterCaller<TRouter, TValidate> {
64
+ const internal = options.internal ?? true
65
+ const validate = options.validate ?? true
66
+
67
+ const caller: Record<string, unknown> = {}
68
+
69
+ for (const key in options.router) {
70
+ const path = [...(options.basePath ?? []), key]
71
+ const item = options.router[key]
72
+
73
+ if (isProcedure(item)) {
74
+ caller[key] = createProcedureCaller({
75
+ procedure: item,
76
+ context: options.context as any,
77
+ hooks: options.hooks as any,
78
+ path: path,
79
+ internal,
80
+ validate,
81
+ })
82
+ } else {
83
+ caller[key] = createRouterCaller({
84
+ router: item as any,
85
+ context: options.context,
86
+ hooks: options.hooks,
87
+ basePath: path,
88
+ internal,
89
+ validate,
90
+ })
91
+ }
92
+ }
93
+
94
+ return caller as RouterCaller<TRouter, TValidate>
95
+ }
@@ -0,0 +1,116 @@
1
+ import { oc } from '@orpc/contract'
2
+ import { z } from 'zod'
3
+ import { os, RouterImplementer } from '.'
4
+
5
+ const cp1 = oc.input(z.string()).output(z.string())
6
+ const cp2 = oc.output(z.string())
7
+ const cp3 = oc.route({ method: 'GET', path: '/test' })
8
+ const cr = oc.router({
9
+ p1: cp1,
10
+ nested: oc.router({
11
+ p2: cp2,
12
+ }),
13
+ nested2: {
14
+ p3: cp3,
15
+ },
16
+ })
17
+
18
+ const osw = os.context<{ auth: boolean }>().contract(cr)
19
+
20
+ const p1 = osw.p1.handler(() => {
21
+ return 'dinwwwh'
22
+ })
23
+
24
+ const p2 = osw.nested.p2.handler(() => {
25
+ return 'dinwwwh'
26
+ })
27
+
28
+ const p3 = osw.nested2.p3.handler(() => {
29
+ return 'dinwwwh'
30
+ })
31
+
32
+ it('required all procedure match', () => {
33
+ const implementer = new RouterImplementer<{ auth: boolean }, typeof cr>({
34
+ contract: cr,
35
+ })
36
+
37
+ implementer.router({
38
+ p1: p1,
39
+ nested: {
40
+ p2: os.contract(cp2).handler(() => ''),
41
+ },
42
+ nested2: {
43
+ p3: p3,
44
+ },
45
+ })
46
+
47
+ expect(() => {
48
+ implementer.router({
49
+ // @ts-expect-error p1 is mismatch
50
+ p1: os.handler(() => {}),
51
+ nested: {
52
+ p2: p2,
53
+ },
54
+ nested2: {
55
+ p3: p3,
56
+ },
57
+ })
58
+ }).toThrowError('Mismatch implementation for procedure at [p1]')
59
+
60
+ expect(() => {
61
+ implementer.router({
62
+ // @ts-expect-error p1 is mismatch
63
+ p1: osw,
64
+ nested: {
65
+ p2: p2,
66
+ },
67
+ nested2: {
68
+ p3: p3,
69
+ },
70
+ })
71
+ }).toThrowError('Mismatch implementation for procedure at [p1]')
72
+
73
+ expect(() => {
74
+ implementer.router({
75
+ // Not allow manual specification
76
+ p1: os
77
+ .input(z.string())
78
+ .output(z.string())
79
+ .handler(() => 'dinwwwh'),
80
+ nested: {
81
+ p2: p2,
82
+ },
83
+ nested2: {
84
+ p3: p3,
85
+ },
86
+ })
87
+ }).toThrowError('Mismatch implementation for procedure at [p1]')
88
+
89
+ expect(() => {
90
+ // @ts-expect-error required all procedure match
91
+ implementer.router({})
92
+ }).toThrowError('Missing implementation for procedure at [p1]')
93
+
94
+ expect(() => {
95
+ implementer.router({
96
+ p1: p1,
97
+ nested: {
98
+ p2: p2,
99
+ },
100
+ // @ts-expect-error missing p3
101
+ nested2: {},
102
+ })
103
+ }).toThrowError('Missing implementation for procedure at [nested2.p3]')
104
+
105
+ expect(() => {
106
+ implementer.router({
107
+ p1: p1,
108
+ nested: {
109
+ p2: p2,
110
+ },
111
+ nested2: {
112
+ p3: p3.prefix('/test'),
113
+ },
114
+ })
115
+ }).toThrowError('Mismatch implementation for procedure at [nested2.p3]')
116
+ })
@@ -0,0 +1,110 @@
1
+ import {
2
+ type ContractProcedure,
3
+ type ContractRouter,
4
+ isContractProcedure,
5
+ } from '@orpc/contract'
6
+ import type { Middleware } from './middleware'
7
+ import { isProcedure } from './procedure'
8
+ import { ProcedureImplementer } from './procedure-implementer'
9
+ import type { RouterWithContract } from './router'
10
+ import type { Context } from './types'
11
+
12
+ export class RouterImplementer<
13
+ TContext extends Context,
14
+ TContract extends ContractRouter,
15
+ > {
16
+ constructor(
17
+ public zz$ri: {
18
+ contract: TContract
19
+ },
20
+ ) {}
21
+
22
+ router(
23
+ router: RouterWithContract<TContext, TContract>,
24
+ ): RouterWithContract<TContext, TContract> {
25
+ assertRouterImplementation(this.zz$ri.contract, router)
26
+
27
+ return router
28
+ }
29
+ }
30
+
31
+ export type ChainedRouterImplementer<
32
+ TContext extends Context,
33
+ TContract extends ContractRouter,
34
+ TExtraContext extends Context,
35
+ > = {
36
+ [K in keyof TContract]: TContract[K] extends ContractProcedure<
37
+ infer UInputSchema,
38
+ infer UOutputSchema
39
+ >
40
+ ? ProcedureImplementer<TContext, TExtraContext, UInputSchema, UOutputSchema>
41
+ : TContract[K] extends ContractRouter
42
+ ? ChainedRouterImplementer<TContext, TContract[K], TExtraContext>
43
+ : never
44
+ } & RouterImplementer<TContext, TContract>
45
+
46
+ export function chainRouterImplementer<
47
+ TContext extends Context,
48
+ TContract extends ContractRouter,
49
+ TExtraContext extends Context,
50
+ >(
51
+ contract: TContract,
52
+ middlewares?: Middleware<any, any, any, any>[],
53
+ ): ChainedRouterImplementer<TContext, TContract, TExtraContext> {
54
+ const result: Record<string, unknown> = {}
55
+
56
+ for (const key in contract) {
57
+ const item = contract[key]
58
+
59
+ if (isContractProcedure(item)) {
60
+ result[key] = new ProcedureImplementer({
61
+ contract: item,
62
+ middlewares,
63
+ })
64
+ } else {
65
+ result[key] = chainRouterImplementer(item as ContractRouter, middlewares)
66
+ }
67
+ }
68
+
69
+ const implementer = new RouterImplementer({ contract })
70
+
71
+ return Object.assign(implementer, result) as any
72
+ }
73
+
74
+ export function assertRouterImplementation(
75
+ contract: ContractRouter,
76
+ router: RouterWithContract<any, any>,
77
+ path: string[] = [],
78
+ ): void {
79
+ for (const key in contract) {
80
+ const currentPath = [...path, key]
81
+ const contractItem = contract[key]
82
+ const routerItem = router[key]
83
+
84
+ if (!routerItem) {
85
+ throw new Error(
86
+ `Missing implementation for procedure at [${currentPath.join('.')}]`,
87
+ )
88
+ }
89
+
90
+ if (isContractProcedure(contractItem)) {
91
+ if (isProcedure(routerItem)) {
92
+ if (routerItem.zz$p.contract !== contractItem) {
93
+ throw new Error(
94
+ `Mismatch implementation for procedure at [${currentPath.join('.')}]`,
95
+ )
96
+ }
97
+ } else {
98
+ throw new Error(
99
+ `Mismatch implementation for procedure at [${currentPath.join('.')}]`,
100
+ )
101
+ }
102
+ } else {
103
+ assertRouterImplementation(
104
+ contractItem as ContractRouter,
105
+ routerItem as any,
106
+ currentPath,
107
+ )
108
+ }
109
+ }
110
+ }