@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,165 @@
|
|
1
|
+
import type { SchemaInput, SchemaOutput } from '@orpc/contract'
|
2
|
+
import { ORPCError } from '@orpc/shared/error'
|
3
|
+
import type { Middleware } from './middleware'
|
4
|
+
import type { Procedure } from './procedure'
|
5
|
+
import type { Context, Hooks, Meta, Promisable } from './types'
|
6
|
+
import { hook, mergeContext } from './utils'
|
7
|
+
|
8
|
+
export interface CreateProcedureCallerOptions<
|
9
|
+
TProcedure extends Procedure<any, any, any, any, any>,
|
10
|
+
TValidate extends boolean,
|
11
|
+
> {
|
12
|
+
procedure: TProcedure
|
13
|
+
|
14
|
+
/**
|
15
|
+
* The context used when calling the procedure.
|
16
|
+
*/
|
17
|
+
context: TProcedure extends Procedure<infer UContext, any, any, any, any>
|
18
|
+
? UContext
|
19
|
+
: never
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Helpful hooks to do some logics on specific time.
|
23
|
+
*/
|
24
|
+
hooks?: (
|
25
|
+
context: TProcedure extends Procedure<infer UContext, any, any, any, any>
|
26
|
+
? UContext
|
27
|
+
: never,
|
28
|
+
meta: Meta<unknown>,
|
29
|
+
) => Promisable<void>
|
30
|
+
|
31
|
+
/**
|
32
|
+
* This is helpful for logging and analytics.
|
33
|
+
*/
|
34
|
+
path?: string[]
|
35
|
+
|
36
|
+
/**
|
37
|
+
* This flag helpful when you want bypass some logics not necessary to internal server calls.
|
38
|
+
*
|
39
|
+
* @default true
|
40
|
+
*/
|
41
|
+
internal?: boolean
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Indicate whether validate input and output.
|
45
|
+
*
|
46
|
+
* @default true
|
47
|
+
*/
|
48
|
+
validate?: TValidate
|
49
|
+
}
|
50
|
+
|
51
|
+
export type ProcedureCaller<
|
52
|
+
TProcedure extends Procedure<any, any, any, any, any>,
|
53
|
+
TValidate extends boolean,
|
54
|
+
> = TProcedure extends Procedure<
|
55
|
+
any,
|
56
|
+
any,
|
57
|
+
infer UInputSchema,
|
58
|
+
infer UOutputSchema,
|
59
|
+
infer UHandlerOutput
|
60
|
+
>
|
61
|
+
? (
|
62
|
+
input: TValidate extends true
|
63
|
+
? SchemaInput<UInputSchema>
|
64
|
+
: SchemaOutput<UInputSchema>,
|
65
|
+
) => Promise<
|
66
|
+
TValidate extends true
|
67
|
+
? SchemaOutput<UOutputSchema, UHandlerOutput>
|
68
|
+
: SchemaInput<UOutputSchema, UHandlerOutput>
|
69
|
+
>
|
70
|
+
: never
|
71
|
+
|
72
|
+
export function createProcedureCaller<
|
73
|
+
TProcedure extends Procedure<any, any, any, any, any>,
|
74
|
+
TValidate extends boolean = true,
|
75
|
+
>(
|
76
|
+
options: CreateProcedureCallerOptions<TProcedure, TValidate>,
|
77
|
+
): ProcedureCaller<TProcedure, TValidate> {
|
78
|
+
const internal = options.internal ?? true
|
79
|
+
const path = options.path ?? []
|
80
|
+
const procedure = options.procedure
|
81
|
+
const validate = options.validate ?? true
|
82
|
+
|
83
|
+
const caller = async (input: unknown) => {
|
84
|
+
const handler = async (
|
85
|
+
input: unknown,
|
86
|
+
context: Context,
|
87
|
+
partialMeta: Omit<Meta<unknown>, keyof Hooks<unknown>>,
|
88
|
+
middlewares: Middleware<any, any, any, any>[],
|
89
|
+
): Promise<unknown> => {
|
90
|
+
if (middlewares[0]) {
|
91
|
+
const [middleware, ...rest] = middlewares
|
92
|
+
|
93
|
+
return await hook(async (hooks) => {
|
94
|
+
const mid = await middleware(input, context, {
|
95
|
+
...partialMeta,
|
96
|
+
...hooks,
|
97
|
+
})
|
98
|
+
return await handler(
|
99
|
+
input,
|
100
|
+
mergeContext(context, mid?.context),
|
101
|
+
partialMeta,
|
102
|
+
rest,
|
103
|
+
)
|
104
|
+
})
|
105
|
+
}
|
106
|
+
|
107
|
+
return await hook(async (hooks) => {
|
108
|
+
const output = await procedure.zz$p.handler(input, context, {
|
109
|
+
...partialMeta,
|
110
|
+
...hooks,
|
111
|
+
})
|
112
|
+
|
113
|
+
const validOutput = await (async () => {
|
114
|
+
if (!validate) return output
|
115
|
+
const schema = procedure.zz$p.contract.zz$cp.OutputSchema
|
116
|
+
if (!schema) return output
|
117
|
+
const result = await schema.safeParseAsync(output)
|
118
|
+
if (result.error)
|
119
|
+
throw new ORPCError({
|
120
|
+
message: 'Validation output failed',
|
121
|
+
code: 'INTERNAL_SERVER_ERROR',
|
122
|
+
cause: result.error,
|
123
|
+
})
|
124
|
+
return result.data
|
125
|
+
})()
|
126
|
+
|
127
|
+
return validOutput
|
128
|
+
})
|
129
|
+
}
|
130
|
+
|
131
|
+
return await hook(async (hooks) => {
|
132
|
+
options.hooks?.(options.context, {
|
133
|
+
...hooks,
|
134
|
+
path,
|
135
|
+
procedure,
|
136
|
+
internal,
|
137
|
+
})
|
138
|
+
|
139
|
+
const validInput = (() => {
|
140
|
+
if (!validate) return input
|
141
|
+
const schema = procedure.zz$p.contract.zz$cp.InputSchema
|
142
|
+
if (!schema) return input
|
143
|
+
|
144
|
+
try {
|
145
|
+
return schema.parse(input)
|
146
|
+
} catch (e) {
|
147
|
+
throw new ORPCError({
|
148
|
+
message: 'Validation input failed',
|
149
|
+
code: 'BAD_REQUEST',
|
150
|
+
cause: e,
|
151
|
+
})
|
152
|
+
}
|
153
|
+
})()
|
154
|
+
|
155
|
+
return await handler(
|
156
|
+
validInput,
|
157
|
+
options.context,
|
158
|
+
{ path, procedure, internal },
|
159
|
+
procedure.zz$p.middlewares ?? [],
|
160
|
+
)
|
161
|
+
})
|
162
|
+
}
|
163
|
+
|
164
|
+
return caller as ProcedureCaller<TProcedure, TValidate>
|
165
|
+
}
|
@@ -0,0 +1,215 @@
|
|
1
|
+
import { DecoratedContractProcedure } from '@orpc/contract'
|
2
|
+
import { z } from 'zod'
|
3
|
+
import { os, type DecoratedProcedure, type Meta, isProcedure } from '.'
|
4
|
+
import { ProcedureImplementer } from './procedure-implementer'
|
5
|
+
|
6
|
+
const p1 = new DecoratedContractProcedure({
|
7
|
+
InputSchema: undefined,
|
8
|
+
OutputSchema: undefined,
|
9
|
+
method: undefined,
|
10
|
+
path: undefined,
|
11
|
+
})
|
12
|
+
const implementer1 = new ProcedureImplementer<
|
13
|
+
{ auth: boolean },
|
14
|
+
undefined,
|
15
|
+
undefined,
|
16
|
+
undefined
|
17
|
+
>({ contract: p1 })
|
18
|
+
|
19
|
+
const schema1 = z.object({ id: z.string() })
|
20
|
+
const schema2 = z.object({ name: z.string() })
|
21
|
+
|
22
|
+
const p2 = new DecoratedContractProcedure({
|
23
|
+
InputSchema: schema1,
|
24
|
+
OutputSchema: schema2,
|
25
|
+
method: 'GET',
|
26
|
+
path: '/test',
|
27
|
+
})
|
28
|
+
|
29
|
+
const implementer2 = new ProcedureImplementer<
|
30
|
+
{ auth: boolean },
|
31
|
+
undefined,
|
32
|
+
typeof schema1,
|
33
|
+
typeof schema2
|
34
|
+
>({ contract: p2 })
|
35
|
+
|
36
|
+
describe('use middleware', () => {
|
37
|
+
it('infer types', () => {
|
38
|
+
const i = implementer1
|
39
|
+
.use((input, context, meta) => {
|
40
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
41
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
42
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
43
|
+
|
44
|
+
return {
|
45
|
+
context: {
|
46
|
+
userId: '1',
|
47
|
+
},
|
48
|
+
}
|
49
|
+
})
|
50
|
+
.use((input, context, meta) => {
|
51
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
52
|
+
expectTypeOf(context).toEqualTypeOf<
|
53
|
+
{ userId: string } & { auth: boolean }
|
54
|
+
>()
|
55
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
56
|
+
})
|
57
|
+
|
58
|
+
expectTypeOf(i).toEqualTypeOf<
|
59
|
+
ProcedureImplementer<
|
60
|
+
{ auth: boolean },
|
61
|
+
{ userId: string },
|
62
|
+
undefined,
|
63
|
+
undefined
|
64
|
+
>
|
65
|
+
>()
|
66
|
+
})
|
67
|
+
|
68
|
+
it('map middleware input', () => {
|
69
|
+
// @ts-expect-error mismatch input
|
70
|
+
implementer2.use((input: { postId: string }) => {
|
71
|
+
return { context: { a: 'a' } }
|
72
|
+
})
|
73
|
+
|
74
|
+
implementer2.use(
|
75
|
+
(input: { postId: string }) => {
|
76
|
+
return { context: { a: 'a' } }
|
77
|
+
},
|
78
|
+
// @ts-expect-error mismatch input
|
79
|
+
(input) => ({ postId: 12455 }),
|
80
|
+
)
|
81
|
+
|
82
|
+
implementer2.use(
|
83
|
+
(input: { postId: string }) => {},
|
84
|
+
(input) => ({ postId: '12455' }),
|
85
|
+
)
|
86
|
+
|
87
|
+
const i = implementer2.use(
|
88
|
+
(input: { id: number }) => {
|
89
|
+
return {
|
90
|
+
context: {
|
91
|
+
userIdd: '1',
|
92
|
+
},
|
93
|
+
}
|
94
|
+
},
|
95
|
+
(input) => ({ id: Number.parseInt(input.id) }),
|
96
|
+
)
|
97
|
+
|
98
|
+
expectTypeOf(i).toEqualTypeOf<
|
99
|
+
ProcedureImplementer<
|
100
|
+
{ auth: boolean },
|
101
|
+
{ userIdd: string },
|
102
|
+
typeof schema1,
|
103
|
+
typeof schema2
|
104
|
+
>
|
105
|
+
>()
|
106
|
+
})
|
107
|
+
})
|
108
|
+
|
109
|
+
describe('output schema', () => {
|
110
|
+
it('auto infer output schema if output schema is not specified', async () => {
|
111
|
+
const sr = os.handler(() => ({ a: 1 }))
|
112
|
+
|
113
|
+
const result = await sr.zz$p.handler({}, undefined, {
|
114
|
+
method: 'GET',
|
115
|
+
path: '/',
|
116
|
+
} as any)
|
117
|
+
|
118
|
+
expectTypeOf(result).toEqualTypeOf<{ a: number }>()
|
119
|
+
})
|
120
|
+
|
121
|
+
it('not infer output schema if output schema is specified', async () => {
|
122
|
+
const srb1 = new ProcedureImplementer({
|
123
|
+
contract: new DecoratedContractProcedure({
|
124
|
+
OutputSchema: z.unknown(),
|
125
|
+
InputSchema: undefined,
|
126
|
+
}),
|
127
|
+
})
|
128
|
+
|
129
|
+
const sr = srb1.handler(() => ({ b: 1 }))
|
130
|
+
|
131
|
+
const result = await sr.zz$p.handler({}, {}, {
|
132
|
+
method: 'GET',
|
133
|
+
path: '/',
|
134
|
+
} as any)
|
135
|
+
|
136
|
+
expectTypeOf(result).toEqualTypeOf<unknown>()
|
137
|
+
})
|
138
|
+
})
|
139
|
+
|
140
|
+
describe('handler', () => {
|
141
|
+
it('infer types', () => {
|
142
|
+
const handler = implementer1.handler((input, context, meta) => {
|
143
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
144
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
145
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
146
|
+
|
147
|
+
return {
|
148
|
+
name: 'dinwwwh',
|
149
|
+
}
|
150
|
+
})
|
151
|
+
|
152
|
+
expectTypeOf(handler).toEqualTypeOf<
|
153
|
+
DecoratedProcedure<
|
154
|
+
{ auth: boolean },
|
155
|
+
undefined,
|
156
|
+
undefined,
|
157
|
+
undefined,
|
158
|
+
{ name: string }
|
159
|
+
>
|
160
|
+
>()
|
161
|
+
expect(isProcedure(handler)).toBe(true)
|
162
|
+
|
163
|
+
implementer2.handler((input, context, meta) => {
|
164
|
+
expectTypeOf(input).toEqualTypeOf<{ id: string }>()
|
165
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
166
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
167
|
+
|
168
|
+
return {
|
169
|
+
name: 'dinwwwh',
|
170
|
+
}
|
171
|
+
})
|
172
|
+
|
173
|
+
// @ts-expect-error mismatch output
|
174
|
+
implementer2.handler(() => {})
|
175
|
+
})
|
176
|
+
|
177
|
+
it('combine middlewares', () => {
|
178
|
+
const mid1 = () => {
|
179
|
+
return {
|
180
|
+
context: {
|
181
|
+
userId: '1',
|
182
|
+
},
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
const mid2 = () => {}
|
187
|
+
|
188
|
+
const handler = implementer2
|
189
|
+
.use(mid1)
|
190
|
+
.use(mid2)
|
191
|
+
.handler((input, context, meta) => {
|
192
|
+
expectTypeOf(input).toEqualTypeOf<{ id: string }>()
|
193
|
+
expectTypeOf(context).toEqualTypeOf<
|
194
|
+
{ auth: boolean } & { userId: string }
|
195
|
+
>()
|
196
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
197
|
+
|
198
|
+
return {
|
199
|
+
name: 'dinwwwh',
|
200
|
+
}
|
201
|
+
})
|
202
|
+
|
203
|
+
expectTypeOf(handler).toEqualTypeOf<
|
204
|
+
DecoratedProcedure<
|
205
|
+
{ auth: boolean },
|
206
|
+
{ userId: string },
|
207
|
+
typeof schema1,
|
208
|
+
typeof schema2,
|
209
|
+
{ name: string }
|
210
|
+
>
|
211
|
+
>()
|
212
|
+
|
213
|
+
expect(handler.zz$p.middlewares).toEqual([mid1, mid2])
|
214
|
+
})
|
215
|
+
})
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import type { ContractProcedure, SchemaOutput } from '@orpc/contract'
|
2
|
+
import type { Schema } from '@orpc/contract'
|
3
|
+
import {
|
4
|
+
type MapInputMiddleware,
|
5
|
+
type Middleware,
|
6
|
+
decorateMiddleware,
|
7
|
+
} from './middleware'
|
8
|
+
import {
|
9
|
+
type DecoratedProcedure,
|
10
|
+
type ProcedureHandler,
|
11
|
+
decorateProcedure,
|
12
|
+
} from './procedure'
|
13
|
+
import type { Context, MergeContext } from './types'
|
14
|
+
|
15
|
+
export class ProcedureImplementer<
|
16
|
+
TContext extends Context,
|
17
|
+
TExtraContext extends Context,
|
18
|
+
TInputSchema extends Schema,
|
19
|
+
TOutputSchema extends Schema,
|
20
|
+
> {
|
21
|
+
constructor(
|
22
|
+
public zz$pi: {
|
23
|
+
contract: ContractProcedure<TInputSchema, TOutputSchema>
|
24
|
+
middlewares?: Middleware<any, any, any, any>[]
|
25
|
+
},
|
26
|
+
) {}
|
27
|
+
|
28
|
+
use<
|
29
|
+
UExtraContext extends
|
30
|
+
| Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
|
31
|
+
| undefined = undefined,
|
32
|
+
>(
|
33
|
+
middleware: Middleware<
|
34
|
+
MergeContext<TContext, TExtraContext>,
|
35
|
+
UExtraContext,
|
36
|
+
SchemaOutput<TInputSchema>,
|
37
|
+
SchemaOutput<TOutputSchema>
|
38
|
+
>,
|
39
|
+
): ProcedureImplementer<
|
40
|
+
TContext,
|
41
|
+
MergeContext<TExtraContext, UExtraContext>,
|
42
|
+
TInputSchema,
|
43
|
+
TOutputSchema
|
44
|
+
>
|
45
|
+
|
46
|
+
use<
|
47
|
+
UExtraContext extends
|
48
|
+
| Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
|
49
|
+
| undefined = undefined,
|
50
|
+
UMappedInput = unknown,
|
51
|
+
>(
|
52
|
+
middleware: Middleware<
|
53
|
+
MergeContext<TContext, TExtraContext>,
|
54
|
+
UExtraContext,
|
55
|
+
UMappedInput,
|
56
|
+
SchemaOutput<TOutputSchema>
|
57
|
+
>,
|
58
|
+
mapInput: MapInputMiddleware<SchemaOutput<TInputSchema>, UMappedInput>,
|
59
|
+
): ProcedureImplementer<
|
60
|
+
TContext,
|
61
|
+
MergeContext<TExtraContext, UExtraContext>,
|
62
|
+
TInputSchema,
|
63
|
+
TOutputSchema
|
64
|
+
>
|
65
|
+
|
66
|
+
use(
|
67
|
+
middleware: Middleware<any, any, any, any>,
|
68
|
+
mapInput?: MapInputMiddleware<any, any>,
|
69
|
+
): ProcedureImplementer<any, any, any, any> {
|
70
|
+
const middleware_ = mapInput
|
71
|
+
? decorateMiddleware(middleware).mapInput(mapInput)
|
72
|
+
: middleware
|
73
|
+
|
74
|
+
return new ProcedureImplementer({
|
75
|
+
...this.zz$pi,
|
76
|
+
middlewares: [...(this.zz$pi.middlewares ?? []), middleware_],
|
77
|
+
})
|
78
|
+
}
|
79
|
+
|
80
|
+
handler<UHandlerOutput extends SchemaOutput<TOutputSchema>>(
|
81
|
+
handler: ProcedureHandler<
|
82
|
+
TContext,
|
83
|
+
TExtraContext,
|
84
|
+
TInputSchema,
|
85
|
+
TOutputSchema,
|
86
|
+
UHandlerOutput
|
87
|
+
>,
|
88
|
+
): DecoratedProcedure<
|
89
|
+
TContext,
|
90
|
+
TExtraContext,
|
91
|
+
TInputSchema,
|
92
|
+
TOutputSchema,
|
93
|
+
UHandlerOutput
|
94
|
+
> {
|
95
|
+
return decorateProcedure({
|
96
|
+
zz$p: {
|
97
|
+
middlewares: this.zz$pi.middlewares,
|
98
|
+
contract: this.zz$pi.contract,
|
99
|
+
handler,
|
100
|
+
},
|
101
|
+
})
|
102
|
+
}
|
103
|
+
}
|