Skip to content

Commit 5049f7c

Browse files
authored
feat(trpc): restructure ORPCMeta + accessible lazy router (#758)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a TypeScript code example to clarify how to use the `.meta` property with route metadata in tRPC procedures for OpenAPI integration. * **Bug Fixes** * Adjusted the structure of metadata for tRPC procedures to nest route information under a `route` key, improving consistency and clarity. * **Tests** * Enhanced tests to verify the updated metadata structure and added layered error handling validation for invalid procedure calls. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent cf4aeb3 commit 5049f7c

5 files changed

Lines changed: 38 additions & 13 deletions

File tree

apps/content/docs/openapi/integrations/trpc.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ const orpcRouter = toORPCRouter(trpcRouter)
5050

5151
::: warning
5252
Ensure you set the `.meta` type to `ORPCMeta` when creating your tRPC builder. This is required for OpenAPI features to function properly.
53+
54+
```ts
55+
const example = t.procedure
56+
.meta({ route: { path: '/hello', summary: 'Hello procedure' } }) // [!code highlight]
57+
.input(z.object({ name: z.string() }))
58+
.query(({ input }) => {
59+
return `Hello, ${input.name}!`
60+
})
61+
```
62+
5363
:::
5464

5565
### Specification Generation

packages/shared/src/object.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function clone<T>(value: T): T {
6363
return value
6464
}
6565

66-
export function get(object: object, path: readonly string[]): unknown {
66+
export function get(object: unknown, path: readonly string[]): unknown {
6767
let current: unknown = object
6868

6969
for (const key of path) {

packages/trpc/src/to-orpc-router.test.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { call, createRouterClient, getEventMeta, isProcedure, ORPCError, unlazy } from '@orpc/server'
1+
import { call, createRouterClient, getEventMeta, isLazy, isProcedure, ORPCError, unlazy } from '@orpc/server'
22
import { isAsyncIteratorObject } from '@orpc/shared'
3-
import { tracked } from '@trpc/server'
3+
import { tracked, TRPCError } from '@trpc/server'
44
import * as z from 'zod'
55
import { inputSchema, outputSchema } from '../../contract/tests/shared'
66
import { t, trpcRouter } from '../tests/shared'
@@ -19,11 +19,16 @@ describe('toORPCRouter', async () => {
1919
expect(orpcRouter.nested.ping).toSatisfy(isProcedure)
2020

2121
const unlazy1 = await unlazy(orpcRouter.lazy)
22-
expect(unlazy1.default.subscribe).toSatisfy(isProcedure)
23-
2422
const unlazy2 = await unlazy(unlazy1.default.lazy)
2523

24+
expect(orpcRouter.lazy).toSatisfy(isLazy)
25+
expect(unlazy1.default.subscribe).toSatisfy(isProcedure)
26+
expect(unlazy1.default.lazy).toSatisfy(isLazy)
2627
expect(unlazy2.default.throw).toSatisfy(isProcedure)
28+
29+
// accessible lazy router
30+
expect(await unlazy(orpcRouter.lazy.subscribe)).toEqual({ default: expect.toSatisfy(isProcedure) })
31+
expect(await unlazy(orpcRouter.lazy.lazy.throw)).toEqual({ default: expect.toSatisfy(isProcedure) })
2732
})
2833

2934
it('with disabled input/output', async () => {
@@ -40,7 +45,7 @@ describe('toORPCRouter', async () => {
4045
it('meta/route', async () => {
4146
expect(orpcRouter.ping['~orpc'].meta).toEqual({ meta1: 'test' })
4247
expect(orpcRouter.nested.ping['~orpc'].route).toEqual({ path: '/nested/ping', description: 'Nested ping procedure' })
43-
expect(orpcRouter.nested.ping['~orpc'].meta).toEqual({ path: '/nested/ping', description: 'Nested ping procedure' })
48+
expect(orpcRouter.nested.ping['~orpc'].meta).toEqual({ route: { path: '/nested/ping', description: 'Nested ping procedure' } })
4449
})
4550

4651
describe('calls', () => {
@@ -61,6 +66,16 @@ describe('toORPCRouter', async () => {
6166
).rejects.toSatisfy((err: any) => {
6267
return err instanceof ORPCError && err.code === 'PARSE_ERROR' && err.message === 'throw'
6368
})
69+
70+
await expect(
71+
call(orpcRouter.ping, { input: 'invalid' } as any, { context: { a: 'test' } }),
72+
).rejects.toSatisfy((err: any) => {
73+
expect(err).toBeInstanceOf(ORPCError)
74+
expect(err.cause).toBeInstanceOf(TRPCError)
75+
expect(err.cause.cause).toBeInstanceOf(z.ZodError)
76+
77+
return true
78+
})
6479
})
6580

6681
it('deep lazy', async () => {

packages/trpc/src/to-orpc-router.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import type { AnyProcedure, AnyRouter, inferRouterContext } from '@trpc/server'
33
import type { inferRouterMeta, Parser, TrackedData } from '@trpc/server/unstable-core-do-not-import'
44
import { mapEventIterator } from '@orpc/client'
55
import * as ORPC from '@orpc/server'
6-
import { isObject, isTypescriptObject } from '@orpc/shared'
6+
import { get, isObject, isTypescriptObject } from '@orpc/shared'
77
import { isTrackedEnvelope, TRPCError } from '@trpc/server'
88
import { getHTTPStatusCodeFromError, isAsyncIterable } from '@trpc/server/unstable-core-do-not-import'
99

10-
export interface experimental_ORPCMeta extends ORPC.Route {
11-
10+
export interface experimental_ORPCMeta {
11+
route?: ORPC.Route
1212
}
1313

1414
export type experimental_ToORPCOutput<T>
@@ -59,10 +59,10 @@ function lazyToORPCRouter(lazies: AnyRouter['_def']['lazy']) {
5959
for (const key in lazies) {
6060
const item = lazies[key]!
6161

62-
orpcRouter[key] = ORPC.lazy(async () => {
62+
orpcRouter[key] = ORPC.createAccessibleLazyRouter(ORPC.lazy(async () => {
6363
const router = await item.ref()
6464
return { default: experimental_toORPCRouter(router) }
65-
})
65+
}))
6666
}
6767

6868
return orpcRouter
@@ -91,7 +91,7 @@ function toORPCProcedure(procedure: AnyProcedure) {
9191
meta: procedure._def.meta ?? {},
9292
inputValidationIndex: 0,
9393
outputValidationIndex: 0,
94-
route: procedure._def.meta ?? {},
94+
route: get(procedure._def.meta, ['route']) ?? {},
9595
middlewares: [],
9696
inputSchema: toDisabledStandardSchema(procedure._def.inputs.at(-1)),
9797
outputSchema: toDisabledStandardSchema((procedure as any)._def.output),

packages/trpc/tests/shared.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const trpcRouter = t.router({
4040

4141
nested: {
4242
ping: t.procedure
43-
.meta({ path: '/nested/ping', description: 'Nested ping procedure' })
43+
.meta({ route: { path: '/nested/ping', description: 'Nested ping procedure' } })
4444
.input(z.object({ a: z.string() }))
4545
.output(z.string().transform(val => Number(val)))
4646
.query(({ input }) => {

0 commit comments

Comments
 (0)