Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/content/docs/client/client-side.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ const output = await client.someProcedure(input, {
Interceptors let you wrap client calls. They are similar to interceptors in links, but are more typesafe because the exact input, output, and error types of each client are known. You can provide per-client interceptors with `scoped`.

```ts
import { isInferableError, safe } from '@orpc/client'
import { isDefinedError, safe } from '@orpc/client'

const client: RouterClient<typeof router, ClientContext> = createORPCClient(link, {
interceptors: [
async ({ context, path, next }) => {
const [error, data] = await safe(next())

if (error) {
if (isInferableError(error)) {
if (isDefinedError(error)) {
// handle typesafe errors
}

Expand All @@ -116,7 +116,7 @@ const client: RouterClient<typeof router, ClientContext> = createORPCClient(link
```

:::info
You can use [`safe` and `isInferableError`](/docs/client/error-handling#using-safe-and-isinferableerror) together for typesafe error handling in interceptors.
You can use [`safe` and `isDefinedError`](/docs/client/error-handling#using-safe-and-isdefinederror) together for typesafe error handling in interceptors.
:::

## Merging Clients
Expand Down
20 changes: 10 additions & 10 deletions apps/content/docs/client/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ catch (error) {
}
```

## Using `safe` and `isInferableError`
## Using `safe` and `isDefinedError`

When working with [Typesafe Errors](/docs/error-handling#typesafe-errors), use `safe` to preserve error type inference. It behaves like `try/catch`, but returns the typesafe result instead of throwing.

```ts twoslash
import { call, os } from '@orpc/server'
import * as z from 'zod'
// ---cut---
import { isInferableError, safe } from '@orpc/client'
import { isDefinedError, safe } from '@orpc/client'

const exampleProcedure = os
.input(z.object({ id: z.string() }))
Expand All @@ -39,15 +39,15 @@ const exampleProcedure = os
throw errors.RATE_LIMIT_EXCEEDED({ data: { retryAfter: 1000 } })
})

// or { error, data, inferableError }
const [error, data, inferableError] = await safe(
// or { error, data, definedError }
const [error, data, definedError] = await safe(
call(exampleProcedure, { id: '123' })
)

if (isInferableError(error)) { // or inferableError
// handle inferable error
if (isDefinedError(error)) { // or definedError
// handle defined error

// or inferableError.data.retryAfter
// or definedError.data.retryAfter
console.log(error.data.retryAfter)
}
else if (error) {
Expand All @@ -62,10 +62,10 @@ else {
:::info
`safe` supports both tuple and object forms:

- `[error, data, inferableError]`
- `{ error, data, inferableError }`
- `[error, data, definedError]`
- `{ error, data, definedError }`

`inferableError` is the same value as `error` when `isInferableError(error)` returns `true`; otherwise it is `null`.
`definedError` is the same value as `error` when `isDefinedError(error)` returns `true`; otherwise it is `null`.
:::

## Safe Client
Expand Down
28 changes: 1 addition & 27 deletions apps/content/docs/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const example = os

## Typesafe Errors

For end-to-end type safety, define your errors with `.errors` or [return `ORPCError`](#returning-an-orpcerror). This lets the client infer each error's shape and handle it safely. You can use any [Standard Schema](https://standardschema.dev/schema#what-schema-libraries-implement-the-spec) library to validate error data.
For end-to-end type safety, define your errors with `.errors`. This lets the client infer each error's shape and handle it safely. You can use any [Standard Schema](https://standardschema.dev/schema#what-schema-libraries-implement-the-spec) library to validate error data.

:::danger
`message` and `data` are sent to the client. Do not include sensitive information in either field.
Expand Down Expand Up @@ -105,32 +105,6 @@ const exampleProcedure = os
})
```

### Returning an `ORPCError`

As an alternative to `.errors`, you can return an `ORPCError` directly from your handler or middleware to achieve end-to-end type safety.

:::warning
When [implementing a contract](/docs/contract/implementation), returning an `ORPCError` is equivalent to throwing one.
:::

```ts
const exampleProcedure = os
.handler(async ({ errors }) => {
if (reachRateLimit) {
return new ORPCError('RATE_LIMITED', {
message: 'You are being rate limited',
data: { retryAfter: 60 }
})
}

return 'Success'
})
```

:::danger
`message` and `data` are sent to the client. Do not include sensitive information in either field.
:::

## Error Factory

An error factory lets you define an error once and reuse it anywhere, keeping error handling consistent across your project.
Expand Down
34 changes: 17 additions & 17 deletions apps/content/docs/integrations/effect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -153,26 +153,26 @@ For app level error handling, we recommend [middleware](/docs/middleware) or int

### Typesafe Errors

When you `yield* Effect.fail(new ORPCError(...))` or `return new ORPCError(...)`, oRPC treats it as a [returned ORPCError](/docs/error-handling#returning-an-orpcerror). On the client, you can handle these errors in a typesafe way:
An `ORPCError` that fails the effect, such as `yield* Effect.fail(new ORPCError(...))`, is thrown from the handler exactly like a thrown `ORPCError` in a regular handler. Define your errors with `.errors` and fail with `errors.X(...)` to make them [typesafe](/docs/error-handling#typesafe-errors) on the client:

```ts
const procedure = os.handler(handlerGen(function* ({ errors }) {
if (resourceNotFound) {
yield* Effect.fail(new ORPCError('NOT_FOUND', {
message: 'The resource you are looking for does not exist',
}))
// -- or -
return new ORPCError('NOT_FOUND', {
const procedure = os
.errors({
NOT_FOUND: {
message: 'The resource you are looking for does not exist',
})
}
},
})
.handler(handlerGen(function* ({ errors }) {
if (resourceNotFound) {
yield* Effect.fail(errors.NOT_FOUND())
}

return 'Success'
}))
return 'Success'
}))

const [error, result] = await call(procedure)
const [error, result] = await safe(call(procedure))

if (isInferableError(error)) {
if (isDefinedError(error)) {
// typesafe error handling
}
```
Expand Down Expand Up @@ -244,14 +244,14 @@ const program = Effect.gen(function* () {
)
```

You can also combine `Effect.catchIf` with [isInferableError](/docs/client/error-handling#using-safe-and-isinferableerror) to recover from every inferable error in a typesafe way:
You can also combine `Effect.catchIf` with [isDefinedError](/docs/client/error-handling#using-safe-and-isdefinederror) to recover from every defined error in a typesafe way:

```ts
import { isInferableError } from '@orpc/client'
import { isDefinedError } from '@orpc/client'
import { Effect } from 'effect'

const recovered = effectClient.planet.find({ id: 1 }).pipe(
Effect.catchIf(isInferableError, (error) => {
Effect.catchIf(isDefinedError, (error) => {
// error is fully typed here
return Effect.succeed(null)
}),
Expand Down
10 changes: 5 additions & 5 deletions apps/content/docs/integrations/next.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Special Next.js errors such as [redirect](https://nextjs.org/docs/app/api-refere

### Typesafe Errors

[Typesafe errors](/docs/error-handling#typesafe-errors) are supported as well. Because errors are serialized before they reach the client, use the `inferable` field to distinguish errors.
[Typesafe errors](/docs/error-handling#typesafe-errors) are supported as well. Because errors are serialized before they reach the client, use the `defined` field to distinguish errors.

<CodeGroup>

Expand All @@ -73,7 +73,7 @@ export default function Page() {
const [error, message] = await serverFunction()

if (error) {
if (error.inferable) {
if (error.defined) {
// handle typesafe error
}
else {
Expand Down Expand Up @@ -165,7 +165,7 @@ This integration also includes React hooks for server functions. `useServerFunct
```tsx useServerFunction
'use client'

import { isInferableError } from '@orpc/client'
import { isDefinedError } from '@orpc/client'
import {
getIssueMessage,
onErrorDeferred,
Expand All @@ -177,7 +177,7 @@ export function MyComponent() {
const { execute, data, error, status } = useServerFunction(serverFunction, {
interceptors: [
onErrorDeferred((error) => {
if (isInferableError(error)) {
if (isDefinedError(error)) {
console.error(error.data)
// ^ Typed error data
}
Expand Down Expand Up @@ -244,7 +244,7 @@ Besides hooks, this integration also re-exports [form-data helpers](/docs/helper
:::

:::info
You can use [`safe` and `isInferableError`](/docs/client/error-handling#using-safe-and-isinferableerror) together for typesafe error handling in interceptors.
You can use [`safe` and `isDefinedError`](/docs/client/error-handling#using-safe-and-isdefinederror) together for typesafe error handling in interceptors.
:::

## Server Form Functions
Expand Down
14 changes: 7 additions & 7 deletions apps/content/docs/integrations/pinia-colada.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ When you configure `queryKey`, it also affects `.queryOptions` because it is use
Interceptors let you wrap `query` and `mutation` calls. Unlike [default options](#default-options), which can be overridden by per-call options, interceptors always run for every query and mutation.

```ts
import { isInferableError, safe } from '@orpc/client'
import { isDefinedError, safe } from '@orpc/client'

const orpc = createPiniaColadaUtils(client, {
queryInterceptors: [],
Expand All @@ -244,7 +244,7 @@ const orpc = createPiniaColadaUtils(client, {
const [error, data] = await safe(next())

if (error) {
if (isInferableError(error)) {
if (isDefinedError(error)) {
// handle typesafe errors
}

Expand All @@ -258,7 +258,7 @@ const orpc = createPiniaColadaUtils(client, {
```

:::info
You can use [`safe` and `isInferableError`](/docs/client/error-handling#using-safe-and-isinferableerror) together for typesafe error handling in interceptors.
You can use [`safe` and `isDefinedError`](/docs/client/error-handling#using-safe-and-isdefinederror) together for typesafe error handling in interceptors.
:::

## Plugins
Expand Down Expand Up @@ -401,22 +401,22 @@ const link = new RPCLink<ClientContext>({

## Typesafe Error Handling

Use the built-in `isInferableError` helper to handle [typesafe errors](/docs/error-handling#typesafe-errors) in queries and mutations.
Use the built-in `isDefinedError` helper to handle [typesafe errors](/docs/error-handling#typesafe-errors) in queries and mutations.

```ts
import { isInferableError } from '@orpc/client'
import { isDefinedError } from '@orpc/client'

const mutation = useMutation(orpc.planet.create.mutationOptions({
onError: (error) => {
if (isInferableError(error)) {
if (isDefinedError(error)) {
// Handle typesafe errors here
}
}
}))

mutation.mutate({ name: 'Earth' })

if (mutation.error.value && isInferableError(mutation.error.value)) {
if (mutation.error.value && isDefinedError(mutation.error.value)) {
// Handle the typesafe errors here
}
```
14 changes: 7 additions & 7 deletions apps/content/docs/integrations/tanstack-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ When you configure `queryKey`, it also affects `.queryOptions` because it is use
Interceptors let you wrap `queryFn` and `mutationFn` calls. Unlike [default options](#default-options), which can be overridden by per-call options, interceptors always run for every query and mutation.

```ts
import { isInferableError, safe } from '@orpc/client'
import { isDefinedError, safe } from '@orpc/client'

const orpc = createTanstackQueryUtils(client, {
queryInterceptors: [],
Expand All @@ -278,7 +278,7 @@ const orpc = createTanstackQueryUtils(client, {
const [error, data] = await safe(next())

if (error) {
if (isInferableError(error)) {
if (isDefinedError(error)) {
// handle typesafe errors
}

Expand All @@ -305,7 +305,7 @@ const orpc = createTanstackQueryUtils(client, {
```

:::info
You can use [`safe` and `isInferableError`](/docs/client/error-handling#using-safe-and-isinferableerror) together for typesafe error handling in interceptors.
You can use [`safe` and `isDefinedError`](/docs/client/error-handling#using-safe-and-isdefinederror) together for typesafe error handling in interceptors.
:::

## Plugins
Expand Down Expand Up @@ -462,22 +462,22 @@ const link = new RPCLink<ClientContext>({

## Typesafe Error Handling

Use the built-in `isInferableError` helper to handle [typesafe errors](/docs/error-handling#typesafe-errors) in queries and mutations.
Use the built-in `isDefinedError` helper to handle [typesafe errors](/docs/error-handling#typesafe-errors) in queries and mutations.

```ts
import { isInferableError } from '@orpc/client'
import { isDefinedError } from '@orpc/client'

const mutation = useMutation(orpc.planet.create.mutationOptions({
onError: (error) => {
if (isInferableError(error)) {
if (isDefinedError(error)) {
// Handle typesafe errors here
}
}
}))

mutation.mutate({ name: 'Earth' })

if (mutation.error && isInferableError(mutation.error)) {
if (mutation.error && isDefinedError(mutation.error)) {
// Handle the typesafe errors here
}
```
Expand Down
15 changes: 7 additions & 8 deletions apps/content/docs/migrations/from-v1.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The Hey API and Durable Iterator integrations no longer exist in v2. In place of
Two formats changed on the wire:

- The [RPC serializer](/docs/rpc/serializer) format, described in the [RPC Protocol](/docs/rpc/protocol).
- The error response body, which adds an `inferable` field and no longer contains a `status` field, since [`status` was removed from errors](#status-removed-from-errors).
- The error response body, which no longer contains a `status` field, since [`status` was removed from errors](#status-removed-from-errors).

Because of these changes, a v1 [RPC Link](/docs/rpc/link) or [OpenAPI Link](/docs/openapi/link) cannot talk to a v2 server (and vice versa). Deploy the upgraded server and clients together.

Expand Down Expand Up @@ -443,19 +443,19 @@ const handler = new RPCHandler(router)

</CodeGroup>

### `isDefinedError` renamed to `isInferableError`
### `safe` result changed

`isDefinedError` still works as a deprecated alias. The `safe` result also changed: the third element is now the typed error itself (or `null`) instead of a boolean, and a fourth `isSuccess` element was added. See [Client Error Handling](/docs/client/error-handling).
The third element of the `safe` result is now the typed error itself (or `null`) instead of a boolean, and a fourth `isSuccess` element was added. See [Client Error Handling](/docs/client/error-handling).

<CodeGroup>

```ts v2
import { isInferableError, safe } from '@orpc/client'
import { isDefinedError, safe } from '@orpc/client'

const [error, data, inferableError, isSuccess] = await safe(client.example({ id: 1 }))
const [error, data, definedError, isSuccess] = await safe(client.example({ id: 1 }))

if (inferableError) {
console.log(inferableError.data.retryAfter)
if (definedError) {
console.log(definedError.data.retryAfter)
}
```

Expand Down Expand Up @@ -1269,7 +1269,6 @@ These renames still compile through deprecated aliases, so you can migrate them

| v1 name | v2 name | Package |
| ---------------------------- | ----------------------------- | -------------------------------- |
| `isDefinedError` | `isInferableError` | `@orpc/client` |
| `InferClientErrorUnion` | `InferClientError` | `@orpc/client` |
| `ClientPromiseResult` | `PromiseWithError` | `@orpc/client` |
| `eventIterator` | `asyncIteratorObject` | `@orpc/server`, `@orpc/contract` |
Expand Down
4 changes: 2 additions & 2 deletions apps/content/docs/recipes/no-throw-literal.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ declare module '@orpc/server' { // or '@orpc/contract', or '@orpc/client'
```

:::info
Avoid using `any` or `unknown` for `ThrowableError` because doing so prevents the client from inferring [typesafe errors](/docs/client/error-handling#using-safe-and-isinferableerror). Instead, use `null | undefined | {}` (equivalent to `unknown`) for stricter error type inference.
Avoid using `any` or `unknown` for `ThrowableError` because doing so prevents the client from inferring [typesafe errors](/docs/client/error-handling#using-safe-and-isdefinederror). Instead, use `null | undefined | {}` (equivalent to `unknown`) for stricter error type inference.
:::

:::warning
Expand All @@ -36,7 +36,7 @@ If `ThrowableError` is configured as `null | undefined | {}`, check `isSuccess`
const { error, data, isSuccess } = await safe(client('input'))

if (!isSuccess) {
if (isInferableError(error)) {
if (isDefinedError(error)) {
// handle typesafe errors
}

Expand Down
Loading
Loading