@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
package/src/builder.ts
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
import {
|
2
|
+
ContractProcedure,
|
3
|
+
type ContractRouter,
|
4
|
+
type HTTPPath,
|
5
|
+
type RouteOptions,
|
6
|
+
type Schema,
|
7
|
+
type SchemaInput,
|
8
|
+
type SchemaOutput,
|
9
|
+
isContractProcedure,
|
10
|
+
} from '@orpc/contract'
|
11
|
+
import type { IsEqual } from '@orpc/shared'
|
12
|
+
import {
|
13
|
+
type DecoratedMiddleware,
|
14
|
+
type MapInputMiddleware,
|
15
|
+
type Middleware,
|
16
|
+
decorateMiddleware,
|
17
|
+
} from './middleware'
|
18
|
+
import {
|
19
|
+
type DecoratedProcedure,
|
20
|
+
type ProcedureHandler,
|
21
|
+
decorateProcedure,
|
22
|
+
} from './procedure'
|
23
|
+
import { ProcedureBuilder } from './procedure-builder'
|
24
|
+
import { ProcedureImplementer } from './procedure-implementer'
|
25
|
+
import type { HandledRouter, Router } from './router'
|
26
|
+
import { RouterBuilder } from './router-builder'
|
27
|
+
import {
|
28
|
+
type ChainedRouterImplementer,
|
29
|
+
chainRouterImplementer,
|
30
|
+
} from './router-implementer'
|
31
|
+
import type { Context, MergeContext } from './types'
|
32
|
+
|
33
|
+
export class Builder<TContext extends Context, TExtraContext extends Context> {
|
34
|
+
constructor(
|
35
|
+
public zz$b: {
|
36
|
+
middlewares?: Middleware<any, any, any, any>[]
|
37
|
+
} = {},
|
38
|
+
) {}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Self chainable
|
42
|
+
*/
|
43
|
+
|
44
|
+
context<UContext extends Context>(): IsEqual<UContext, Context> extends true
|
45
|
+
? Builder<TContext, TExtraContext>
|
46
|
+
: Builder<UContext, TExtraContext> {
|
47
|
+
return this as any
|
48
|
+
}
|
49
|
+
|
50
|
+
use<
|
51
|
+
UExtraContext extends
|
52
|
+
| Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
|
53
|
+
| undefined = undefined,
|
54
|
+
>(
|
55
|
+
middleware: Middleware<
|
56
|
+
MergeContext<TContext, TExtraContext>,
|
57
|
+
UExtraContext,
|
58
|
+
unknown,
|
59
|
+
unknown
|
60
|
+
>,
|
61
|
+
): Builder<TContext, MergeContext<TExtraContext, UExtraContext>>
|
62
|
+
|
63
|
+
use<
|
64
|
+
UExtraContext extends
|
65
|
+
| Partial<MergeContext<Context, MergeContext<TContext, TExtraContext>>>
|
66
|
+
| undefined = undefined,
|
67
|
+
UMappedInput = unknown,
|
68
|
+
>(
|
69
|
+
middleware: Middleware<
|
70
|
+
MergeContext<TContext, TExtraContext>,
|
71
|
+
UExtraContext,
|
72
|
+
UMappedInput,
|
73
|
+
unknown
|
74
|
+
>,
|
75
|
+
mapInput: MapInputMiddleware<unknown, UMappedInput>,
|
76
|
+
): Builder<TContext, MergeContext<TExtraContext, UExtraContext>>
|
77
|
+
|
78
|
+
use(
|
79
|
+
middleware: Middleware<any, any, any, any>,
|
80
|
+
mapInput?: MapInputMiddleware<any, any>,
|
81
|
+
): Builder<any, any> {
|
82
|
+
const middleware_ = mapInput
|
83
|
+
? decorateMiddleware(middleware).mapInput(mapInput)
|
84
|
+
: middleware
|
85
|
+
|
86
|
+
return new Builder({
|
87
|
+
...this.zz$b,
|
88
|
+
middlewares: [...(this.zz$b.middlewares || []), middleware_],
|
89
|
+
})
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* Convert to ContractProcedureBuilder
|
94
|
+
*/
|
95
|
+
|
96
|
+
route(
|
97
|
+
opts: RouteOptions,
|
98
|
+
): ProcedureBuilder<TContext, TExtraContext, undefined, undefined> {
|
99
|
+
return new ProcedureBuilder({
|
100
|
+
middlewares: this.zz$b.middlewares,
|
101
|
+
contract: new ContractProcedure({
|
102
|
+
...opts,
|
103
|
+
InputSchema: undefined,
|
104
|
+
OutputSchema: undefined,
|
105
|
+
}),
|
106
|
+
})
|
107
|
+
}
|
108
|
+
|
109
|
+
input<USchema extends Schema = undefined>(
|
110
|
+
schema: USchema,
|
111
|
+
example?: SchemaInput<USchema>,
|
112
|
+
): ProcedureBuilder<TContext, TExtraContext, USchema, undefined> {
|
113
|
+
return new ProcedureBuilder({
|
114
|
+
middlewares: this.zz$b.middlewares,
|
115
|
+
contract: new ContractProcedure({
|
116
|
+
OutputSchema: undefined,
|
117
|
+
InputSchema: schema,
|
118
|
+
inputExample: example,
|
119
|
+
}),
|
120
|
+
})
|
121
|
+
}
|
122
|
+
|
123
|
+
output<USchema extends Schema = undefined>(
|
124
|
+
schema: USchema,
|
125
|
+
example?: SchemaOutput<USchema>,
|
126
|
+
): ProcedureBuilder<TContext, TExtraContext, undefined, USchema> {
|
127
|
+
return new ProcedureBuilder({
|
128
|
+
middlewares: this.zz$b.middlewares,
|
129
|
+
contract: new ContractProcedure({
|
130
|
+
InputSchema: undefined,
|
131
|
+
OutputSchema: schema,
|
132
|
+
outputExample: example,
|
133
|
+
}),
|
134
|
+
})
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Convert to Procedure
|
139
|
+
*/
|
140
|
+
handler<UHandlerOutput = undefined>(
|
141
|
+
handler: ProcedureHandler<
|
142
|
+
TContext,
|
143
|
+
TExtraContext,
|
144
|
+
undefined,
|
145
|
+
undefined,
|
146
|
+
UHandlerOutput
|
147
|
+
>,
|
148
|
+
): DecoratedProcedure<
|
149
|
+
TContext,
|
150
|
+
TExtraContext,
|
151
|
+
undefined,
|
152
|
+
undefined,
|
153
|
+
UHandlerOutput
|
154
|
+
> {
|
155
|
+
return decorateProcedure({
|
156
|
+
zz$p: {
|
157
|
+
middlewares: this.zz$b.middlewares,
|
158
|
+
contract: new ContractProcedure({
|
159
|
+
InputSchema: undefined,
|
160
|
+
OutputSchema: undefined,
|
161
|
+
}),
|
162
|
+
handler,
|
163
|
+
},
|
164
|
+
})
|
165
|
+
}
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Convert to ProcedureImplementer | RouterBuilder
|
169
|
+
*/
|
170
|
+
|
171
|
+
contract<UContract extends ContractProcedure<any, any> | ContractRouter>(
|
172
|
+
contract: UContract,
|
173
|
+
): UContract extends ContractProcedure<
|
174
|
+
infer UInputSchema,
|
175
|
+
infer UOutputSchema
|
176
|
+
>
|
177
|
+
? ProcedureImplementer<TContext, TExtraContext, UInputSchema, UOutputSchema>
|
178
|
+
: UContract extends ContractRouter
|
179
|
+
? ChainedRouterImplementer<TContext, UContract, TExtraContext>
|
180
|
+
: never {
|
181
|
+
if (isContractProcedure(contract)) {
|
182
|
+
return new ProcedureImplementer({
|
183
|
+
contract,
|
184
|
+
middlewares: this.zz$b.middlewares,
|
185
|
+
}) as any
|
186
|
+
}
|
187
|
+
|
188
|
+
return chainRouterImplementer(
|
189
|
+
contract as ContractRouter,
|
190
|
+
this.zz$b.middlewares,
|
191
|
+
) as any
|
192
|
+
}
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Create ExtendedMiddleware
|
196
|
+
*/
|
197
|
+
|
198
|
+
middleware<UExtraContext extends Context = undefined, TInput = unknown>(
|
199
|
+
middleware: Middleware<
|
200
|
+
MergeContext<TContext, TExtraContext>,
|
201
|
+
UExtraContext,
|
202
|
+
TInput,
|
203
|
+
unknown
|
204
|
+
>,
|
205
|
+
): DecoratedMiddleware<
|
206
|
+
MergeContext<TContext, TExtraContext>,
|
207
|
+
UExtraContext,
|
208
|
+
TInput,
|
209
|
+
unknown
|
210
|
+
> {
|
211
|
+
return decorateMiddleware(middleware)
|
212
|
+
}
|
213
|
+
|
214
|
+
prefix(prefix: HTTPPath): RouterBuilder<TContext, TExtraContext> {
|
215
|
+
return new RouterBuilder({
|
216
|
+
...this.zz$b,
|
217
|
+
prefix,
|
218
|
+
})
|
219
|
+
}
|
220
|
+
|
221
|
+
tags(...tags: string[]): RouterBuilder<TContext, TExtraContext> {
|
222
|
+
return new RouterBuilder({
|
223
|
+
...this.zz$b,
|
224
|
+
tags,
|
225
|
+
})
|
226
|
+
}
|
227
|
+
|
228
|
+
/**
|
229
|
+
* Create DecoratedRouter
|
230
|
+
*/
|
231
|
+
router<URouter extends Router<TContext>>(
|
232
|
+
router: URouter,
|
233
|
+
): HandledRouter<URouter> {
|
234
|
+
return new RouterBuilder<TContext, TExtraContext>(this.zz$b).router(router)
|
235
|
+
}
|
236
|
+
}
|
package/src/index.ts
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
import { Builder } from './builder'
|
2
|
+
|
3
|
+
export * from './builder'
|
4
|
+
export * from '@orpc/shared/error'
|
5
|
+
export * from './middleware'
|
6
|
+
export * from './procedure'
|
7
|
+
export * from './procedure-caller'
|
8
|
+
export * from './procedure-builder'
|
9
|
+
export * from './procedure-implementer'
|
10
|
+
export * from './router'
|
11
|
+
export * from './router-caller'
|
12
|
+
export * from './router-implementer'
|
13
|
+
export * from './types'
|
14
|
+
export * from './utils'
|
15
|
+
|
16
|
+
export const os = new Builder<undefined | Record<string, unknown>, undefined>()
|
@@ -0,0 +1,279 @@
|
|
1
|
+
import {
|
2
|
+
type DecoratedMiddleware,
|
3
|
+
type Middleware,
|
4
|
+
decorateMiddleware,
|
5
|
+
} from './middleware'
|
6
|
+
import type { Meta } from './types'
|
7
|
+
|
8
|
+
describe('middleware', () => {
|
9
|
+
it('just a function', () => {
|
10
|
+
const mid: Middleware<
|
11
|
+
{ auth: boolean },
|
12
|
+
{ userId: string },
|
13
|
+
unknown,
|
14
|
+
unknown
|
15
|
+
> = (input, context, meta) => {
|
16
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
17
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
18
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
19
|
+
|
20
|
+
return {
|
21
|
+
context: {
|
22
|
+
userId: '1',
|
23
|
+
},
|
24
|
+
}
|
25
|
+
}
|
26
|
+
})
|
27
|
+
|
28
|
+
it('expect required return if has extra context', () => {
|
29
|
+
const mid: Middleware<
|
30
|
+
{ auth: boolean },
|
31
|
+
{ userId: string },
|
32
|
+
unknown,
|
33
|
+
unknown
|
34
|
+
> = (input, context, meta) => {
|
35
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
36
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
37
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
38
|
+
|
39
|
+
return {
|
40
|
+
context: {
|
41
|
+
userId: '1',
|
42
|
+
},
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
// @ts-expect-error mid is not return extra context
|
47
|
+
const mid2: Middleware<
|
48
|
+
{ auth: boolean },
|
49
|
+
{ userId: string },
|
50
|
+
unknown,
|
51
|
+
unknown
|
52
|
+
> = (input, context, meta) => {
|
53
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
54
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
55
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
56
|
+
}
|
57
|
+
|
58
|
+
// @ts-expect-error mid return invalid context
|
59
|
+
const mid3: Middleware<
|
60
|
+
{ auth: boolean },
|
61
|
+
{ userId: string },
|
62
|
+
unknown,
|
63
|
+
unknown
|
64
|
+
> = (input, context, meta) => {
|
65
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
66
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
67
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
68
|
+
|
69
|
+
return {
|
70
|
+
context: {
|
71
|
+
valid: false,
|
72
|
+
},
|
73
|
+
}
|
74
|
+
}
|
75
|
+
})
|
76
|
+
|
77
|
+
it('not allow return if has no extra context', () => {
|
78
|
+
const mid: Middleware<{ auth: boolean }, undefined, unknown, unknown> = (
|
79
|
+
input,
|
80
|
+
context,
|
81
|
+
meta,
|
82
|
+
) => {
|
83
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
84
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
85
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
86
|
+
}
|
87
|
+
|
88
|
+
// @ts-expect-error mid2 is not return extra context
|
89
|
+
const mid2: Middleware<{ auth: boolean }, undefined, unknown, unknown> = (
|
90
|
+
input,
|
91
|
+
context,
|
92
|
+
meta,
|
93
|
+
) => {
|
94
|
+
expectTypeOf(input).toEqualTypeOf<unknown>()
|
95
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
96
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<unknown>>()
|
97
|
+
|
98
|
+
return {
|
99
|
+
context: {
|
100
|
+
userId: '1',
|
101
|
+
},
|
102
|
+
}
|
103
|
+
}
|
104
|
+
})
|
105
|
+
})
|
106
|
+
|
107
|
+
describe('decorateMiddleware', () => {
|
108
|
+
it('infer types', () => {
|
109
|
+
const mid = decorateMiddleware<
|
110
|
+
{ auth: boolean },
|
111
|
+
{ userId: string },
|
112
|
+
{ id: string },
|
113
|
+
{ name: string }
|
114
|
+
>((input, context, meta) => {
|
115
|
+
expectTypeOf(input).toEqualTypeOf<{ id: string }>()
|
116
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
117
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<{ name: string }>>()
|
118
|
+
|
119
|
+
return {
|
120
|
+
context: {
|
121
|
+
userId: '1',
|
122
|
+
},
|
123
|
+
}
|
124
|
+
})
|
125
|
+
|
126
|
+
expectTypeOf(mid).toEqualTypeOf<
|
127
|
+
DecoratedMiddleware<
|
128
|
+
{ auth: boolean },
|
129
|
+
{ userId: string },
|
130
|
+
{ id: string },
|
131
|
+
{ name: string }
|
132
|
+
>
|
133
|
+
>()
|
134
|
+
|
135
|
+
expectTypeOf(mid).toMatchTypeOf<
|
136
|
+
Middleware<
|
137
|
+
{ auth: boolean },
|
138
|
+
{ userId: string },
|
139
|
+
{ id: string },
|
140
|
+
{ name: string }
|
141
|
+
>
|
142
|
+
>()
|
143
|
+
})
|
144
|
+
|
145
|
+
it('concat: infer types', () => {
|
146
|
+
const mid = decorateMiddleware<
|
147
|
+
{ auth: boolean },
|
148
|
+
undefined,
|
149
|
+
{ id: string },
|
150
|
+
{ name: string }
|
151
|
+
>(() => {}).concat((input, context, meta) => {
|
152
|
+
expectTypeOf(input).toEqualTypeOf<{ id: string }>()
|
153
|
+
expectTypeOf(context).toEqualTypeOf<{ auth: boolean }>()
|
154
|
+
expectTypeOf(meta).toEqualTypeOf<Meta<{ name: string }>>()
|
155
|
+
|
156
|
+
return {
|
157
|
+
context: {
|
158
|
+
userId: '1',
|
159
|
+
},
|
160
|
+
}
|
161
|
+
})
|
162
|
+
|
163
|
+
expectTypeOf(mid).toEqualTypeOf<
|
164
|
+
DecoratedMiddleware<
|
165
|
+
{ auth: boolean },
|
166
|
+
{ userId: string },
|
167
|
+
{ id: string },
|
168
|
+
{ name: string }
|
169
|
+
>
|
170
|
+
>()
|
171
|
+
})
|
172
|
+
|
173
|
+
it('concat: can expect input', () => {
|
174
|
+
const mid = decorateMiddleware<
|
175
|
+
{ auth: boolean },
|
176
|
+
undefined,
|
177
|
+
unknown,
|
178
|
+
unknown
|
179
|
+
>(() => {})
|
180
|
+
.concat((input: { id: string }) => {})
|
181
|
+
.concat((input: { status: string }) => {})
|
182
|
+
|
183
|
+
expectTypeOf(mid).toEqualTypeOf<
|
184
|
+
DecoratedMiddleware<
|
185
|
+
{ auth: boolean },
|
186
|
+
undefined,
|
187
|
+
{ id: string } & { status: string },
|
188
|
+
unknown
|
189
|
+
>
|
190
|
+
>()
|
191
|
+
|
192
|
+
// MID2 isn't usable because input type is wrong
|
193
|
+
const mid2 = mid.concat((input: { id: number }) => {})
|
194
|
+
expectTypeOf(mid2).toMatchTypeOf<
|
195
|
+
DecoratedMiddleware<
|
196
|
+
{ auth: boolean },
|
197
|
+
undefined,
|
198
|
+
{ id: never; status: string },
|
199
|
+
unknown
|
200
|
+
>
|
201
|
+
>()
|
202
|
+
})
|
203
|
+
|
204
|
+
it('concat: deep copy', () => {
|
205
|
+
const middleware = decorateMiddleware(() => {})
|
206
|
+
const mid2 = middleware.concat(() => {})
|
207
|
+
expect(mid2).not.toBe(middleware)
|
208
|
+
})
|
209
|
+
|
210
|
+
it('concat: can map input', async () => {
|
211
|
+
const middleware = decorateMiddleware<
|
212
|
+
{ auth: boolean },
|
213
|
+
undefined,
|
214
|
+
unknown,
|
215
|
+
unknown
|
216
|
+
>(() => {})
|
217
|
+
|
218
|
+
const mid2 = middleware.concat(
|
219
|
+
(input: { postId: number }) => {
|
220
|
+
return { context: { a: 'a' } }
|
221
|
+
},
|
222
|
+
(input) => ({ postId: 12455 }),
|
223
|
+
)
|
224
|
+
|
225
|
+
// mid2 input is unknown, because it's map input does not expect anything
|
226
|
+
expectTypeOf(mid2).toEqualTypeOf<
|
227
|
+
DecoratedMiddleware<{ auth: boolean }, { a: string }, unknown, unknown>
|
228
|
+
>()
|
229
|
+
|
230
|
+
const fn = vi.fn()
|
231
|
+
const mid3 = middleware.concat(
|
232
|
+
(input: { postId: string }) => {
|
233
|
+
fn()
|
234
|
+
expect(input).toEqual({ postId: '123' })
|
235
|
+
},
|
236
|
+
(input: { postId: number }) => {
|
237
|
+
fn()
|
238
|
+
expect(input).toEqual({ postId: 123 })
|
239
|
+
return {
|
240
|
+
postId: `${input.postId}`,
|
241
|
+
}
|
242
|
+
},
|
243
|
+
)
|
244
|
+
|
245
|
+
await mid3({ postId: 123 }, {} as any, {} as any)
|
246
|
+
expect(fn).toHaveBeenCalledTimes(2)
|
247
|
+
|
248
|
+
// INPUT now follow expect types from map not from middleware
|
249
|
+
expectTypeOf(mid3).toMatchTypeOf<
|
250
|
+
DecoratedMiddleware<
|
251
|
+
{ auth: boolean },
|
252
|
+
undefined,
|
253
|
+
{ postId: number },
|
254
|
+
unknown
|
255
|
+
>
|
256
|
+
>()
|
257
|
+
})
|
258
|
+
|
259
|
+
it('mapInput', async () => {
|
260
|
+
const fn = vi.fn()
|
261
|
+
|
262
|
+
const mid = decorateMiddleware<
|
263
|
+
undefined,
|
264
|
+
undefined,
|
265
|
+
{ id: string },
|
266
|
+
unknown
|
267
|
+
>(fn).mapInput((input: { postId: string }) => {
|
268
|
+
return { id: input.postId }
|
269
|
+
})
|
270
|
+
|
271
|
+
expectTypeOf(mid).toEqualTypeOf<
|
272
|
+
DecoratedMiddleware<undefined, undefined, { postId: string }, unknown>
|
273
|
+
>()
|
274
|
+
|
275
|
+
await mid({ postId: '1' }, undefined, {} as any)
|
276
|
+
|
277
|
+
expect(fn).toHaveBeenCalledWith({ id: '1' }, undefined, {})
|
278
|
+
})
|
279
|
+
})
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import type { Context, MergeContext, Meta, Promisable } from './types'
|
2
|
+
import { mergeContext } from './utils'
|
3
|
+
|
4
|
+
export interface Middleware<
|
5
|
+
TContext extends Context,
|
6
|
+
TExtraContext extends Context,
|
7
|
+
TInput,
|
8
|
+
TOutput,
|
9
|
+
> {
|
10
|
+
(
|
11
|
+
input: TInput,
|
12
|
+
context: TContext,
|
13
|
+
meta: Meta<TOutput>,
|
14
|
+
): Promisable<
|
15
|
+
TExtraContext extends undefined ? void : { context: TExtraContext }
|
16
|
+
>
|
17
|
+
}
|
18
|
+
|
19
|
+
export interface MapInputMiddleware<TInput, TMappedInput> {
|
20
|
+
(input: TInput): TMappedInput
|
21
|
+
}
|
22
|
+
|
23
|
+
export interface DecoratedMiddleware<
|
24
|
+
TContext extends Context,
|
25
|
+
TExtraContext extends Context,
|
26
|
+
TInput,
|
27
|
+
TOutput,
|
28
|
+
> extends Middleware<TContext, TExtraContext, TInput, TOutput> {
|
29
|
+
concat<UExtraContext extends Context = undefined, UInput = TInput>(
|
30
|
+
middleware: Middleware<
|
31
|
+
MergeContext<TContext, TExtraContext>,
|
32
|
+
UExtraContext,
|
33
|
+
UInput & TInput,
|
34
|
+
TOutput
|
35
|
+
>,
|
36
|
+
): DecoratedMiddleware<
|
37
|
+
TContext,
|
38
|
+
MergeContext<TExtraContext, UExtraContext>,
|
39
|
+
TInput & UInput,
|
40
|
+
TOutput
|
41
|
+
>
|
42
|
+
|
43
|
+
concat<
|
44
|
+
UExtraContext extends Context = undefined,
|
45
|
+
UInput = TInput,
|
46
|
+
UMappedInput = unknown,
|
47
|
+
>(
|
48
|
+
middleware: Middleware<
|
49
|
+
MergeContext<TContext, TExtraContext>,
|
50
|
+
UExtraContext,
|
51
|
+
UMappedInput,
|
52
|
+
TOutput
|
53
|
+
>,
|
54
|
+
mapInput: MapInputMiddleware<UInput, UMappedInput>,
|
55
|
+
): DecoratedMiddleware<
|
56
|
+
TContext,
|
57
|
+
MergeContext<TExtraContext, UExtraContext>,
|
58
|
+
TInput & UInput,
|
59
|
+
TOutput
|
60
|
+
>
|
61
|
+
|
62
|
+
mapInput<UInput = unknown>(
|
63
|
+
map: MapInputMiddleware<UInput, TInput>,
|
64
|
+
): DecoratedMiddleware<TContext, TExtraContext, UInput, TOutput>
|
65
|
+
}
|
66
|
+
|
67
|
+
const decoratedMiddlewareSymbol = Symbol('🔒decoratedMiddleware')
|
68
|
+
|
69
|
+
export function decorateMiddleware<
|
70
|
+
TContext extends Context,
|
71
|
+
TExtraContext extends Context,
|
72
|
+
TInput,
|
73
|
+
TOutput,
|
74
|
+
>(
|
75
|
+
middleware: Middleware<TContext, TExtraContext, TInput, TOutput>,
|
76
|
+
): DecoratedMiddleware<TContext, TExtraContext, TInput, TOutput> {
|
77
|
+
if (Reflect.get(middleware, decoratedMiddlewareSymbol)) {
|
78
|
+
return middleware as any
|
79
|
+
}
|
80
|
+
|
81
|
+
const concat = (
|
82
|
+
concatMiddleware: Middleware<any, any, any, any>,
|
83
|
+
mapInput?: MapInputMiddleware<any, any>,
|
84
|
+
) => {
|
85
|
+
const concatMiddleware_ = mapInput
|
86
|
+
? decorateMiddleware(concatMiddleware).mapInput(mapInput)
|
87
|
+
: concatMiddleware
|
88
|
+
|
89
|
+
return decorateMiddleware(async (input, context, meta, ...rest) => {
|
90
|
+
const input_ = input as any
|
91
|
+
const context_ = context as any
|
92
|
+
const meta_ = meta as any
|
93
|
+
|
94
|
+
const m1 = await middleware(input_, context_, meta_, ...rest)
|
95
|
+
const m2 = await concatMiddleware_(
|
96
|
+
input_,
|
97
|
+
mergeContext(context_, m1?.context),
|
98
|
+
meta_,
|
99
|
+
...rest,
|
100
|
+
)
|
101
|
+
|
102
|
+
return { context: mergeContext(m1?.context, m2?.context) }
|
103
|
+
})
|
104
|
+
}
|
105
|
+
|
106
|
+
const mapInput = <UInput = unknown>(
|
107
|
+
map: MapInputMiddleware<UInput, TInput>,
|
108
|
+
): DecoratedMiddleware<TContext, TExtraContext, UInput, TOutput> => {
|
109
|
+
return decorateMiddleware((input, ...rest) =>
|
110
|
+
middleware(map(input), ...rest),
|
111
|
+
)
|
112
|
+
}
|
113
|
+
|
114
|
+
return Object.assign(middleware, {
|
115
|
+
[decoratedMiddlewareSymbol]: true,
|
116
|
+
concat: concat as any,
|
117
|
+
mapInput,
|
118
|
+
})
|
119
|
+
}
|