@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.
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
+ }