---
name: result-rpc
description: Build type-safe RPC between a server and a React app with result-rpc — one Result<T,E> and one wire-safe tagged-error union per procedure, error-owning React shells, a normalized entity cache, source-checked models, cursor pagination, and offline handling. Use when working in a project that depends on `result-rpc`, defining contracts/routers/procedures, wiring the browser client, writing hooks (useResultQuery/useResultMutation/useResultPaginatedQuery/useResultSubscription), boundaryShells, entity models, or migrating from tRPC.
---

# result-rpc

result-rpc is the RPC layer for a React app that outgrew tRPC's happy path:
production teams hitting offline, 5xx, session expiry, and observability. Every
procedure returns one **`Result<T, E>`** against a **declared, closed tagged-error
union**. The result-rpc wire reconstructs the Result behavior and real
`TaggedError` instances on the client; failures are values you narrow, not
exceptions you catch. React **shells** own failures by position, an entity cache patches by
identity, and models can be checked against **any upstream source type** without
pulling that source's runtime graph into the client.

This skill is a map. The documentation at **https://result-rpc.com** is
the single source of truth — every page is also served as raw Markdown by
appending `.md` to its URL, and the whole set is indexed at
`https://result-rpc.com/llms.txt` (full text at `/llms-full.txt`). For
any task below, **fetch the linked `.md` page** rather than guessing — the docs
carry the current API, and this file only routes you there.

## Before writing code

Choose the relevant path before editing:

- Handler calls `fetch`, a database, the filesystem, or a third-party SDK →
  read **Results and Errors** first. These two pages are mandatory for fallible
  external I/O.
- Output contains `Date`, `Map`, `Set`, `BigInt`, URL-like, or custom values →
  read **Wire**.
- Browser code is involved → read **Client Boundary** before creating imports.
- Query freshness, hydration, or refetching is involved → read **React hooks**.
- Operation is a mutation → read **Mutations** before adding invalidation or
  optimistic callbacks.
- App-wide auth, access, offline, redirect, modal, or escalation behavior is
  involved → read **Shells and Layers**.

Preflight before the first build:

- [ ] Browser imports a contract value, never the implemented router.
- [ ] Expected failures are declared and returned, not thrown.
- [ ] Provider/database failures are folded before entering a public contract.
- [ ] Procedure input, output, and error types are inferred instead of copied.
- [ ] Rich values use their actual wire codecs.
- [ ] At least one real wire round-trip is exercised.

## Read this first — the one rule that is a security bug if broken

result-rpc ships a **real client value** to the browser (not just a type, the
way tRPC ships `AppRouter`). So **what you import decides what bundles**:

> Build the browser client from a **`contract()`**, and define that contract in
> a module that **never imports handler or server code**. Importing the
> **router** (or a contract module that value-imports server code) ships your
> handlers, database driver, and any secret they close over to every visitor.
> Bundlers do **not** tree-shake this away.

If you touch client wiring, read
**https://result-rpc.com/concepts/client-boundary.md** before anything
else. `import type` from a server module is safe (erased); a value import is the
footgun.

## Task → page map

Fetch the `.md` version (append `.md`) of the page you need:

| When you're…                                  | Read                                                 |
| --------------------------------------------- | ---------------------------------------------------- |
| Getting the mental model                      | `/start/introduction` · `/start/quickstart`          |
| Installing / project layout                   | `/start/installation`                                |
| **Wiring the browser client (do this right)** | **`/concepts/client-boundary`** · `/concepts/client` |
| Fallible fetch/db/SDK handler (**mandatory**) | `/concepts/results` · `/concepts/errors`             |
| Declaring errors + visibility                 | `/concepts/errors`                                   |
| Working with Result values (gen, all, match)  | `/concepts/results`                                  |
| Defining procedures/routers/contracts         | `/concepts/contract`                                 |
| Passing request context                       | `/concepts/context`                                  |
| Wire codecs (input/output shapes)             | `/concepts/wire`                                     |
| React hooks + provider                        | `/concepts/react`                                    |
| Mutations, optimistic updates, `.affects()`   | `/concepts/mutations`                                |
| Entity models + normalized cache              | `/concepts/entities`                                 |
| Cursor pagination                             | `/concepts/pagination`                               |
| Subscriptions                                 | `/concepts/subscriptions`                            |
| Error-owning shells (boundaryShells)          | `/concepts/shells`                                   |
| Layered context/auth                          | `/concepts/layers`                                   |
| Stale clients across deploys                  | `/concepts/deploys`                                  |
| Forms + validateStandard                      | `/guides/forms`                                      |
| Checking models against source types          | `/concepts/model-sources`                            |
| Folding database constraint failures          | `/guides/database-errors`                            |
| TanStack Router integration                   | `/guides/routing`                                    |
| Testing (parity client, counter-pins)         | `/guides/testing`                                    |
| Sentry/observability                          | `/guides/observability`                              |
| Coming from tRPC                              | `/guides/migrating-from-trpc`                        |
| Worked examples (01–11)                       | `/reference/examples`                                |
| Known sharp edges                             | `/reference/sharp-edges`                             |

## Non-negotiables when writing result-rpc code

- **Errors are declared and closed.** Each procedure lists its error union with
  `.errors({...})`; handlers `return err(...)`, they don't throw. Private
  (`visibility: "private"`) errors are server-only composition currency and
  are rejected from procedure, middleware, and layer `.errors()` maps.
- **Errors are instances, wire forms are structural.** Use a declared definition
  such as `err(authErrors.unauthorized())`, never fabricate `{ _tag, data }`.
  Only the result-rpc wire reifies Results and TaggedErrors; unwrap before passing
  them through JSON, framework RPC, or component-prop serialization.
- **The contract is the error registry.** One tag → one definition across the
  app; shells claim by tag.
- **Contracts are handler-free.** See the client-boundary rule above.
- **Mutations declare their blast radius** in the contract (`.affects()`,
  `.writes()`, or by returning an entity) — not with ad-hoc `onSettled` at call
  sites.
- **Paginated queries** use `.paginate({ cursor })` on the server and
  `useResultPaginatedQuery` on the client; one cache entry per list.

When a page's guidance conflicts with this file, the page wins — it is
maintained; this map is not.
