@orpc/server 0.0.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.
- 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 '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
|
+
}
|