Skip to content

Commit cf3573d

Browse files
authored
feat(trpc): trpc router to orpc router (#699)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a new package, `@orpc/trpc`, enabling integration between oRPC and tRPC applications. * Added comprehensive documentation for integrating oRPC with tRPC, including installation, usage, error handling, and OpenAPI support. * Enhanced documentation site navigation with new sidebar links to the tRPC integration guide. * **Tests** * Added extensive tests to verify type transformations and runtime behavior of the tRPC-to-oRPC router conversion. * **Chores** * Added configuration, README, and .gitignore files to support development and publishing of the new package. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 7f4a918 commit cf3573d

12 files changed

Lines changed: 643 additions & 0 deletions

File tree

apps/content/.vitepress/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export default defineConfig({
158158
{ text: 'Durable Event Iterator', link: '/docs/integrations/durable-event-iterator' },
159159
{ text: 'Hey API', link: '/docs/integrations/hey-api' },
160160
{ text: 'NestJS', link: '/docs/openapi/integrations/implement-contract-in-nest' },
161+
{ text: 'tRPC', link: '/docs/openapi/integrations/trpc' },
161162
],
162163
},
163164
{
@@ -221,6 +222,7 @@ export default defineConfig({
221222
collapsed: true,
222223
items: [
223224
{ text: 'Implement Contract in NestJS', link: '/docs/openapi/integrations/implement-contract-in-nest' },
225+
{ text: 'tRPC', link: '/docs/openapi/integrations/trpc' },
224226
],
225227
},
226228
{
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
title: tRPC Integration
3+
description: Use oRPC features in your tRPC applications.
4+
---
5+
6+
# tRPC Integration
7+
8+
This guide explains how to integrate oRPC with tRPC, allowing you to leverage oRPC features in your existing tRPC applications.
9+
10+
## Installation
11+
12+
::: code-group
13+
14+
```sh [npm]
15+
npm install @orpc/trpc@latest
16+
```
17+
18+
```sh [yarn]
19+
yarn add @orpc/trpc@latest
20+
```
21+
22+
```sh [pnpm]
23+
pnpm add @orpc/trpc@latest
24+
```
25+
26+
```sh [bun]
27+
bun add @orpc/trpc@latest
28+
```
29+
30+
```sh [deno]
31+
deno install npm:@orpc/trpc@latest
32+
```
33+
34+
:::
35+
36+
## OpenAPI Support
37+
38+
By converting a [tRPC router](https://trpc.io/docs/server/routers) to an [oRPC router](/docs/router), you can utilize most oRPC features, including OpenAPI specification generation and request handling.
39+
40+
```ts
41+
import {
42+
experimental_ORPCMeta as ORPCMeta,
43+
experimental_toORPCRouter as toORPCRouter
44+
} from '@orpc/trpc'
45+
46+
export const t = initTRPC.context<Context>().meta<ORPCMeta>().create()
47+
48+
const orpcRouter = toORPCRouter(trpcRouter)
49+
```
50+
51+
::: warning
52+
Ensure you set the `.meta` type to `ORPCMeta` when creating your tRPC builder. This is required for OpenAPI features to function properly.
53+
:::
54+
55+
### Specification Generation
56+
57+
```ts
58+
const openAPIGenerator = new OpenAPIGenerator({
59+
schemaConverters: [
60+
new ZodToJsonSchemaConverter(), // <-- if you use Zod
61+
new ValibotToJsonSchemaConverter(), // <-- if you use Valibot
62+
new ArkTypeToJsonSchemaConverter(), // <-- if you use ArkType
63+
],
64+
})
65+
66+
const spec = await openAPIGenerator.generate(orpcRouter, {
67+
info: {
68+
title: 'My App',
69+
version: '0.0.0',
70+
},
71+
})
72+
```
73+
74+
::: info
75+
Learn more about [oRPC OpenAPI Specification Generation](/docs/openapi/openapi-specification).
76+
:::
77+
78+
### Request Handling
79+
80+
```ts
81+
const handler = new OpenAPIHandler(orpcRouter, {
82+
plugins: [new CORSPlugin()],
83+
interceptors: [
84+
onError(error => console.error(error))
85+
],
86+
})
87+
88+
export async function fetch(request: Request) {
89+
const { matched, response } = await handler.handle(request, {
90+
prefix: '/api',
91+
context: {} // Add initial context if needed
92+
})
93+
94+
return response ?? new Response('Not Found', { status: 404 })
95+
}
96+
```
97+
98+
::: info
99+
Learn more about [oRPC OpenAPI Handler](/docs/openapi/openapi-handler).
100+
:::
101+
102+
## Error Formatting
103+
104+
The `toORPCRouter` does not support [tRPC Error Formatting](https://trpc.io/docs/server/error-formatting). You should catch errors and format them manually using interceptors:
105+
106+
```ts
107+
const handler = new OpenAPIHandler(orpcRouter, {
108+
interceptors: [
109+
onError((error) => {
110+
if (
111+
error instanceof ORPCError
112+
&& error.cause instanceof TRPCError
113+
&& error.cause.cause instanceof ZodError
114+
) {
115+
throw new ORPCError('INPUT_VALIDATION_FAILED', {
116+
status: 422,
117+
data: error.cause.cause.flatten(),
118+
cause: error.cause.cause,
119+
})
120+
}
121+
})
122+
],
123+
})
124+
```

packages/trpc/.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Hidden folders and files
2+
.*
3+
!.gitignore
4+
!.*.example
5+
6+
# Common generated folders
7+
logs/
8+
node_modules/
9+
out/
10+
dist/
11+
dist-ssr/
12+
build/
13+
coverage/
14+
temp/
15+
16+
# Common generated files
17+
*.log
18+
*.log.*
19+
*.tsbuildinfo
20+
*.vitest-temp.json
21+
vite.config.ts.timestamp-*
22+
vitest.config.ts.timestamp-*
23+
24+
# Common manual ignore files
25+
*.local
26+
*.pem

packages/trpc/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<div align="center">
2+
<image align="center" src="https://orpc.unnoq.com/logo.webp" width=280 alt="oRPC logo" />
3+
</div>
4+
5+
<h1></h1>
6+
7+
<div align="center">
8+
<a href="https://codecov.io/gh/unnoq/orpc">
9+
<img alt="codecov" src="https://codecov.io/gh/unnoq/orpc/branch/main/graph/badge.svg">
10+
</a>
11+
<a href="https://www.npmjs.com/package/@orpc/trpc">
12+
<img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Ftrpc?logo=npm" />
13+
</a>
14+
<a href="https://github.com/unnoq/orpc/blob/main/LICENSE">
15+
<img alt="MIT License" src="https://img.shields.io/github/license/unnoq/orpc?logo=open-source-initiative" />
16+
</a>
17+
<a href="https://discord.gg/TXEbwRBvQn">
18+
<img alt="Discord" src="https://img.shields.io/discord/1308966753044398161?color=7389D8&label&logo=discord&logoColor=ffffff" />
19+
</a>
20+
</div>
21+
22+
<h3 align="center">Typesafe APIs Made Simple 🪄</h3>
23+
24+
**oRPC is a powerful combination of RPC and OpenAPI**, makes it easy to build APIs that are end-to-end type-safe and adhere to OpenAPI standards
25+
26+
---
27+
28+
## Highlights
29+
30+
- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server.
31+
- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard.
32+
- **📝 Contract-First Development**: Optionally define your API contract before implementation.
33+
- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte, Angular), Pinia Colada, and more.
34+
- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
35+
- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators.
36+
- **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL, and more.
37+
- **⏱️ Lazy Router**: Enhance cold start times with our lazy routing feature.
38+
- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming.
39+
- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun, Node.js, and beyond.
40+
- **🔌 Extendability**: Easily extend functionality with plugins, middleware, and interceptors.
41+
- **🛡️ Reliability**: Well-tested, TypeScript-based, production-ready, and MIT licensed.
42+
43+
## Documentation
44+
45+
You can find the full documentation [here](https://orpc.unnoq.com).
46+
47+
## Packages
48+
49+
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
50+
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
51+
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
52+
- [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
53+
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with [NestJS](https://nestjs.com/).
54+
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
55+
- [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): [TanStack Query](https://tanstack.com/query/latest) integration.
56+
- [@orpc/vue-colada](https://www.npmjs.com/package/@orpc/vue-colada): Integration with [Pinia Colada](https://pinia-colada.esm.dev/).
57+
- [@orpc/hey-api](https://www.npmjs.com/package/@orpc/hey-api): [Hey API](https://heyapi.dev/) integration.
58+
- [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): More schemas that [Zod](https://zod.dev/) doesn't support yet.
59+
- [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): OpenAPI spec generation from [Valibot](https://valibot.dev/).
60+
- [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): OpenAPI spec generation from [ArkType](https://arktype.io/).
61+
62+
## `@orpc/trpc`
63+
64+
Bridge between [tRPC](https://trpc.io/) and oRPC
65+
66+
## Sponsors
67+
68+
<p align="center">
69+
<a href="https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg">
70+
<img src='https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg'/>
71+
</a>
72+
</p>
73+
74+
## License
75+
76+
Distributed under the MIT License. See [LICENSE](https://github.com/unnoq/orpc/blob/main/LICENSE) for more information.

packages/trpc/package.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "@orpc/trpc",
3+
"type": "module",
4+
"version": "0.0.0",
5+
"license": "MIT",
6+
"homepage": "https://orpc.unnoq.com",
7+
"repository": {
8+
"type": "git",
9+
"url": "git+https://github.com/unnoq/orpc.git",
10+
"directory": "packages/trpc"
11+
},
12+
"keywords": [
13+
"unnoq",
14+
"orpc",
15+
"trpc"
16+
],
17+
"publishConfig": {
18+
"exports": {
19+
".": {
20+
"types": "./dist/index.d.mts",
21+
"import": "./dist/index.mjs",
22+
"default": "./dist/index.mjs"
23+
}
24+
}
25+
},
26+
"exports": {
27+
".": "./src/index.ts"
28+
},
29+
"files": [
30+
"dist"
31+
],
32+
"scripts": {
33+
"build": "unbuild",
34+
"build:watch": "pnpm run build --watch",
35+
"type:check": "tsc -b"
36+
},
37+
"peerDependencies": {
38+
"@trpc/server": ">=11.4.2"
39+
},
40+
"dependencies": {
41+
"@orpc/server": "workspace:*",
42+
"@orpc/shared": "workspace:*"
43+
},
44+
"devDependencies": {
45+
"@trpc/server": "^11.4.2",
46+
"zod": "^3.25.67"
47+
}
48+
}

packages/trpc/src/index.ts

Whitespace-only changes.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { ContractRouter, InferRouterInitialContext, Procedure, Router, Schema } from '@orpc/server'
2+
import type { inferRouterContext } from '@trpc/server'
3+
import type { inferRouterMeta } from '@trpc/server/unstable-core-do-not-import'
4+
import type { TRPCContext, TRPCMeta, trpcRouter } from '../tests/shared'
5+
import type { experimental_ToORPCRouterResult as ToORPCRouterResult } from './to-orpc-router'
6+
7+
it('ToORPCRouterResult', () => {
8+
const orpcRouter = {} as ToORPCRouterResult<
9+
inferRouterContext<typeof trpcRouter>,
10+
inferRouterMeta<typeof trpcRouter>,
11+
typeof trpcRouter['_def']['record']
12+
>
13+
14+
expectTypeOf(orpcRouter).toExtend<Router<ContractRouter<TRPCMeta>, TRPCContext>>()
15+
16+
expectTypeOf<InferRouterInitialContext<typeof orpcRouter>>().toEqualTypeOf<{ a: string }>()
17+
18+
expectTypeOf(orpcRouter.ping).toEqualTypeOf<
19+
Procedure<TRPCContext, object, Schema<{ input: number }, unknown>, Schema<unknown, { output: string }>, object, TRPCMeta>
20+
>()
21+
22+
expectTypeOf(orpcRouter.throw).toEqualTypeOf<
23+
Procedure<TRPCContext, object, Schema<{ b: number, c: string }, unknown>, Schema<unknown, never>, object, TRPCMeta>
24+
>()
25+
26+
expectTypeOf(orpcRouter.subscribe).toEqualTypeOf<
27+
Procedure<TRPCContext, object, Schema<{ u: string }, unknown>, Schema<unknown, AsyncIterable<string, void, any>>, object, TRPCMeta>
28+
>()
29+
30+
expectTypeOf(orpcRouter.nested).toEqualTypeOf<
31+
{
32+
ping: Procedure<TRPCContext, object, Schema<{ a: string }, unknown>, Schema<unknown, number>, object, TRPCMeta>
33+
}
34+
>()
35+
36+
expectTypeOf(orpcRouter.lazy).toEqualTypeOf<
37+
{
38+
subscribe: Procedure<TRPCContext, object, Schema<void, unknown>, Schema<unknown, AsyncIterable<string, void, any>>, object, TRPCMeta>
39+
lazy: {
40+
throw: Procedure<TRPCContext, object, Schema<{ input: number }, unknown>, Schema<unknown, { output: string }>, object, TRPCMeta>
41+
}
42+
}
43+
>()
44+
})

0 commit comments

Comments
 (0)