Skip to content

Commit cba521d

Browse files
authored
feat(server)!: middleware can run both before/after validation step (#83)
* builders * fix tests for basic builders * procedure decorated tests * tests update * procedure client with new workflow * fix middleware undefinable * fix types
1 parent 5d1444a commit cba521d

31 files changed

Lines changed: 422 additions & 281 deletions

packages/openapi/src/adapters/fetch/openapi-handler.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ describe.each(hono)('openAPIHandler: %s', (_, HonoConstructor) => {
216216
errorMap: undefined,
217217
}),
218218
handler: vi.fn(),
219+
postMiddlewares: [],
220+
preMiddlewares: [],
219221
}),
220222
}
221223

@@ -241,6 +243,8 @@ describe.each(hono)('openAPIHandler: %s', (_, HonoConstructor) => {
241243
errorMap: undefined,
242244
}),
243245
handler: vi.fn(),
246+
postMiddlewares: [],
247+
preMiddlewares: [],
244248
}),
245249
}
246250

packages/server/src/adapters/fetch/orpc-handler.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ describe('rpcHandler', () => {
1717
errorMap: undefined,
1818
}),
1919
handler: vi.fn(),
20+
postMiddlewares: [],
21+
preMiddlewares: [],
2022
})
2123
const pong = new Procedure({
2224
contract: new ContractProcedure({
@@ -25,6 +27,8 @@ describe('rpcHandler', () => {
2527
errorMap: undefined,
2628
}),
2729
handler: vi.fn(),
30+
postMiddlewares: [],
31+
preMiddlewares: [],
2832
})
2933

3034
const router = {

packages/server/src/builder.test-d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Builder } from './builder'
12
import type { ChainableImplementer } from './implementer-chainable'
23
import type { DecoratedLazy } from './lazy-decorated'
34
import type { Middleware, MiddlewareOutputFn } from './middleware'
@@ -9,11 +10,10 @@ import type { AdaptedRouter, RouterBuilder } from './router-builder'
910
import type { WELL_CONTEXT } from './types'
1011
import { oc } from '@orpc/contract'
1112
import { z } from 'zod'
12-
import { Builder } from './builder'
1313

1414
const schema = z.object({ val: z.string().transform(v => Number.parseInt(v)) })
1515

16-
const builder = new Builder<{ auth: boolean }, { db: string }>({})
16+
const builder = {} as Builder<{ auth: boolean }, { db: string }>
1717

1818
describe('self chainable', () => {
1919
it('define context', () => {

packages/server/src/builder.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ describe('self chainable', () => {
3434
expect(applied).not.toBe(builder)
3535
expect(applied).toBeInstanceOf(Builder)
3636

37-
expect(applied['~orpc'].middlewares).toEqual(undefined)
37+
expect(applied['~orpc'].middlewares).toEqual([])
3838
})
3939

4040
it('use middleware', () => {
4141
const builder = new Builder({
42+
middlewares: [],
4243
})
4344

4445
const mid1 = vi.fn()
@@ -111,7 +112,7 @@ describe('to DecoratedProcedure', () => {
111112
const result = builder.handler(fn)
112113

113114
expect(result).toSatisfy(isProcedure)
114-
expect(result['~orpc'].middlewares).toEqual([mid])
115+
expect(result['~orpc'].preMiddlewares).toEqual([mid])
115116
expect(result['~orpc'].handler).toBe(fn)
116117
})
117118
})

packages/server/src/builder.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { DecoratedProcedure } from './procedure-decorated'
1414
import { RouterBuilder } from './router-builder'
1515

1616
export interface BuilderDef<TContext extends Context, TExtraContext extends Context> {
17-
middlewares?: Middleware<MergeContext<TContext, TExtraContext>, Partial<TExtraContext> | undefined, unknown, any, Record<string, unknown>>[]
17+
middlewares: Middleware<MergeContext<TContext, TExtraContext>, Partial<TExtraContext> | undefined, unknown, any, Record<string, unknown>>[]
1818
}
1919

2020
export class Builder<TContext extends Context, TExtraContext extends Context> {
@@ -26,7 +26,9 @@ export class Builder<TContext extends Context, TExtraContext extends Context> {
2626
}
2727

2828
context<UContext extends Context = WELL_CONTEXT>(): Builder<UContext, undefined> {
29-
return new Builder({})
29+
return new Builder({
30+
middlewares: [],
31+
})
3032
}
3133

3234
use<U extends Context & Partial<MergeContext<TContext, TExtraContext>> | undefined = undefined>(
@@ -40,7 +42,7 @@ export class Builder<TContext extends Context, TExtraContext extends Context> {
4042
): Builder<TContext, MergeContext<TExtraContext, U>> {
4143
return new Builder({
4244
...this['~orpc'],
43-
middlewares: [...(this['~orpc'].middlewares ?? []), middleware as any],
45+
middlewares: [...this['~orpc'].middlewares, middleware as any],
4446
})
4547
}
4648

@@ -123,7 +125,8 @@ export class Builder<TContext extends Context, TExtraContext extends Context> {
123125
handler: ProcedureHandler<TContext, TExtraContext, undefined, undefined, UFuncOutput, undefined>,
124126
): DecoratedProcedure<TContext, TExtraContext, undefined, undefined, UFuncOutput, undefined> {
125127
return new DecoratedProcedure({
126-
middlewares: this['~orpc'].middlewares,
128+
preMiddlewares: this['~orpc'].middlewares,
129+
postMiddlewares: [],
127130
contract: new ContractProcedure({
128131
InputSchema: undefined,
129132
OutputSchema: undefined,

packages/server/src/implementer-chainable.test.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('createChainableImplementer', () => {
2727
const implementer = createChainableImplementer(ping, [mid1, mid2])
2828

2929
expect(implementer).toBeInstanceOf(ProcedureImplementer)
30-
expect(implementer['~orpc'].middlewares).toEqual([mid1, mid2])
30+
expect(implementer['~orpc'].preMiddlewares).toEqual([mid1, mid2])
3131
expect(implementer['~orpc'].contract).toBe(ping)
3232
})
3333

@@ -38,22 +38,22 @@ describe('createChainableImplementer', () => {
3838
expect(implementer.use(mid3)['~orpc'].contract).toBe(contract)
3939

4040
expect(implementer.ping).toBeInstanceOf(ProcedureImplementer)
41-
expect(implementer.ping['~orpc'].middlewares).toEqual([mid1, mid2])
41+
expect(implementer.ping['~orpc'].preMiddlewares).toEqual([mid1, mid2])
4242
expect(implementer.ping['~orpc'].contract).toBe(ping)
4343

4444
expect(implementer.pong).toBeInstanceOf(ProcedureImplementer)
45-
expect(implementer.pong['~orpc'].middlewares).toEqual([mid1, mid2])
45+
expect(implementer.pong['~orpc'].preMiddlewares).toEqual([mid1, mid2])
4646
expect(implementer.pong['~orpc'].contract).toBe(pong)
4747

4848
expect(implementer.nested.use(mid3)['~orpc'].middlewares).toEqual([mid1, mid2, mid3])
4949
expect(implementer.nested.use(mid3)['~orpc'].contract).toBe(contract.nested)
5050

5151
expect(implementer.nested.ping).toBeInstanceOf(ProcedureImplementer)
52-
expect(implementer.nested.ping['~orpc'].middlewares).toEqual([mid1, mid2])
52+
expect(implementer.nested.ping['~orpc'].preMiddlewares).toEqual([mid1, mid2])
5353
expect(implementer.nested.ping['~orpc'].contract).toBe(contract.nested.ping)
5454

5555
expect(implementer.nested.pong).toBeInstanceOf(ProcedureImplementer)
56-
expect(implementer.nested.pong['~orpc'].middlewares).toEqual([mid1, mid2])
56+
expect(implementer.nested.pong['~orpc'].preMiddlewares).toEqual([mid1, mid2])
5757
expect(implementer.nested.pong['~orpc'].contract).toBe(contract.nested.pong)
5858
})
5959

@@ -82,7 +82,8 @@ describe('createChainableImplementer', () => {
8282

8383
expect(implementer.use).toBeTypeOf('function')
8484
expect(implementer.use.use(mid3)).toBeInstanceOf(ProcedureImplementer)
85-
expect(implementer.use.use(mid3)['~orpc'].middlewares).toEqual([mid1, mid2, mid3])
85+
expect(implementer.use.use(mid3)['~orpc'].preMiddlewares).toEqual([mid1, mid2])
86+
expect(implementer.use.use(mid3)['~orpc'].postMiddlewares).toEqual([mid3])
8687
expect(implementer.use['~orpc'].contract).toBe(ping)
8788

8889
expect(implementer.router).toBeTypeOf('function')
@@ -92,17 +93,20 @@ describe('createChainableImplementer', () => {
9293

9394
expect(implementer.router.router).toBeTypeOf('function')
9495
expect(implementer.router.router.use(mid3)).toBeInstanceOf(ProcedureImplementer)
95-
expect(implementer.router.router.use(mid3)['~orpc'].middlewares).toEqual([mid1, mid2, mid3])
96+
expect(implementer.router.router.use(mid3)['~orpc'].preMiddlewares).toEqual([mid1, mid2])
97+
expect(implementer.router.router.use(mid3)['~orpc'].postMiddlewares).toEqual([mid3])
9698
expect(implementer.router.router['~orpc'].contract).toBe(contract.router.router)
9799

98100
expect(implementer.router.use).toBeTypeOf('function')
99101
expect(implementer.router.use.use(mid3)).toBeInstanceOf(ProcedureImplementer)
100-
expect(implementer.router.use.use(mid3)['~orpc'].middlewares).toEqual([mid1, mid2, mid3])
102+
expect(implementer.router.use.use(mid3)['~orpc'].preMiddlewares).toEqual([mid1, mid2])
103+
expect(implementer.router.use.use(mid3)['~orpc'].postMiddlewares).toEqual([mid3])
101104
expect(implementer.router.use['~orpc'].contract).toBe(contract.router.use)
102105

103106
expect(implementer['~orpc'].use).toBeTypeOf('function')
104107
expect(implementer['~orpc'].use.use(mid3)).toBeInstanceOf(ProcedureImplementer)
105-
expect(implementer['~orpc'].use.use(mid3)['~orpc'].middlewares).toEqual([mid1, mid2, mid3])
108+
expect(implementer['~orpc'].use.use(mid3)['~orpc'].preMiddlewares).toEqual([mid1, mid2])
109+
expect(implementer['~orpc'].use.use(mid3)['~orpc'].postMiddlewares).toEqual([mid3])
106110
expect(implementer['~orpc'].use['~orpc'].contract).toBe(contract.router.use)
107111
})
108112

packages/server/src/implementer-chainable.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ export function createChainableImplementer<
2121
TContract extends ContractRouter = any,
2222
>(
2323
contract: TContract,
24-
middlewares?: Middleware<MergeContext<TContext, TExtraContext>, Partial<TExtraContext> | undefined, unknown, any, Record<string, unknown>>[],
24+
middlewares: Middleware<MergeContext<TContext, TExtraContext>, Partial<TExtraContext> | undefined, unknown, any, Record<string, unknown>>[] = [],
2525
): ChainableImplementer<TContext, TExtraContext, TContract> {
2626
if (isContractProcedure(contract)) {
2727
const implementer = new ProcedureImplementer({
2828
contract,
29-
middlewares,
29+
preMiddlewares: middlewares,
30+
postMiddlewares: [],
3031
})
3132

3233
return implementer as any

packages/server/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ export * from './types'
2323
export * from './utils'
2424
export { configGlobal, fallbackToGlobalConfig, isDefinedError, ORPCError, safe } from '@orpc/contract'
2525

26-
export const os = new Builder<WELL_CONTEXT, undefined>({})
26+
export const os = new Builder<WELL_CONTEXT, undefined>({
27+
middlewares: [],
28+
})

packages/server/src/lazy-decorated.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ describe('decorated lazy', () => {
1818
errorMap: undefined,
1919
}),
2020
handler: vi.fn(),
21-
middlewares: [],
21+
preMiddlewares: [],
22+
postMiddlewares: [],
2223
})
2324

2425
const lazyPing = lazy(() => Promise.resolve({ default: ping }))

packages/server/src/lazy-utils.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ describe('createLazyProcedureFormAnyLazy', () => {
1111
errorMap: undefined,
1212
}),
1313
handler: vi.fn(),
14+
postMiddlewares: [],
15+
preMiddlewares: [],
1416
})
1517

1618
it('return a Lazy<ANY_PROCEDURE>', async () => {

0 commit comments

Comments
 (0)