SDK Configuration
SDK Configuration
One config. Every endpoint. Fully typed.
const sdk = createSdk(client).$configure({
"users.$get": { cache: true, cacheTime: 30000 },
"/admin/*": { auth: true, retry: 0 },
});
Your API schema generates a fully typed SDK, but the generated code only knows about endpoints and types. Real projects
still need caching policies, retry rules, auth settings, and response mappers. $configure lets you inject all of that
into the SDK in one place, so every request comes pre-configured the moment you access it. It also makes testing easier
by letting you swap in mocks without tools like msw or nock.
This tutorial was written for Hyper Fetch 8.0.
Dot-path keys
The SDK uses property chains like sdk.users.$get. Configuration keys mirror that path:
const configured = sdk.$configure({
// Cache the user list for 30s
"users.$get": { cache: true, cacheTime: 30000 },
// Not idempotent, should not retry
"users.$post": { retry: 0 },
// Serve stale profile data while revalidating in background
"users.$userId.$get": { staleTime: 5000 },
});
"users.$get" targets exactly sdk.users.$get. It does not affect $post or nested routes.
To apply settings across an entire resource, use endpoint-group keys:
const configured = sdk.$configure({
// All /users endpoints get caching
"/users": { cache: true },
// Admin routes require auth, no retry on privileged actions
"/admin/*": { auth: true, retry: 0 },
});
Function values
Plain objects cover simple settings. When you need mappers or conditional logic, pass a function that receives the
Request instance and returns a modified one:
const configured = sdk.$configure({
// Base retry for all requests
"*": { retry: 3 },
// Normalize the API's snake_case into camelCase for the frontend
"users.$get": (request) => request.setResponseMapper(snakeToCamelMapper).setCache(true).setCacheTime(30000),
// Attach the org header that the billing API requires
"billing.invoices.$get": (request) => request.setHeaders({ "X-Org-Id": getCurrentOrgId() }),
});
Every Request setter is available: mappers, headers, auth, mocks, interceptors.
SDK-level mocks for testing
Instead of maintaining a separate network mock layer, mock directly on the SDK:
export const testSdk = sdk.$configure({
"users.$get": (req) => req.setMock(() => ({ data: [{ id: 1, name: "Alice" }] })),
"users.$userId.$get": (req) => req.setMock(() => ({ data: { id: 1, name: "Alice" } })),
"billing.invoices.$get": (req) => req.setMock(() => ({ data: [] })),
});
Import testSdk instead of sdk in your test setup. No interceptors, no service workers, no polyfills.
Application order
When multiple keys match a single request, they stack in a fixed order:
- Global (
"*") - applied first, base defaults - Endpoint groups (
"/users","/admin/*") - domain-level policies - Dot-path keys (
"users.$get") - per-request overrides, wins on conflict
Each level preserves settings from earlier levels unless it explicitly overrides them.
const configured = sdk.$configure({
// Retry everything 3 times by default
"*": { retry: 3 },
// Cache all user-related endpoints
"/users": { cache: true },
// Deduplicate only the user list fetch
"users.$get": { deduplicate: true },
});
const request = configured.users.$get;
// Result: retry: 3 + cache: true + deduplicate: true
Set global defaults once, add domain-level policies per resource, and fine-tune individual endpoints only when needed.
Socket SDK
@hyper-fetch/sockets uses the same pattern. Leaf keys are $listener and $emitter instead of HTTP methods:
import { createSocketSdk } from "@hyper-fetch/sockets";
const sdk = createSocketSdk<typeof socket, MyChatSchema>(socket);
sdk.chat.messages.$listener.listen(({ data }) => {
appendMessage(data.text, data.user);
});
sdk.chat.messages.$emitter.emit({ payload: { text: "Hello!" } });
Configuration uses the same 3-level order and supports both objects and functions:
const configured = sdk.$configure({
// Auto-reconnect all socket listeners
"*": { options: { reconnect: true } },
// Prioritize chat topic delivery
"chat/*": { options: { priority: "high" } },
// Sanitize outgoing messages before sending
"chat.messages.$emitter": (instance) => instance.setPayloadMapper(sanitizeMessage),
});
Dot-path keys narrow the callback type automatically: $listener keys receive ListenerInstance, $emitter keys
receive EmitterInstance.
Splitting config across files
When your app grows past a dozen endpoints, split configuration by domain:
import { createConfiguration } from "@hyper-fetch/core";
export const usersConfig = createConfiguration<ApiSchema>()({
// Normalize and cache the user list
"users.$get": (request) => request.setResponseMapper(snakeToCamelMapper).setCache(true),
// Require auth, allow stale data for 5s on profile pages
"users.$userId.$get": (request) => request.setAuth(true).setStaleTime(5000),
});
export const billingConfig = createConfiguration<ApiSchema>()({
// Cache invoices for 1 minute
"billing.invoices.$get": { cache: true, cacheTime: 60000 },
// Never retry invoice creation
"billing.invoices.$post": { retry: 0 },
});
export const api = createSdk(client).$configure(usersConfig).$configure(billingConfig);
Keys are validated against your schema at compile time. A typo in "users.$gett" fails the build, not a production
request.

