@http-client-toolkit/core 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +514 -33
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +194 -2
- package/lib/index.d.ts +194 -2
- package/lib/index.js +503 -30
- package/lib/index.js.map +1 -1
- package/package.json +2 -3
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,158 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
interface CacheControlDirectives {
|
|
4
|
+
maxAge?: number;
|
|
5
|
+
sMaxAge?: number;
|
|
6
|
+
noCache: boolean;
|
|
7
|
+
noStore: boolean;
|
|
8
|
+
mustRevalidate: boolean;
|
|
9
|
+
proxyRevalidate: boolean;
|
|
10
|
+
public: boolean;
|
|
11
|
+
private: boolean;
|
|
12
|
+
immutable: boolean;
|
|
13
|
+
staleWhileRevalidate?: number;
|
|
14
|
+
staleIfError?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse a Cache-Control header value into structured directives.
|
|
18
|
+
*
|
|
19
|
+
* Lenient: unrecognised directives are silently ignored, malformed
|
|
20
|
+
* numeric values result in undefined (treated as absent).
|
|
21
|
+
*/
|
|
22
|
+
declare function parseCacheControl(header: string | null | undefined): CacheControlDirectives;
|
|
23
|
+
|
|
24
|
+
interface CacheEntryMetadata {
|
|
25
|
+
/** ETag response header, for If-None-Match conditional requests */
|
|
26
|
+
etag?: string;
|
|
27
|
+
/** Last-Modified response header, for If-Modified-Since conditional requests */
|
|
28
|
+
lastModified?: string;
|
|
29
|
+
/** Parsed Cache-Control directives */
|
|
30
|
+
cacheControl: CacheControlDirectives;
|
|
31
|
+
/**
|
|
32
|
+
* Date response header as epoch ms.
|
|
33
|
+
* Falls back to storedAt if the server didn't send Date.
|
|
34
|
+
*/
|
|
35
|
+
responseDate: number;
|
|
36
|
+
/** Epoch ms when this entry was written to the cache */
|
|
37
|
+
storedAt: number;
|
|
38
|
+
/** Value of the Age response header at receipt time (seconds) */
|
|
39
|
+
ageHeader: number;
|
|
40
|
+
/** Raw Vary header value (e.g. "Accept, Accept-Encoding") */
|
|
41
|
+
varyHeaders?: string;
|
|
42
|
+
/** Captured request header values for Vary matching */
|
|
43
|
+
varyValues?: Record<string, string | undefined>;
|
|
44
|
+
/** HTTP status code of the original response */
|
|
45
|
+
statusCode: number;
|
|
46
|
+
/**
|
|
47
|
+
* Expires header as epoch ms. Used as freshness fallback
|
|
48
|
+
* when Cache-Control max-age is absent.
|
|
49
|
+
*/
|
|
50
|
+
expires?: number;
|
|
51
|
+
}
|
|
52
|
+
interface CacheEntry<T = unknown> {
|
|
53
|
+
/** Discriminant field for the isCacheEntry type guard */
|
|
54
|
+
__cacheEntry: true;
|
|
55
|
+
/** The actual response value the caller requested */
|
|
56
|
+
value: T;
|
|
57
|
+
/** RFC 9111 metadata for freshness/revalidation decisions */
|
|
58
|
+
metadata: CacheEntryMetadata;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Type guard: distinguishes a CacheEntry envelope from a raw cached value.
|
|
62
|
+
*
|
|
63
|
+
* When respectCacheHeaders is enabled on a cache that previously stored raw
|
|
64
|
+
* values, those old entries will fail this check and be treated as cache misses.
|
|
65
|
+
*/
|
|
66
|
+
declare function isCacheEntry<T>(value: unknown): value is CacheEntry<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Parse an HTTP-date string (RFC 7231) into epoch ms.
|
|
69
|
+
* Returns undefined if the value is missing or unparseable.
|
|
70
|
+
*
|
|
71
|
+
* Handles:
|
|
72
|
+
* - IMF-fixdate: "Sun, 06 Nov 1994 08:49:37 GMT"
|
|
73
|
+
* - RFC 850: "Sunday, 06-Nov-94 08:49:37 GMT"
|
|
74
|
+
* - asctime: "Sun Nov 6 08:49:37 1994"
|
|
75
|
+
* - "0" (treated as already-expired per Expires spec)
|
|
76
|
+
*/
|
|
77
|
+
declare function parseHttpDate(value: string | null | undefined): number | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Create a CacheEntry from a response value and the HTTP response headers.
|
|
80
|
+
*
|
|
81
|
+
* Call this after response body parsing + transformation + validation,
|
|
82
|
+
* right before storing in the cache.
|
|
83
|
+
*/
|
|
84
|
+
declare function createCacheEntry<T>(value: T, headers: Headers, statusCode: number): CacheEntry<T>;
|
|
85
|
+
/**
|
|
86
|
+
* Refresh a cache entry after receiving a 304 Not Modified response.
|
|
87
|
+
*
|
|
88
|
+
* Updates metadata from the 304 response headers while keeping the
|
|
89
|
+
* existing cached value (body). Per RFC 9111 §4.3.4, the 304 response
|
|
90
|
+
* headers replace the stored headers.
|
|
91
|
+
*/
|
|
92
|
+
declare function refreshCacheEntry<T>(existing: CacheEntry<T>, newHeaders: Headers): CacheEntry<T>;
|
|
93
|
+
|
|
94
|
+
type FreshnessStatus = 'fresh' | 'stale' | 'must-revalidate' | 'stale-while-revalidate' | 'stale-if-error' | 'no-cache';
|
|
95
|
+
/**
|
|
96
|
+
* Calculate the freshness lifetime of a cache entry in seconds.
|
|
97
|
+
*
|
|
98
|
+
* Priority order for a private cache (RFC 9111 §4.2.2):
|
|
99
|
+
* 1. max-age (s-maxage is ignored — shared-cache-only)
|
|
100
|
+
* 2. Expires − Date
|
|
101
|
+
* 3. Heuristic: 10% of (Date − Last-Modified)
|
|
102
|
+
* 4. 0 (treat as immediately stale)
|
|
103
|
+
*/
|
|
104
|
+
declare function calculateFreshnessLifetime(metadata: CacheEntryMetadata): number;
|
|
105
|
+
/**
|
|
106
|
+
* Calculate the current age of a cache entry in seconds.
|
|
107
|
+
*
|
|
108
|
+
* Per RFC 9111 §4.2.3:
|
|
109
|
+
* apparent_age = max(0, response_time − date_value)
|
|
110
|
+
* corrected_age_value = age_value + response_delay
|
|
111
|
+
* corrected_initial = max(apparent_age, corrected_age_value)
|
|
112
|
+
* resident_time = now − response_time
|
|
113
|
+
* current_age = corrected_initial + resident_time
|
|
114
|
+
*
|
|
115
|
+
* We approximate response_delay as 0 since we don't track request_time.
|
|
116
|
+
* This is conservative (slightly underestimates age).
|
|
117
|
+
*/
|
|
118
|
+
declare function calculateCurrentAge(metadata: CacheEntryMetadata, now?: number): number;
|
|
119
|
+
/**
|
|
120
|
+
* Determine the freshness status of a cache entry.
|
|
121
|
+
*
|
|
122
|
+
* Returns the most specific applicable status, used by HttpClient
|
|
123
|
+
* to decide whether to serve from cache, revalidate, or re-fetch.
|
|
124
|
+
*/
|
|
125
|
+
declare function getFreshnessStatus(metadata: CacheEntryMetadata, now?: number): FreshnessStatus;
|
|
126
|
+
/**
|
|
127
|
+
* Calculate the TTL to pass to CacheStore.set().
|
|
128
|
+
*
|
|
129
|
+
* This must be long enough to cover the freshness lifetime PLUS any
|
|
130
|
+
* stale-serving windows (SWR, SIE), so the entry remains available
|
|
131
|
+
* in the store during those windows.
|
|
132
|
+
*
|
|
133
|
+
* Falls back to defaultTTL when no cache headers provide a lifetime.
|
|
134
|
+
*/
|
|
135
|
+
declare function calculateStoreTTL(metadata: CacheEntryMetadata, defaultTTL: number): number;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Parse a Vary header value into normalised (lowercased) header names.
|
|
139
|
+
* Returns ['*'] for Vary: * (meaning the response varies on everything).
|
|
140
|
+
*/
|
|
141
|
+
declare function parseVaryHeader(varyHeader: string | null | undefined): Array<string>;
|
|
142
|
+
/**
|
|
143
|
+
* Extract the values of Vary-listed headers from a request.
|
|
144
|
+
* Stored alongside the cache entry so we can compare on lookup.
|
|
145
|
+
*/
|
|
146
|
+
declare function captureVaryValues(varyFields: Array<string>, requestHeaders: Record<string, string | undefined>): Record<string, string | undefined>;
|
|
147
|
+
/**
|
|
148
|
+
* Check whether a cached entry's Vary values match the current request.
|
|
149
|
+
*
|
|
150
|
+
* Returns false if:
|
|
151
|
+
* - Vary includes '*' (never matches — always revalidate)
|
|
152
|
+
* - Any Vary-listed header has a different value in the current request
|
|
153
|
+
*/
|
|
154
|
+
declare function varyMatches(cachedVaryValues: Record<string, string | undefined> | undefined, cachedVaryHeader: string | undefined, currentRequestHeaders: Record<string, string | undefined>): boolean;
|
|
155
|
+
|
|
3
156
|
/**
|
|
4
157
|
* Interface for caching API responses with TTL support
|
|
5
158
|
*/
|
|
@@ -140,6 +293,12 @@ type AdaptiveConfig = z.infer<typeof AdaptiveConfigSchema>;
|
|
|
140
293
|
* Interface for rate limiting API requests per resource
|
|
141
294
|
*/
|
|
142
295
|
interface RateLimitStore {
|
|
296
|
+
/**
|
|
297
|
+
* Atomically acquire capacity for a request if the implementation supports it.
|
|
298
|
+
* When present and returning true, callers should treat the request as already
|
|
299
|
+
* recorded for rate-limit accounting.
|
|
300
|
+
*/
|
|
301
|
+
acquire?(resource: string): Promise<boolean>;
|
|
143
302
|
/**
|
|
144
303
|
* Check if a request to a resource can proceed based on rate limits
|
|
145
304
|
* @param resource The resource name (e.g., 'issues', 'characters')
|
|
@@ -177,6 +336,10 @@ interface RateLimitStore {
|
|
|
177
336
|
* Enhanced interface for adaptive rate limiting stores with priority support
|
|
178
337
|
*/
|
|
179
338
|
interface AdaptiveRateLimitStore extends RateLimitStore {
|
|
339
|
+
/**
|
|
340
|
+
* Atomically acquire capacity for a request if the implementation supports it.
|
|
341
|
+
*/
|
|
342
|
+
acquire?(resource: string, priority?: RequestPriority): Promise<boolean>;
|
|
180
343
|
/**
|
|
181
344
|
* Check if a request to a resource can proceed based on rate limits
|
|
182
345
|
* @param resource The resource name (e.g., 'issues', 'characters')
|
|
@@ -333,11 +496,31 @@ interface HttpClientOptions {
|
|
|
333
496
|
reset?: Array<string>;
|
|
334
497
|
combined?: Array<string>;
|
|
335
498
|
};
|
|
499
|
+
/**
|
|
500
|
+
* When true, the client respects HTTP cache headers (Cache-Control, ETag,
|
|
501
|
+
* Last-Modified, Expires) per RFC 9111. When false (default), caching uses
|
|
502
|
+
* only the static defaultCacheTTL.
|
|
503
|
+
*/
|
|
504
|
+
respectCacheHeaders?: boolean;
|
|
505
|
+
/**
|
|
506
|
+
* Override specific cache header behaviors. Only applies when
|
|
507
|
+
* respectCacheHeaders is true.
|
|
508
|
+
*/
|
|
509
|
+
cacheHeaderOverrides?: {
|
|
510
|
+
/** Cache responses even when Cache-Control: no-store is set */
|
|
511
|
+
ignoreNoStore?: boolean;
|
|
512
|
+
/** Skip revalidation even when Cache-Control: no-cache is set */
|
|
513
|
+
ignoreNoCache?: boolean;
|
|
514
|
+
/** Minimum TTL in seconds — floor on header-derived freshness */
|
|
515
|
+
minimumTTL?: number;
|
|
516
|
+
/** Maximum TTL in seconds — cap on header-derived freshness */
|
|
517
|
+
maximumTTL?: number;
|
|
518
|
+
};
|
|
336
519
|
}
|
|
337
520
|
declare class HttpClient implements HttpClientContract {
|
|
338
|
-
private _http;
|
|
339
521
|
private stores;
|
|
340
522
|
private serverCooldowns;
|
|
523
|
+
private pendingRevalidations;
|
|
341
524
|
private options;
|
|
342
525
|
constructor(stores?: HttpClientStores, options?: HttpClientOptions);
|
|
343
526
|
private normalizeRateLimitHeaders;
|
|
@@ -363,7 +546,16 @@ declare class HttpClient implements HttpClientContract {
|
|
|
363
546
|
private applyServerRateLimitHints;
|
|
364
547
|
private enforceServerCooldown;
|
|
365
548
|
private enforceStoreRateLimit;
|
|
549
|
+
/**
|
|
550
|
+
* Wait for all pending background revalidations to complete.
|
|
551
|
+
* Primarily useful in tests to avoid dangling promises.
|
|
552
|
+
*/
|
|
553
|
+
flushRevalidations(): Promise<void>;
|
|
554
|
+
private backgroundRevalidate;
|
|
555
|
+
private clampTTL;
|
|
556
|
+
private isServerErrorOrNetworkFailure;
|
|
366
557
|
private generateClientError;
|
|
558
|
+
private parseResponseBody;
|
|
367
559
|
get<Result>(url: string, options?: {
|
|
368
560
|
signal?: AbortSignal;
|
|
369
561
|
priority?: RequestPriority;
|
|
@@ -379,4 +571,4 @@ declare class HttpClientError extends Error {
|
|
|
379
571
|
constructor(message: string, statusCode?: number);
|
|
380
572
|
}
|
|
381
573
|
|
|
382
|
-
export { type ActivityMetrics, AdaptiveCapacityCalculator, type AdaptiveConfig, AdaptiveConfigSchema, type AdaptiveRateLimitStore, type CacheStore, DEFAULT_RATE_LIMIT, type DedupeStore, type DynamicCapacityResult, HttpClient, type HttpClientContract, HttpClientError, type HttpClientOptions, type HttpClientStores, type RateLimitConfig, type RateLimitStore, type RequestPriority, hashRequest };
|
|
574
|
+
export { type ActivityMetrics, AdaptiveCapacityCalculator, type AdaptiveConfig, AdaptiveConfigSchema, type AdaptiveRateLimitStore, type CacheControlDirectives, type CacheEntry, type CacheEntryMetadata, type CacheStore, DEFAULT_RATE_LIMIT, type DedupeStore, type DynamicCapacityResult, type FreshnessStatus, HttpClient, type HttpClientContract, HttpClientError, type HttpClientOptions, type HttpClientStores, type RateLimitConfig, type RateLimitStore, type RequestPriority, calculateCurrentAge, calculateFreshnessLifetime, calculateStoreTTL, captureVaryValues, createCacheEntry, getFreshnessStatus, hashRequest, isCacheEntry, parseCacheControl, parseHttpDate, parseVaryHeader, refreshCacheEntry, varyMatches };
|