@orpc/server 0.0.0-unsafe-pr-2-20241118033608
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/chunk-ACLC6USM.js +262 -0
- package/dist/chunk-ACLC6USM.js.map +1 -0
- package/dist/fetch.js +648 -0
- package/dist/fetch.js.map +1 -0
- package/dist/index.js +403 -0
- package/dist/index.js.map +1 -0
- package/dist/src/adapters/fetch.d.ts +36 -0
- package/dist/src/adapters/fetch.d.ts.map +1 -0
- package/dist/src/builder.d.ts +49 -0
- package/dist/src/builder.d.ts.map +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/middleware.d.ts +16 -0
- package/dist/src/middleware.d.ts.map +1 -0
- package/dist/src/procedure-builder.d.ts +31 -0
- package/dist/src/procedure-builder.d.ts.map +1 -0
- package/dist/src/procedure-caller.d.ts +33 -0
- package/dist/src/procedure-caller.d.ts.map +1 -0
- package/dist/src/procedure-implementer.d.ts +19 -0
- package/dist/src/procedure-implementer.d.ts.map +1 -0
- package/dist/src/procedure.d.ts +29 -0
- package/dist/src/procedure.d.ts.map +1 -0
- package/dist/src/router-builder.d.ts +22 -0
- package/dist/src/router-builder.d.ts.map +1 -0
- package/dist/src/router-caller.d.ts +36 -0
- package/dist/src/router-caller.d.ts.map +1 -0
- package/dist/src/router-implementer.d.ts +20 -0
- package/dist/src/router-implementer.d.ts.map +1 -0
- package/dist/src/router.d.ts +14 -0
- package/dist/src/router.d.ts.map +1 -0
- package/dist/src/types.d.ts +18 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +4 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +57 -0
- package/src/adapters/fetch.test.ts +399 -0
- package/src/adapters/fetch.ts +228 -0
- package/src/builder.test.ts +362 -0
- package/src/builder.ts +236 -0
- package/src/index.ts +16 -0
- package/src/middleware.test.ts +279 -0
- package/src/middleware.ts +119 -0
- package/src/procedure-builder.test.ts +219 -0
- package/src/procedure-builder.ts +158 -0
- package/src/procedure-caller.test.ts +210 -0
- package/src/procedure-caller.ts +165 -0
- package/src/procedure-implementer.test.ts +215 -0
- package/src/procedure-implementer.ts +103 -0
- package/src/procedure.test.ts +312 -0
- package/src/procedure.ts +251 -0
- package/src/router-builder.test.ts +106 -0
- package/src/router-builder.ts +120 -0
- package/src/router-caller.test.ts +173 -0
- package/src/router-caller.ts +95 -0
- package/src/router-implementer.test.ts +116 -0
- package/src/router-implementer.ts +110 -0
- package/src/router.test.ts +142 -0
- package/src/router.ts +69 -0
- package/src/types.test.ts +18 -0
- package/src/types.ts +28 -0
- package/src/utils.test.ts +243 -0
- 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 'unnoq'
|
22
|
+
})
|
23
|
+
|
24
|
+
const p2 = osw.nested.p2.handler(() => {
|
25
|
+
return 'unnoq'
|
26
|
+
})
|
27
|
+
|
28
|
+
const p3 = osw.nested2.p3.handler(() => {
|
29
|
+
return 'unnoq'
|
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(() => 'unnoq'),
|
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
|
+
}
|