SuperAPI AI 工具集

orpc-contract-first

前端与建站

查看项目

更新于 10-15 12:43

技能介绍

oRPC 契约优先开发

意图

将契约作为唯一事实来源,保存在 web/contract/* 中。

默认查询用法:当端点行为与契约 1:1 对应时,在调用点使用 useQuery(consoleQuery|marketplaceQuery.xxx.queryOptions(...))。

保持抽象最小化,并保留 TypeScript 推断。

最小结构

web/contract/ ├── base.ts ├── router.ts ├── marketplace.ts └── console/ ├── billing.ts └── ...其他领域 web/service/client.ts

核心工作流

  1. 在 web/contract/console/{domain}.ts 或 web/contract/marketplace.ts 中定义契约

使用 base.route({...}).output(type<...>()) 作为基线。

仅当请求包含 params/query/body 时才添加 .input(type<...>())。

对于没有输入的 GET,省略 .input(...)(不要使用 .input(type<unknown>()))。

  1. 在 web/contract/router.ts 中注册契约

直接从领域文件导入,并按 API 前缀嵌套。

  1. 通过 oRPC 查询工具从 UI 调用点消费。

import { useQuery } from '@tanstack/react-query' import { consoleQuery } from '@/service/client'

const invoiceQuery = useQuery(consoleQuery.billing.invoices.queryOptions({ staleTime: 5 * 60 * 1000, throwOnError: true, select: invoice => invoice.url, }))

查询使用决策规则

默认:调用点直接使用 *.queryOptions(...)。

如果 3 个以上调用点共享相同的额外选项(例如 retry: false),则提取一个小型 queryOptions 助手,而不是 use-* 透传 hook。

仅在需要编排时创建 web/service/use-{domain}.ts:

  • 组合多个查询/变更。
  • 共享领域级派生状态或失效助手。

const invoicesBaseQueryOptions = () => consoleQuery.billing.invoices.queryOptions({ retry: false })

const invoiceQuery = useQuery({ ...invoicesBaseQueryOptions(), throwOnError: true, })

变更使用决策规则

默认:从 consoleQuery / marketplaceQuery 调用变更助手,例如 useMutation(consoleQuery.billing.bindPartnerStack.mutationOptions(...))。

如果变更流程高度自定义,使用 oRPC 客户端作为 mutationFn(例如 consoleClient.xxx / marketplaceClient.xxx),而不是手写通用的非 oRPC 变更逻辑。

关键 API 指南(.key vs .queryKey vs .mutationKey)

.key(...):

用于部分匹配操作(推荐用于失效/重新获取/取消模式)。

示例:queryClient.invalidateQueries({ queryKey: consoleQuery.billing.key() })

.queryKey(...):

用于某个具体查询的完整 key(精确查询标识/直接缓存寻址)。

.mutationKey(...):

用于某个具体变更的完整 key。

典型用例:变更默认值注册、变更状态过滤(useIsMutating、queryClient.isMutating),或显式的 devtools 分组。

反模式

不要用 options?: Partial<UseQueryOptions> 包装 useQuery。

当 oRPC queryOptions 已存在且适合用例时,不要拆分本地 queryKey/queryFn。

不要为单个端点创建薄薄的 use-* 透传 hook。

原因:这些模式会降低推断能力(data 可能变为 unknown,尤其是在 throwOnError/select 附近),并增加不必要的间接层。

契约规则

输入结构:始终使用 { params, query?, body? } 格式。

无输入 GET:省略 .input(...);不要使用 .input(type<unknown>())。

路径参数:在路径中使用 {paramName},在 params 对象中匹配。

路由嵌套:按 API 前缀分组(例如 /billing/* -> billing: {})。

禁止 barrel 文件:直接从具体文件导入。

类型:从 @/types/ 导入,使用 type<T>() helper。

变更:优先使用 mutationOptions;主要为了默认值/过滤/devtools 才使用显式 mutationKey。

类型导出

export type ConsoleInputs = InferContractRouterInputs<typeof consoleRouterContract>