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(options)Options
Section titled “Options”name is required. All other fields are optional — pass only what you need.
| Property | Type | Default | Description |
|---|---|---|---|
name | string | required | Name for the client instance |
cache | HttpClientCacheOptions | — | Cache configuration (see below) |
dedupe | DedupeStore<T> | — | Request deduplication |
rateLimit | HttpClientRateLimitOptions | — | Rate limit configuration (see below) |
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 |
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 |
retry | RetryOptions | false | — | Automatic retry configuration. See Retries guide |
cache Options (HttpClientCacheOptions)
Section titled “cache Options (HttpClientCacheOptions)”| Property | Type | Default | Description |
|---|---|---|---|
store | CacheStore<T> | required | Cache store instance |
globalScope | boolean | false | When true, cache keys are not prefixed with the client name. By default, keys are prefixed with name: to isolate each client’s cache entries |
ttl | number | 3600 | Cache TTL in seconds. Used when response has no cache headers |
overrides | CacheOverrideOptions | — | Override specific cache header behaviors (see below) |
rateLimit Options (HttpClientRateLimitOptions)
Section titled “rateLimit Options (HttpClientRateLimitOptions)”| Property | Type | Default | Description |
|---|---|---|---|
store | RateLimitStore | AdaptiveRateLimitStore | — | Rate limit store instance (optional — server cooldown logic works without a store) |
throw | boolean | true | Throw when rate limited vs. wait |
maxWaitTime | number | 60000 | Max wait time in ms before throwing |
headers | RateLimitHeaderConfig | defaults | Configure standard/custom header names |
resourceExtractor | (url: string) => string | URL origin | Extract rate-limit resource key from URL |
configs | RateLimitConfigMap | — | Per-resource rate limit configurations |
defaultConfig | RateLimitConfig | — | Fallback rate limit config when no per-resource config matches |
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 |
cache | { ttl?: number; overrides?: CacheOverrideOptions; tags?: string[] } | — | Per-request cache options. ttl overrides constructor TTL; overrides are shallow-merged with constructor-level; tags associates the cached entry with tags for later invalidation |
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({ name: 'my-api', rateLimit: { headers: { 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) |
invalidateByTag(tag)
Section titled “invalidateByTag(tag)”Invalidates all cache entries associated with the given tag. Returns the number of entries removed.
const count = await client.invalidateByTag('users');invalidateByTags(tags)
Section titled “invalidateByTags(tags)”Invalidates all cache entries associated with any of the given tags. Returns the number of unique entries removed.
const count = await client.invalidateByTags(['users', 'user:123']);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({ name: 'my-api', cache: { store: new InMemoryCacheStore() } });Full Stack with Adaptive Rate Limiting
Section titled “Full Stack with Adaptive Rate Limiting”const client = new HttpClient({ name: 'my-api', cache: { store: new SQLiteCacheStore({ database: db }), ttl: 600 }, dedupe: new SQLiteDedupeStore({ database: db }), rateLimit: { store: new SqliteAdaptiveRateLimitStore({ database: db, defaultConfig: { limit: 200, windowMs: 3_600_000 }, }), throw: 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({ name: 'my-api', 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({ name: 'my-api', 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({ name: 'my-api', 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; },});