How to install orpc-contract-first
npx skills add null --skill orpc-contract-firstFull instructions (SKILL.md)
Source of truth, from langgenius/dify.
name: orpc-contract-first description: 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.
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>
Related skills
More from langgenius/dify and the wider catalog.
frontend-code-review
Review Dify frontend code for correctness, accessibility, component design, dify-ui usage, data/query boundaries, performance, and tests. Trigger for `.tsx`, `.ts`, `.js`, UI, React, Next.js, pending-change, or focused frontend review requests.
component-refactoring
Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component --json` shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction, or when `pnpm analyze-component` warns to refactor before testing; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.
frontend-testing
Generate Vitest + React Testing Library tests for Dify frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Vitest, RTL, unit tests, integration tests, or write/review test requests.
backend-code-review
Review backend code for quality, security, maintainability, and best practices based on established checklist rules. Use when the user requests a review, analysis, or improvement of backend files (e.g., `.py`) under the `api/` directory. Do NOT use for frontend files (e.g., `.tsx`, `.ts`, `.js`). Supports pending-change review, code snippets review, and file-focused review.
skill-creator
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
web-design-guidelines
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".