@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,312 @@
|
|
1
|
+
import { ContractProcedure, DecoratedContractProcedure } from '@orpc/contract'
|
2
|
+
import { z } from 'zod'
|
3
|
+
import { os, type Meta } from '.'
|
4
|
+
import {
|
5
|
+
type DecoratedProcedure,
|
6
|
+
Procedure,
|
7
|
+
decorateProcedure,
|
8
|
+
isProcedure,
|
9
|
+
} from './procedure'
|
10
|
+
|
11
|
+
it('isProcedure', () => {
|
12
|
+
expect(
|
13
|
+
isProcedure(
|
14
|
+
decorateProcedure(
|
15
|
+
new Procedure({
|
16
|
+
contract: new ContractProcedure({
|
17
|
+
InputSchema: undefined,
|
18
|
+
OutputSchema: undefined,
|
19
|
+
}),
|
20
|
+
handler: () => {},
|
21
|
+
}),
|
22
|
+
),
|
23
|
+
),
|
24
|
+
).toBe(true)
|
25
|
+
expect({
|
26
|
+
zz$p: {
|
27
|
+
contract: new DecoratedContractProcedure({
|
28
|
+
InputSchema: undefined,
|
29
|
+
OutputSchema: undefined,
|
30
|
+
}),
|
31
|
+
handler: () => {},
|
32
|
+
},
|
33
|
+
}).toSatisfy(isProcedure)
|
34
|
+
|
35
|
+
expect({
|
36
|
+
zz$p: {
|
37
|
+
contract: new DecoratedContractProcedure({
|
38
|
+
InputSchema: undefined,
|
39
|
+
OutputSchema: undefined,
|
40
|
+
}),
|
41
|
+
},
|
42
|
+
}).not.toSatisfy(isProcedure)
|
43
|
+
|
44
|
+
expect({
|
45
|
+
zz$p: {
|
46
|
+
handler: () => {},
|
47
|
+
},
|
48
|
+
}).not.toSatisfy(isProcedure)
|
49
|
+
|
50
|
+
expect({}).not.toSatisfy(isProcedure)
|
51
|
+
expect(12233).not.toSatisfy(isProcedure)
|
52
|
+
expect('12233').not.toSatisfy(isProcedure)
|
53
|
+
expect(undefined).not.toSatisfy(isProcedure)
|
54
|
+
expect(null).not.toSatisfy(isProcedure)
|
55
|
+
})
|
56
|
+
|
57
|
+
describe('route method', () => {
|
58
|
+
it('sets route options correctly', () => {
|
59
|
+
const p = os.context<{ auth: boolean }>().handler(() => {
|
60
|
+
return 'test'
|
61
|
+
})
|
62
|
+
|
63
|
+
const p2 = p.route({ path: '/test', method: 'GET' })
|
64
|
+
|
65
|
+
expect(p2.zz$p.contract.zz$cp.path).toBe('/test')
|
66
|
+
expect(p2.zz$p.contract.zz$cp.method).toBe('GET')
|
67
|
+
})
|
68
|
+
|
69
|
+
it('preserves existing context and handler', () => {
|
70
|
+
const handler = () => 'test'
|
71
|
+
const p = os.context<{ auth: boolean }>().handler(handler)
|
72
|
+
|
73
|
+
const p2 = p.route({ path: '/test' })
|
74
|
+
|
75
|
+
expect(p2.zz$p.handler).toBe(handler)
|
76
|
+
// Context type is preserved through the route method
|
77
|
+
expectTypeOf(p2).toEqualTypeOf<
|
78
|
+
DecoratedProcedure<
|
79
|
+
{ auth: boolean },
|
80
|
+
undefined,
|
81
|
+
undefined,
|
82
|
+
undefined,
|
83
|
+
string
|
84
|
+
>
|
85
|
+
>()
|
86
|
+
})
|
87
|
+
|
88
|
+
it('works with prefix method', () => {
|
89
|
+
const p = os
|
90
|
+
.context<{ auth: boolean }>()
|
91
|
+
.route({ path: '/api', method: 'POST' })
|
92
|
+
.handler(() => 'test')
|
93
|
+
|
94
|
+
const p2 = p.prefix('/v1')
|
95
|
+
|
96
|
+
expect(p2.zz$p.contract.zz$cp.path).toBe('/v1/api')
|
97
|
+
expect(p2.zz$p.contract.zz$cp.method).toBe('POST')
|
98
|
+
})
|
99
|
+
|
100
|
+
it('works with middleware', () => {
|
101
|
+
const mid = vi.fn(() => ({ context: { userId: '1' } }))
|
102
|
+
|
103
|
+
const p = os
|
104
|
+
.context<{ auth: boolean }>()
|
105
|
+
.route({ path: '/test' })
|
106
|
+
.use(mid)
|
107
|
+
.handler((input, context) => {
|
108
|
+
expectTypeOf(context).toEqualTypeOf<
|
109
|
+
{ auth: boolean } & { userId: string }
|
110
|
+
>()
|
111
|
+
return 'test'
|
112
|
+
})
|
113
|
+
|
114
|
+
expect(p.zz$p.contract.zz$cp.path).toBe('/test')
|
115
|
+
expect(p.zz$p.middlewares).toEqual([mid])
|
116
|
+
})
|
117
|
+
|
118
|
+
it('overrides existing route options', () => {
|
119
|
+
const p = os
|
120
|
+
.context<{ auth: boolean }>()
|
121
|
+
.route({ path: '/test1', method: 'GET' })
|
122
|
+
.handler(() => 'test')
|
123
|
+
|
124
|
+
const p2 = p.route({ path: '/test2', method: 'POST' })
|
125
|
+
|
126
|
+
expect(p2.zz$p.contract.zz$cp.path).toBe('/test2')
|
127
|
+
expect(p2.zz$p.contract.zz$cp.method).toBe('POST')
|
128
|
+
})
|
129
|
+
|
130
|
+
it('preserves input/output schemas', () => {
|
131
|
+
const inputSchema = z.object({ id: z.number() })
|
132
|
+
const outputSchema = z.string()
|
133
|
+
const p = os
|
134
|
+
.context<{ auth: boolean }>()
|
135
|
+
.input(inputSchema)
|
136
|
+
.output(outputSchema)
|
137
|
+
.route({ path: '/test' })
|
138
|
+
.handler((input) => {
|
139
|
+
expectTypeOf(input).toEqualTypeOf<{ id: number }>()
|
140
|
+
return 'test'
|
141
|
+
})
|
142
|
+
|
143
|
+
const p2 = p.route({ path: '/test2' })
|
144
|
+
|
145
|
+
// Type checking that schemas are preserved
|
146
|
+
expectTypeOf(p2).toEqualTypeOf<
|
147
|
+
DecoratedProcedure<
|
148
|
+
{ auth: boolean },
|
149
|
+
undefined,
|
150
|
+
typeof inputSchema,
|
151
|
+
typeof outputSchema,
|
152
|
+
string
|
153
|
+
>
|
154
|
+
>()
|
155
|
+
})
|
156
|
+
})
|
157
|
+
|
158
|
+
test('prefix method', () => {
|
159
|
+
const p = os.context<{ auth: boolean }>().handler(() => {
|
160
|
+
return 'dinwwwh'
|
161
|
+
})
|
162
|
+
|
163
|
+
const p2 = p.prefix('/test')
|
164
|
+
|
165
|
+
expect(p2.zz$p.contract.zz$cp.path).toBe(undefined)
|
166
|
+
|
167
|
+
const p3 = os
|
168
|
+
.context<{ auth: boolean }>()
|
169
|
+
.route({ path: '/test1' })
|
170
|
+
.handler(() => {
|
171
|
+
return 'dinwwwh'
|
172
|
+
})
|
173
|
+
|
174
|
+
const p4 = p3.prefix('/test')
|
175
|
+
expect(p4.zz$p.contract.zz$cp.path).toBe('/test/test1')
|
176
|
+
})
|
177
|
+
|
178
|
+
describe('use middleware', () => {
|
179
|
+
it('infer types', () => {
|
180
|
+
const p1 = os
|
181
|
+
.context<{ auth: boolean }>()
|
182
|
+
.use(() => {
|
183
|
+
return { context: { postId: 'string' } }
|
184
|
+
})
|
185
|
+
.handler(() => {
|
186
|
+
return 'dinwwwh'
|
187
|
+
})
|
188
|
+
|
189
|
+
const p2 = p1
|
190
|
+
.use((input, context, meta) => {
|
191
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
192
|
+
expectTypeOf(context).toEqualTypeOf<
|
193
|
+
{ auth: boolean } & { postId: string }
|
194
|
+
>()
|
195
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<string>>()
|
196
|
+
|
197
|
+
return {
|
198
|
+
context: {
|
199
|
+
userId: '1',
|
200
|
+
},
|
201
|
+
}
|
202
|
+
})
|
203
|
+
.use((input, context, meta) => {
|
204
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
205
|
+
expectTypeOf(context).toEqualTypeOf<
|
206
|
+
{ userId: string } & { postId: string } & { auth: boolean }
|
207
|
+
>()
|
208
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<string>>()
|
209
|
+
})
|
210
|
+
|
211
|
+
expectTypeOf(p2).toEqualTypeOf<
|
212
|
+
DecoratedProcedure<
|
213
|
+
{ auth: boolean },
|
214
|
+
{ postId: string } & { userId: string },
|
215
|
+
undefined,
|
216
|
+
undefined,
|
217
|
+
string
|
218
|
+
>
|
219
|
+
>()
|
220
|
+
})
|
221
|
+
|
222
|
+
it('can map input', () => {
|
223
|
+
const mid = (input: { id: number }) => {}
|
224
|
+
|
225
|
+
os.input(z.object({ postId: z.number() })).use(mid, (input) => {
|
226
|
+
expectTypeOf(input).toEqualTypeOf<{ postId: number }>()
|
227
|
+
|
228
|
+
return {
|
229
|
+
id: input.postId,
|
230
|
+
}
|
231
|
+
})
|
232
|
+
|
233
|
+
// @ts-expect-error mismatch input
|
234
|
+
os.input(z.object({ postId: z.number() })).use(mid)
|
235
|
+
|
236
|
+
// @ts-expect-error mismatch input
|
237
|
+
os.input(z.object({ postId: z.number() })).use(mid, (input) => {
|
238
|
+
return {
|
239
|
+
wrong: input.postId,
|
240
|
+
}
|
241
|
+
})
|
242
|
+
})
|
243
|
+
|
244
|
+
it('add middlewares to beginning', () => {
|
245
|
+
const mid1 = vi.fn()
|
246
|
+
const mid2 = vi.fn()
|
247
|
+
const mid3 = vi.fn()
|
248
|
+
|
249
|
+
const p1 = os.use(mid1).handler(() => 'dinwwwh')
|
250
|
+
const p2 = p1.use(mid2).use(mid3)
|
251
|
+
|
252
|
+
expect(p2.zz$p.middlewares).toEqual([mid3, mid2, mid1])
|
253
|
+
})
|
254
|
+
})
|
255
|
+
|
256
|
+
describe('server action', () => {
|
257
|
+
it('only accept undefined context', () => {
|
258
|
+
expectTypeOf(os.handler(() => {})).toMatchTypeOf<(...args: any[]) => any>()
|
259
|
+
expectTypeOf(
|
260
|
+
os.context<{ auth: boolean } | undefined>().handler(() => {}),
|
261
|
+
).toMatchTypeOf<(...args: any[]) => any>()
|
262
|
+
expectTypeOf(
|
263
|
+
os.context<{ auth: boolean }>().handler(() => {}),
|
264
|
+
).not.toMatchTypeOf<(...args: any[]) => any>()
|
265
|
+
})
|
266
|
+
|
267
|
+
it('infer types', () => {
|
268
|
+
const p = os
|
269
|
+
.input(z.object({ id: z.number() }))
|
270
|
+
.output(z.string())
|
271
|
+
.handler(() => 'string')
|
272
|
+
|
273
|
+
expectTypeOf(p).toMatchTypeOf<
|
274
|
+
(input: { id: number } | FormData) => Promise<string>
|
275
|
+
>()
|
276
|
+
|
277
|
+
const p2 = os.input(z.object({ id: z.number() })).handler(() => 12333)
|
278
|
+
|
279
|
+
expectTypeOf(p2).toMatchTypeOf<
|
280
|
+
(input: { id: number } | FormData) => Promise<number>
|
281
|
+
>()
|
282
|
+
})
|
283
|
+
|
284
|
+
it('works with input', async () => {
|
285
|
+
const p = os
|
286
|
+
.input(z.object({ id: z.number(), date: z.date() }))
|
287
|
+
.handler(async (input, context) => {
|
288
|
+
expect(context).toBe(undefined)
|
289
|
+
return input
|
290
|
+
})
|
291
|
+
|
292
|
+
expect(await p({ id: 123, date: new Date('2022-01-01') })).toEqual({
|
293
|
+
id: 123,
|
294
|
+
date: new Date('2022-01-01'),
|
295
|
+
})
|
296
|
+
})
|
297
|
+
|
298
|
+
it('can deserialize form data', async () => {
|
299
|
+
const p = os
|
300
|
+
.input(z.object({ id: z.number(), nested: z.object({ date: z.date() }) }))
|
301
|
+
.handler(async (input) => input)
|
302
|
+
|
303
|
+
const form = new FormData()
|
304
|
+
form.append('id', '123')
|
305
|
+
form.append('nested[date]', '2022-01-01')
|
306
|
+
|
307
|
+
expect(await p(form)).toEqual({
|
308
|
+
id: 123,
|
309
|
+
nested: { date: new Date('2022-01-01') },
|
310
|
+
})
|
311
|
+
})
|
312
|
+
})
|
package/src/procedure.ts
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
import {
|
2
|
+
type ContractProcedure,
|
3
|
+
DecoratedContractProcedure,
|
4
|
+
type HTTPPath,
|
5
|
+
type SchemaInput,
|
6
|
+
type SchemaOutput,
|
7
|
+
isContractProcedure,
|
8
|
+
} from '@orpc/contract'
|
9
|
+
import type { RouteOptions, Schema } from '@orpc/contract'
|
10
|
+
import { OpenAPIDeserializer } from '@orpc/transformer'
|
11
|
+
import {
|
12
|
+
type MapInputMiddleware,
|
13
|
+
type Middleware,
|
14
|
+
decorateMiddleware,
|
15
|
+
} from './middleware'
|
16
|
+
import { createProcedureCaller } from './procedure-caller'
|
17
|
+
import type { Context, MergeContext, Meta, Promisable } from './types'
|
18
|
+
|
19
|
+
export class Procedure<
|
20
|
+
TContext extends Context,
|
21
|
+
TExtraContext extends Context,
|
22
|
+
TInputSchema extends Schema,
|
23
|
+
TOutputSchema extends Schema,
|
24
|
+
THandlerOutput extends SchemaOutput<TOutputSchema>,
|
25
|
+
> {
|
26
|
+
constructor(
|
27
|
+
public zz$p: {
|
28
|
+
middlewares?: Middleware<any, any, any, any>[]
|
29
|
+
contract: ContractProcedure<TInputSchema, TOutputSchema>
|
30
|
+
handler: ProcedureHandler<
|
31
|
+
TContext,
|
32
|
+
TExtraContext,
|
33
|
+
TInputSchema,
|
34
|
+
TOutputSchema,
|
35
|
+
THandlerOutput
|
36
|
+
>
|
37
|
+
},
|
38
|
+
) {}
|
39
|
+
}
|
40
|
+
|
41
|
+
export type DecoratedProcedure<
|
42
|
+
TContext extends Context,
|
43
|
+
TExtraContext extends Context,
|
44
|
+
TInputSchema extends Schema,
|
45
|
+
TOutputSchema extends Schema,
|
46
|
+
THandlerOutput extends SchemaOutput<TOutputSchema>,
|
47
|
+
> = Procedure<
|
48
|
+
TContext,
|
49
|
+
TExtraContext,
|
50
|
+
TInputSchema,
|
51
|
+
TOutputSchema,
|
52
|
+
THandlerOutput
|
53
|
+
> & {
|
54
|
+
prefix(
|
55
|
+
prefix: HTTPPath,
|
56
|
+
): DecoratedProcedure<
|
57
|
+
TContext,
|
58
|
+
TExtraContext,
|
59
|
+
TInputSchema,
|
60
|
+
TOutputSchema,
|
61
|
+
THandlerOutput
|
62
|
+
>
|
63
|
+
|
64
|
+
route(
|
65
|
+
opts: RouteOptions,
|
66
|
+
): DecoratedProcedure<
|
67
|
+
TContext,
|
68
|
+
TExtraContext,
|
69
|
+
TInputSchema,
|
70
|
+
TOutputSchema,
|
71
|
+
THandlerOutput
|
72
|
+
>
|
73
|
+
|
74
|
+
use<
|
75
|
+
UExtraContext extends
|
76
|
+
| Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
|
77
|
+
| undefined = undefined,
|
78
|
+
>(
|
79
|
+
middleware: Middleware<
|
80
|
+
MergeContext<TContext, TExtraContext>,
|
81
|
+
UExtraContext,
|
82
|
+
SchemaOutput<TInputSchema>,
|
83
|
+
SchemaOutput<TOutputSchema, THandlerOutput>
|
84
|
+
>,
|
85
|
+
): DecoratedProcedure<
|
86
|
+
TContext,
|
87
|
+
MergeContext<TExtraContext, UExtraContext>,
|
88
|
+
TInputSchema,
|
89
|
+
TOutputSchema,
|
90
|
+
THandlerOutput
|
91
|
+
>
|
92
|
+
|
93
|
+
use<
|
94
|
+
UExtraContext extends
|
95
|
+
| Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
|
96
|
+
| undefined = undefined,
|
97
|
+
UMappedInput = unknown,
|
98
|
+
>(
|
99
|
+
middleware: Middleware<
|
100
|
+
MergeContext<TContext, TExtraContext>,
|
101
|
+
UExtraContext,
|
102
|
+
UMappedInput,
|
103
|
+
SchemaOutput<TOutputSchema, THandlerOutput>
|
104
|
+
>,
|
105
|
+
mapInput: MapInputMiddleware<
|
106
|
+
SchemaOutput<TInputSchema, THandlerOutput>,
|
107
|
+
UMappedInput
|
108
|
+
>,
|
109
|
+
): DecoratedProcedure<
|
110
|
+
TContext,
|
111
|
+
MergeContext<TExtraContext, UExtraContext>,
|
112
|
+
TInputSchema,
|
113
|
+
TOutputSchema,
|
114
|
+
THandlerOutput
|
115
|
+
>
|
116
|
+
} & (undefined extends TContext
|
117
|
+
? (
|
118
|
+
input: SchemaInput<TInputSchema> | FormData,
|
119
|
+
) => Promise<SchemaOutput<TOutputSchema, THandlerOutput>>
|
120
|
+
: // biome-ignore lint/complexity/noBannedTypes: {} seem has no side-effect on this case
|
121
|
+
{})
|
122
|
+
|
123
|
+
export interface ProcedureHandler<
|
124
|
+
TContext extends Context,
|
125
|
+
TExtraContext extends Context,
|
126
|
+
TInputSchema extends Schema,
|
127
|
+
TOutputSchema extends Schema,
|
128
|
+
TOutput extends SchemaOutput<TOutputSchema>,
|
129
|
+
> {
|
130
|
+
(
|
131
|
+
input: SchemaOutput<TInputSchema>,
|
132
|
+
context: MergeContext<TContext, TExtraContext>,
|
133
|
+
meta: Meta<unknown>,
|
134
|
+
): Promisable<SchemaInput<TOutputSchema, TOutput>>
|
135
|
+
}
|
136
|
+
|
137
|
+
const DECORATED_PROCEDURE_SYMBOL = Symbol('DECORATED_PROCEDURE')
|
138
|
+
|
139
|
+
export function decorateProcedure<
|
140
|
+
TContext extends Context,
|
141
|
+
TExtraContext extends Context,
|
142
|
+
TInputSchema extends Schema,
|
143
|
+
TOutputSchema extends Schema,
|
144
|
+
THandlerOutput extends SchemaOutput<TOutputSchema>,
|
145
|
+
>(
|
146
|
+
procedure: Procedure<
|
147
|
+
TContext,
|
148
|
+
TExtraContext,
|
149
|
+
TInputSchema,
|
150
|
+
TOutputSchema,
|
151
|
+
THandlerOutput
|
152
|
+
>,
|
153
|
+
): DecoratedProcedure<
|
154
|
+
TContext,
|
155
|
+
TExtraContext,
|
156
|
+
TInputSchema,
|
157
|
+
TOutputSchema,
|
158
|
+
THandlerOutput
|
159
|
+
> {
|
160
|
+
if (DECORATED_PROCEDURE_SYMBOL in procedure) {
|
161
|
+
return procedure as any
|
162
|
+
}
|
163
|
+
|
164
|
+
const serverAction = async (input: unknown) => {
|
165
|
+
const input_ = (() => {
|
166
|
+
if (!(input instanceof FormData)) return input
|
167
|
+
|
168
|
+
const transformer = new OpenAPIDeserializer({
|
169
|
+
schema: procedure.zz$p.contract.zz$cp.InputSchema,
|
170
|
+
})
|
171
|
+
|
172
|
+
return transformer.deserializeAsFormData(input)
|
173
|
+
})()
|
174
|
+
|
175
|
+
const procedureCaller = createProcedureCaller({
|
176
|
+
procedure,
|
177
|
+
context: undefined as any,
|
178
|
+
internal: false,
|
179
|
+
validate: true,
|
180
|
+
})
|
181
|
+
|
182
|
+
return await procedureCaller(input_ as any)
|
183
|
+
}
|
184
|
+
|
185
|
+
return Object.assign(serverAction, {
|
186
|
+
[DECORATED_PROCEDURE_SYMBOL]: true,
|
187
|
+
zz$p: procedure.zz$p,
|
188
|
+
|
189
|
+
prefix(prefix: HTTPPath) {
|
190
|
+
return decorateProcedure({
|
191
|
+
zz$p: {
|
192
|
+
...procedure.zz$p,
|
193
|
+
contract: DecoratedContractProcedure.decorate(
|
194
|
+
procedure.zz$p.contract,
|
195
|
+
).prefix(prefix),
|
196
|
+
},
|
197
|
+
})
|
198
|
+
},
|
199
|
+
|
200
|
+
route(opts: RouteOptions) {
|
201
|
+
return decorateProcedure({
|
202
|
+
zz$p: {
|
203
|
+
...procedure.zz$p,
|
204
|
+
contract: DecoratedContractProcedure.decorate(
|
205
|
+
procedure.zz$p.contract,
|
206
|
+
).route(opts),
|
207
|
+
},
|
208
|
+
})
|
209
|
+
},
|
210
|
+
|
211
|
+
use(
|
212
|
+
middleware: Middleware<any, any, any, any>,
|
213
|
+
mapInput?: MapInputMiddleware<any, any>,
|
214
|
+
) {
|
215
|
+
const middleware_ = mapInput
|
216
|
+
? decorateMiddleware(middleware).mapInput(mapInput)
|
217
|
+
: middleware
|
218
|
+
|
219
|
+
return decorateProcedure({
|
220
|
+
zz$p: {
|
221
|
+
...procedure.zz$p,
|
222
|
+
middlewares: [middleware_, ...(procedure.zz$p.middlewares ?? [])],
|
223
|
+
},
|
224
|
+
})
|
225
|
+
},
|
226
|
+
}) as any
|
227
|
+
}
|
228
|
+
|
229
|
+
export type WELL_DEFINED_PROCEDURE = Procedure<
|
230
|
+
Context,
|
231
|
+
Context,
|
232
|
+
Schema,
|
233
|
+
Schema,
|
234
|
+
unknown
|
235
|
+
>
|
236
|
+
|
237
|
+
export function isProcedure(item: unknown): item is WELL_DEFINED_PROCEDURE {
|
238
|
+
if (item instanceof Procedure) return true
|
239
|
+
|
240
|
+
return (
|
241
|
+
(typeof item === 'object' || typeof item === 'function') &&
|
242
|
+
item !== null &&
|
243
|
+
'zz$p' in item &&
|
244
|
+
typeof item.zz$p === 'object' &&
|
245
|
+
item.zz$p !== null &&
|
246
|
+
'contract' in item.zz$p &&
|
247
|
+
isContractProcedure(item.zz$p.contract) &&
|
248
|
+
'handler' in item.zz$p &&
|
249
|
+
typeof item.zz$p.handler === 'function'
|
250
|
+
)
|
251
|
+
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import { ContractProcedure } from '@orpc/contract'
|
2
|
+
import { z } from 'zod'
|
3
|
+
import { os, Procedure, decorateProcedure, isProcedure } from '.'
|
4
|
+
import { RouterBuilder } from './router-builder'
|
5
|
+
|
6
|
+
const builder = new RouterBuilder<undefined, undefined>({})
|
7
|
+
const ping = os
|
8
|
+
.route({ method: 'GET', path: '/ping', tags: ['ping'] })
|
9
|
+
.handler(() => 'ping')
|
10
|
+
const pong = os
|
11
|
+
.output(z.object({ id: z.string() }))
|
12
|
+
.handler(() => ({ id: '123' }))
|
13
|
+
|
14
|
+
describe('prefix', () => {
|
15
|
+
it('chainable prefix', () => {
|
16
|
+
expect(builder.prefix('/1').prefix('/2').prefix('/3').zz$rb.prefix).toEqual(
|
17
|
+
'/1/2/3',
|
18
|
+
)
|
19
|
+
})
|
20
|
+
|
21
|
+
it('router', () => {
|
22
|
+
const router = builder
|
23
|
+
.prefix('/api')
|
24
|
+
.prefix('/users')
|
25
|
+
.router({ ping: ping, pong })
|
26
|
+
|
27
|
+
expect(router.ping.zz$p.contract.zz$cp.path).toEqual('/api/users/ping')
|
28
|
+
expect(router.pong.zz$p.contract.zz$cp.path).toEqual(undefined)
|
29
|
+
})
|
30
|
+
})
|
31
|
+
|
32
|
+
describe('tags', () => {
|
33
|
+
it('chainable tags', () => {
|
34
|
+
expect(builder.tags('1', '2').tags('3').tags('4').zz$rb.tags).toEqual([
|
35
|
+
'1',
|
36
|
+
'2',
|
37
|
+
'3',
|
38
|
+
'4',
|
39
|
+
])
|
40
|
+
})
|
41
|
+
|
42
|
+
it('router', () => {
|
43
|
+
const router = builder
|
44
|
+
.tags('api')
|
45
|
+
.tags('users')
|
46
|
+
.router({ ping: ping, pong })
|
47
|
+
|
48
|
+
expect(router.ping.zz$p.contract.zz$cp.tags).toEqual([
|
49
|
+
'ping',
|
50
|
+
'api',
|
51
|
+
'users',
|
52
|
+
])
|
53
|
+
expect(router.pong.zz$p.contract.zz$cp.tags).toEqual(['api', 'users'])
|
54
|
+
})
|
55
|
+
})
|
56
|
+
|
57
|
+
describe('middleware', () => {
|
58
|
+
const mid1 = vi.fn()
|
59
|
+
const mid2 = vi.fn()
|
60
|
+
const mid3 = vi.fn()
|
61
|
+
|
62
|
+
it('chainable middleware', () => {
|
63
|
+
expect(builder.use(mid1).use(mid2).use(mid3).zz$rb.middlewares).toEqual([
|
64
|
+
mid1,
|
65
|
+
mid2,
|
66
|
+
mid3,
|
67
|
+
])
|
68
|
+
})
|
69
|
+
|
70
|
+
it('router', () => {
|
71
|
+
const router = builder.use(mid1).use(mid2).router({ ping: ping, pong })
|
72
|
+
|
73
|
+
expect(router.ping.zz$p.middlewares).toEqual([mid1, mid2])
|
74
|
+
expect(router.pong.zz$p.middlewares).toEqual([mid1, mid2])
|
75
|
+
})
|
76
|
+
|
77
|
+
it('decorate items', () => {
|
78
|
+
const ping = new Procedure({
|
79
|
+
contract: new ContractProcedure({
|
80
|
+
InputSchema: undefined,
|
81
|
+
OutputSchema: undefined,
|
82
|
+
}),
|
83
|
+
handler: () => {},
|
84
|
+
})
|
85
|
+
|
86
|
+
const decorated = decorateProcedure({
|
87
|
+
zz$p: {
|
88
|
+
contract: new ContractProcedure({
|
89
|
+
InputSchema: undefined,
|
90
|
+
OutputSchema: undefined,
|
91
|
+
}),
|
92
|
+
handler: () => {},
|
93
|
+
},
|
94
|
+
})
|
95
|
+
|
96
|
+
const router = builder.router({ ping, nested: { ping } })
|
97
|
+
|
98
|
+
expectTypeOf(router).toEqualTypeOf<{
|
99
|
+
ping: typeof decorated
|
100
|
+
nested: { ping: typeof decorated }
|
101
|
+
}>()
|
102
|
+
|
103
|
+
expect(router.ping).satisfies(isProcedure)
|
104
|
+
expect(router.nested.ping).satisfies(isProcedure)
|
105
|
+
})
|
106
|
+
})
|