---
title: "RPC Client"
description: "The RPC client provides a type-safe client interface for Application Core instances via the hc client factory and recursive proxy mechanism. It bridges server route definitions with client-side inv..."
last_updated: "2026-10-12T08:14:12.75099+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/helpers-and-tooling/rpc-client"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/client/types.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts)
- [perf-measures/type-check/client.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/perf-measures/type-check/client.ts)
- [src/client/client.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts)
- [src/types.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/types.ts)
- [src/request.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/request.ts)
- [src/client/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/index.ts)
- [src/helper/testing/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/testing/index.ts)
- [src/adapter/lambda-edge/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/handler.ts)
- [src/client/fetch-result-please.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts)
- [src/client/utils.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts)
- [src/helper/conninfo/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/conninfo/index.ts)
- [package.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/package.json)
- [src/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/context.ts)
- [src/hono-base.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/hono-base.ts)
- [src/helper/conninfo/types.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/conninfo/types.ts)
- [src/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/index.ts)
- [src/helper/websocket/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/websocket/index.ts)
- [src/helper/factory/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/factory/index.ts)
</details>

## Overview

The RPC client provides a type-safe client interface for [Application Core](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/core-engine/application-core) instances via the `hc` client factory and recursive proxy mechanism. It bridges server route definitions with client-side invocation by translating property accesses and method calls into standard HTTP requests, URL parameters, query strings, and WebSocket handshakes [WebSocket Support](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/helpers-and-tooling/websocket-support). By leveraging TypeScript type inference, the client maps server schemas directly into typed methods and response types [Execution Context](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/core-engine/execution-context), eliminating the need for manual API contract definitions while supporting custom fetch functions, headers, and error handling.

Sources: [src/client/client.ts:133-223](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L133-L223), [src/client/types.ts:67-119](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L67-L119)

## Client factory and proxy architecture

### Proxy creation and callback mechanism

The client factory `hc` initializes a recursive JavaScript `Proxy` object that captures arbitrary property accesses and function invocations [src/client/client.ts:15-31](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L15-L31). The `createProxy` helper function wraps a target function `() => {}` with traps for `get` and `apply` [src/client/client.ts:15-29](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L15-L29). When a property key is accessed, the `get` trap verifies that the key is a string and ignores built-in symbols such as `then`, returning a new `createProxy` instance with the accumulated path array extended by the accessed key [src/client/client.ts:17-22](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L17-L22). When the proxy is invoked as a function via `apply`, it executes the callback function passing an object containing the accumulated path segments and invocation arguments [src/client/client.ts:23-28](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L23-L28).

```typescript
const createProxy = (callback: Callback, path: string[]) => {
  const proxy: unknown = new Proxy(() => {}, {
    get(_obj, key) {
      if (typeof key !== 'string' || key === 'then') {
        return undefined
      }
      return createProxy(callback, [...path, key])
    },
    apply(_1, _2, args) {
      return callback({
        path,
        args,
      })
    },
  })
  return proxy
}
```
Sources: [src/client/client.ts:15-31](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L15-L31)

```mermaid
flowchart TD
    A[Property Access .foo] --> B{Key is string & not 'then'?}
    B -->|Yes| C[createProxy with path + key]
    B -->|No| D[Return undefined]
    E["Function Invocation (...args)"] --> F[Trigger proxyCallback]
    F --> G[Execute callback with path and args]
```
Sources: [src/client/client.ts:15-31](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L15-L31)

### Proxy callback routing and special methods

The `hc` factory function defines the `proxyCallback` implementation that intercepts invocations on the proxy chain [src/client/client.ts:133-223](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L133-L223). It handles special built-in inspections such as `.toString()` and `.valueOf()` to support debugging and path naming [src/client/client.ts:143-159](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L143-L159), and inspects trailing segments prefixed with a dollar sign `$` to route specific utility actions or HTTP method executions [src/client/client.ts:161-223](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L161-L223).

| Special Method / Property | Trigger Condition | Behavior |
| :--- | :--- | :--- |
| `toString()` / `valueOf()` | `lastParts[0]` is `'toString'` or `'valueOf'` | If preceded by `name`, returns the target path segment name; otherwise returns function string representation or proxy callback [src/client/client.ts:143-159](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L143-L159). |
| `url` | `method === 'url'` | Constructs and returns a `URL` object with substituted path parameters and query strings [src/client/client.ts:171-184](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L171-L184). |
| `path` | `method === 'path'` | Returns the relative URL path string with path parameters and query parameters applied [src/client/client.ts:171-185](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L171-L185). |
| `ws` | `method === 'ws'` | Upgrades the target URL protocol to `ws`, appends query parameters, and establishes a WebSocket connection [src/client/client.ts:187-212](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L187-L212). |
| HTTP Methods (`$get`, `$post`, etc.) | Method string starts with `$` | Instantiates `ClientRequestImpl` and performs an HTTP fetch request [src/client/client.ts:161-167](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L161-L167), [src/client/client.ts:214-221](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L214-L221). |

Sources: [src/client/client.ts:133-223](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L133-L223)

> [!NOTE]
> When accessing `.name.toString()` or `.name.valueOf()` on a proxy node, the callback extracts the parent path segment rather than evaluating the function code, enabling runtime introspection of route segment names [src/client/client.ts:143-156](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L143-L156).

## Type-level inference and schema propagation

### Schema mapping and route chaining

Hono translates server route definitions into strongly-typed nested client interfaces using recursive conditional types [src/client/types.ts:311-318](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L311-L318). The `Client` type unwraps a `HonoBase` instance, extracts its schema via `ExtractSchema`, and processes route path keys into a nested property chain via `PathToChain` [src/client/types.ts:311-318](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L311-L318). Each path segment in the router schema maps to an object property containing `ClientRequest` methods for HTTP interaction, URL generation, and WebSocket connections [src/client/types.ts:67-119](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L67-L119), [src/client/types.ts:294-310](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L294-L310).

```typescript
type PathToChain<
  Prefix extends string,
  Path extends string,
  E extends Schema,
  Original extends string = Path,
> = Path extends `/${infer P}`
  ? PathToChain<Prefix, P, E, Path>
  : Path extends `${infer P}/${infer R}`
    ? { [K in P]: PathToChain<Prefix, R, E, Original> }
    : {
        [K in Path extends '' ? 'index' : Path]: ClientRequest<
          Prefix,
          Original,
          E extends Record<string, unknown> ? E[Original] : never
        >
      }

export type Client<T, Prefix extends string> =
  T extends HonoBase<any, infer S, any>
    ? S extends Record<infer K, Schema>
      ? K extends string
        ? PathToChain<Prefix, K, S>
        : never
      : never
    : never
```
Sources: [src/client/types.ts:294-318](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L294-L318)

> [!TIP]
> The `PathToChain` helper checks whether a path begins with a leading slash and recursively splits segments on slashes, transforming root paths like `/api/users` into nested client property accesses `client.api.users.$get()` [src/client/types.ts:294-310](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L294-L310).

### Method expansion and input inference

HTTP methods on endpoints are governed by `ExpandAllMethod`, which transforms `$all` wildcard definitions into explicit standard HTTP methods (`$get`, `$post`, `$put`, `$delete`, `$options`, and `$patch`) pointing to the same endpoint definition [src/client/types.ts:17-26](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L17-L26). The `ClientRequest` type inspects the input parameters of each endpoint, requiring arguments when the input schema defines required keys or optional arguments when all input properties are optional [src/client/types.ts:67-80](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L67-L80).

```typescript
type StandardMethods = `$${(typeof METHODS)[number]}`

/**
 * Expands '$all' into all standard HTTP methods.
 * If the schema contains '$all', it creates a type where all standard HTTP methods
 * point to the same endpoint definition as '$all', while removing '$all' itself.
 */
type ExpandAllMethod<S> = MethodNameAll extends keyof S
  ? { [M in StandardMethods]: S[MethodNameAll] } & Omit<S, MethodNameAll>
  : S
```
Sources: [src/client/types.ts:17-26](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L17-L26)

| Type Helper | Target Input / Structure | Resolution Behavior |
| :--- | :--- | :--- |
| `ExpandAllMethod<S>` | Schema `S` containing `$all` | Expands `$all` into all standard HTTP method keys while omitting the original `$all` key [src/client/types.ts:24-26](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L24-L26). |
| `ClientRequest<Prefix, Path, S>` | Endpoint schema `S` | Maps HTTP method keys to typed function signatures accepting inferred input parameters and options [src/client/types.ts:67-80](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L67-L80). |
| `InferResponseType<T, U>` | Client fetch function `T` | Extracts response body types filtered by optional status code `U` [src/client/types.ts:251-254](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L251-L254). |
| `InferRequestType<T>` | Client fetch function `T` | Extracts the expected input argument type `R` accepted by the client method [src/client/types.ts:265-271](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L265-L271). |

Sources: [src/client/types.ts:17-26](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L17-L26), [src/client/types.ts:67-80](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L67-L80), [src/client/types.ts:251-271](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L251-L271)

### Schema filtering and response transformation

Global response definitions and status code filtering permit fine-tuning of client type definitions [src/client/types.ts:332-392](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L332-L392). The `PickResponseByStatusCode` helper evaluates an application schema and restricts all route responses to specific status codes, stripping error response types when error handling is managed centrally [src/client/types.ts:388-392](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L388-L392). Similarly, `ApplyGlobalResponse` injects global response definitions into an application schema prior to client generation [src/client/types.ts:358-363](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L358-L363).

```typescript
export type PickResponseByStatusCode<App, U extends StatusCode> =
  App extends HonoBase<infer E, infer _ extends Schema, infer B>
    ? PickSchema<ExtractSchema<App>, U> extends infer S extends Schema
      ? Hono<E, S, B>
      : never
```
Sources: [src/client/types.ts:388-392](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L388-L392)

> [!WARNING]
> When using `PickResponseByStatusCode`, any route responses outside the specified status code list evaluate to `never`, which prevents callers from handling unlisted error codes without casting [src/client/types.ts:365-392](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/types.ts#L365-L392).

## Request dispatch and path serialization

### Path formatting and parameter substitution

The RPC client handles path construction and serialization through helper utilities in `src/client/utils.ts` and `src/client/client.ts`. When a request is dispatched, `mergePath` cleans and joins the base URL with route segments by stripping trailing slashes from the base and leading slashes from the path segment [src/client/utils.ts:11-16](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L11-L16). URL parameters declared in route paths using colon syntax (such as `/:id` or optional patterns like `/:id{[^/]+}?`) are substituted dynamically via `replaceUrlParam`, which evaluates incoming key-value records and replaces matching path segments or removes them if the parameter value is undefined [src/client/utils.ts:18-24](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L18-L24). Additionally, `removeIndexString` scans target URLs to strip trailing `/index` or `/index?` suffixes, normalizing route destinations prior to fetch dispatch [src/client/utils.ts:55-60](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L55-L60).

```typescript
export const mergePath = (base: string, path: string) => {
  base = base.replace(/\/+$/, '')
  base = base + '/'
  path = path.replace(/^\/+/, '')
  return base + path
}

export const replaceUrlParam = (urlString: string, params: Record<string, string | undefined>) => {
  for (const [k, v] of Object.entries(params)) {
    const reg = new RegExp('/:' + k + '(?:{[^/]+})?\\??')
    urlString = urlString.replace(reg, v ? `/${v}` : '')
  }
  return urlString
}
```
Sources: [src/client/utils.ts:11-24](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L11-L24)

> [!NOTE]
> The `replaceUrlParam` regular expression `/:' + k + '(?:{[^/]+})?\\??/` matches standard named route parameters as well as custom regex constraints and optional modifiers [src/client/utils.ts:18-24](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L18-L24).

### Query formatting and request bodies

Query parameters and request bodies are constructed inside the `ClientRequestImpl` class [src/client/client.ts:33-130](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L33-L130). Query objects are serialized into `URLSearchParams` using `buildSearchParams`, which iterates over input entries, skips undefined values, appends multi-value array entries, and sets scalar values [src/client/utils.ts:26-44](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L26-L44). Request bodies support multiple content types based on provided validation targets:

```typescript
export const buildSearchParams = (query: Record<string, string | string[]>) => {
  const searchParams = new URLSearchParams()

  for (const [k, v] of Object.entries(query)) {
    if (v === undefined) {
      continue
    }

    if (Array.isArray(v)) {
      for (const v2 of v) {
        searchParams.append(k, v2)
      }
    } else {
      searchParams.set(k, v)
    }
  }

  return searchParams
}
```
Sources: [src/client/utils.ts:26-44](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L26-L44)

| Validation Target | Serialization Mechanism | Resulting Content-Type / Body Behavior |
| :--- | :--- | :--- |
| `args.json` | `JSON.stringify(args.json)` | Sets `Content-Type: application/json` and assigns stringified JSON to `rBody` [src/client/client.ts:81-84](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L81-L84). |
| `args.form` | `FormData` iteration | Appends scalar and array field values into a `FormData` instance assigned to `rBody` [src/client/client.ts:64-79](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L64-L79). |
| `args.query` | `buildSearchParams(args.query)` | Appends serialized query string parameters onto the destination URL [src/client/client.ts:60-62](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L60-L62). |
| `args.cookie` | `serialize(key, value, { path: '/' })` | Serializes cookie entries and joins them into the `Cookie` header [src/client/client.ts:98-104](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L98-L104). |

Sources: [src/client/client.ts:60-104](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L60-L104)

### Cookie serialization and fetch dispatch

Header construction merges explicit request headers with optional headers provided via client options, which can be evaluated dynamically if defined as a function [src/client/client.ts:93-96](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L93-L96). If cookies are supplied in the request arguments, each cookie key-value pair is passed through cookie serialization with a default root path (`/`) and combined into a comma-delimited `Cookie` header string [src/client/client.ts:98-104](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L98-L104). 

Before dispatching, the HTTP method is normalized to uppercase [src/client/client.ts:91](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L91). The `setBody` check ensures that `GET` and `HEAD` requests omit the request body [src/client/client.ts:120](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L120). Finally, `fetch` is invoked with the fully serialized URL, method, headers, body, and any custom options or fetch implementations supplied by the user [src/client/client.ts:123-128](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L123-L128).

```typescript
  const headers = new Headers(headerValues ?? undefined)
  let url = this.url

  url = removeIndexString(url)
  url = replaceUrlParam(url, this.pathParams)

  if (this.queryParams) {
    url = url + '?' + this.queryParams.toString()
  }
  methodUpperCase = this.method.toUpperCase()
  const setBody = !(methodUpperCase === 'GET' || methodUpperCase === 'HEAD')

  // Pass URL string to 1st arg for testing with MSW and node-fetch
  return (opt?.fetch || fetch)(url, {
    body: setBody ? this.rBody : undefined,
    method: methodUpperCase,
    headers: headers,
    ...opt?.init,
  })
}
```
Sources: [src/client/client.ts:110-129](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L110-L129)

> [!WARNING]
> Request bodies are strictly suppressed for `GET` and `HEAD` operations regardless of whether an `rBody` was inadvertently populated [src/client/client.ts:120-124](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L120-L124).

## Response parsing and error handling

### Response consumption and type detection

Response parsing and error handling are orchestrated by `parseResponse`, which delegates to `fetchRP`—a minimalized variant of `fetch-result-please` adapted to Hono's RPC client [src/client/fetch-result-please.ts:2-4](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L2-L4), [src/client/utils.ts:84-114](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/utils.ts#L84-L114). When a fetch response or a promise resolving to one is received, `fetchRP` unwraps it and evaluates whether the response contains a body that requires parsing [src/client/fetch-result-please.ts:14-27](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L14-L27). Status codes indicating empty responses (`101`, `204`, `205`, and `304`) are checked against `nullBodyResponses` to prevent body consumption errors [src/client/fetch-result-please.ts:7](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L7), [src/client/fetch-result-please.ts:25-26](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L25-L26).

```typescript
const nullBodyResponses = new Set([101, 204, 205, 304])

/**
 * Smartly parses and return the consumable result from a fetch `Response`.
 *
 * Throwing a structured error if the response is not `ok`. ({@link DetailedError})
 */
export async function fetchRP(fetchRes: Response | Promise<Response>): Promise<any> {
  const _fetchRes = (await fetchRes) as unknown as Response & {
    _data: any
    /**
     * @description BodyInit property from whatwg-fetch polyfill
     *
     * @link https://github.com/JakeChampion/fetch/blob/main/fetch.js#L238
     */
    _bodyInit?: any
  }

  const hasBody =
    (_fetchRes.body || _fetchRes._bodyInit) && !nullBodyResponses.has(_fetchRes.status)

  if (hasBody) {
    const responseType = detectResponseType(_fetchRes)
    _fetchRes._data = await _fetchRes[responseType]()
  }

```
Sources: [src/client/fetch-result-please.ts:7-32](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L7-L32)

When a valid body is present, `detectResponseType` inspects the `content-type` header to choose between `json` and `text` parsing [src/client/fetch-result-please.ts:28-30](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L28-L30), [src/client/fetch-result-please.ts:80-95](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L80-L95). The header value is split at the semicolon to isolate the media type before evaluating it against `jsonRegex` (`/^application\/(?:[\w!#$%&*.^`~-]*\+)?json(?:;.+)?$/i`) [src/client/fetch-result-please.ts:78](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L78), [src/client/fetch-result-please.ts:87-92](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L87-L92). If no content type header exists or if the type does not match the JSON pattern, it defaults to `text` [src/client/fetch-result-please.ts:83-84](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L83-L84), [src/client/fetch-result-please.ts:94](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L94).

```typescript
const jsonRegex = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(?:;.+)?$/i

function detectResponseType(response: Response): 'json' | 'text' {
  const _contentType = response.headers.get('content-type')

  if (!_contentType) {
    return 'text'
  }

  // `_contentType` might look like: `application/json; charset=utf-8`, `text/plain`, so we get the first part before `;`
  const contentType = _contentType.split(';').shift()!

  if (jsonRegex.test(contentType)) {
    return 'json'
  }

  return 'text'
}
```
Sources: [src/client/fetch-result-please.ts:78-95](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L78-L95)

> [!NOTE]
> The `jsonRegex` pattern accounts for vendor-specific and structured syntax suffixes such as `+json` while safely ignoring trailing parameters like character sets [src/client/fetch-result-please.ts:78](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L78).

### Error wrapping and DetailedError structure

If the response `ok` flag is false after body parsing, `fetchRP` throws a `DetailedError` containing the status code, status text, and parsed response payload [src/client/fetch-result-please.ts:33-41](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L33-L41). The `DetailedError` class extends the native `Error` object and accepts structured configuration options [src/client/fetch-result-please.ts:46-75](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L46-L75).

```typescript
export class DetailedError extends Error {
  /**
   * Additional `message` that will be logged AND returned to client
   */
  public detail?: any
  /**
   * Additional `code` that will be logged AND returned to client
   */
  public code?: any
  /**
   * Additional value that will be logged AND NOT returned to client
   */
  public log?: any
  /**
   * Optionally set the status code to return, in a web server context
   */
  public statusCode?: any

  constructor(
    message: string,
    options: { detail?: any; code?: any; statusCode?: number; log?: any } = {}
  ) {
    super(message)
    this.name = 'DetailedError'
    this.log = options.log
    this.detail = options.detail
    this.code = options.code
    this.statusCode = options.statusCode
  }
}
```
Sources: [src/client/fetch-result-please.ts:46-75](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L46-L75)

| Property | Type | Description |
| :--- | :--- | :--- |
| `message` | `string` | Human-readable error message passed to `super()` [src/client/fetch-result-please.ts:65](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L65), [src/client/fetch-result-please.ts:68](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L68). |
| `detail` | `any` | Additional details logged and returned to the client, including response data and status text [src/client/fetch-result-please.ts:36-39](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L36-L39), [src/client/fetch-result-please.ts:50](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L50). |
| `code` | `any` | Additional error code logged and returned to the client [src/client/fetch-result-please.ts:52-54](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L52-L54). |
| `log` | `any` | Additional value that is logged internally but *not* returned to the client [src/client/fetch-result-please.ts:56-58](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L56-L58), [src/client/fetch-result-please.ts:70](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L70). |
| `statusCode` | `any` | Optional HTTP status code to return in a server context [src/client/fetch-result-please.ts:60-62](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L60-L62), [src/client/fetch-result-please.ts:73](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L73). |

Sources: [src/client/fetch-result-please.ts:33-41](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L33-L41), [src/client/fetch-result-please.ts:46-75](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L46-L75)

> [!WARNING]
> The `log` property on `DetailedError` is strictly reserved for server-side logging and is intentionally excluded from payloads returned to clients [src/client/fetch-result-please.ts:56-58](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/fetch-result-please.ts#L56-L58).

## WebSocket handshake and connection lifecycle

### Protocol transformation and URL construction

When an RPC client proxy invokes the `.ws()` method, the client initiates a WebSocket connection by transforming the HTTP or HTTPS base URL scheme into a WebSocket-specific protocol (`ws` or `wss`) via `replaceUrlProtocol` [src/client/client.ts:187-191](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L187-L191). The proxy inspects the arguments passed to `.ws()` to resolve optional path parameters through `replaceUrlParam` and appends any query parameters supplied in the options object directly to the target URL's search parameters [src/client/client.ts:188-203](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L188-L203). 

Sources: [src/client/client.ts:187-203](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L187-L203)

> [!NOTE]
> Array-valued query parameters are iterated and appended individually to preserve multiple values for the same key during the WebSocket handshake [src/client/client.ts:196-202](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L196-L202).

### Client-side WebSocket establishment

The actual connection instance is created by an internal `establishWebSocket` helper function [src/client/client.ts:204-209](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L204-L209). This helper checks if a custom `webSocket` factory option was supplied in the global client request options and delegates to it; otherwise, it instantiates a native `WebSocket` object with the fully constructed target URL [src/client/client.ts:205-211](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L205-L211).

Sources: [src/client/client.ts:204-212](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L204-L212)

> [!WARNING]
> Omitting the protocol transformation step would cause standard WebSocket constructors to fail when initialized against `http://` or `https://` base URLs [src/client/client.ts:187-191](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts#L187-L191).

## Related

- [Application Core](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/core-engine/application-core)
- [Validation](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/helpers-and-tooling/validation)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
