home / skills / langgenius / dify / orpc-contract-first

orpc-contract-first skill

/.agents/skills/orpc-contract-first

This skill guides implementing oRPC contract-first API patterns in the Dify frontend, from contract creation to router registration and typed service hooks.

npx playbooks add skill langgenius/dify --skill orpc-contract-first

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
1.7 KB
---
name: orpc-contract-first
description: Guide for implementing oRPC contract-first API patterns in Dify frontend. Triggers when creating new API contracts, adding service endpoints, integrating TanStack Query with typed contracts, or migrating legacy service calls to oRPC. Use for all API layer work in web/contract and web/service directories.
---

# oRPC Contract-First Development

## Project Structure

```
web/contract/
├── base.ts           # Base contract (inputStructure: 'detailed')
├── router.ts         # Router composition & type exports
├── marketplace.ts    # Marketplace contracts
└── console/          # Console contracts by domain
    ├── system.ts
    └── billing.ts
```

## Workflow

1. **Create contract** in `web/contract/console/{domain}.ts`
   - Import `base` from `../base` and `type` from `@orpc/contract`
   - Define route with `path`, `method`, `input`, `output`

2. **Register in router** at `web/contract/router.ts`
   - Import directly from domain file (no barrel files)
   - Nest by API prefix: `billing: { invoices, bindPartnerStack }`

3. **Create hooks** in `web/service/use-{domain}.ts`
   - Use `consoleQuery.{group}.{contract}.queryKey()` for query keys
   - Use `consoleClient.{group}.{contract}()` for API calls

## Key Rules

- **Input structure**: Always use `{ params, query?, body? }` format
- **Path params**: Use `{paramName}` in path, match in `params` object
- **Router nesting**: Group by API prefix (e.g., `/billing/*` → `billing: {}`)
- **No barrel files**: Import directly from specific files
- **Types**: Import from `@/types/`, use `type<T>()` helper

## Type Export

```typescript
export type ConsoleInputs = InferContractRouterInputs<typeof consoleRouterContract>
```

Overview

This skill guides contract-first oRPC API development for the Dify frontend, focused on the web/contract and web/service layers. It codifies where to add new contracts, how to register them in the router, and how to create typed service hooks. The goal is consistent, typed API surface and smooth migration of legacy calls to oRPC patterns.

How this skill works

The skill inspects project layout and enforces adding contracts under web/contract/console/{domain}.ts using the base contract and @orpc/contract types. It ensures routes declare path, method, and structured inputs (params, query?, body?) and that each domain contract is registered in web/contract/router.ts without barrel imports. For client usage it expects hooks in web/service/use-{domain}.ts that rely on consoleQuery.{group}.{contract}.queryKey() and consoleClient.{group}.{contract}() for typed calls.

When to use it

  • When creating a new API contract for any console domain.
  • When adding or grouping service endpoints under an API prefix (e.g., /billing).
  • When integrating TanStack Query with typed oRPC contracts and query keys.
  • When migrating legacy service calls to typed oRPC client methods.
  • For all API layer work in web/contract and web/service directories.

Best practices

  • Always use input structure { params, query?, body? } to keep shapes consistent.
  • Declare path params as {paramName} and mirror them in params for type safety.
  • Register domain contracts directly in router.ts; avoid barrel file imports.
  • Group router entries by API prefix (billing: { invoices, ... }) to reflect URL structure.
  • Import runtime types from @/types and wrap schemas with type<T>() helpers.

Example use cases

  • Add a new marketplace endpoint: create web/contract/console/marketplace.ts and register it in router.ts.
  • Migrate a legacy fetch call to consoleClient.billing.getInvoice() and use consoleQuery.billing.getInvoice.queryKey() for caching.
  • Create a typed hook use-billing.ts that calls consoleClient.billing.bindPartnerStack() and invalidates keys after mutations.
  • Define a route with path '/invoices/{invoiceId}' and ensure invoiceId appears in params.

FAQ

Can I use barrel files to import contracts?

No. Import contracts directly from their domain files to keep tree-shaking predictable and avoid circular dependency issues.

How should path parameters be declared?

Use {paramName} in the path and include the same key in the params object of the input structure for proper typing.