Orpc contract first
Skill ulpi-io/plugin-marketplace/plugins/langgenius/skills/orpc-contract-first
A curated collection of 7,800+ agent skills for Claude Desktop, sourced from skills.sh
npx -y skills add ulpi-io/plugin-marketplace --skill orpc-contract-firstAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Guide for implementing oRPC contract-first API patterns in Dify frontend. Trigger when creating or updating contracts in web/contract, wiring router composition, integrating TanStack Query with typed contracts, migrating legacy service calls to oRPC, or deciding whether to call queryOptions directly vs extracting a helper or use-* hook in web/service.
SKILL.md
4.3 KB, as published. Nobody here has run it
oRPC Contract-First Development
Intent
- Keep contract as single source of truth in
web/contract/*. - Default query usage: call-site
useQuery(consoleQuery|marketplaceQuery.xxx.queryOptions(...))when endpoint behavior maps 1:1 to the contract. - Keep abstractions minimal and preserve TypeScript inference.
Minimal Structure
web/contract/
├── base.ts
├── router.ts
├── marketplace.ts
└── console/
├── billing.ts
└── ...other domains
web/service/client.ts
Core Workflow
- Define contract in
web/contract/console/{domain}.tsorweb/contract/marketplace.ts- Use
base.route({...}).output(type<...>())as baseline. - Add
.input(type<...>())only when request hasparams/query/body. - For
GETwithout input, omit.input(...)(do not use.input(type<unknown>())).
- Use
- Register contract in
web/contract/router.ts- Import directly from domain files and nest by API prefix.
- Consume from UI call sites via oRPC query utils.
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,
}))
Query Usage Decision Rule
- Default: call site directly uses
*.queryOptions(...). - If 3+ call sites share the same extra options (for example
retry: false), extract a small queryOptions helper, not ause-*passthrough hook. - Create
web/service/use-{domain}.tsonly for orchestration:- Combine multiple queries/mutations.
- Share domain-level derived state or invalidation helpers.
const invoicesBaseQueryOptions = () =>
consoleQuery.billing.invoices.queryOptions({ retry: false })
const invoiceQuery = useQuery({
...invoicesBaseQueryOptions(),
throwOnError: true,
})
Mutation Usage Decision Rule
- Default: call mutation helpers from
consoleQuery/marketplaceQuery, for exampleuseMutation(consoleQuery.billing.bindPartnerStack.mutationOptions(...)). - If mutation flow is heavily custom, use oRPC clients as
mutationFn(for exampleconsoleClient.xxx/marketplaceClient.xxx), instead of generic handwritten non-oRPC mutation logic.
Key API Guide (.key vs .queryKey vs .mutationKey)
.key(...):- Use for partial matching operations (recommended for invalidation/refetch/cancel patterns).
- Example:
queryClient.invalidateQueries({ queryKey: consoleQuery.billing.key() })
.queryKey(...):- Use for a specific query's full key (exact query identity / direct cache addressing).
.mutationKey(...):- Use for a specific mutation's full key.
- Typical use cases: mutation defaults registration, mutation-status filtering (
useIsMutating,queryClient.isMutating), or explicit devtools grouping.
Anti-Patterns
- Do not wrap
useQuerywithoptions?: Partial<UseQueryOptions>. - Do not split local
queryKey/queryFnwhen oRPCqueryOptionsalready exists and fits the use case. - Do not create thin
use-*passthrough hooks for a single endpoint. - Reason: these patterns can degrade inference (
datamay becomeunknown, especially aroundthrowOnError/select) and add unnecessary indirection.
Contract Rules
- Input structure: Always use
{ params, query?, body? }format - No-input GET: Omit
.input(...); do not use.input(type<unknown>()) - Path params: Use
{paramName}in path, match inparamsobject - Router nesting: Group by API prefix (e.g.,
/billing/*->billing: {}) - No barrel files: Import directly from specific files
- Types: Import from
@/types/, usetype<T>()helper - Mutations: Prefer
mutationOptions; use explicitmutationKeymainly for defaults/filtering/devtools
Type Export
export type ConsoleInputs = InferContractRouterInputs<typeof consoleRouterContract>