@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,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
+ })
@@ -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
+ })