Skip to content

Commit 718ad4c

Browse files
committed
fix: adding oRPC, upgrading tRPC
1 parent 3e597f2 commit 718ad4c

File tree

15 files changed

+417
-38
lines changed

15 files changed

+417
-38
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { createRouterClient } from '@orpc/server'
2+
import type { RouterClient } from '@orpc/server'
3+
import { createORPCClient } from '@orpc/client'
4+
import { RPCLink } from '@orpc/client/fetch'
5+
import { createTanstackQueryUtils } from '@orpc/tanstack-query'
6+
import { getHeaders } from '@tanstack/react-start/server'
7+
import { createIsomorphicFn } from '@tanstack/react-start'
8+
9+
import router from '@/orpc/router'
10+
11+
const getORPCClient = createIsomorphicFn()
12+
.server(() =>
13+
createRouterClient(router, {
14+
context: async () => ({
15+
headers: getHeaders(),
16+
}),
17+
}),
18+
)
19+
.client((): RouterClient<typeof router> => {
20+
const link = new RPCLink({
21+
url: `${window.location.origin}/api/rpc`,
22+
})
23+
return createORPCClient(link)
24+
})
25+
26+
export const client: RouterClient<typeof router> = getORPCClient()
27+
28+
export const orpc = createTanstackQueryUtils(client)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { addTodo, listTodos } from './todos'
2+
3+
export default {
4+
listTodos,
5+
addTodo,
6+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { os } from '@orpc/server'
2+
import * as z from 'zod'
3+
4+
const todos = [
5+
{ id: 1, name: 'Get groceries' },
6+
{ id: 2, name: 'Buy a new phone' },
7+
{ id: 3, name: 'Finish the project' },
8+
]
9+
10+
export const listTodos = os.input(z.object({})).handler(async () => {
11+
return todos
12+
})
13+
14+
export const addTodo = os
15+
.input(z.object({ name: z.string() }))
16+
.handler(async ({ input }) => {
17+
const newTodo = { id: todos.length + 1, name: input.name }
18+
todos.push(newTodo)
19+
return newTodo
20+
})
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { z } from 'zod'
2+
3+
export const TodoSchema = z.object({
4+
id: z.number().int().min(1),
5+
name: z.string(),
6+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { File } from "node:buffer";
2+
3+
/**
4+
* This file aims to polyfill missing APIs in Node.js 18 that oRPC depends on.
5+
*
6+
* Since Stackblitz runs on Node.js 18, these polyfills ensure oRPC works in that environment.
7+
* If you're running oRPC locally, please use Node.js 20 or later for full compatibility.
8+
*/
9+
10+
/**
11+
* Note: Stackblitz provides an emulated Node.js environment with inherent limitations.
12+
* If you encounter issues, please test on a local setup with Node.js 20 or later before reporting them.
13+
*/
14+
15+
/**
16+
* The `oz.file()` schema depends on the `File` API.
17+
* If you're not using `oz.file()`, you can safely remove this polyfill.
18+
*/
19+
if (typeof globalThis.File === "undefined") {
20+
globalThis.File = File as any;
21+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import '@/polyfill'
2+
3+
import { OpenAPIHandler } from '@orpc/openapi/fetch'
4+
import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4'
5+
import { experimental_SmartCoercionPlugin as SmartCoercionPlugin } from '@orpc/json-schema'
6+
import { createServerFileRoute } from '@tanstack/react-start/server'
7+
import { onError } from '@orpc/server'
8+
import { OpenAPIReferencePlugin } from '@orpc/openapi/plugins'
9+
10+
import { TodoSchema } from '@/orpc/schema'
11+
import router from '@/orpc/router'
12+
13+
const handler = new OpenAPIHandler(router, {
14+
interceptors: [
15+
onError((error) => {
16+
console.error(error)
17+
}),
18+
],
19+
plugins: [
20+
new SmartCoercionPlugin({
21+
schemaConverters: [new ZodToJsonSchemaConverter()],
22+
}),
23+
new OpenAPIReferencePlugin({
24+
schemaConverters: [new ZodToJsonSchemaConverter()],
25+
specGenerateOptions: {
26+
info: {
27+
title: 'TanStack ORPC Playground',
28+
version: '1.0.0',
29+
},
30+
commonSchemas: {
31+
Todo: { schema: TodoSchema },
32+
UndefinedError: { error: 'UndefinedError' },
33+
},
34+
security: [{ bearerAuth: [] }],
35+
components: {
36+
securitySchemes: {
37+
bearerAuth: {
38+
type: 'http',
39+
scheme: 'bearer',
40+
},
41+
},
42+
},
43+
},
44+
docsConfig: {
45+
authentication: {
46+
securitySchemes: {
47+
bearerAuth: {
48+
token: 'default-token',
49+
},
50+
},
51+
},
52+
},
53+
}),
54+
],
55+
})
56+
57+
async function handle({ request }: { request: Request }) {
58+
const { response } = await handler.handle(request, {
59+
prefix: '/api',
60+
context: {},
61+
})
62+
63+
return response ?? new Response('Not Found', { status: 404 })
64+
}
65+
66+
export const ServerRoute = createServerFileRoute('/api/$').methods({
67+
HEAD: handle,
68+
GET: handle,
69+
POST: handle,
70+
PUT: handle,
71+
PATCH: handle,
72+
DELETE: handle,
73+
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import '@/polyfill'
2+
3+
import { RPCHandler } from '@orpc/server/fetch'
4+
import { createServerFileRoute } from '@tanstack/react-start/server'
5+
import router from '@/orpc/router'
6+
7+
const handler = new RPCHandler(router)
8+
9+
async function handle({ request }: { request: Request }) {
10+
const { response } = await handler.handle(request, {
11+
prefix: '/api/rpc',
12+
context: {},
13+
})
14+
15+
return response ?? new Response('Not Found', { status: 404 })
16+
}
17+
18+
export const ServerRoute = createServerFileRoute('/api/rpc/$').methods({
19+
HEAD: handle,
20+
GET: handle,
21+
POST: handle,
22+
PUT: handle,
23+
PATCH: handle,
24+
DELETE: handle,
25+
})
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { useCallback, useState } from 'react'
2+
import { createFileRoute } from '@tanstack/react-router'
3+
import { useMutation, useQuery } from '@tanstack/react-query'
4+
5+
import { orpc } from '@/orpc/client'
6+
7+
export const Route = createFileRoute('/demo/orpc-todo')({
8+
component: ORPCTodos,
9+
loader: async ({ context }) => {
10+
await context.queryClient.prefetchQuery(
11+
orpc.listTodos.queryOptions({
12+
input: {},
13+
}),
14+
)
15+
},
16+
})
17+
18+
function ORPCTodos() {
19+
const { data, refetch } = useQuery(
20+
orpc.listTodos.queryOptions({
21+
input: {},
22+
}),
23+
)
24+
25+
const [todo, setTodo] = useState('')
26+
const { mutate: addTodo } = useMutation({
27+
mutationFn: orpc.addTodo.call,
28+
onSuccess: () => {
29+
refetch()
30+
setTodo('')
31+
},
32+
})
33+
34+
const submitTodo = useCallback(() => {
35+
addTodo({ name: todo })
36+
}, [addTodo, todo])
37+
38+
return (
39+
<div
40+
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
41+
style={{
42+
backgroundImage:
43+
'radial-gradient(50% 50% at 50% 50%, #D2149D 0%, #8E1066 50%, #2D0A1F 100%)',
44+
}}
45+
>
46+
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
47+
<h1 className="text-2xl mb-4">Todos list</h1>
48+
<ul className="mb-4 space-y-2">
49+
{data?.map((todo) => (
50+
<li
51+
key={todo.id}
52+
className="bg-white/10 border border-white/20 rounded-lg p-3 backdrop-blur-sm shadow-md"
53+
>
54+
<span className="text-lg text-white">{todo.name}</span>
55+
</li>
56+
))}
57+
</ul>
58+
<div className="flex flex-col gap-2">
59+
<input
60+
type="text"
61+
value={todo}
62+
onChange={(e) => setTodo(e.target.value)}
63+
onKeyDown={(e) => {
64+
if (e.key === 'Enter') {
65+
submitTodo()
66+
}
67+
}}
68+
placeholder="Enter a new todo..."
69+
className="w-full px-4 py-3 rounded-lg border border-white/20 bg-white/10 backdrop-blur-sm text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent"
70+
/>
71+
<button
72+
disabled={todo.trim().length === 0}
73+
onClick={submitTodo}
74+
className="bg-blue-500 hover:bg-blue-600 disabled:bg-blue-500/50 disabled:cursor-not-allowed text-white font-bold py-3 px-4 rounded-lg transition-colors"
75+
>
76+
Add todo
77+
</button>
78+
</div>
79+
</div>
80+
</div>
81+
)
82+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "oRPC",
3+
"description": "Integrate oRPC into your application.",
4+
"phase": "add-on",
5+
"modes": ["file-router"],
6+
"link": "https://orpc.unnoq.com/",
7+
"dependsOn": ["tanstack-query", "start"],
8+
"type": "add-on",
9+
"routes": [
10+
{
11+
"url": "/demo/orpc-todo",
12+
"name": "oRPC Todo",
13+
"path": "src/routes/demo.orpc-todo.tsx",
14+
"jsName": "ORPCTodos"
15+
}
16+
]
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"dependencies": {
3+
"@orpc/client": "^1.7.5",
4+
"@orpc/json-schema": "^1.7.5",
5+
"@orpc/openapi": "^1.7.5",
6+
"@orpc/server": "^1.7.5",
7+
"@orpc/tanstack-query": "^1.7.5",
8+
"@orpc/zod": "^1.7.5",
9+
"zod": "^4.0.10"
10+
}
11+
}

0 commit comments

Comments
 (0)