-
-
Notifications
You must be signed in to change notification settings - Fork 870
Description
Description
The RPC client accepts a custom fetch implementation via options, but the ClientResponse types are tightly coupled to the native fetch API. This makes it impractical to use popular fetch wrappers like ofetch, ky, or axios without significant type workarounds.
Current Behavior
When using a custom fetch client, the ClientResponse types still expect native fetch methods like res.ok, res.json(), res.text(), etc. However, custom fetch clients have different APIs and behaviors:
- ofetch: Throws errors on non-2xx responses instead of returning a response object with
ok: false - ky: Similar behavior with a different API surface
- axios: Uses
dataproperty instead of methods like.json()
Example
Here's a real-world case using ofetch in a Nuxt application:
import { createClient } from '@sdk/v1'
import type { ClientResponse } from 'hono/client'
export default function useApi() {
const client = createClient('', {
fetch: useAuthFetch(), // ofetch-based custom fetcher
})
return client
}
// Workaround: Type utility to extract 200 response since ofetch throws on non-2xx
export function res200<T>(
res: T,
): Extract<T, { status: 200 }> extends ClientResponse<infer Body, 200, any>
? Body
: Extract<T, { status: 200 }> {
return res as any
}
// Usage - must use .then(res200) after every RPC call
const result = await client.v1.orders.$get({ query: q }).then(res200)The custom fetch implementation:
export function useAuthFetch() {
const instance = $fetch.create({ // ofetch
baseURL: '/api',
onRequest({ options }) {
// Add auth headers
},
async onResponseError({ response }) {
// Handle 401, refresh tokens, etc.
// ofetch throws here for non-2xx responses
},
})
return instance
}Problem
- The
ClientResponsetype assumes native fetch behavior - Custom fetch clients have different APIs and error handling strategies
- TypeScript doesn't know that non-2xx responses will never arrive as normal response objects when using
ofetch - Users must create type workarounds like
res200()for every RPC call - This defeats the purpose of having type-safe RPC calls
Impact
- Loss of type safety when using custom fetch clients
- Boilerplate workarounds required for every API call
- Custom fetch clients (which are essential for auth, retries, interceptors) become impractical
- The
fetchoption in RPC client is advertised but not truly usable with real-world fetch implementations
Possible Solutions
-
Generic type parameter for fetch response type
type ClientResponse<TBody, TStatus, TFormat, TFetchClient = 'native'> = ...
-
Separate type for custom fetch clients
type CustomClientResponse<TBody, TStatus> = TBody
-
Configuration option to specify fetch behavior
createClient('/', { fetch: customFetch, fetchBehavior: 'throw-on-error' // or 'native' })
-
Response transformer function
Allow users to provide a type-safe transformer that maps their fetch client's response to a known shape
Environment
- Hono version: Latest
- Custom fetch client: ofetch (part of Nuxt/Nitro ecosystem)
- Use case: Nuxt 4 application with authentication, token refresh, and error handling
Related
This affects anyone using:
- ofetch (Nuxt/Nitro)
- ky
- axios
- Any custom fetch wrapper with authentication/retry logic
The current workaround significantly reduces the developer experience and type safety that Hono's RPC client promises to deliver.