-
Notifications
You must be signed in to change notification settings - Fork 0
FAQ
Because the boring parts are always rewritten by hand. Token refresh with proper concurrency control, cross-tab logout, upload content-type handling, nested query params — every project reimplements them, usually with a race condition in the refresh queue. This packages them, with zero dependencies and no bundler configuration.
Yes:
<script type="module">
import { createClient } from "https://esm.sh/@mrzr/api-client";
</script>Yes — anything with fetch and AbortController. Worker isolation and tab sync self-disable there.
Enable cancellation once, then call cancel():
const api = createClient({ baseUrl, cancel: true });
router.on("navigate", () => api.cancel());Narrow it with a URL pattern (api.cancel("/api/v1/products")), a cancelScope for a modal, or takeLatest for a search box. It is opt-in, and covers GET only unless you widen methods — a canceled write may already have been committed by the server. See Cancellation.
No. It's compiled to a string and instantiated from a blob at runtime — no separate file, no ?worker import, no bundler plugin. The only requirement is a CSP that permits worker-src blob:.
One per API. Each client owns a worker and a BroadcastChannel. Multiple APIs? Multiple clients with distinct storageKeys. On the server, create one per request and destroy() it.
Yes, and it's designed for it — failures reject with a typed ApiError, which is what both libraries expect. See Framework Recipes.
Because with false, a 500 arrives as a successful result and React Query caches it as data — no error UI, no retry. Throwing is what every data-fetching library expects. The envelope style is one flag away.
createClient({ throwError: false });Only the 401 → refresh → retry flow, exactly once. Everything else is your policy — see the backoff recipe in Cookbook. Retries have security and idempotency implications, so they aren't a default.
Exactly one refresh call. AuthStore.coalesceRefresh hands every concurrent caller the same promise. No polling, no stampede.
In worker mode with storage: "memory": yes. They live in the worker's closure, and only AuthState (booleans, timestamps, user) crosses the boundary. Even the login() response — which usually contains the tokens — is stripped of its token fields before it resolves on the main thread. With storage: "local" the persisted copy is readable by page scripts regardless of worker mode. One caveat: the function forms of extractTokens and buildRefreshBody cannot cross the boundary, so passing a function disables worker mode (inline fallback) — the declarative TokenFieldMap / RefreshBodyConfig forms keep it.
No. It stops token theft, not token use. An attacker with XSS can still call your client. It shrinks the blast radius from "indefinite account access" to "access while the tab is open". Prevent XSS with a strict CSP first. See Security Model.
httpOnly cookies (authMode: "cookie") if you control the backend and share a site. Otherwise header mode with storage: "memory" and worker isolation. "local" only when persistence outweighs the XSS exposure. See Storage Adapters.
Have your server set a long-lived httpOnly refresh cookie, keep storage: "memory", and call api.refresh() on boot. You get persistence with no readable token.
Not specially, but GraphQL is just a POST:
const { data } = await api.post<{ data: T }>("/graphql", { query, variables }, { fullData: true });You lose GraphQL-specific error handling; a dedicated client is better for a GraphQL-first app.
Not directly — fetch has no upload-progress event, so no fetch-based client can. Chunk the upload and count chunks, wrap the body in a counting ReadableStream (with worker: false), or use XMLHttpRequest for uploads only. See Uploads and Binary Bodies.
It doesn't open them — use EventSource, WebSocket or socket.io directly. It does give them a credential: api.getSocketToken(url) fetches a socket ticket from your server over the authenticated client. See WebSockets and Socket.io.
Yes, with a plugin: beforeRequest and afterResponse run around every call, like global interceptors. For a single call, headers, beforeFunc and afterFunc do the same. See Plugins.
Functions can't be structured-cloned across a postMessage boundary. extractTokens and buildRefreshBody run inside the request pipeline, so a function form must live where the pipeline does and disables worker mode. Both have declarative forms — TokenFieldMap / RefreshBodyConfig — that are plain data and keep worker mode on. Other function options (getCsrfToken, beforeFunc, afterFunc, callbacks) run on the host and keep worker mode.
api.isWorker;No, and it shouldn't. Verification requires the server's secret; doing it client-side is security theatre. The client only reads the exp claim to schedule refreshes.
Opaque tokens work fine. The client can't predict their expiry, so proactive refresh is skipped and the 401 → refresh → retry path handles it. Supply expiresAt in setTokens if you know it.
Separate clients with separate storageKeys:
export const userApi = createClient({ storageKey: "user" });
export const adminApi = createClient({ storageKey: "admin" });Yes. The default token extractor covers DRF and SimpleJWT out of the box, and CSRF cookie/header names are configurable for all of them. See Client Options and CSRF Protection.
Because a 204, an empty body, or a parse failure legitimately produces no payload. Narrow with res.status, or use the type guard in TypeScript Types.
statusCode is the number; status is the 2xx boolean. It reads well at call sites (if (!res.status)) — but it's the number one gotcha when migrating from axios.
Yes, with worker: false, multiTab: false, and a custom storage adapter over AsyncStorage or expo-secure-store. See Storage Adapters.
Mock globalThis.fetch, or use MSW. Always construct test clients with worker: false, multiTab: false. See Cookbook.
It ships with 121 verification assertions covering the request engine, worker parity, uploads, React Query and SWR integration, throwError semantics, long-upload token expiry and CSRF — all run against real HTTP servers, not mocks. Run them yourself with npm run verify.
Semantic versioning. Breaking changes to ClientOptions, RequestConfig, IRes or ApiClient are major.
Open a security advisory, not a public issue.
Yes — see Contributing.
Getting started
Requests
Authentication
Advanced
- Web Worker Isolation
- Multi-Tab Sync
- WebSockets and Socket.io
- Plugins
- Logging and Observability
- Security Model
Reference
Guides