HttpClient
The main client class that orchestrates caching, deduplication, and rate limiting.
Import
Section titled “Import”import { HttpClient } from '@http-client-toolkit/core';Constructor
Section titled “Constructor”new HttpClient(stores?, options?)Stores
Section titled “Stores”All stores are optional. Pass only the ones you need.
| Property | Type | Description |
|---|---|---|
cache | CacheStore<T> | Response caching |
dedupe | DedupeStore<T> | Request deduplication |
rateLimit | RateLimitStore | AdaptiveRateLimitStore | Rate limiting |
Options
Section titled “Options”| Property | Type | Default | Description |
|---|---|---|---|
fetchFn | (url: string, init?: RequestInit) => Promise<Response> | globalThis.fetch | Custom fetch implementation |
requestInterceptor | (url: string, init: RequestInit) => Promise<RequestInit> | RequestInit | — | Pre-request hook to modify the outgoing request |
responseInterceptor | (response: Response, url: string) => Promise<Response> | Response | — | Post-response hook to inspect/modify the raw Response |
defaultCacheTTL | number | 3600 | Cache TTL in seconds |
throwOnRateLimit | boolean | true | Throw when rate limited vs. wait |
maxWaitTime | number | 60000 | Max wait time in ms before throwing |
responseTransformer | (data: unknown) => unknown | — | Transform parsed response data before caching (e.g. snake_case to camelCase) |
responseHandler | (data: unknown) => unknown | — | Post-transformation hook for validation or domain-level error detection. Throw to reject 2xx responses with application-level errors |
errorHandler | (context: HttpErrorContext) => Error | — | Convert HTTP errors to domain-specific types. Context includes url, response status, parsed data, and headers. Not called for network failures |
rateLimitHeaders | RateLimitHeaderConfig | defaults | Configure standard/custom header names |
retry | RetryOptions | false | — | Automatic retry configuration. See Retries guide |
cacheOverrides | object | — | Override specific cache header behaviors (see below) |
Methods
Section titled “Methods”get<T>(url, options?)
Section titled “get<T>(url, options?)”Makes a GET request through the configured pipeline.
const data = await client.get<{ name: string }>( 'https://api.example.com/user/1',);The url must be an absolute URL (e.g. https://api.example.com/items).
Request Options
| Property | Type | Default | Description |
|---|---|---|---|
signal | AbortSignal | — | Cancels wait + request when aborted |
priority | 'user' | 'background' | 'background' | Used by adaptive rate-limit stores |
headers | Record<string, string> | — | Custom headers sent with the request; also used for Vary-based cache matching |
retry | RetryOptions | false | — | Per-request retry override. Pass false to disable retries for this request |
Request Flow
Section titled “Request Flow”When client.get(url) is called, the request passes through each configured layer:
- Cache — Return cached response if available
- Dedupe — If an identical request is already in-flight, wait for its result
- Rate Limit — Wait or throw if the rate limit is exceeded
- Request Interceptor — Modify the outgoing request (e.g. inject auth headers)
- Fetch — Execute the HTTP request via
fetchFn(orglobalThis.fetch) - Response Interceptor — Inspect or modify the raw
Response - Retry — On transient failure, repeat steps 4–6 with exponential backoff (if configured)
- Transform & Validate — Apply
responseTransformerthenresponseHandler - Store — Cache the result, record the rate limit hit, and resolve any deduplicated waiters
See the Interceptors guide for detailed usage.
Cache TTL Semantics
Section titled “Cache TTL Semantics”Consistent across all built-in stores:
| Value | Behavior |
|---|---|
ttlSeconds > 0 | Expires after N seconds |
ttlSeconds === 0 | Never expires (permanent) |
ttlSeconds < 0 | Immediately expired |
Header-Based Rate Limiting
Section titled “Header-Based Rate Limiting”The client respects these headers out of the box:
Retry-AfterRateLimit-Remaining/RateLimit-ResetX-RateLimit-Remaining/X-RateLimit-ResetRate-Limit-Remaining/Rate-Limit-Reset- Combined structured
RateLimit(e.g."default";r=0;t=30)
Cooldowns are enforced when:
- The response is a throttling status (
429or503), or - Remaining quota is explicitly exhausted (
remaining <= 0)
Custom Header Names
Section titled “Custom Header Names”const client = new HttpClient(stores, { rateLimitHeaders: { retryAfter: ['RetryAfterSeconds'], remaining: ['Remaining-Requests'], reset: ['Window-Reset-Seconds'], },});Cache Header Support
Section titled “Cache Header Support”The client respects Cache-Control, ETag, Last-Modified, and Expires headers per RFC 9111. See the Caching guide for details.
cacheOverrides
Section titled “cacheOverrides”| Property | Type | Description |
|---|---|---|
ignoreNoStore | boolean | Cache responses even when no-store is set |
ignoreNoCache | boolean | Skip revalidation even when no-cache is set |
minimumTTL | number | Floor on header-derived freshness (seconds) |
maximumTTL | number | Cap on header-derived freshness (seconds) |
flushRevalidations()
Section titled “flushRevalidations()”Waits for all pending stale-while-revalidate background fetches to complete. Useful in tests.
await client.flushRevalidations();Examples
Section titled “Examples”Cache-Only Client
Section titled “Cache-Only Client”const client = new HttpClient({ cache: new InMemoryCacheStore() });Full Stack with Adaptive Rate Limiting
Section titled “Full Stack with Adaptive Rate Limiting”const client = new HttpClient( { cache: new SQLiteCacheStore({ database: db }), dedupe: new SQLiteDedupeStore({ database: db }), rateLimit: new SqliteAdaptiveRateLimitStore({ database: db, defaultConfig: { limit: 200, windowMs: 3_600_000 }, }), }, { defaultCacheTTL: 600, throwOnRateLimit: false, maxWaitTime: 30_000, },);With Cancellation
Section titled “With Cancellation”const controller = new AbortController();const data = await client.get(url, { signal: controller.signal });
// Cancel from elsewherecontroller.abort();With Interceptors
Section titled “With Interceptors”const client = new HttpClient(stores, { requestInterceptor: async (url, init) => { const token = await getAccessToken(); const headers = new Headers(init.headers); headers.set('Authorization', `Bearer ${token}`); return { ...init, headers }; }, responseInterceptor: (response, url) => { console.log(`${response.status} ${url}`); return response; },});With Custom Fetch
Section titled “With Custom Fetch”const client = new HttpClient(stores, { fetchFn: async (url, init) => { const response = await fetch(url, init); // Follow pre-signed URL redirects before caching if (response.headers.has('x-redirect-url')) { return fetch(response.headers.get('x-redirect-url')!, init); } return response; },});With Response Transformation
Section titled “With Response Transformation”import camelcaseKeys from 'camelcase-keys';
const client = new HttpClient(stores, { responseTransformer: (data) => camelcaseKeys(data as Record<string, unknown>, { deep: true }), responseHandler: (data) => { if (!data || typeof data !== 'object') { throw new Error('Unexpected response shape'); } return data; },});