From 3d319dd8ba5e5751e1356d6f30f36a141dd0f1c1 Mon Sep 17 00:00:00 2001 From: unnoq Date: Sat, 12 Jul 2025 13:58:20 +0700 Subject: [PATCH 1/2] feat(trpc)!: restructure ORPCMeta for better organization --- apps/content/docs/openapi/integrations/trpc.md | 10 ++++++++++ packages/shared/src/object.ts | 2 +- packages/trpc/src/to-orpc-router.test.ts | 14 ++++++++++++-- packages/trpc/src/to-orpc-router.ts | 8 ++++---- packages/trpc/tests/shared.ts | 2 +- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/content/docs/openapi/integrations/trpc.md b/apps/content/docs/openapi/integrations/trpc.md index 40eb0b9ac..eea63d06e 100644 --- a/apps/content/docs/openapi/integrations/trpc.md +++ b/apps/content/docs/openapi/integrations/trpc.md @@ -50,6 +50,16 @@ const orpcRouter = toORPCRouter(trpcRouter) ::: warning Ensure you set the `.meta` type to `ORPCMeta` when creating your tRPC builder. This is required for OpenAPI features to function properly. + +```ts +const example = t.procedure + .meta({ route: { path: '/hello', summary: 'Hello procedure' } }) // [!code highlight] + .input(z.object({ name: z.string() })) + .query(({ input }) => { + return `Hello, ${input.name}!` + }) +``` + ::: ### Specification Generation diff --git a/packages/shared/src/object.ts b/packages/shared/src/object.ts index 84ff7a4d2..b98bb9938 100644 --- a/packages/shared/src/object.ts +++ b/packages/shared/src/object.ts @@ -63,7 +63,7 @@ export function clone(value: T): T { return value } -export function get(object: object, path: readonly string[]): unknown { +export function get(object: unknown, path: readonly string[]): unknown { let current: unknown = object for (const key of path) { diff --git a/packages/trpc/src/to-orpc-router.test.ts b/packages/trpc/src/to-orpc-router.test.ts index 6677a6bd2..4c35500a8 100644 --- a/packages/trpc/src/to-orpc-router.test.ts +++ b/packages/trpc/src/to-orpc-router.test.ts @@ -1,6 +1,6 @@ import { call, createRouterClient, getEventMeta, isProcedure, ORPCError, unlazy } from '@orpc/server' import { isAsyncIteratorObject } from '@orpc/shared' -import { tracked } from '@trpc/server' +import { tracked, TRPCError } from '@trpc/server' import { z } from 'zod' import { inputSchema, outputSchema } from '../../contract/tests/shared' import { t, trpcRouter } from '../tests/shared' @@ -40,7 +40,7 @@ describe('toORPCRouter', async () => { it('meta/route', async () => { expect(orpcRouter.ping['~orpc'].meta).toEqual({ meta1: 'test' }) expect(orpcRouter.nested.ping['~orpc'].route).toEqual({ path: '/nested/ping', description: 'Nested ping procedure' }) - expect(orpcRouter.nested.ping['~orpc'].meta).toEqual({ path: '/nested/ping', description: 'Nested ping procedure' }) + expect(orpcRouter.nested.ping['~orpc'].meta).toEqual({ route: { path: '/nested/ping', description: 'Nested ping procedure' } }) }) describe('calls', () => { @@ -61,6 +61,16 @@ describe('toORPCRouter', async () => { ).rejects.toSatisfy((err: any) => { return err instanceof ORPCError && err.code === 'PARSE_ERROR' && err.message === 'throw' }) + + await expect( + call(orpcRouter.ping, { input: 'invalid' } as any, { context: { a: 'test' } }), + ).rejects.toSatisfy((err: any) => { + expect(err).toBeInstanceOf(ORPCError) + expect(err.cause).toBeInstanceOf(TRPCError) + expect(err.cause.cause).toBeInstanceOf(z.ZodError) + + return true + }) }) it('deep lazy', async () => { diff --git a/packages/trpc/src/to-orpc-router.ts b/packages/trpc/src/to-orpc-router.ts index f93323493..4d29f3e76 100644 --- a/packages/trpc/src/to-orpc-router.ts +++ b/packages/trpc/src/to-orpc-router.ts @@ -3,12 +3,12 @@ import type { AnyProcedure, AnyRouter, inferRouterContext } from '@trpc/server' import type { inferRouterMeta, Parser, TrackedData } from '@trpc/server/unstable-core-do-not-import' import { mapEventIterator } from '@orpc/client' import * as ORPC from '@orpc/server' -import { isObject, isTypescriptObject } from '@orpc/shared' +import { get, isObject, isTypescriptObject } from '@orpc/shared' import { isTrackedEnvelope, TRPCError } from '@trpc/server' import { getHTTPStatusCodeFromError, isAsyncIterable } from '@trpc/server/unstable-core-do-not-import' -export interface experimental_ORPCMeta extends ORPC.Route { - +export interface experimental_ORPCMeta { + route?: ORPC.Route } export type experimental_ToORPCOutput @@ -91,7 +91,7 @@ function toORPCProcedure(procedure: AnyProcedure) { meta: procedure._def.meta ?? {}, inputValidationIndex: 0, outputValidationIndex: 0, - route: procedure._def.meta ?? {}, + route: get(procedure._def.meta, ['route']) ?? {}, middlewares: [], inputSchema: toDisabledStandardSchema(procedure._def.inputs.at(-1)), outputSchema: toDisabledStandardSchema((procedure as any)._def.output), diff --git a/packages/trpc/tests/shared.ts b/packages/trpc/tests/shared.ts index 4025de247..e28664982 100644 --- a/packages/trpc/tests/shared.ts +++ b/packages/trpc/tests/shared.ts @@ -40,7 +40,7 @@ export const trpcRouter = t.router({ nested: { ping: t.procedure - .meta({ path: '/nested/ping', description: 'Nested ping procedure' }) + .meta({ route: { path: '/nested/ping', description: 'Nested ping procedure' } }) .input(z.object({ a: z.string() })) .output(z.string().transform(val => Number(val))) .query(({ input }) => { From 17a1d9e8276407bb62c7d0ecddfef5af495b59cb Mon Sep 17 00:00:00 2001 From: unnoq Date: Sun, 13 Jul 2025 09:06:21 +0700 Subject: [PATCH 2/2] accessible lazy router --- packages/trpc/src/to-orpc-router.test.ts | 11 ++++++++--- packages/trpc/src/to-orpc-router.ts | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/trpc/src/to-orpc-router.test.ts b/packages/trpc/src/to-orpc-router.test.ts index 4c35500a8..e04be26b6 100644 --- a/packages/trpc/src/to-orpc-router.test.ts +++ b/packages/trpc/src/to-orpc-router.test.ts @@ -1,4 +1,4 @@ -import { call, createRouterClient, getEventMeta, isProcedure, ORPCError, unlazy } from '@orpc/server' +import { call, createRouterClient, getEventMeta, isLazy, isProcedure, ORPCError, unlazy } from '@orpc/server' import { isAsyncIteratorObject } from '@orpc/shared' import { tracked, TRPCError } from '@trpc/server' import { z } from 'zod' @@ -19,11 +19,16 @@ describe('toORPCRouter', async () => { expect(orpcRouter.nested.ping).toSatisfy(isProcedure) const unlazy1 = await unlazy(orpcRouter.lazy) - expect(unlazy1.default.subscribe).toSatisfy(isProcedure) - const unlazy2 = await unlazy(unlazy1.default.lazy) + expect(orpcRouter.lazy).toSatisfy(isLazy) + expect(unlazy1.default.subscribe).toSatisfy(isProcedure) + expect(unlazy1.default.lazy).toSatisfy(isLazy) expect(unlazy2.default.throw).toSatisfy(isProcedure) + + // accessible lazy router + expect(await unlazy(orpcRouter.lazy.subscribe)).toEqual({ default: expect.toSatisfy(isProcedure) }) + expect(await unlazy(orpcRouter.lazy.lazy.throw)).toEqual({ default: expect.toSatisfy(isProcedure) }) }) it('with disabled input/output', async () => { diff --git a/packages/trpc/src/to-orpc-router.ts b/packages/trpc/src/to-orpc-router.ts index 4d29f3e76..62ac89067 100644 --- a/packages/trpc/src/to-orpc-router.ts +++ b/packages/trpc/src/to-orpc-router.ts @@ -59,10 +59,10 @@ function lazyToORPCRouter(lazies: AnyRouter['_def']['lazy']) { for (const key in lazies) { const item = lazies[key]! - orpcRouter[key] = ORPC.lazy(async () => { + orpcRouter[key] = ORPC.createAccessibleLazyRouter(ORPC.lazy(async () => { const router = await item.ref() return { default: experimental_toORPCRouter(router) } - }) + })) } return orpcRouter