feat: 使用 zod 合约实现类型安全的 todo 服务

- 添加 @orpc/contract 依赖以支持合约定义和类型安全。
- 添加 @orpc/contract 依赖以支持契约定义和类型安全。
- 更新客户端类型定义并移除冗余的 APIRouterClient 引入,确保客户端实例类型与路由定义一致。
- 添加基于 zod 的类型安全接口定义,包含待办事项的增删改查操作契约及对应的输入输出验证规则。
- 使用合约定义重构 Todo 处理函数,统一接口输入输出验证并移除冗余的 Zod 模式定义。
- 更新导出模块,将路由功能改为导出合约定义。
- 移除未使用的导入和类型定义,精简路由配置文件。
This commit is contained in:
2026-01-18 03:20:05 +08:00
parent 26c50acdf6
commit f0ae8196cd
7 changed files with 63 additions and 51 deletions

View File

@@ -6,6 +6,7 @@
"name": "fullstack-starter", "name": "fullstack-starter",
"dependencies": { "dependencies": {
"@orpc/client": "^1.13.4", "@orpc/client": "^1.13.4",
"@orpc/contract": "^1.13.4",
"@orpc/server": "^1.13.4", "@orpc/server": "^1.13.4",
"@orpc/tanstack-query": "^1.13.4", "@orpc/tanstack-query": "^1.13.4",
"@t3-oss/env-core": "^0.13.10", "@t3-oss/env-core": "^0.13.10",

View File

@@ -15,6 +15,7 @@
}, },
"dependencies": { "dependencies": {
"@orpc/client": "^1.13.4", "@orpc/client": "^1.13.4",
"@orpc/contract": "^1.13.4",
"@orpc/server": "^1.13.4", "@orpc/server": "^1.13.4",
"@orpc/tanstack-query": "^1.13.4", "@orpc/tanstack-query": "^1.13.4",
"@t3-oss/env-core": "^0.13.10", "@t3-oss/env-core": "^0.13.10",

View File

@@ -1,10 +1,10 @@
import { createORPCClient } from '@orpc/client' import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch' import { RPCLink } from '@orpc/client/fetch'
import { createRouterClient } from '@orpc/server' import { createRouterClient, type RouterClient } from '@orpc/server'
import { createTanstackQueryUtils } from '@orpc/tanstack-query' import { createTanstackQueryUtils } from '@orpc/tanstack-query'
import { createIsomorphicFn } from '@tanstack/react-start' import { createIsomorphicFn } from '@tanstack/react-start'
import { getRequestHeaders } from '@tanstack/react-start/server' import { getRequestHeaders } from '@tanstack/react-start/server'
import { type APIRouterClient, router } from '@/orpc' import { router } from './router'
const getORPCClient = createIsomorphicFn() const getORPCClient = createIsomorphicFn()
.server(() => .server(() =>
@@ -14,13 +14,13 @@ const getORPCClient = createIsomorphicFn()
}), }),
}), }),
) )
.client(() => { .client((): RouterClient<typeof router> => {
const link = new RPCLink({ const link = new RPCLink({
url: `${window.location.origin}/api/rpc`, url: `${window.location.origin}/api/rpc`,
}) })
return createORPCClient<APIRouterClient>(link) return createORPCClient(link)
}) })
const client: APIRouterClient = getORPCClient() const client: RouterClient<typeof router> = getORPCClient()
export const orpc = createTanstackQueryUtils(client) export const orpc = createTanstackQueryUtils(client)

49
src/orpc/contract.ts Normal file
View File

@@ -0,0 +1,49 @@
import { oc } from '@orpc/contract'
import { z } from 'zod'
export const todoSchema = z.object({
id: z.uuid(),
title: z.string(),
completed: z.boolean(),
createdAt: z.date(),
updatedAt: z.date(),
})
export const todoInsertSchema = z.object({
title: z.string().min(1),
completed: z.boolean().optional(),
})
export const todoUpdateSchema = z.object({
title: z.string().min(1).optional(),
completed: z.boolean().optional(),
})
export const todoContract = {
list: oc.input(z.void()).output(z.array(todoSchema)),
create: oc.input(todoInsertSchema).output(todoSchema),
update: oc
.input(
z.object({
id: z.uuid(),
data: todoUpdateSchema,
}),
)
.output(todoSchema),
remove: oc
.input(
z.object({
id: z.uuid(),
}),
)
.output(z.void()),
}
export const contract = {
todo: todoContract,
}
export type Contract = typeof contract

View File

@@ -1,31 +1,10 @@
import { ORPCError, os } from '@orpc/server' import { implement, ORPCError } from '@orpc/server'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
import {
createInsertSchema,
createSelectSchema,
createUpdateSchema,
} from 'drizzle-zod'
import { z } from 'zod'
import { todoTable } from '@/db/schema' import { todoTable } from '@/db/schema'
import { todoContract } from '@/orpc/contract'
import { dbProvider } from '@/orpc/middlewares' import { dbProvider } from '@/orpc/middlewares'
const selectSchema = createSelectSchema(todoTable) export const list = implement(todoContract.list)
const insertSchema = createInsertSchema(todoTable).omit({
id: true,
createdAt: true,
updatedAt: true,
})
const updateSchema = createUpdateSchema(todoTable).omit({
id: true,
createdAt: true,
updatedAt: true,
})
export const list = os
.input(z.void())
.output(z.array(selectSchema))
.use(dbProvider) .use(dbProvider)
.handler(async ({ context }) => { .handler(async ({ context }) => {
const todos = await context.db.query.todoTable.findMany({ const todos = await context.db.query.todoTable.findMany({
@@ -34,9 +13,7 @@ export const list = os
return todos return todos
}) })
export const create = os export const create = implement(todoContract.create)
.input(insertSchema)
.output(selectSchema)
.use(dbProvider) .use(dbProvider)
.handler(async ({ context, input }) => { .handler(async ({ context, input }) => {
const [newTodo] = await context.db const [newTodo] = await context.db
@@ -51,14 +28,7 @@ export const create = os
return newTodo return newTodo
}) })
export const update = os export const update = implement(todoContract.update)
.input(
z.object({
id: z.uuid(),
data: updateSchema,
}),
)
.output(selectSchema)
.use(dbProvider) .use(dbProvider)
.handler(async ({ context, input }) => { .handler(async ({ context, input }) => {
const [updatedTodo] = await context.db const [updatedTodo] = await context.db
@@ -74,13 +44,7 @@ export const update = os
return updatedTodo return updatedTodo
}) })
export const remove = os export const remove = implement(todoContract.remove)
.input(
z.object({
id: z.uuid(),
}),
)
.output(z.void())
.use(dbProvider) .use(dbProvider)
.handler(async ({ context, input }) => { .handler(async ({ context, input }) => {
await context.db.delete(todoTable).where(eq(todoTable.id, input.id)) await context.db.delete(todoTable).where(eq(todoTable.id, input.id))

View File

@@ -1,2 +1,2 @@
export * from './client' export * from './client'
export * from './router' export * from './contract'

View File

@@ -1,8 +1,5 @@
import type { RouterClient } from '@orpc/server'
import * as todo from './handlers/todo' import * as todo from './handlers/todo'
export const router = { export const router = {
todo, todo,
} }
export type APIRouterClient = RouterClient<typeof router>