reqkit
v1.0.5
Published
Fast cross-platform request engine with caching, background execution, streaming, and React hooks.
Maintainers
Readme
Reqkit
Reqkit — a fast, cross-platform API engine for React, React Native (including Expo), and web apps. It unifies networking, caching, background execution, streaming/polling, offline queueing, and typed React hooks into a single, composable developer experience.
Built for performance-critical apps (banking, trading, AI, dashboards) where UI responsiveness and predictable network behavior matter.
Quick facts
- ✅ Cross-platform: Web + React Native + Expo
- ⚡ Background execution: react-native-worklets (RN) + Web Workers (Web) with safe main-thread fallback
- 🧠 Hooks:
useReqkitQuery,useReqkitMutation,useReqkitStream, pluscreateQueryHook/createMutationHook/createStreamHook - 🗂️ Caching: in-memory with tag-based invalidation and dedupe for in-flight requests
- 🔁 Offline queue: persist-and-retry for non-GET requests (pluggable storage)
- 📡 Streaming & polling: append/replace modes and configurable intervals
- 🔧 TypeScript-first API and DI-friendly (inject executor, fetch, storage)
Installation
# npm
npm install reqkit
# pnpm
pnpm add reqkit
# react-native-worklets for better RN background performance (optional but recommended)
npm install react-native-workletsIf you don't install
react-native-worklets, Reqkit will still work — heavy transforms run on the main JS thread as a fallback.
Table of contents
Quick start
// src/reqkit.ts
import { createReqkit } from "reqkit";
import AsyncStorage from "@react-native-async-storage/async-storage"; // RN/Expo
const storageAdapter = {
getItem: (k: string) => AsyncStorage.getItem(k),
setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),
removeItem: (k: string) => AsyncStorage.removeItem(k),
};
export const {
ReqkitProvider,
useReqkitQuery,
useReqkitMutation,
useReqkitStream,
createQueryHook,
createMutationHook,
createStreamHook,
useReqkitGlobal,
invalidateTag,
invalidateKey,
setOnline,
flushOfflineQueue,
client,
} = createReqkit({
baseUrl: "https://api.example.com",
defaultTimeoutMs: 12_000,
offlineQueue: { enabled: true, storage: storageAdapter },
});Wrap your app:
// App.tsx
import React from "react";
import { ReqkitProvider } from "./reqkit";
export default function App() {
return (
<ReqkitProvider>
{/* your app */}
</ReqkitProvider>
);
}Core concepts
ApiClient — the core HTTP client. Handles fetch, timeouts, retry/backoff, caching, dedupe, and optional offline enqueue.
Background Executor — executes heavy transforms off the main thread. On RN, uses react-native-worklets if available; in Web, uses a Web Worker. Fallback runs on main thread.
Cache — in-memory cache with TTLs and tag indices. It also stores inflight promises to dedupe concurrent requests.
OfflineQueue — persistent queue for non-GET requests while offline. Flushable when connectivity returns.
Hooks — useReqkitQuery / useReqkitMutation / useReqkitStream provide typed, declarative access to ApiClient functionality and global state.
Named hooks — factory helpers (createQueryHook, createMutationHook, createStreamHook) to create business-level hooks like useGetUserProfile().
API reference
createReqkit(config)
Creates Reqkit instance bound to a client configuration.
Config (selected fields):
baseUrl: string— base URL used for requestsdefaultHeaders?: Record<string, string>getAuthToken?: () => Promise<string | undefined>— attach tokens to outgoing requestsdefaultTimeoutMs?: numberofflineQueue?: { enabled: boolean; storage: StorageAdapter }fetchImpl?: typeof fetch— inject custom fetch (useful for tests)backgroundExecutor?: BackgroundExecutor— inject custom executor (useful in tests)
Returns an object with:
ReqkitProvider— React provideruseReqkitQuery/useReqkitMutation/useReqkitStreamcreateQueryHook,createMutationHook,createStreamHook- Cache invalidation helpers and offline utilities
client— the underlyingApiClient
Hooks
useReqkitQuery(options)
Options (common):
path: stringmethod?: "GET" | ...(defaults toGET)params?: Record<string, any>cache?: { key?: string; tags?: string[]; cacheTimeMs?: number; staleTimeMs?: number }transformResponse?: (raw) => any— can be run in background ifisHeavy: trueisHeavy?: boolean— opt into background executionenabled?: boolean— control whether it runsrefetchOnMount?: booleanrefetchIntervalMs?: number— for polling
Returns:
{ data, error, status, isLoading, isFetching, refetch }useReqkitMutation(options)
Options:
path: stringmethod?: "POST" | "PATCH" | "PUT" | "DELETE"(defaultPOST)offline?: { enqueueIfNetworkError?: boolean }cache?: { tags?: string[] }— tags to invalidate / refreshtransformResponse?/isHeavy?/backgroundStrategy?
Returns:
{ mutate, data, error, status, isLoading }mutate(vars) executes the request and returns the ApiResponse.
useReqkitStream(options)
Options:
path: stringintervalMs: number— poll frequencyappend?: boolean— append results into arraymaxItems?: number— for rolling windowsenabled?: booleanautoStart?: boolean— setfalsein tests and callstart()manually
Returns:
{ data, error, isStreaming, start, stop, restart }useReqkitGlobal()
Returns global request state:
{ activeRequests: number, lastError?: ApiError, extras: Record<string, unknown> }Use it for global indicators and centralized error toasts.
Examples
Query example (GET)
function Profile() {
const { data, isLoading, error, refetch } = useReqkitQuery({
path: "/me",
cache: { key: "me", tags: ["user:me"], cacheTimeMs: 60_000 }
});
if (isLoading) return <div>Loading…</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Hello {data?.name}</div>;
}Mutation example (POST/PATCH)
function EditProfile() {
const { mutate, isLoading } = useReqkitMutation({
path: "/me",
method: "PATCH",
cache: { tags: ["user:me"] }
});
const save = async (payload) => {
try {
await mutate(payload);
// optimistic UI or success actions
} catch (err) {
// handle error
}
};
}Streaming / Polling example
function PriceTicker() {
const { data, isStreaming } = useReqkitStream({
path: "/ticker/BTC-USD",
intervalMs: 1000,
append: true,
maxItems: 100,
});
return <div>{isStreaming ? "Live" : "Stopped"} — {JSON.stringify(data)}</div>;
}React Native / Expo
- Add
react-native-workletsfor best background execution performance. - Use
AsyncStorageas the default storage adapter for offline queueing. - For Expo-managed apps:
react-native-workletsworks in most managed builds; otherwise Reqkit falls back to main-thread execution.
Example storage adapter for createReqkit in RN:
import AsyncStorage from '@react-native-async-storage/async-storage';
const storageAdapter = {
getItem: (k: string) => AsyncStorage.getItem(k),
setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),
removeItem: (k: string) => AsyncStorage.removeItem(k),
};Web
- Reqkit uses a Web Worker for background transforms if available.
- No extra dependency required for workers — Reqkit auto-generates a worker blob to run pure transform functions.
- Use
localStorageor IndexedDB as a storage adapter for offline queues.
Example storage adapter for Web:
const storageAdapter = {
getItem: (k) => Promise.resolve(localStorage.getItem(k)),
setItem: (k, v) => Promise.resolve(localStorage.setItem(k, v)),
removeItem: (k) => Promise.resolve(localStorage.removeItem(k)),
};Background execution
Reqkit will attempt to offload heavy JSON transforms and parsing off the main thread.
- RN: uses
react-native-workletsrunOnUIAsync when available (mark heavy functions with"worklet"where applicable). - Web: uses a generated Web Worker (Blob) to
evaland run the transform function (pure functions only). - Fallback: main-thread execution when neither is available.
Important: transformResponse must be a pure function without closure over React component state — this allows safe serialization / execution in the background.
Caching & invalidation
Cache keys are derived from method + path + params by default, but you can specify
cache.key.cache.tagslets you group resources (e.g.user:me).Helpers:
invalidateTag(tag)— invalidates all cache entries that were indexed undertag.invalidateKey(key)— remove a single key.invalidatePrefix(prefix)— invalidates keys with a prefix.
Dedupe: concurrent identical GET requests share the same in-flight promise so you only execute the network call once.
Offline queue
- Off-line queue persists non-GET requests when offline and retries them when connectivity returns.
- Queue uses a pluggable storage adapter (AsyncStorage or localStorage) and is flushable via
flushOfflineQueue(). - Config example in
createReqkit:
createReqkit({
baseUrl: "https://api.example.com",
offlineQueue: { enabled: true, storage: storageAdapter }
});To mark a mutation for offline enqueue:
useReqkitMutation({
path: "/transfer",
method: "POST",
offline: { enqueueIfNetworkError: true }
});Error handling & retries
- Retry is automatic for transient failures (configurable backoff and retry count).
- Errors surfaced by hooks are
ApiErrorobjects withcodeandmessage. - Global error surface:
useReqkitGlobal()exposeslastErrorfor centralized toasts.
Contributing
Contributions welcome! Suggested workflow:
- Fork repo and create a branch.
- Add tests for new features or bug fixes.
- Keep PRs focused and small.
- Update README and CHANGELOG.
Please follow conventional commits for release automation.
License
MIT © Ebubechi Ihediwa
