Announcing Hyper Fetch 8.0
Version 8.0 is a foundational release — we replaced the entire HTTP layer with native
fetch, added first-class streaming, modernized every React hook withuseSyncExternalStore, and introduced client/server cache modes for safe server-side usage. Every change is driven by a single goal: fewer abstractions, more platform alignment, better DX.
- Unified Fetch Adapter: One adapter for browser and Node — no more XHR or dual builds.
- First-Class Streaming: Native
ReadableStreamsupport in core and a newuseStreamReact hook. - React Hooks Modernization:
useSyncExternalStore,clearState, smartkeepPreviousData, and readable console output. - Client/Server Cache Modes: Safe server-side caching with explicit scope isolation via
setScope(). - Request Lifecycle Hooks: Persistent
$hookson Request instances — no more repeating callbacks on everysend(). - SDK Configuration: Type-safe per-endpoint defaults with
createConfiguration()andsdk.$configure(). - CLI Auto-Init: Any CLI command auto-initializes when
api.jsonis missing — just run and go. - TypeScript Optimizations: Reduced type instantiation count and faster IDE responsiveness.
- Optimistic Mutations: First-class
request.setOptimistic()with auto-rollback, cache invalidation, and typedmutationContextflowing through all callbacks and hooks.
What is Hyper Fetch?
HyperFetch is a data-fetching framework that unifies HTTP, GraphQL, Firebase, and real-time sources like WebSockets and Server-Sent Events into a single, type-safe API. It works across React, React Native, and Node.js with consistent patterns for caching, queuing, offline support, and request lifecycle management.
Whether you're building a small app or a large-scale platform, HyperFetch provides architectural uniformity, end-to-end type safety, and adapters that let you swap transports without changing your application code.
Highlights
1. Unified Fetch Adapter
We replaced both the browser XMLHttpRequest adapter and the Node.js http/https adapter with a single
implementation built on the native fetch API.
One codebase. One build. No more dual isomorphic builds or conditional entry points.
- Abort via
AbortController: the standardAbortController/AbortSignalpattern replaces XHR.abort() - Download progress via
response.body.getReader(): native streaming progress reporting - Full
RequestInitpassthrough: credentials, referrer policy, keepalive, and any other standard fetch option
import { createClient } from "@hyper-fetch/core";
const client = createClient({ url: "https://api.example.com" });
const getUsers = client.createRequest<{ response: User[] }>()({
endpoint: "/users",
method: "GET",
});
getUsers.setOptions({ credentials: "include", timeout: 5000 }).send();
2. First-Class Streaming
When you set streaming: true, the adapter returns the raw ReadableStream instead of buffering the entire response.
Think real-time AI chat, large file downloads with progress, or server-sent data processing.
Core usage, get the stream directly:
const streamRequest = client.createRequest<{ response: ReadableStream }>()({
endpoint: "/ai/chat",
method: "POST",
});
const { data } = await streamRequest.setOptions({ streaming: true }).send();
React: the new useStream hook manages the full lifecycle for you:
import { useStream } from "@hyper-fetch/react";
function AiChat({ prompt }) {
const { text, streaming, start, abort } = useStream(chatRequest.setPayload({ prompt }));
return (
<div>
<p>{text}</p>
{streaming ? <button onClick={abort}>Stop</button> : <button onClick={start}>Send</button>}
</div>
);
}
It automatically clones the request with streaming: true, accumulates chunks via TextDecoder, and exposes start,
abort, and reset controls. Works for text, files, or binary data.
3. React Hooks Modernization
Four interconnected improvements to the React hooks layer.
useSyncExternalStore
All hooks now use useSyncExternalStore (React 18+) for proper concurrent mode support. This fixes potential tearing
issues while preserving field-level dependency tracking. A component that only reads data still won't re-render when
error changes.
Smart keepPreviousData
Switching between resources (product #1 → product #2) now clears stale data by default instead of flashing the previous resource. Three modes give you full control:
| Mode | Behavior |
|---|---|
"auto" (default) | Clears on identity change (method + endpoint + path params), preserves on query-only change. Pagination just works |
"preserve" | Always keeps previous data visible during loading (old v7 behavior) |
"clean" | Always clears state on any key change |
// Pagination: data stays visible while loading the next page
const { data } = useFetch(getUsers.setQueryParams({ page }), {
keepPreviousData: "preserve",
});
// Detail view: clears immediately when switching products
const { data } = useFetch(getProduct.setParams({ productId }));
clearState() and readable console output
- New
clearState()onuseFetch,useSubmit, anduseCacheresets all tracked fields back to their initial values - Hook return values now implement
toJSON()soconsole.logshows actual values instead of(...)getter placeholders
4. Client/Server Cache Modes
Server-side HyperFetch has a cache safety problem: the in-process cache is shared across all incoming requests, meaning user A could see user B's data.
v8 solves this with a mode option on the client:
const client = createClient({
url: "https://api.example.com",
// mode: "auto" (default) → detects environment automatically
// mode: "client" → cache works as before
// mode: "server" → cache disabled by default, opt-in with setScope()
});
On the server, caching only activates when you explicitly scope it, typically with a session ID, or when you set mode to "client".
// NOT cached, safe by default (no scope)
await getUser.setParams({ userId: 1 }).send();
// CACHED, scoped to this user's session
await getUser.setParams({ userId: 1 }).setScope(req.session.id).send();
Cache keys are automatically namespaced: ${scope}__${originalCacheKey}. No scope = no caching on the server. On the
client side, setScope is optional and acts as a key prefix for organizing cache by tenant, workspace, or feature.
5. Request Lifecycle Hooks
Define persistent lifecycle hooks directly on a request via $hooks. Unlike per-send() callbacks you have to repeat
every time, these travel with the request instance and survive .clone().
const getUser = client.createRequest<{ response: User }>()({
endpoint: "/users/:userId",
method: "GET",
});
getUser.$hooks.onResponse(({ response }) => {
console.log("User loaded:", response.data);
});
getUser.$hooks.onRequestStart(({ requestId }) => {
console.log("Loading user...", requestId);
});
// Hooks fire on every send, no need to repeat them
await getUser.setParams({ userId: 1 }).send();
await getUser.setParams({ userId: 2 }).send();
Each hook method returns an unsubscribe function. Multiple listeners per hook are supported. The $ prefix means
runtime-only, so hooks don't interfere with serialization, persistence, or DevTools.
6. SDK Configuration
For projects using createSdk, v8 adds a typed configuration system. Define defaults per request once, and they apply
automatically. No more repeating .setHeaders() or .setResponseMapper() on every call.
import { createSdk, createConfiguration } from "@hyper-fetch/core";
import type { OurSdk } from "./sdk";
const authConfig = createConfiguration<OurSdk>()({
"auth.login.$post": (req) => req.setResponseMapper(loginMapper).setHeaders({ "X-Custom": "value" }),
"auth.register.$post": (req) => req.setResponseMapper(registerMapper),
});
const sdk = createSdk(client);
const configuredSdk = sdk.$configure(authConfig);
// Configured defaults are applied automatically
await configuredSdk.auth.login.$post.send({ payload: credentials });
Use dot-path keys (like "users.$get") to target a specific method, or endpoint groups (like "/users") to
configure all methods at once. Split configs across files by domain and merge with chained $configure() calls.
7. CLI Auto-Init
No more "run init first" errors. Every CLI command auto-initializes when api.json is missing. Just run your command
and go, similar to how shadcn works.
# Fresh project: auto-creates api.json and src/api directory
npx hyper-fetch generate --url https://api.example.com/openapi.json
8. TypeScript Optimizations
We profiled and optimized the heaviest type constructs:
- Flattened nested conditional chains into lookup types in
RequestandAdaptergenerics - Split
ExtractUnionAdapternarrowing to avoid "excessively deep" instantiation errors - Cached intermediate type expressions via aliases instead of re-instantiating at every usage
The result: noticeably faster IDE autocomplete and type-checking, especially in files with many request definitions.
9. Optimistic Mutations
Define cache manipulation, rollback logic, and invalidation targets directly on the request with setOptimistic(). The
framework orchestrates everything, including auto-rollback on failure and retry awareness.
const patchUser = client
.createRequest<{ response: User; payload: Partial<User> }>()({
endpoint: "/users/:userId",
method: "PATCH",
})
.setOptimistic(({ client, payload, request }) => {
const snapshot = client.cache.get(getUsers.cacheKey);
client.cache.update(getUsers, (prev) => ({
...prev,
data: prev.data.map((u) => (u.id === Number(request.params?.userId) ? { ...u, ...payload } : u)),
}));
return {
context: { snapshot },
rollback: () => {
if (snapshot) client.cache.set(getUsers, snapshot);
},
invalidate: [], // use the requests here to invalidate t hem without writing rollback!
};
});
Use it with React hooks. Rollback happens automatically, and mutationContext is fully typed:
const { submit, onSubmitSuccess, onSubmitError } = useSubmit(patchUser);
onSubmitSuccess(({ mutationContext }) => {
console.log("Updated! Previous:", mutationContext?.snapshot);
});
onSubmitError(() => {
alert("Update failed, changes reverted.");
});
- Auto-rollback on failure or abort, no manual error handling
- Retry-aware: rollback only fires on final failure, not intermediate retries
- Concurrent-safe: each
submit()call gets its own isolated optimistic result - Type inference:
mutationContexttype is inferred from yourcontextreturn value
Infrastructure Changes
Vitest migration. The entire test suite moved from Jest to Vitest, bringing faster execution, native ESM
support, and better TypeScript integration. All packages now share a single vitest.workspace.ts configuration.
Vite build pipeline. All packages are now built with Vite, replacing the previous Rollup and esbuild setup.
E2E test infrastructure. A new reusable test server covers every HTTP method, file upload/download, redirects, streaming responses, error codes, and timeouts for both Node.js and browser contexts.
Breaking Changes
Review these changes before upgrading. Most migrations are straightforward.
1. Native Fetch Replaces XHR
The XHR browser adapter and Node http/https adapter have been removed. The unified fetch adapter requires Node.js
18+. If you relied on XHR-specific features, migrate to fetch/RequestInit equivalents.
// v7: Two separate adapters, chosen by build target
// http-adapter.browser.ts (XHR)
// http-adapter.server.ts (Node http)
// v8: Single fetch-based adapter for all environments
request.setOptions({ credentials: "include", redirect: "follow" });
2. keepPreviousData Default
State is now cleared by default when the resource identity changes. The old behavior is opt-in:
const { data } = useFetch(getProduct.setParams({ productId }));
const { data } = useFetch(getProduct.setParams({ productId }), {
keepPreviousData: "preserve",
});
3. React 18+ Required
The useSyncExternalStore migration means React 18 or later is now required.
4. Jest → Vitest
The test infrastructure now uses Vitest. The public testing API (@hyper-fetch/testing) remains compatible.
Other Changes
- ESM-only: continues to be ESM-only, aligning with the broader ecosystem direction
- Simplified build: dual isomorphic build system and Rollup removed in favor of Vite
- GraphQL E2E tests: comprehensive end-to-end tests for the GraphQL adapter with a real server
- Improved CLI error handling: better error messages and edge case handling across all commands
What's Next?
v8 completes the modernization of HyperFetch's foundation. With native fetch, streaming, and safe server-side caching
in place, here's what we're building next:
- Expanded E2E coverage: completing test suites for streaming, cache modes, React hooks, and SDK configuration.
- Framework integrations: first-class support for Next.js App Router, Remix, and other server-first frameworks.
- AI/LLM patterns: guides and utilities for common AI streaming patterns built on
useStream. - Plugin ecosystem: growing the plugin system with community-contributed adapters and utilities.
Thank you for all the support. We can't wait to see what you build with Hyper Fetch 8.0!


